ADR 0001 - Agent conventions live in the MCP layer

  • Status: Accepted
  • Date: 2026-06-02
  • Tracking: internal pre-Codeberg tracker (1316 agent self-sufficiency, 1317 “decide first” sub-issue)
  • Amended: 2026-07-02 — the Tier taxonomy below is forked by ADR 0008; see “ADR 0008 update” at the end of this document for what changed and what didn’t.

Context

madtea ships as both a CLI binary and a Model Context Protocol (MCP) server, and as a Claude Code plugin that bundles skills and PreToolUse hooks around that same binary. The plugin manifest advertises support for “Claude Code, OpenAI Codex CLI, and Google Gemini CLI”, and the README documents install paths for those plus Cursor, Windsurf, Cline, Zed, and “any MCP client”.

That breadth creates a hazard. Today an agent only drives the forge correctly on the first try because the operator’s personal CLAUDE.md (or AGENTS.md) already encodes the conventions: use madtea for remote operations, never raw git push/fetch/clone, complete a branch with madt_finish, use closes to close issues (branch-detected issues are only referenced), batch IDs rather than looping. None of that travels inside the package. For the public release the goal is self-sufficiency: a fresh install into a clean environment with an empty CLAUDE.md/AGENTS.md must let the agent use the tools correctly without the operator pasting anything into their own config.

The surfaces that could carry a convention differ sharply in reach:

  • The MCP server instructions string (serverInstructions() in internal/mcp/server.go), the consolidated tool schemas (the madt_* tools registered across internal/mcp/), and the madt_help(topic) manual are delivered by the MCP protocol itself. Every MCP client receives them — Claude Code, Cursor, Codex, Cline, Gemini, Zed, custom clients alike.
  • The Claude Code plugin’s skills (skills/dev-workflow, skills/forge-workflow), its PreToolUse hooks (hooks/scripts/check-git-remote.sh, check-git-commit.sh, check-git-local.sh), and any slash commands are read only by Claude Code. The README already states this plainly: “Only Claude Code uses the plugin’s bundled skills and safety hooks; other clients get the MCP tools alone.”

If a safety rule or a correctness fact lives only in a skill or a hook, an agent on Cursor or Codex never sees it. A rule whose enforcement is a Claude Code hook silently does nothing on a non-Claude-Code client. So where each fact is authored is not a documentation-tidiness question; it decides whether a given agent is safe at all. This ADR settles that question once so the sibling issues inherit a fixed target.

Decision

The MCP server layer is the universal source of truth for every agent-critical convention and safety rule. Specifically, the canonical home of any such fact is one of:

  1. the MCP server instructions (when to reach for the tools + the non-negotiable safety rails), or
  2. a tool/action schema description (how to call an action, with its primary gotcha stated inline), or
  3. a madt_help(topic) entry (the on-demand manual for deeper procedure).

These three travel with every install on every MCP-capable client at varying load costs — instructions is always loaded and cheap, schemas load with the tool, madt_help costs nothing until fetched — so an agent on any client can reach the full safety-and-usage picture without external config.

Claude-Code-specific surfaces — skills, hooks, slash commands — are enhancements only. They may make the workflow nicer, faster, or mechanically enforced for Claude Code users (a hook that blocks a credential-leaking git push before it runs is strictly better than a written rule), but they layer on top of the universal facts. They never introduce a fact that is absent from the MCP layer.

The invariant

No safety- or correctness-critical fact may live solely in a Claude-Code-specific surface (skill, hook, or slash command). Every such fact must also be present in the MCP layer — the server instructions, a tool/action schema, or a madt_help topic — so it reaches every MCP client.

A Claude Code enhancement may restate, enforce, or operationalize a universal fact; it may not be the only place that fact exists. Concretely: the check-git-remote.sh hook that blocks raw remote git is allowed and encouraged, but the underlying rule (“use madtea for remote operations; never raw git push/fetch”) must also be stated in the MCP instructions, because a Cursor or Codex agent has the same credential- leak exposure and no hook to catch it.

Agent portability tiers

Two supported tiers, named so the sibling issues can tag each surface they touch.

