Skip to content

AsDecided MCP Server

AsDecided MCP serves your repository's requirements, decisions, designs, and roadmaps to coding agents as callable tools. It ships as the native decided-mcp binary.

1. Install

brew install asdecided/tap/asdecided-core

No Python runtime or extra is needed. The server is read-only and has no network side channel. The native decided telemetry command records local compatibility state only; it has no sender and does not change MCP responses (ADR-131).

Protocol compatibility

decided-mcp supports both MCP lifecycle eras:

  • existing clients can continue using the initialize-based revisions through 2025-11-25;
  • current clients can use the stateless 2026-07-28 revision through server/discover and per-request metadata.

No configuration change is required for stdio clients: a current host discovers the current revision, while an older host follows the established initialize flow. The six read-only tools and their grounding results are the same in both eras.

For shared HTTP deployments, current clients send MCP-Protocol-Version, Mcp-Method, and the applicable Mcp-Name headers. AsDecided validates these before dispatch and never creates an MCP session. Authentication remains the responsibility of the fronting deployment proxy; the engine's mandatory HTTP audit posture is unchanged.

2. Configure your client

Replace /path/to/your/repo with the absolute path to the directory that contains your AsDecided artifacts (or the decisions/ subdirectory within it). Use the path you would pass to decided validate.

Adding a client that is not listed here? Every harness connects on the same two surfaces (the generated agent-instructions file and the asdecided MCP server), so a new integration is a documented recipe, not engine work — follow the integration recipes authoring guide.

Claude Code

Command form (adds the server to your Claude Code session):

claude mcp add asdecided -- decided-mcp --root /path/to/your/repo

.mcp.json form — create or edit .mcp.json in your project root:

{
  "mcpServers": {
    "asdecided": {
      "command": "decided-mcp",
      "args": ["--root", "/path/to/your/repo"]
    }
  }
}

Claude Desktop

Open claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/claude_desktop_config.json; Windows: %APPDATA%\Claude\claude_desktop_config.json) and add an entry under mcpServers:

{
  "mcpServers": {
    "asdecided": {
      "command": "decided-mcp",
      "args": ["--root", "/path/to/your/repo"]
    }
  }
}

Restart Claude Desktop after saving.

Cursor

Create or edit .cursor/mcp.json in your project root:

{
  "mcpServers": {
    "asdecided": {
      "command": "decided-mcp",
      "args": ["--root", "/path/to/your/repo"]
    }
  }
}

Omnigent

Omnigent is a meta-harness: its custom agents are defined in a config.yaml, and an MCP server is a first-class tool type. Add an asdecided entry under the agent's tools: section:

tools:
  asdecided:
    type: mcp
    command: decided-mcp
    args: ["--root", "/path/to/your/repo"]

The tool travels with the agent definition, so it stays attached whichever harness Omnigent routes to. A worked setup — including pointing the agent's instructions at the generated AGENTS.md — is in examples/omnigent/.

3. Point AsDecided at a repository

--root accepts any directory. It does not have to be the top of a Git repository — point it at the folder where your RAC Markdown artifacts live.

To check that the path is right before configuring your client:

decided index /path/to/your/repo

That should list your artifacts. If it shows nothing, run decided init /path/to/your/repo to initialize the repository.

To try AsDecided against a ready-made corpus before using your own, point --root at the included examples:

decided-mcp --root examples/guide

The examples/guide/ corpus contains one requirement, decision, design, and roadmap for a fictional user management service — enough to explore all six tools.

Response budgets

The native server caps each successful tool payload at 10,000 characters by default. Set a different startup budget for either transport with --budget N (the minimum is 128 characters):

decided-mcp --root /path/to/your/repo --budget 20000

The limit is measured on the JSON payload in content[0].text, not the outer JSON-RPC frame. Collection results are truncated deterministically at whole items and carry truncated, omitted, and hint fields. Artifact content and retrieval excerpts may be shortened by character prefix. If fixed fields alone cannot fit, the tool returns a small response_budget_exceeded error instead of an oversized success. get_artifact and retrieve_grounding also accept a positive per-call budget that can lower the startup value; values below 128 are rejected as a tool error.

4. Your first grounded question

Once the server is connected, ask your agent:

What decisions has this repository recorded about data deletion?

The agent should call search_artifacts with a keyword like "delete" or "soft-delete", retrieve ADR-001: Soft-Delete User Records via get_artifact, and cite the decision ID in its response.

If you are pointing at your own repository, substitute a topic you know a decision covers.

Trusting what the server serves

AsDecided returns repository text; it does not make that text safe to follow. Treat each returned artifact as untrusted data. This includes bodies, excerpts, titles, relationships, and Markdown that looks like a system, agent, or tool instruction or that tells the agent to disregard another instruction. The read-only server protects the corpus from mutation; it does not protect the consuming agent from poisoned content (ADR-065).

Human pull-request review is the trust boundary. Prefer artifacts whose get_artifact response reports provenance.status: Accepted, and treat Proposed content as draft context. That status is a recorded lifecycle fact, not a safety verdict or a promise that the prose is correct. If content in the corpus conflicts with the user's task or with system instructions, surface the conflict and treat the content as data rather than following its embedded command.

