feat(mcp): connect authenticated AquaPortal tools to Vocero (#1)

Vocero needs to query AquaPortal on behalf of the caller selected by the existing phone link. This adds server-owned conversations and MCP tool execution for voice and HTTP reasoning while leaving the speech pipeline unchanged.

Release notes:

- Added browser-bound conversations that keep backend credentials server-side and isolate callers.
- Connected the eight-tool AquaPortal catalog through authenticated Streamable HTTP with full input schemas and structured results.
- Added bounded multi-step tool execution with cancellation, expiry, and stale-result protection.
- Updated browser bootstrap, tool progress, reasoning headers, and conversation cleanup.
- Disabled legacy unscoped content preloads in MCP mode and documented configuration and deployment constraints.
- Preserved disabled-mode contracts and added local SDK, WebSocket, and browser regression coverage.

Reviewed-on: AquaPortal/s2s#1
This commit is contained in:
2026-10-08 08:49:39 -04:00
parent ccb1f708ca
commit 96895996d9
37 changed files with 2498 additions and 55 deletions
+17
View File
@@ -204,3 +204,20 @@ if SPEECH_TO_SPEECH_URL or S2S_PROXY_TARGET:
SERPER_URL = "https://google.serper.dev/search"
# Cap results so the tool output stays small enough to feed back to the model.
MAX_RESULTS = 5
# Optional authenticated AquaPortal MCP integration. Legacy deployments remain unchanged.
S2S_MCP_URL = os.environ.get("S2S_MCP_URL", "").strip()
def _bounded_mcp_int(name: str, default: int, minimum: int, maximum: int) -> int:
try:
value = int(os.environ.get(name, str(default)))
except ValueError:
value = default
return max(minimum, min(maximum, value))
S2S_MCP_TIMEOUT_SEC = _bounded_mcp_int("S2S_MCP_TIMEOUT_SEC", 20, 1, 120)
S2S_MCP_MAX_CALLS_PER_TURN = _bounded_mcp_int("S2S_MCP_MAX_CALLS_PER_TURN", 6, 1, 20)
S2S_CONVERSATION_TTL_SEC = _bounded_mcp_int("S2S_CONVERSATION_TTL_SEC", 3600, 60, 86400)
S2S_MAX_CONVERSATIONS = _bounded_mcp_int("S2S_MAX_CONVERSATIONS", 1000, 1, 10000)