TierAudienceSurfacesGuarantee
Tier 1 — Universal (MCP)Any MCP-capable client (Claude Code, Cursor, Cline, Codex, Gemini, Zed, custom)Server instructions; consolidated tool/action schemas; madt_help(topic)Full safety and usage. An agent gets the trigger conditions, the safety rails, and the how-to for every action with zero external config.
Tier 2 — Enhancement (Claude Code)Claude Code onlyPlugin skills (dev-workflow, forge-workflow); PreToolUse hooks (check-git-remote.sh, check-git-commit.sh, check-git-local.sh, check-foreign-primary.sh); slash commandsErgonomics and mechanical enforcement on top of Tier 1. Never the sole home of a fact. May be entirely absent and Tier 1 still suffices for safe, correct use.

Updated by ADR 0008: within Tier 1, the tool/action schema is now the self-sufficient reference (action/op as JSON-Schema enums with per-value descriptions) and madt_help(topic) is demoted to a thin, verb-aware compatibility surface — see “ADR 0008 update” at the end of this document. The Tier 1 / Tier 2 boundary itself, and the guarantee row above, are unchanged.

First-class target: any MCP-capable client, served by Tier 1. Claude Code is a superset experience (Tier 1 + Tier 2), not a separate first-class track — choosing “Claude Code only” was rejected because the package already ships to multiple clients and the safety exposure (credential leakage through raw git) is identical on all of them.

Two properties make the tiers usable as references:

  • Tier 1 is self-sufficient. Removing all of Tier 2 must leave a client able to use madtea safely and correctly. This is the clean-room exit criterion of the epic: fresh install, empty CLAUDE.md, representative tasks succeed first-try with no raw-git fallback.
  • Tier 2 is additive-only. Every Tier 2 surface either restates a Tier 1 fact (skills, as procedure) or enforces one (hooks). It adds no new fact.

A third, explicitly out-of-scope category exists and should be named to avoid confusion: an operator’s hand-written CLAUDE.md/AGENTS.md is Tier 0 — not a supported home for any convention. The whole goal is to drain Tier 0. The migration matrix must show no row whose canonical home is the operator’s config.

How the sibling issues map onto the tiers

WorkTier it targets
Inventory → migration matrix (canonical in-package home per convention)Tier 1 (drives the placement of every fact)
Tighten server instructions into the self-contained source of truth (≤ ~2KB)Tier 1
Self-documenting tool/action descriptions + drift guardTier 1
Complete the madt_help manual (every workflow reachable by topic)Tier 1
(Stretch) expose MCP resources + prompts — promoted from optional to first-class per-noun reference delivery by ADR 0008; see “ADR 0008 update” belowTier 1
Ship git-safety hook (block raw remote git → redirect to madtea)Tier 2 (enforces a Tier 1 fact)
Skills audit — procedures only; no sole-source critical factsTier 2 (must honor the invariant)
Zero-config install docs (no required CLAUDE.md/AGENTS.md edits)Tier 1 proof (drains Tier 0)
Clean-room acceptance test (epic exit criterion)Tier 1 guarantee

Consequences

  • Tier 1 carries a token budget. The MCP instructions reach every client on every session, so they must stay terse — the instructions are capped at ~2KB and push procedure into madt_help. Tool descriptions similarly carry only their action’s primary gotcha inline, not a manual. Depth lives in madt_help, which costs nothing until fetched.
  • Skills get trimmed, not deleted. The skills audit keeps skill bodies as procedures (ordered workflows) and removes any safety/correctness fact that exists only there, after confirming the fact is present in the MCP layer. Skills remain valuable as Claude Code ergonomics; they stop being a second, divergent source of truth.
  • Hooks stay, as enforcement. A PreToolUse hook that blocks credential-leaking git is a real safety gain for Claude Code users and is kept. It is explicitly redundant with a Tier 1 rule by design, not in place of one.
  • One more axis to weigh when adding a fact. Any new agent-facing convention must declare its canonical Tier 1 home before (optionally) being mirrored into a Tier 2 enhancement. “Put it in the skill” is no longer a complete answer.

