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:
+66
-2
@@ -134,14 +134,78 @@ instructions. Caller links may include `phoneNumber=%2B...`; episode links may
|
||||
include `episode_id=...`. See [context injection](context-injection.md) for
|
||||
enabled data sources and limits.
|
||||
|
||||
### Authenticated AquaPortal MCP
|
||||
|
||||
`S2S_MCP_URL` enables the MCP client transport. Set it to the backend's complete
|
||||
Streamable HTTP endpoint, for example `https://backend.example.test/api/v1/mcp`.
|
||||
Set `S2S_LOGIN_BY_PHONE` to that backend's `POST /api/v1/auth/phone-login` URL.
|
||||
The phone parameter remains an identity selector without proof of ownership.
|
||||
MCP uses the returned opaque `sessionToken`, never an ApiPilot API key or service token.
|
||||
|
||||
For voice reasoning (`S2S_REASONING_TYPE=PROXY`), set `S2S_PROXY_TARGET` to the
|
||||
speech service and leave `SPEECH_TO_SPEECH_URL` and `LOAD_BALANCER_URL` unset.
|
||||
The browser must connect to Vocero's same-origin `/v1/realtime` relay so tool
|
||||
selections reach its executor. An incompatible direct configuration returns
|
||||
503 from `/api/config`. `OPENAI` reasoning uses the authenticated HTTP loop and
|
||||
can retain a direct or load-balanced speech connection. No speech-service code
|
||||
or audio-format change is required.
|
||||
|
||||
The backend must expose the eight-tool catalog and phone-login contract described
|
||||
in [tool calling](tool-calling.md#authenticated-aquaportal-mcp). Leave
|
||||
`S2S_MCP_URL` unset to retain the existing ApiPilot and caller-context paths.
|
||||
Setting it makes MCP the exclusive AquaPortal content path: legacy profile,
|
||||
report, latest-summary, and episode preloads are skipped. Existing ApiPilot
|
||||
and caller HTTP routes return 409 while MCP is enabled.
|
||||
|
||||
| Setting | Default | Meaning |
|
||||
|---|---|---|
|
||||
| `S2S_MCP_URL` | Empty, disabled | Backend MCP endpoint |
|
||||
| `S2S_MCP_TIMEOUT_SEC` | `20` | Entire MCP operation timeout, clamped to 1–120 seconds |
|
||||
| `S2S_MCP_MAX_CALLS_PER_TURN` | `6` | Tool attempts per user turn, clamped to 1–20 |
|
||||
| `S2S_CONVERSATION_TTL_SEC` | `3600` | Absolute conversation lifetime, clamped to 60–86400 seconds |
|
||||
| `S2S_MAX_CONVERSATIONS` | `1000` | Process-local conversation capacity, clamped to 1–10000 |
|
||||
|
||||
`POST /api/conversations` accepts `{"phoneNumber":"+56000000000"}` and returns
|
||||
`conversationId`, `expiresIn`, and a minimal caller profile. An HttpOnly,
|
||||
SameSite=Strict cookie binds the browser to its conversations; HTTPS sets Secure.
|
||||
Backend credentials stay in memory on the API server. HTTP conversation requests
|
||||
use `X-Vocero-Conversation`; the same-origin WebSocket uses `conversation_id`.
|
||||
The conversation ID alone is insufficient without its owning browser cookie.
|
||||
Different tabs can own different caller identities without replacing one another.
|
||||
|
||||
`GET /api/mcp/tools` discovers the authenticated catalog.
|
||||
`POST /api/reasoning` requires an increasing positive `X-Vocero-Turn` header
|
||||
in addition to the conversation header in MCP mode.
|
||||
`POST /api/conversations/current/interrupt` cancels the matching `X-Vocero-Turn`, and
|
||||
`DELETE /api/conversations/current` clears that conversation. These endpoints
|
||||
require the cookie and conversation header. They do not change the existing
|
||||
load-balancer `/api/session` contract. Stopping a conversation drops its local
|
||||
credentials; it does not revoke backend sessions globally.
|
||||
|
||||
Conversation state is bounded, process-local, and cleared on restart. Use one
|
||||
API worker, as in the launcher and Docker command. Multiple workers or replicas
|
||||
require a shared conversation store before enabling this integration. Browser
|
||||
sessions must restart after expiry or an API reload. Backend access is checked
|
||||
on each MCP request. There is no shared content or authorization cache. HTTP
|
||||
reasoning retains up to six completed turns, bounded to 96,000 characters,
|
||||
within that conversation; the speech service keeps its existing conversation
|
||||
history. These histories are cleared when their sessions end.
|
||||
|
||||
The browser waits for successful phone login and discovery before starting an
|
||||
MCP conversation. Failure blocks startup and displays a retryable error. Phone
|
||||
and `episode_id` page parameters remain supported, but automatic selection of
|
||||
the episode from the link is deferred: resolve it explicitly with the podcast
|
||||
tools for now. Changing browser-supplied profile, phone, or episode values on a
|
||||
reasoning request cannot change the stored backend identity.
|
||||
|
||||
## Theme and session startup
|
||||
|
||||
`S2S_THEME=white` selects the light interface. `dark` or an empty value selects
|
||||
the dark aurora theme. Theme-specific sections remain hidden until configuration
|
||||
resolves, with a fallback to avoid an invisible page after config failure.
|
||||
Configuration failures offer **Reintentar** and block session startup until a
|
||||
retry succeeds. Reports load independently. Caller context gets an eight-second
|
||||
loading window before the interface allows a generic session.
|
||||
retry succeeds. With MCP disabled, reports load independently and caller context
|
||||
gets an eight-second loading window before the interface allows a generic session.
|
||||
|
||||
`S2S_START_SESSION_MODE` controls initial microphone/session startup:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user