feat(guide): RAC Guide MCP server, onboarding, and grounding demo [roadmap:v0.10.x] - #66
Conversation
….10.0] Implements rac/roadmaps/v0.10.x-guide/v0.10.0-guide-foundation.md. Stands up the Guide MCP server as a new in-process consumer of RAC Core (ADR-031): a FastMCP application factory bound to a repository root, with the four read-only tools the guide-tool-surface design pins — - get_artifact: resolve one artifact, add its full Markdown content - search_artifacts: rac find semantics with an optional type filter - get_related: outgoing sections plus incoming references, filtered at the consumer boundary (presentation only) - get_summary: the portfolio summary, unchanged Tool descriptions ship verbatim from the design. Responses serialize the same to_dict contracts the CLI emits, are deterministic, and re-read the repository on every call (no cache, no session state; ADR-032). Adds: - rac.mcp.server: build_server / run_server, tool registration - rac.mcp.budget: shared per-response character budget (default 10,000, configurable at startup) with whole-item truncation and the pinned marker fields (ADR-033) - rac.mcp.errors: structured not-found / duplicate results as data (ADR-034) - mcp>=1.0 as a base dependency (ADR-029)
Adds `rac mcp [--root PATH]`, defaulting to the current directory and serving over stdio (ADR-029). A non-directory --root exits 2; a clean client disconnect exits 0. The MCP SDK is imported lazily inside the command so the base CLI pays no import cost for other commands, and stdout is left to the MCP protocol with diagnostics on stderr.
Adds the `mcp` CI battery (ADR-027) and three test files: - test_mcp_server: factory construction, verbatim descriptions, CLI registration, --root handling, exit codes - test_mcp_tools: contract tests pinning all four tool output shapes, truncation boundaries, and error shapes against fixture corpora; equivalence assertions that tool payloads match the corresponding CLI JSON output; a freshness test that edits a fixture between calls (ADR-032) - test_mcp_isolation: AST battery asserting rac.core / rac.services never import mcp or rac.mcp, and that the server layer imports no write-capable service (ADR-031) Adds fixture corpora under tests/fixtures/mcp/ with canonical IDs and resolvable cross-references for the outgoing/incoming and duplicate cases.
…0.1] Implements rac/roadmaps/v0.10.x-guide/v0.10.1-guide-onboarding.md. Adds _check_corpus() to run_server(): emits a one-line diagnostic to stderr when the repository root contains no recognized artifacts, so the first run against a misconfigured or empty root fails visibly. stdout belongs to the MCP protocol; only stderr carries diagnostics. The server still starts and get_summary still returns the empty-state summary — absence of artifacts is not a fatal error.
Implements rac/roadmaps/v0.10.x-guide/v0.10.1-guide-onboarding.md. Adds examples/guide/ — a self-contained RAC corpus with its own repository key (GUIDE) containing one requirement, one decision, one design, and one roadmap, connected through their relationship sections. The decision artifact (ADR-001: Soft-Delete User Records) meets the true-to-life bar required by the guide-grounding-demo design: a real team decision whose naive violation (writing DELETE instead of setting deleted_at) is the natural implementer mistake, with named technologies (PostgreSQL) and a plausible business context (Meridian user service). Corpus passes rac validate and rac relationships --validate. Extends test_dogfood.py to gate the corpus in CI so regressions fail the dogfood battery.
Implements rac/roadmaps/v0.10.x-guide/v0.10.1-guide-onboarding.md. Adds docs/mcp.md: full onboarding path covering install, configuration for three clients (Claude Code, Claude Desktop, Cursor), pointing Guide at a repository, the first grounded question, troubleshooting (server not listed, wrong root, empty corpus). Updates README.md with a Guide section: what Guide is in two sentences, the three client configuration blocks, and pointers to examples/guide/ and docs/mcp.md. Configuration blocks are written from each client's documented config format. In-environment verification was performed for the MCP stdio handshake (the server starts, get_summary returns correct JSON, and _check_corpus emits no spurious warning against a populated corpus). Full client-level verification (Claude Desktop, Cursor) requires a human reviewer with those clients installed; each block carries a TODO comment marking the open verification step.
Add the Meridian user-service code slice the grounding demo runs against: a repository layer with the soft-delete patterns visible and a delete() stub whose naive implementation (hard DELETE FROM users) violates ADR-001. Lives under examples/guide/task/, outside the corpus, so it does not affect examples/guide/rac/ validation or relationship checks. Implements rac/roadmaps/v0.10.x-guide/v0.10.2-guide-grounding-demo.md.
Add examples/guide/demo.md: the verbatim task prompt (identical for both runs, no tool coaching), the ungrounded and grounded run setups with their expected observable behaviour, the Claude Code client configuration step, the 10-run measurement protocol (pass at >= 8 decision-ID citations) with an auditable run-log template, the <= 90-second recording shot list with accessibility notes, and the human-only release-tail checklist (measurement, recording, registry submissions, announcement) left explicitly pending. Implements rac/roadmaps/v0.10.x-guide/v0.10.2-guide-grounding-demo.md.
Pin the mechanics the grounded run depends on: the natural task keywords (delete user, delete, soft-delete) surface ADR-001 through find_artifacts (the search_artifacts path), the decision resolves with its prohibition content, and it carries the related artifacts get_related shows. Guards the demo against a corpus rename or description change silently breaking it. Implements rac/roadmaps/v0.10.x-guide/v0.10.2-guide-grounding-demo.md.
Adds a manually triggered workflow that mirrors the release pipeline (test gate, build, trusted publishing) against TestPyPI, so release builds can be exercised end-to-end without publishing to PyPI. The build strips the setuptools-scm local version segment, which PyPI-family indexes reject, keeping rehearsal uploads unique per commit.
Test-installing the Guide build from TestPyPIOnce the Test Publish (TestPyPI) workflow has been run against this branch (Actions tab → Test Publish (TestPyPI) → Run workflow → select 1. Find the rehearsal versionThe exact version string is printed in the run's Build rehearsal distributions step (e.g. 2. Install into a clean environmentpython -m venv ~/rac-rehearsal && source ~/rac-rehearsal/bin/activate
pip install --index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/ \
"requirements-as-code==<rehearsal version>"The 3. Smoke-check the server binaryrac --version
rac mcp --root /nonexistent ; echo "exit: $?" # expect usage error, exit 24. Wire it into Claude Code and test against the examples corpusUse the venv's absolute claude mcp add rac-guide -- ~/rac-rehearsal/bin/rac mcp --root /path/to/clone/examples/guide
claude mcp list # rac-guide should be listed and connectedIn a Claude Code session, 5. While you're there (release-gate two-for-one)A successful step 4 is the v0.10.1 "verified against Claude Code" checkbox — note the client version ( Cleanupclaude mcp remove rac-guide
deactivate && rm -rf ~/rac-rehearsalRemember |
Documents the router pattern: standing agent guidance recorded as a validated RAC prompt artifact and imported from CLAUDE.md, mirroring the dogfood repository convention.
…v0.10.0] Collapse get_related from four corpus walks to one. A single walk_corpus snapshot feeds index_from_corpus and relationships_from_corpus; outgoing and incoming edges are filtered from the Core-computed references rather than re-resolving per declared reference, keeping resolution Core-owned (ADR-031). The whole response now reflects one atomic snapshot. Payloads are unchanged. Implements rac/designs/guide-tool-surface.md.
…:v0.10.0] _read_content could raise (file deleted between walk and read, permissions, non-UTF-8) and the exception escaped to the protocol. get_artifact now catches OSError and UnicodeDecodeError and returns a structured unreadable error serialized through the budget like every response, so the agent recovers from data, not an exception (ADR-034). Implements rac/designs/guide-tool-surface.md.
get_summary has no truncatable field; its over-budget branch reused HINT_CONTENT, which tells the agent to request a more specific artifact — wrong for a whole-repository overview. Add HINT_SUMMARY and use it in that branch. Also correct the _truncate_content docstring: the shrink is a deterministic binary search for the largest fitting prefix. Implements rac/designs/guide-tool-surface.md.
…10.0] Add coverage for the new and pre-existing budget/error behaviour: - unreadable artifact returns the structured unreadable shape (OSError and UnicodeDecodeError), via monkeypatched read so it is privilege-independent - get_summary over budget carries the new HINT_SUMMARY marker - the budget counts characters, not UTF-8 bytes (multibyte content and matches) - over-budget envelope fallbacks for the list and content paths stay marked - type filter combined with truncation - ambiguous reference produces no incoming edge in get_related - an artifact with no relationships yields an empty outgoing object - get_related performs exactly one corpus walk (spy regression for the one-snapshot refactor)
ADR-033: note that the budget counts characters of the serialized JSON, so the UTF-8 byte length on the wire may exceed it for non-ASCII content — accepted to keep the unit deterministic across models. guide-tool-surface: note that FastMCP wraps the serialized string in a structuredContent envelope, which is not part of the v1 contract (the text content block is). Implements rac/decisions/adr-033-guide-response-budget.md.
Summary
Implements the v0.10.x Guide series:
rac/roadmaps/v0.10.x-guide/v0.10.0-guide-foundation.md,v0.10.1-guide-onboarding.md, andv0.10.2-guide-grounding-demo.md.Adds:
rac mcp [--root PATH]— a stdio MCP server that serves RAC repository knowledge to coding agents, shipped inside the existing package as a standard dependency (mcp>=1.0)get_artifact,search_artifacts,get_related,get_summary— with descriptions shipped verbatim from theguide-tool-surfacedesigntruncated/omitted/hintmarkersdocs/mcp.md(per-client configuration, team CLAUDE.md routing setup, troubleshooting), and configuration blocks for Claude Code, Claude Desktop, and Cursorexamples/guide/(one connected requirement, decision, design, roadmap under its ownGUIDErepository key) wired into the dogfood CI gateexamples/guide/demo.md— a stranger-runnable contrast script with measurement protocol, run-log template, and recording shot listmcpCI battery (server, tool contracts, truncation boundaries, error paths, isolation) plus dogfood coverage of the examples corpus and demo searchabilitytest-publish.yml) mirroring the release pipeline, so builds can be exercised end-to-end without publishing to PyPIrac/assets/images/(ADR-019 asset convention)Roadmap / ADR Trace
Roadmap:
rac/roadmaps/v0.10.x-guide/v0.10.0-guide-foundation.mdrac/roadmaps/v0.10.x-guide/v0.10.1-guide-onboarding.mdrac/roadmaps/v0.10.x-guide/v0.10.2-guide-grounding-demo.mdRelevant ADRs:
rac/decisions/adr-029-guide-delivery-surface.md— stdio only, standard dependency,rac mcpsubcommandrac/decisions/adr-030-guide-tools-only-surface.md— exactly four tools, no resources or promptsrac/decisions/adr-031-guide-in-process-core-consumption.md— in-process service consumption, read-only by constructionrac/decisions/adr-032-guide-stateless-reads.md— every call re-reads the repository, no cachingrac/decisions/adr-033-guide-response-budget.md— character budget and truncation contract (clarified in this PR: the unit is characters of serialized JSON; UTF-8 wire bytes may exceed it for non-ASCII content)rac/decisions/adr-034-guide-agent-reasoning-boundary.md— no judgment-shaped toolsrac/decisions/adr-007-json-contract-stability.md,adr-015-explorer-as-consumer.md,adr-019-asset-management.md,adr-026-opaque-artifact-identities.md,adr-027-ci-test-topology.mdDesigns:
rac/designs/guide-tool-surface.md(pinned tool surface; gains theunreadableerror shape and astructuredContentscope note in this PR),rac/designs/guide-grounding-demo.md(demo specification).Scope
Included
src/rac/mcp/package:server.py(FastMCP factorybuild_server(root, budget)andrun_server),budget.py,errors.py; only this package imports the MCP SDKget_summaryreports the empty stateget_relatedcallexamples/guide/task/(minimal Meridian user-service slice with thedelete()stub) andexamples/guide/demo.mdtest-publish.yml, a workflow_dispatch rehearsal of the publish pipeline targeting TestPyPI via trusted publishing (strips the setuptools-scm local version segment, which PyPI-family indexes reject)rac/assets/images/, referenced from the README topExcluded
get_relatedcall is per-call onlysearch_artifactspinsfind_artifactsmetadata-field matching only (design open question, deferred)build_serverstartup parameter onlyuvxetc.); the documented path ispip installv1.2-mcp-serverstub, superseded by ADR-030structuredContent: tools return serialized JSON text (the pinned contract); FastMCP'sstructuredContentenvelope is explicitly out of contract in v1 (noted in the design)Product / Architecture Decisions
get_summaryreturnsPortfolioSummary.to_dict()unchanged, including thehealthblock: the design's prose pins "the same contractrac portfolio --jsonemits"; its illustrative JSON had merely elided the field. An equivalence test asserts byte-for-byte parity with the CLI.get_relatedserves resolution, outgoing, and incoming from ONE corpus walk per call: the response is an atomic snapshot of repository state (no mid-call drift), and incoming edges filter Core's ownresolved_path— the server invents no matching semantics (ADR-031). Caching across calls remains forbidden (ADR-032); the walk count is pinned by a regression test.{"error": "unreadable", "id": ..., "path": ...}body — an additive contract change documented in the design — keeping the "errors as data, never protocol exceptions" rule airtight.ResolutionResult.to_dict()for the failure outcomes, so Guide errors cannot drift fromrac resolve --jsonerrors.truncatedmarker is absent (notfalse) on complete responses; marker names and placement are pinned by contract tests. Each tool's truncation hint is standalone prose;get_summaryhas its own (raise the server budget), since "request a specific artifact" made no sense there.raccommand avoids its import cost.get_summaryreports zero artifacts).User-Facing Contract
CLI
Human Output
JSON Output
All tool payloads carry
"schema_version": "1". Shapes reuse existingto_dictcontracts (SearchResult,PortfolioSummary,ResolutionResult);get_artifactaddscontent(file text exactly as stored);get_relatedaddsoutgoing/incoming. Error bodies:not-found,duplicate(resolver shapes), andunreadable(resolved but unreadable file, withidandpath). Truncated responses add:{ "truncated": true, "omitted": 12, "hint": "Narrow the query or request a specific artifact ID." }Exit Codes
0: clean server shutdown (client disconnect)2: usage error (--rootis not a directory)Verification
Ran
Plus a live stdio smoke test: an
mcpSDK client spawningrac mcpexercised all four tools against the dogfood corpus (219 artifacts) and the examples corpus (4 artifacts), including the not-found error path;get_relatedoutput was verified identical before and after the one-walk refactor. The setuptools-scmno-local-versionoverride used by the rehearsal workflow was verified locally (0.1.dev64+gb6cf33a8ebuilds as0.1.dev64).Covered
{}get_relatedcall (regression-pinned)rac.core/rac.servicesimport neithermcpnorrac.mcp;rac.mcpimports no write-capable service--roothandling, both exit codesget_summary, no spurious notice on populated corporadelete user,delete,soft-deleteeach surface the demo decision as the top decision match)Review Path
src/rac/mcp/server.py— the four tools, the consumer boundary, the one-walk snapshotsrc/rac/mcp/budget.py,src/rac/mcp/errors.py— the pinned response mechanicssrc/rac/cli.py,pyproject.toml,.github/workflows/tests.yml— delivery surface and battery registrationtests/test_mcp_tools.py,test_mcp_server.py,test_mcp_isolation.py— the contract pinsexamples/guide/— corpus, task, anddemo.mdREADME.md,docs/mcp.md— onboarding, team routing setup, header banner.github/workflows/test-publish.yml— release rehearsal toolingNotes For Reviewer
examples/guide/demo.mdends with the human-only release-tail checklist: the 10-run measurement (gate: at least 8 correct decision-ID citations), the 90-second recording, registry submissions, and the announcement. None of these is pre-filled or faked.test-publish.ymland thetestpypienvironment, plus that environment created in the repository settings.deleted_at IS NULLpattern (the compliant fix is impossible without knowing the column exists), so a strong ungrounded run could occasionally infer soft-delete unaided. If measurement shows a weak contrast, adjust the task wording — not the tool surface.Implementation Process
Implemented with AI assistance under the roadmap contract. Final scope, review, and acceptance decisions were made by the maintainer.