Verification and enforcement

  • Drift guards (mechanical). madtea already enforces convention with AST-scanning tests — the context-threading guard (internal/gitea/context_convention_test.go) and the mutation-confirmation guard (internal/parity/mutation_drift_test.go) are the precedent. The tool-description drift guard extends that pattern: a test fails if a documented behavior/gotcha is missing from the corresponding tool description, keeping Tier 1 schemas honest in CI.
  • The invariant, audited. The skills audit checks the bundled skills against the invariant — every safety/correctness rule a skill mentions must also be present in the MCP layer. Future skills/hooks are reviewed the same way.
  • The end-to-end proof. The clean-room test is the standing check that Tier 1 is genuinely self-sufficient: fresh install, empty CLAUDE.md, representative tasks (file an issue, open a PR, finish a branch) succeed first-try with no fumbling and no raw-git fallback. If that test needs anything from Tier 2 to pass, the invariant has been violated and the fact must move down to Tier 1.

ADR 0008 update

ADR 0008 (docs/adr/0008-operation-registry-source-of-record.md, Accepted, 2026-06-28) forks the Tier taxonomy defined above, as ADR 0008’s own ## Consequences states explicitly. This section is that update, made in this ADR per that cross-reference, and records what changed and — just as important — what the fork leaves untouched.

What changed, now shipped:

  • The tool/action schema is now the self-sufficient Tier-1 reference, not merely “how to call an action, with its primary gotcha stated inline” (surface 2 above). action=/op= parameters are JSON-Schema enums with a per-value description sourced from the live OperationSpec/ActionSpec registry — the model sees every verb’s reference on every call, with no separate lookup required.

Updated 2026-07-08: the per-value descriptions on the action=/op= enums were removed — the enum now carries bare registry-derived values for machine validation and discoverability, and the tool Description (buildToolDescription) is the sole carrier of every action/op summary (schema self-sufficiency unchanged). See the ADR 0015 amendment for the authoritative record.

  • MCP Resources move from the “(Stretch)” entry to first-class reference delivery. Every noun is reachable as an MCP Resource (madtea://reference/{noun}), still a pointer/restatement of the same generated content the schema and madt_help carry — never a new sole-sourced fact.
  • madt_help(topic) (surface 3 above) is demoted to a thin, verb-aware compatibility surface. It still answers noun-topic lookups in full, and a verb lookup that used to dead-end (topic="import") now resolves to, or names, the owning noun instead — but it is no longer the primary place an agent learns an action’s shape; the schema enum is. madt_help is kept, not deleted.

What did NOT change — the invariant above holds exactly as stated:

  • Tier 1 remains self-sufficient without Tier 2 (skills/hooks), and, per ADR 0008, also without Resources: the clean-room test asserts this on the credential-less server using only server instructions and the enum-bearing tool schemas — madt_help and Resources are convenience, not a requirement.
  • The Tier 1 / Tier 2 boundary itself is untouched. The fork changes which Tier 1 surface is primary for verb reference (schema over madt_help); it does not move any fact onto a Claude-Code-only surface.
  • The safety-exposure boundary is unchanged. ADR 0008 unifies description text across CLI and MCP, never the exposed surface (CLI-only safety-withheld ops, gated domains, MCP-only fail-closed guards all stay exactly as this ADR and ADR 0008 both describe).

See ADR 0008 for the full decision, the OperationSpec registry shape, and the safety non-goal in detail. agent-onboarding.md row 6 and mcp-registration.md reflect this fork.

Alternatives considered

  • First-class target = Claude Code only. Rejected. The package already installs into Codex, Gemini, Cursor, Windsurf, Cline, and Zed, and the core safety exposure (a credential leaking through raw git in the agent’s shell) is identical on every one of them. Making safety Claude-Code-only would ship a knowingly unsafe default to the majority of advertised clients.
  • Author conventions in the operator’s CLAUDE.md (status quo). Rejected as the explicit anti-goal of this ADR: it does not travel with the install, forces every operator to hand-maintain it, and silently fails for any agent whose operator left it empty.
  • Skills as the primary home, MCP as a thin pointer. Rejected: skills are Claude-Code-only, so this strands every other client. Skills are retained strictly as Tier 2 procedure.

Canonical source: docs/adr/0001-mcp-universal-source-of-truth.md in the madtea repo.