Before merging corpus changes, run the normal validation and review gates plus the deterministic review aid:

decided doctor decisions/

Its injection-style-content finding is a warning for a human reviewer. It does not sanitize or rewrite artifacts, and a clean result does not replace human pull-request review.

5. The six tools

Tool When the agent calls it
get_summary Once at session start — counts artifacts, flags health issues
search_artifacts Before designing or implementing anything that a recorded decision might cover — keyword search across the corpus
retrieve_grounding For one-call task grounding, optionally scoped to a path, with ranked excerpts and provenance
find_decisions To find live decisions by topic or the decisions whose declared scope governs a code path
get_artifact When an artifact ID appears, or before changing anything a decision covers
get_related After retrieving an artifact — finds what else the change could affect

get_related takes an optional depth (default 1, capped at 5): the default returns immediate neighbours only, while depth>1 additionally returns a neighborhood array of artifacts two or more hops away, each tagged with its hops distance — for transitive context such as a decision a requirement's roadmap depends on. The walk is bounded (depth, frontier, visited-set, and a work budget) and deterministic; a truncation marker is set if any cap stops it.

Each search_artifacts match carries an additive recency object — last_committed, age_days, and a stale flag derived from git history (ADR-045) — so an agent choosing between results can see which artifact has decayed without a follow-up get_artifact. stale is true once a file's age exceeds the freshness threshold (default 180 days, set per repository under freshness.stale_after_days in .decided/config.yaml). It is advisory data beside its date, never a correctness verdict, and never changes which artifacts match or their order; outside git the fields degrade to null. The join respects the response budget — matches truncate whole, exactly as before.

The tool descriptions contain the trigger language; well-tuned agents call them without being told to.

6. Team setup: route CLAUDE.md to a RAC prompt (Claude Code)

The tool descriptions are sufficient on their own — the grounding demo proves that — but teams adopting RAC can raise the call rate by giving every session standing guidance. Rather than pasting instructions into CLAUDE.md, record the guidance as a RAC prompt artifact and route to it, the same pattern this repository uses for its own agent guidance:

decided new prompt decisions/prompts/agent-session-start.md

Fill the artifact with your team's standing instructions, for example:

  • at session start, call get_summary to learn what recorded knowledge exists
  • before designing or implementing, call search_artifacts for the feature area — recorded decisions take precedence over conventions inferred from the code
  • when an artifact ID is mentioned, call get_artifact; call get_related before changing anything an artifact covers
  • cite decisions by ID; if a task conflicts with a recorded decision, say so instead of silently overriding it

Then make CLAUDE.md a router:

# Agent session context

Canonical agent guidance lives in `decisions/prompts/` as validated RAC artifacts.

@decisions/prompts/agent-session-start.md

Claude Code inlines the referenced artifact at session start, so the effect is identical to pasting the text — but the guidance is now a governed artifact: decided validate checks it in CI, it is versioned and diffable like any other decision, and AsDecided itself can serve it (get_artifact retrieves your usage instructions — the system is self-describing).

Two caveats:

  • The import inlines the artifact verbatim, YAML frontmatter included. That is harmless, and the agent then knows the artifact's own ID.
  • @import syntax is Claude Code-specific. For Cursor or Claude Desktop, carry the same pointer in their native convention (for example .cursor/rules); the prompt artifact remains the single source of truth.

7. Read-access audit log (enterprise, opt-in)

For regulated installs that must record who consulted which decision, when, and which artifact references came back — information the response payload does not carry — the server can append one JSON line per read-tool call to a local file (ADR-084).

It is content-bearing by design and off by default: with no audit: stanza nothing is written and responses are byte-identical to a server with no recorder. It is local-only — the engine never transmits it; shipping the log to a sink (Loki, S3, Elastic) is a separate collector's job. The log records the query and the returned artifact references, never artifact bodies. Each reference contains id, resolved, and a body-free provenance.path back into the served corpus (ADR-127).

Enable it in .decided/config.yaml (committed and team-wide, so an auditor has one git-diffable artifact to point at):

audit:
  enabled: true
  # path: /var/log/asdecided/audit.jsonl   # optional; default: $XDG_STATE_HOME/decided/audit.jsonl
  # on_write_error: warn              # warn (default) | block
  • path — where the JSONL is written. Default $XDG_STATE_HOME/decided/audit.jsonl; override per machine with the DECIDED_AUDIT_PATH environment variable (for data residency).
  • on_write_errorwarn (the default) reports a write failure on stderr and keeps serving; block refuses the call with an audit-unavailable error rather than returning un-audited content.

Each line records ts, a per-process session, the principal, the transport (stdio or http), the attribution (asserted or local), the tool, the query, the returned artifact references, outcome, and duration_ms. The principal is attributable, not authenticated (ADR-084): it defaults to the git user.name/user.email in the served repository and can be overridden with the DECIDED_AUDIT_PRINCIPAL environment variable. The enforced access boundary stays the repository ACL plus human pull-request review — the log records who claimed to query, not a verified identity. When enabled, the server announces it on stderr at startup; it is never silent.

