Skip to content

feat: ana mcp — read-only, provenance-stamped MCP server over scan + proof chain #303

Description

@rpatricksmith

Summary

Add ana mcp — a read-only MCP server over scan.json + the proof chain, as a long-running stdio subcommand of the existing anatomia-cli binary (not a separate package). It exposes a small set of task-shaped, provenance-stamped tools so any MCP-capable agent (Cursor, Cline, Zed, Claude Desktop, Claude Code) can query Anatomia's ground truth — and so the Claude plugin's .mcp.json and the MCP Registry listing have a target.

This is the "one binary, three channels" model RepoWise uses: their server.json runs repowise mcp <path> --transport stdio — the MCP server is the CLI in a different mode. We mirror it with ana mcp.

Listing on the MCP Registry is a separate issue (procedural/GTM, DNS auth, blocked-by this build). This issue is the code.

Why it matters

  • Reaches every non-Claude MCP agent — the literal "extend to other CLI agents cheaply" constraint (project-context.md).
  • Carries the "Verified Context" wedge (COMPETITIVE_RESEARCH_repowise.md §5): our answers carry a provenance stamp competitors structurally cannot — "verified 2026-06-01 by proof entry X" vs "inferred."
  • Unblocks the plugin's .mcp.json block.

Recommended tool surface (all read-only; reuse existing readers)

Tool Args Reuses Returns
get_scan_context files? scan.json reader (check.ts:672), projecting stack/conventions.*/patterns.* house style + stack for those files (our conventions wedge)
query_findings files getProofContext(files, root) verbatim (proofSummary.ts:1198) active proof findings touching those files, stamped
get_proof slug slug lookup (proof.ts:822) over proof_chain.json one proof entry: contract, assertions, result, findings
should_i_touch files composite of the above (+ blast-radius once #293/#297 land) "before you edit" digest; degrades gracefully today

Ship get_scan_context, query_findings, get_proof in v1; should_i_touch as a thin composite (enriches later).

Provenance-stamp design (the wedge — and the honesty rule)

Every answer carries:

provenance: {
  status: "verified" | "detected" | "inferred",
  source: "proof_chain" | "scan",
  entry?: string,        // present iff verified — the proof slug
  verified_at?: string,  // entry.completed_at
  confidence?: number    // for scan conventions
}
  • proof-chain answers (query_findings, get_proof) → status:"verified", source:"proof_chain", entry, verified_at (honest because the entry passed an independent verify stage: result:'PASS' + assertions[].status, proof.ts:127,133).
  • scan conventions → status:"detected" with the existing confidence. Never stamp scan data as verified — that hollows out the word the product is built on (COMPETITIVE_RESEARCH_repowise.md §3.3/§9.2).

Integration points (verified)

  • New src/commands/mcp.ts — structured like _capture.ts (process-lifecycle, stdout discipline) but a normal long-running command. Uses @modelcontextprotocol/sdk McpServer + StdioServerTransport; registerTool(name, {description, inputSchema: zodSchema}, handler). Resolve root via findProjectRoot() (validators.ts:169) — no repo_path arg needed (cleaner than RepoWise, matches every other ana command).
  • src/index.ts — register in the INTELLIGENCE group (near proof/learn, index.ts:75-78); NOT hidden (unlike _capture at :82).
  • Dependency: add @modelcontextprotocol/sdk (^1.29.x) to packages/cli/package.json:57. zod is already ^4.3.6 (SDK peer ^3.25 || ^4.0 — compatible, no bump). The SDK transitively pulls express/hono/jose/cors (HTTP-transport deps); verify tsup tree-shakes the unused HTTP path so the global install doesn't balloon — mark them external if not.
  • mcpName in package.json: add "mcpName": "dev.anatomia/anatomia" (registry link; must equal server.json name — handled in the listing issue).

Phasing

Gotchas

  • Read-only is absolute. Never mutate .ana/*, never call scanProject() (no re-scan), never spawn the pipeline. Surface scannedAt/verified_at; if stale, that's the user's ana scan problem.
  • stdout is JSON-RPC — no stray logging. Any console.log corrupts the stream (inverse of normal ana commands). All diagnostics → stderr. Audit read paths that log (e.g. check.ts:689).
  • Stateful-server test strategy: follow the _capture spawn-based harness (tests/commands/_capture.test.ts) — spawn dist/index.js mcp, write one JSON-RPC initialize + tools/call to stdin, read one framed response, assert, proc.kill(). Use a tmp project with fixture .ana/scan.json + .ana/proof_chain.json. Unit-test the pure handlers directly + one spawn smoke test. (Note: the real CI matrix is ubuntu × Node 22/24, test.yml:23 — the "3 OS" line in project-context.md is stale; lower flakiness risk. Coverage thresholds 80/75/80/80, vitest.config.ts:26; consider excluding the connect/transport glue.)
  • Provenance honesty — never stamp scan-detected conventions as verified.
  • Bundle weight — confirm the unused HTTP transport is tree-shaken.

Acceptance criteria

  1. node dist/index.js mcp --transport stdio responds to JSON-RPC initialize; tools/list returns exactly the v1 tools.
  2. tools/call query_findings {files:[…]} returns the same data as ana proof context <files> --json (reuses getProofContext), each finding stamped status:"verified", source:"proof_chain", entry, verified_at.
  3. get_scan_context conventions carry status:"detected" (never "verified") with the scan's confidence; an assertion enforces no scan-sourced field is stamped verified.
  4. The server performs zero writes under .ana/ and never invokes scanProject (spawn test against a read-only fixture confirms no files change).
  5. Nothing on stdout except framed JSON-RPC (clean round-trip parse, no leading/trailing noise).
  6. pnpm --filter anatomia-cli build && pnpm vitest run passes, test count not decreased, dist/index.js builds cleanly with the SDK bundled, existing ana scan/ana proof unchanged.

Effort

~2–3 engineer-days. The tools are thin wrappers over getProofContext, the slug lookup, and a scan.json reader — real reuse. Cost is SDK + stdio wiring, the provenance-stamp layer, the stdout-discipline audit, bundle verification, and the spawn-based test harness.

Sequencing

Plugin ships first (its .mcp.json is added when this lands). Design tools to degrade gracefully now and enrich when #293/#297/#301 populate the dormant scan fields. Per the research, the MCP server should come after #295 proves ambient-context value — this unlocks non-Claude agents but isn't the most urgent distribution move.

Metadata

Metadata

Assignees

No one assigned

    Labels

    developer-experienceCLI UX, error messages, ergonomicsfeature-requestNew capabilityplatform-supportMulti-platform pipeline support (Codex, Copilot, Cursor)

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions