Orchestrata Ultimate › Plans › briefs › 2026-10-13-mcp-gateway-transport-and-deployed-registration
Brief: ADR-0038, orchestrata half — the gateway's MCP transport generated from its handler table, and both servers registered with Claude by the deployed configuration
Source: plans/briefs/2026-10-13-mcp-gateway-transport-and-deployed-registration.md at 8a2cb42ad · last changed 2026-10-13 · first committed 2026-10-13 · 50 lines.
Prose pending.
Plan text
Brief: ADR-0038, orchestrata half — the gateway's MCP transport generated from its handler table, and both servers registered with Claude by the deployed configuration
Date: 2026-10-13 · DESIGN_WAIVER: ADR-0038 (Accepted ruling, rules 2, 3, 4; the codetools half landed on codetools master at b39d26f and 74cad1e) · Executor: Sonnet builder in worktree orchestrata-ultimate-worktrees/mcp-gateway, branch feat/mcp-gateway-transport off origin/master (afd39e93f or newer) · Design settled below; no design forks are open. If a fork appears, STOP and report.
GOAL: the coordinating Claude session calls orchestrata and codetools as tools. (a) orchestrata_ultimate.gateway gains an MCP transport over stdio whose tool table is generated from the gateway's handler table, with the same approval contract codetools ships (single-use, tool-bound, 300 s token issued by an approve tool; refusals carry a ledger row id). (b) deploy/install-claude-config.sh registers both servers at user scope from the deployed release paths, idempotently, and the identity check refuses success unless each registered command resolves inside its deployed release.
RULED DESIGN (ADR-0099, Accepted 2026-10-13, rules 1 to 3; these replace the earlier premise that the gateway had an HTTP verb surface and mutation markers — it has neither):
- Transport.
orchestrata-mcpis a WebSocket JSON-RPC client of the deployed gateway atGATEWAY_HOST:GATEWAY_PORT, authenticating withGATEWAY_TOKENfrom the deployed secrets layer (never a flag, never in the ledger); every tool call is forwarded as the JSON-RPC method it names and passes the gateway's ownScopeGuard, auth and audit. No new HTTP RPC route, no in-process dispatch. - One declarative method table. Refactor
_build_method_registryingateway/rpc.pyinto a module-levelMETHODS: tuple[MethodSpec, ...]in a newgateway/method_table.py(name, handler by dotted path,mutates: bool,expose: bool, optionalparams_schema), importable without constructing anything;RPCHandlerbuilds its dict from it andMcpTransportbuilds its tool list from it; a test proves a method added to the table appears in both and thatRPCHandler's dict equals the table. - Gated unless marked read-only; not everything is a tool.
mutatesdefaults toTrue; the read-only set is marked explicitly after reading each handler (start from names ending in list/get/query/status/info/rules/decisions/current/health plusping); streaming methods (subscribe,publish,cxp.subscribe, …), legacy aliases andauth.authenticateareexpose=False; with no schema the tool'sinput_schemais{"type":"object"}and the gateway validates. The report lists every method with itsmutatesandexposevalues. FILES gains: src/orchestrata_ultimate/gateway/method_table.py and method_spec.py (new, one class each), therpc.pyrefactor; tests/gateway/test_method_table.py. ThemcpSDK is confirmed as a new dependency (uv add mcp).
WHAT CODETOOLS ALREADY PROVIDES (read, do not reimplement in codetools):
codetools deploy render-mcp --prefix /opt/codetoolswrites/opt/codetools/mcp/codetools.mcp.json={"mcpServers":{"codetools":{"command":"/opt/codetools/bin/codetools","args":["mcp","serve"]}}}; every promote renders it.codetools deploy verify-identity --prefix /opt/codetools --expect-commit <sha> --mcp-json <flat {"mcpServers":{...}} document>adds themcp-registrationcheck: fails when the registered args are notmcp serve, the command does not resolve, or its realpath is outside realpath(/opt/codetools/current).- The approval contract to copy (contract, not code; orchestrata cannot import codetools, codetools depends on orchestrata by ADR-0037 rule 6): mutating tool without
approval_token→ error result{"message":"PERMISSION_DENIED","capability":<tool>,"ledger_row":<id>}and nothing runs;approve {tool, reason}→secrets.token_urlsafe(24), bound to that tool, single use, 300 s TTL; reuse, forgery, cross-tool use, approve on a read-only tool, unknown tool → refused with a ledger row; the token never appears in the ledger.
FILES (repo-relative): Create:
- src/orchestrata_ultimate/gateway/mcp_transport.py (
McpTransport: builds the tool table from the gateway's handler table at start — the registry ingateway/handlers/andhttp_surface.pyis the source; nothing hand-listed; each tool records whether it mutates; runs each call over a WebSocket JSON-RPC connection to the running gateway at the deployed address (ADR-0099 rule 1), never in-process) - src/orchestrata_ultimate/gateway/mcp_approval_gate.py (
McpApprovalGate, the contract above) - src/orchestrata_ultimate/gateway/mcp_ledger.py (
McpLedger, ndjson at$ORCHESTRATA_MCP_LEDGERor~/.local/state/orchestrata/mcp-ledger.ndjson) - src/orchestrata_ultimate/gateway/mcp_tool_spec.py (
McpToolSpec.from_handler) - an entry point
orchestrata-mcp(pyproject[project.scripts]) that runs the stdio server; deployed bydeploy/install-orchestrata.shinto/opt/orchestrata/bin/orchestrata-mcpthrough the existing wheel install, so it resolves under/opt/orchestrata - tests/gateway/test_mcp_transport.py, test_mcp_approval_gate.py, test_mcp_ledger.py, test_mcp_registration_installer.py (real pipe through the MCP SDK client, real gateway on a test port, real files; no doubles)
- specs: the gateway spec (find it in specs/ by the handler table's existing REQ ids) gains REQ ids for generated tools, approval parity, deployed registration, identity; one BDD scenario each with steps Modify:
- deploy/install-claude-config.sh: after the hooks step, (1) run
/opt/codetools/bin/codetools deploy render-mcp --prefix /opt/codetoolswhen/opt/codetools/currentexists; (2) registercodetoolsandorchestrataat user scope through the Claude CLI, idempotently (claude mcp remove <name> -s userwhen present, thenclaude mcp add-json -s user <name> '<json>'), never by editing~/.claude.jsonby hand; (3) write the flat registration document~/.claude/mcp-registered.jsonfromclaude mcp get <name>for both names; (4) runcodetools deploy verify-identity --prefix /opt/codetools --expect-commit $(cat /opt/codetools/VERSION | …) --mcp-json ~/.claude/mcp-registered.jsonand the orchestrata equivalent check (the registeredorchestrata-mcpcommand's realpath is under/opt/orchestrata); any failure fails the install - deploy/install-orchestrata.sh --verify: the identity verifier gains the
orchestrata-mcpresolution check - scripts/sync_architecture_to_graph.py: the
--rebaseline-idpath logsREBASELINED: <id> <before> -> <after> under <authority>with the two hashes (today it prints the docs file name); one line, its own commit, with the existing test updated Read-only: docs/adr/0038 (in codetools:/home/mohamed/workspace/orchestrata.codetools/docs/adr/0038-*.md), codetoolssrc/codetools/mcp/,src/codetools/verbs/,src/codetools/deploy/mcp_registration.py,src/codetools/deploy/identity.py(the contract to mirror), orchestratadeploy/install-claude-config.sh,deploy/install-orchestrata.sh,gateway/handlers/,gateway/http_surface.py.
CONSTRAINTS:
- Dependency: if the
mcpSDK is not already in orchestrata's lock, add it withuv add mcp(the same package was confirmed for codetools today, mcp 2.3 withmcp_types); record it in the report. No other new dependency. - The gateway's handler table is the single source; a handler added later appears as a tool with no edit to the transport. Prove it with a test that registers one extra handler and lists tools.
- Read-only handlers (review, test-review, diagram read, discover, graph query, daemon query) need no token; anything that writes, dispatches or spends tokens does.
- The deployed Claude config is installed from a pinned ref; do not edit
~/.claudein place; test the installer against a temp--prefixand a scratch user-scope registration name (codetools-test, removed in teardown). - Tests run observed (
pytest-live),timeout 300, targeted modules, basetemp/var/tmp/ct-mcpgw/<name>: never /tmp, never under the worktree, never a dot-directory. Run every command asdirenv exec <worktree> uv run …;direnv allowonce;uv sync --frozenfirst. - The post-edit hooks are the ADR-0097 publishing hooks (deployed 16:45); an edit costs under a second at the hook and the daemon publishes in the background.
- Commits by Mohamed only, through the real pre-commit (no --no-verify), no footers; never commit
plans/ast-*.jsonordocs/CODEBASE_MAP*. Do not ship, push, install or restart anything; the coordinator deploys. - ADR-0036 rule 1: targeted evidence under five minutes per run; no full gates run.
DONE WHEN:
orchestrata-mcpover a real pipe lists one tool per gateway handler plusapprove; a mutating call without a token is refused with a ledger row and nothing reaches the gateway; with a token it runs once; the reused token is refused.- The extra-handler test shows the new tool with no transport edit.
bash deploy/install-claude-config.sh --prefix <tmp>registerscodetools-testandorchestrata-testat user scope, writes the flat document, and the identity checks pass; pointing the registration at a foreign path fails exactly the registration check.- The
--rebaseline-idlog line carries both hashes (test updated). - ruff, mypy on changed files, one class per file, coverage ≥ 90 on new modules, targeted tests and BDD green, clean tree, every commit through pre-commit.
Report back with: the generated tool table (name, mutates, approval), the approval flow transcript, the installer's registration lines, the identity lines, REQ ids, commit shas, and anything that stopped you. Observed numbers only; no "should".