Stage 1: mount MCP server, bearer auth, outbound-call guards #1
Reference in New Issue
Block a user
Delete Branch "feature/stage1-agent-surface"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
First stage of the architecture-review roadmap: make the agent surface real and safe.
What was broken
main.pycreated it and stashed it onapp.state— no MCP client could ever connect, despite docs describing an SSE endpoint at/mcp.learn_call_flowcrashed on invocation — wrong constructor args (CallFlowLearner(event_bus, settings)vs__init__(self, llm_client=None)) and a nonexistentlearn_from_call()method. No MCP tests existed to catch it.make_call(real PSTN dialing) were all open.GET /api/calls/{call_id}shadowedGET /api/calls/history(registration order).debug=True(with SQL echo), credential baked into the defaultDATABASE_URL, secrets as plainstr.Changes
/mcp/(streamable HTTP, FastMCP 3.x) with combined lifespan; gateway resolved lazily per request so mounting happens at app construction.API_TOKENacross all three surfaces: REST dependency (compare_digest), WS check beforeaccept()(query param or header, close 4401), MCPStaticTokenVerifier. Startup refuses a tokenless non-loopback bind.MAX_CONCURRENT_CALLScap ingateway.make_call; surfaces as HTTP 400 / MCP ToolError. README 911 caution rewritten to match reality.learn_call_flowtool and the never-fedCallAnalyticsservice (analytics will be recomputed from DB call records later);call_flow_learner.pykept for proper wiring in stage 6.dial_plan.pytrimmed to what's actually referenced (emergency set + extension allocation); the unreferenced matcher/E.164 normaliser deleted.call_historyrouter registered beforecalls; regression test included.SecretStrfor SIP/LLM/TTS credentials and the API token;debug=False;DATABASE_URLhas no default and is validated at startup with clear guidance.claude mcp addexample, actual tool table (14 tools + 3 resources), auth setup, pjsua2 as a system dependency, port 8100 typo fixed.Tests & verification
tests/test_mcp.py(8): in-memory FastMCP client — tool surface, lazy-gateway error, make_call happy path, emergency refusal (unit + gateway + tool), call cap.tests/test_api_security.py(8): 401 paths on all routers, valid-token pass-through, tokenless-loopback mode, route order, mount presence.test_complex_tone_as_music,test_trunk_status) are pre-existing onmain./api/calls/historyreturns 200, MCP 401 without token, and a realfastmcpclient over streamable HTTP listed all 14 tools, calledgateway_status, and hadmake_call("911")refused. TokenlessHOST=0.0.0.0startup refused with guidance.Notes for reviewer
fastmcpis 3.3.1, so the pin is>=3.0(plan drafted>=2.3; wrote against the live version).API_TOKENwas appended to the local.env(gitignored) so the existingHOST=0.0.0.0dev setup keeps booting.SIP_TRUNK_*/TTS_*settings classes don't read.env(noenv_fileon the sub-settings, and the parent doesn't export) — they only see real process env vars. Likely why the engine silently runs in mock mode. Not touched here; it belongs with stage 4's explicit engine-mode work.🤖 Generated with Claude Code
The MCP server was created but never mounted — no client could reach it. Mount it at /mcp/ over streamable HTTP with a combined lifespan, resolving the gateway lazily so mounting happens at app construction. Security and safety for the agent surface: - One static API_TOKEN (SecretStr) enforced across REST (dependency), WebSocket (query param/header before accept), and MCP (StaticTokenVerifier). Startup refuses tokenless non-loopback binds. - Emergency numbers (911/9911/112) always refused on make_call, plus a MAX_CONCURRENT_CALLS cap; ValueError surfaces as 400/ToolError. - Safe defaults: debug off, no credential in default DATABASE_URL, SIP/LLM/TTS secrets as SecretStr. Cleanups: - Delete broken learn_call_flow tool (wrong ctor args, nonexistent method) and the never-fed CallAnalytics service; keep call_flow_learner for proper wiring later. - Trim dial_plan to what is actually used (emergency guard, extension allocation); delete the unreferenced matcher/normaliser. - Register call_history before calls so /api/calls/history is no longer shadowed by /api/calls/{call_id}. - fastmcp pinned >=3.0 (http_app + StaticTokenVerifier). New tests: MCP in-memory client (tool surface, lazy gateway, emergency refusal, call cap) and API security (401 paths, route order, mount). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>