Skip to content

ADR-010: MCP Trace Tools Reuse Route Handlers In-Process

Status: Accepted Date: 2026-06-03 Deciders: Heiko Sundermann, AI dev-lead Context: Studio MCP Trace-Inspection Server (docs/superpowers/specs/2026-06-02-studio-mcp-trace-server-design.md), PR-2


Context and Problem Statement

The MCP trace-inspection server exposes the 14 studio-trace capabilities as native MCP tools. The tools must return data identical to the existing backend routes (UBER-AC-1 parity) with zero duplicated trace logic (UBER-AC-3). Two implementation realities surfaced during PR-2 that are non-obvious enough to record:

  1. How tools reach the trace logic. Options were: (a) call the existing FastAPI route handler functions in-process, (b) self-HTTP back to /api/v1/…, or (c) extract a shared trace_inspection service the routes and tools both call.
  2. Calling route handlers in-process bypasses FastAPI's response_model. list_projects declares response_model=ProjectListResponse, which strips the secret github_token field on the HTTP path. An in-process call returns the raw Project ORM rows — secret included.

Decision

1. Tools call the route handler functions directly, in-process (option a), with an injected AsyncSession. Tools are thin adapters: arg-map → route call → result-shape → error-map. They never construct queries or import ORM models (enforced by tests/mcp/test_no_requery.py, T-AC-9.1). Service extraction (option c) is explicitly deferred (spec NG1 / R-1) — the web-layer coupling is accepted and isolated in the adapters; parity tests catch route-signature drift.

2. Adapters whose route returns secret-bearing ORM rows MUST re-project through the route's published response model before shaping. list_projects_tool projects through ProjectListResponse(...).model_dump(by_alias=True) so the MCP output equals the route's published HTTP/CLI body (camelCase, no github_token). The other 13 tools return route dicts verbatim because their routes carry no response_model (handler return == HTTP body).

3. The to_structured result-shaping (artifact A4) is a verbatim pass-through. The spec originally described it as returning a (content, meta) tuple that lifts tokens_estimated/truncated/hint into a separate MCP _meta channel. We collapsed it: those keys already live inside the route's meta dict, which rides along in the verbatim structured content, so a second channel added complexity without adding access.


Consequences

Positive: - Zero logic duplication; one source of truth for trace shaping (the routes). - Parity is structurally guaranteed for the 13 verbatim tools. - The secret-projection rule is a clear, testable invariant (tests/mcp/test_list_projects_security.py).

Negative / risks: - Tools are coupled to route-handler signatures (R-1, accepted). Mitigated by the data-driven parity test (tests/mcp/test_mcp_parity.py). - Asymmetry: one adapter (list_projects) re-projects while 13 pass through. Any future tool wrapping a route that returns ORM rows with sensitive fields MUST follow the §2 rule, or it will leak. This ADR is the durable reminder.


Alternatives Considered

  • Self-HTTP hop: rejected — extra latency, re-auth, and a network dependency for an in-process capability.
  • Service extraction: deferred (NG1) — larger refactor of the fat observations.py; a future spec may extract trace_inspection.
  • Per-tool manual field allow-lists: rejected in favour of reusing the routes' own response_model, so the MCP surface tracks the published contract automatically.