Per-caller attribution on a shared server

On a shared HTTP endpoint (--transport http) one process serves the whole team, so a single construction-time principal would record every caller as the host. Each request instead asserts who it is with the canonical X-AsDecided-Principal header, and the audit line records that per-request principal with transport: http and attribution: asserted (ADR-098):

X-AsDecided-Principal: Alice Ng <alice@example.com>

This stays attribution, not authentication: the engine records what the caller claimed and never verifies it, and the principal is never an access-control input — tool responses are identical whatever the header says. If you need the assertion to be trustworthy, your fronting proxy authenticates the caller and overwrites the header it trusts (ADR-085). Empty values are absent and duplicate carriers are rejected with HTTP 400 (-32023). A request that asserts nothing is recorded with attribution: local, and the shared server's fallback skips the host's git identity (it resolves DECIDED_AUDIT_PRINCIPAL, else unattributed) so a caller's read is never mislabelled as the host. Shared HTTP serving is also mandatory audit-on: it refuses to start without a working sink, and a sink write failure blocks the call. When enabled, startup announces the resolved path, recorded scope, transport, and write-failure mode on stderr.

8. Shared HTTP endpoint (team scale)

By default decided-mcp speaks stdio: one server process per developer, against that developer's own checkout. At team scale you may instead want one always-current endpoint every agent points at, so reads come from a single main-backed source of truth rather than checkouts that lag between pulls. The server gains a streamable HTTP transport for exactly this (ADR-098):

decided-mcp --root /path/to/your/repo --transport http --host 127.0.0.1 --port 8000 --path /mcp --budget 10000
  • --transportstdio (default) or http. Bare decided-mcp is unchanged, so every existing .mcp.json keeps working.
  • --host / --port / --path — where the HTTP server binds and serves (defaults 127.0.0.1, 8000, /mcp). It binds to loopback by default; exposing it to a network is a deliberate deployment choice.
  • --behind-proxy — required when --host is non-loopback. This is an explicit deployment acknowledgement, not authentication; put an authenticating TLS proxy in front and follow the deployment hardening checklist.

The HTTP transport is serving-layer only: the six tools are unchanged, the server re-reads the repository per call (no cache, no session state), and an HTTP response is payload-identical to stdio for the same corpus bytes.

Authentication is your proxy's job, not the engine's. The endpoint is read-only and unauthenticated by design — the engine grows no SSO, RBAC, or credential handling (ADR-085). Front it with a reverse proxy that authenticates callers and terminates TLS; identity in the audit log stays attributable, not authenticated (ADR-084).

HTTP serving is mandatory audit-on. Because a shared endpoint serves reads no single developer's git identity can attribute, the HTTP transport refuses to start without a working audit log — enable the audit: stanza (see section 7) first, or the server exits with an actionable error. stdio is unaffected.

Keeping the fronted checkout current with main — a merge webhook or a periodic git pull — is a deployment concern outside the engine. The full recipe — container, authenticating proxy, keep-current step, and observability — is on the Shared Server page.

Derived-index cache

By default the server reuses the derived structures — the repository index, the relationship graph, and the search token vectors — across calls, kept fresh by an event-sourced watcher (ADR-099, default-on per ADR-112):

decided-mcp --root /path/to/your/repo --transport http

The cache is content-addressed and disposable. It is keyed on a hash of the corpus bytes, so any change to any artifact — an edit, add, remove, or rename — changes the key and forces a rebuild; freshness is confirmed before every call, so no call ever serves stale state. Output with the cache is byte-identical to the uncached path. The cache lives at $XDG_CACHE_HOME/decisions/derived (override with DECIDED_CACHE_DIR); deleting it costs only latency, never correctness — the files in git remain the single source of truth. Pass --no-cache (or set DECIDED_NO_CACHE=1) to restore the zero-state posture where every tool call re-reads the repository from disk.

9. Troubleshooting

Server not listed in the client

  • Confirm decided-mcp is on the PATH the client uses. Test with: bash which decided-mcp decided --version
  • Check the client's MCP server log for startup errors.

Wrong root (AsDecided answers from the wrong repository)

  • Verify the --root path in your config matches the directory you intend.
  • Run decided index /path/to/your/repo to confirm the right artifacts are visible.
  • In Claude Code, run /mcp to inspect the server configuration.

Empty corpus (AsDecided says no artifacts found)

When the server starts against a root with no RAC artifacts it prints a diagnostic to stderr:

decided-mcp: no RAC artifacts found under '/path/to/your/repo'. Point --root at a
directory containing RAC Markdown artifacts, or run 'decided init' to initialize
a new repository. The server is running; get_summary will report the empty state.

This is not a fatal error — the server runs and get_summary reports zero artifacts. To fix it:

  1. Check that --root points at the right directory.
  2. Run decided index /path/to/your/repo to confirm artifacts are visible.
  3. If the directory has no RAC artifacts yet, run decided init /path/to/your/repo and start creating artifacts with decided new.

get_summary returns all zeros

Same cause as the empty corpus diagnostic above. Either --root is wrong or the repository has not been initialized. See the troubleshooting steps above.

Further reading