Spec: Integrated MCP Server — Tools, Sessions & Result Shaping¶
File: .ai/specs/core-engine/2026-07-02-mcp-tools.md Status: Approved (implemented at PR-041/042 — deviations in Changelog v0.2) Owners: API lane; reviewers: app-layer lane (ai-assistant consumer) Related: PR-041/042/056; query-language spec (findRecords subset); grpc-contracts spec (auth doctrine); Task 12a/12d
1. Overview¶
Defines the MCP server built into the Telha Core binary: tool schemas and their phase availability, session authentication and tenant binding, result shaping for LLM consumption, and the spec-resource exposure that lets the system read its own design manuals.
2. Problem Statement¶
MCP tools are an API surface consumed by a language model on behalf of a human — the failure modes differ from REST: oversized results blow context windows; ambiguous tool descriptions cause wrong-tool selection; and an unauthenticated or mis-scoped MCP session is a tenant-isolation hole with a natural-language interface.
3. Proposed Solution¶
rmcp server, streamable HTTP transport, in-process next to REST/gRPC. Session auth: MCP client presents the same signed-token scheme as gRPC (grpc-contracts spec, audience=mcp); the session binds one TenantScope at initialize and every tool call inherits it — tools accept no tenant parameters, ever. Tools return shaped results: hard caps on rows (default 50) and per-field text (500 chars with ellipsis + full-fetch hint), counts always included so the model knows what was truncated. Tools not yet implemented register with informative errors naming the phase.
4. Architecture¶
ai-assistant (app layer) ↔ MCP over streamable HTTP ↔ tool adapters (thin, no logic) ↔ engine APIs. RBAC (which user may invoke which tool) lives in the app layer per PR-042; core enforces tenancy only — defense in depth, single-responsibility per wall.
5. Data Models¶
Tool inventory and availability:
| Tool | Wraps | Available |
|---|---|---|
| createRecord | records create | Phase 2 |
| findRecords | query DSL subset (no cursor, limit ≤ 50, where/atValidTime/expand depth ≤ 2) | Phase 2 |
| getHistory | version history | Phase 2 |
| getSchema | schema_as_of | Phase 2 |
| semanticSearch | vector query | Phase 2 |
| compareSnapshots | delta engine | ACTIVE (PR-053) |
| generate | plan+generate+verify | Phase 3 (PR-051/052) |
| getTrace | trace fetch | Phase 3 (PR-052) |
Resource: specs:// exposes .ai/specs/**/*.md read-only (Task 12a self-verification). Tool descriptions are normative text in this spec's appendix-by-reference (kept in code, snapshot-tested against this table).
6. API Contracts¶
JSON Schemas per tool pinned in /proto/mcp/ (yes, alongside proto — one contracts directory) and snapshot-tested. Errors: tenancy failures return MCP errors without partial data; validation failures echo the query-language pointer format so the model can self-correct. compare/generate/trace pre-Phase-3: {error: "available_in_phase_3", hint}.
7. UI/UX¶
Result shaping IS the UX here: tabular shaping for compareSnapshots (added/removed/modified sections), claim-grouped shaping for getTrace. The chat trace viewer (PR-056) consumes getTrace's shaped form directly.
8. Configuration¶
mcp.enabled, mcp.addr, mcp.max_rows (50), mcp.max_field_chars (500), mcp.specs_resource (on).
9. Alternatives Considered¶
MCP server in the app layer proxying core (rejected: Task 11 mandates integration in the core binary; also keeps deep-tech tools available to non-Mercato MCP clients). Tenant as tool parameter (rejected outright: a model-supplied tenant id is an injection vector; session binding only). Unbounded results with client-side truncation (rejected: context blowouts are a server-side responsibility when the client is an LLM). stdio transport (rejected for the primary path: the consumer is a web app; streamable HTTP fits; stdio may be added for local dev later without spec change — transport is not semantics).
10. Implementation Approach¶
PR-041: this spec → rmcp server + session auth → Phase-2 tools + shaping → phase-gated stubs → specs resource → conformance + isolation tests. PR-042 consumes; PR-056 activates Phase-3 shaping.
11. Migration Path¶
Greenfield. Tool schema evolution: additive parameters only within a tool version; breaking changes ship as toolName_v2 alongside the old for one release.
12. Success Metrics¶
Two-session isolation test (different tenants, identical calls, zero cross-visibility). Truncation test: oversized results shaped within caps with accurate counts. Snapshot tests: tool schemas and descriptions match this spec's table. Scripted-chat e2e (PR-042): the four canonical operator scenarios resolve via correct tool selection.
13. Open Questions¶
- OQ-1: Should findRecords allow vector clauses, overlapping semanticSearch? Stance: no — one tool per intent aids model tool-selection; semanticSearch owns vectors, findRecords owns structured queries; revisit if traces show models fighting the split.
14. Changelog¶
-
2026-07-04 — v0.4: getTrace claim-grouped shaping (PR-056 core leg; §7 "claim-grouped shaping for getTrace" now implemented, not just passthrough). Generation traces return {traceId, verification, question, draft, models{generate,embed,decompose}, counts{claims,supported,partial,unsupported}, claims:[{text, verdict, score, citedMarkers, evidence:{sourceId, sourceVersionTx, start, end, excerpt}|null}], planSummary{spans,budget,strandedBudget}, truncatedFields?} — evidence excerpts resolved server-side via the ingestion slice API, field caps applied to the whole body. Query traces keep the generic envelope. The chat trace viewer (PR-056 app leg) consumes this shape directly.
-
2026-07-04 — v0.3: compareSnapshots ACTIVATED (PR-053) — the full eight-tool suite is live. Flat coordinate params (baselineValidTime/baselineTxTime/comparisonValidTime/comparisonTxTime, RFC3339 or µs) + find/where/limit; wraps the same executor as POST /v1/compare. Output per §7: counts + three shaped sections (added/removed/modified with per-property old→new), field caps applied,
moreAvailable+ a paginate/narrow hint (the tool itself exposes no cursor, matching findRecords posture). Tool schemas regenerated (proto/mcp/tools.json). - 2026-07-02 — v0.1: initial draft for peer review.
- 2026-07-03 — v0.2: implementation notes recorded at PR-041/042 landing. (1) Key reuse: MCP sessions authenticate with the same signing key as gRPC (
grpc.token_key) under a distinct audiencemcp— one keyset to rotate; audience separation provides surface isolation. The listener stays down until the key is configured. (2) Config: §8 gainsmcp.specs_dir(thespecs://resource needs a path when the binary's working directory isn't the repo root; default./.ai/specs). (3) Result shaping: tools return one text block of compact JSON — an envelope{returned, moreAvailable, truncatedFields?, hint?, <rows>}with per-field truncation atmax_field_chars(char-boundary safe,…suffix) and a full-fetch hint naming RESTGET /v1/records/{id}. (4) semanticSearch takes a requiredlabel(the DSL'sfindis label-scoped) and, until PR-040 activates the vector stage, relays the executor's explicit Unimplemented as a tool-level error naming the activating PR — the tool starts working with zero changes when PR-040 lands. (5) findRecords enforces the §5 subset post-parse (expand depth ≤ 2 with a pointered error; limit clamped tomax_rows; no cursor parameter). (6) Schemas pinned as one file/proto/mcp/tools.json(inventory + descriptions + input schemas), snapshot-tested; regenerate viaUPDATE_MCP_SCHEMAS=1 cargo test -p telha-core --lib mcp. (7) Session binding: scope binds atinitializevia aOnceLockon the per-session handler; a mid-session request carrying a valid token for a different tenant is rejected (invalid_request) — covered by the conformance suite. (8) PR-042 consumer: app-layer RBAC is deny-by-default (viewer: read tools; editor: +createRecord; admin: all) and the allowlist filters tool discovery, not just execution; audit rows land in PGai_tool_invocations.