Files
hold-slayer/.claude/rules/mcp-tools.md
Robert Helewka 4a3c14d4af
All checks were successful
CVE Scan & Docker Build / security-scan (push) Successful in 45s
CVE Scan & Docker Build / build-and-push (push) Successful in 1m53s
docs: add Claude AI assistant rules and configuration
Add comprehensive rule documentation for AI-assisted development covering
authentication surfaces, outbound-call safety invariants, and other project
conventions to guide Claude's understanding of critical system behaviors.
2026-07-28 19:01:38 -04:00

3.2 KiB

description, paths
description paths
MCP tools return formatted strings; ToolError-vs-return-string convention; lazy gateway resolution; resources are JSON
mcp_server/server.py

MCP tools & resources

The MCP server (mcp_server/server.py) is the AI-assistant control surface. It's built by create_mcp_server(get_gateway) and mounted at /mcp/ before the lifespan runs, so everything is resolved lazily.

  • Auth is the ASGI _owner_only_mcp wrapper in main.py, not FastMCP. create_mcp_server builds FastMCP(auth=None); the wrapper resolves the Authorization bearer (Casdoor JWT or owner-minted PAT) via the shared resolve_from_header_or_query + is_owner and returns 401/403 before the inner app runs. Don't reintroduce a FastMCP verifier here — one resolver gates all four surfaces (see the auth-surfaces rule). MCP clients use a PAT (hs_pat_…) minted from the dashboard's Tokens modal.

  • Tools return plain formatted strings; resources return JSON strings. Tools produce human-readable text an assistant reads back to a user ("Call abc123 initiated. …"). Resources (gateway://status, gateway://call-flows, gateway://active-calls) return json.dumps(...). Don't blur these — a tool that returns raw JSON, or a resource that returns prose, breaks the contract.

  • Error convention — match the two existing patterns:

    • Raise ToolError when the request is invalid or unsafe: emergency number, bad mode, concurrency cap hit, or "gateway still starting" (require_gateway). The assistant should treat these as errors.
    • Return an error string for a lookup that simply found nothing or hit a recoverable snag: "Call {id} not found.", "No stored call flow for …", "Error looking up …: {e}". The assistant reads these as content.
    • Rule of thumb: "you asked for something invalid/unsafe" → raise; "I looked, here's the (maybe empty/failed) answer" → return.
  • require_gateway() gates every tool that needs the live gateway. It raises ToolError("Gateway is still starting up …") when get_gateway() returns None. This is why the MCP app can mount before the lifespan builds the gateway. Call it at the top of any tool that touches the gateway; never assume the gateway exists.

  • make_call is the one tool that dials. It maps the string mode to CallMode, defaults unknown modes to DIRECT, and lets gateway.make_call's refusals (ValueError) surface as ToolError. The emergency guard and concurrency cap live in the gateway, not here — don't reimplement or skip them at the tool layer (see the call-safety rule).

  • DB-backed tools use session_scope() and read from call_persistence. Completed-call history, summaries, recordings, and stored flows come from the database, not from active_calls (those are live only). Keep the async with session_scope() as session: pattern; don't open ad-hoc sessions.

  • Keep the tool count and README table in sync. There are 15 tools + 3 resources. If you add/remove one, update the README's MCP table and the docs/mcp-server.md reference — a drifting tool list is a documented lie.