Reference
MCP reference
Nauro exposes its decision and context store to agents over the Model Context Protocol. One shared tool registry (nauro_core.mcp_tools.ALL_TOOLS) holds 11 tools, 8 read and 3 write, consumed by two transports: a local stdio server and a hosted HTTP endpoint. Tool descriptions, titles, JSON input schemas, and behavioral annotations are centralized in mcp_tools.py so both transports stay in sync.
Transports: local stdio vs hosted endpoint
The local stdio server is run with nauro serve --stdio. Claude Code and other MCP clients spawn this process and talk over stdin and stdout. The --stdio flag is a hidden no-op kept for backward compatibility, since stdio is the only supported local transport (the former local FastAPI HTTP transport was retired).
There is no --project flag on the local server. It resolves the project from the cwd's .nauro/config.json.
On startup the stdio server pulls latest from the remote (best-effort) before accepting tool calls, then runs mcp.run(transport="stdio"). Pull failures are logged and the server starts with local state.
The server is a FastMCP("nauro", instructions=MCP_INSTRUCTIONS, log_level="WARNING") instance. Read tools that have a renderer in nauro_core.renderers.RENDERERS return a CallToolResult with a single TextContent block carrying the renderer output. Write tools, get_raw_file, and diff_since_last_session keep a single-block shape.
The hosted endpoint is https://mcp.nauro.ai/mcp. Add it as an MCP connector in your tool's settings to reach a cloud-synced project from surfaces without a local copy (for example claude.ai web) or from another machine.
The hosted path requires nauro auth login, then a one-time nauro link --cloud to promote the local project, then nauro sync. The hosted server implementation lives in a private repository but consumes the same ALL_TOOLS registry and operations kernel.
The local FastMCP server and the remote HTTP server read tool metadata from the same nauro_core.mcp_tools registry. The local server derives input schemas from Python type hints and consumes title, description, and annotations; the remote (JSON-RPC) server consumes the whole ToolSpec entry directly.
# Local stdio server (spawned by the MCP client; runnable directly)
$ nauro serve --stdio
Source: packages/nauro/src/nauro/cli/commands/serve.py; packages/nauro/src/nauro/mcp/stdio_server.py; packages/nauro/README.md; README.md.
Read/write annotation model
Every tool is closed-world (openWorldHint: False). It operates only on the local or remote Nauro store.
Writes are non-destructive (destructiveHint: False). The source comment states writes are additive and nothing is ever deleted.
Read tools carry _READ_ANNOTATIONS = {readOnlyHint: True, openWorldHint: False} plus idempotentHint: True. Write tools carry _WRITE_ANNOTATIONS = {readOnlyHint: False, destructiveHint: False, openWorldHint: False} plus idempotentHint: False.
_READ_ANNOTATIONS = {"readOnlyHint": True, "openWorldHint": False}
_WRITE_ANNOTATIONS = {"readOnlyHint": False, "destructiveHint": False, "openWorldHint": False}
Source: packages/nauro-core/src/nauro_core/mcp_tools.py.
Connector config (mcp.json)
nauro adopt (or nauro setup claude-code) writes the entry into the repo's project-scope .mcp.json under mcpServers["nauro"]. The entry shape is {"command": "nauro", "args": ["serve", "--stdio"]}, with the command resolved via shutil.which("nauro"), falling back to the literal "nauro".
Cursor uses the same shape in <repo>/.cursor/mcp.json. Codex uses ~/.codex/config.toml.
The local stdio connector below applies to both Claude Code (.mcp.json) and Cursor (.cursor/mcp.json).
{
"mcpServers": {
"nauro": {
"command": "nauro",
"args": ["serve", "--stdio"]
}
}
}
The hosted remote connector is registered under a name distinct from the local nauro stdio server. The Codex example below is from the README.
$ codex mcp add nauro-cloud --url https://mcp.nauro.ai/mcp
# pin the OAuth callback port in ~/.codex/config.toml (Codex 0.131.0+)
mcp_oauth_callback_port = 8765
Source: packages/nauro/src/nauro/cli/commands/setup.py; README.md.
Tool reference (11 tools)
Each entry below gives the tool name, READ or WRITE kind, a one-line purpose, input parameters (type, required or optional plus default), and return shape. Schemas are from mcp_tools.py (input_schema); return shapes from the kernel *Result Pydantic models.
Every tool except list_projects accepts an optional project_id (string), used by the remote surface to disambiguate when a user has multiple projects; local installs auto-resolve from cwd. list_projects takes an empty object schema.
The local stdio surface also accepts an undocumented internal cwd (string) parameter for store resolution.
| Tool | Kind | Purpose |
|---|---|---|
get_context | read | Return project context at the requested detail level. |
get_raw_file | read | Return the raw markdown of any store file. |
list_decisions | read | Browse the full decision history. |
get_decision | read | Return a specific decision by number. |
diff_since_last_session | read | Show what changed since the last snapshot. |
search_decisions | read | Search decisions by BM25 relevance. |
check_decision | read | Check overlap with existing decisions without writing. |
propose_decision | write | Record an architectural decision. |
flag_question | write | Flag or resolve an open question. |
update_state | write | Update current state with a progress delta. |
list_projects | read, remote-only | Return the projects this user can access. |
get_context (READ)
Returns project context at the requested detail level (L0 concise, L1 working set, L2 full dump).
Inputs:
level(string enumL0|L1|L2, optional, defaultL0). Detail level. The kernel also accepts int0|1|2.project_id(string, optional).
Returns GetContextResult: content (markdown payload string) on success, or error (kind="rejected", invalid level) otherwise. L0 appends a *Last synced: ...* trailer when present; L2 appends a ## Snapshot Diff (...) block; empty or no-decision stores get a NO_CONTEXT_YET body. The envelope adds store and project identity.
get_raw_file (READ)
Returns the raw markdown of any file in the project store (a low-level escape hatch).
Inputs:
path(string, required). Path relative to store root, e.g.project.md,decisions/001-initial-architecture.md.project_id(string, optional).
Returns GetRawFileResult: content (file text) on success, or error (kind="error") on a miss. The adapter rejects path traversal with Invalid path: <path>, and on a miss adds an available_files list (capped at 20, excludes snapshots/).
list_decisions (READ)
Browse the full decision history (beyond the last 10 in get_context, or with the include-superseded filter).
Inputs:
limit(integer, optional, default20). Max decisions returned.include_superseded(boolean, optional, defaultfalse).project_id(string, optional).
Returns ListDecisionsResult: decisions, a list of DecisionSummary rows (number, title, date?, status, type?, confidence), sorted by number descending and truncated to limit.
get_decision (READ)
Returns a specific decision by its number, as a header triage projection or the full body.
Inputs:
number(integer, required). Decision number, e.g.23.mode(string enumheader|full, optional, defaultfull).headeris frontmatter plus title plus short lede;fullis the complete decision markdown.project_id(string, optional).
Returns GetDecisionResult: content (decision markdown body) on success, or error (kind="error") on a miss.
diff_since_last_session (READ)
Shows what changed in the project context since the last snapshot, or since N days ago.
Inputs:
days(integer, optional). Look back N days; finds the nearest snapshot to N days ago and diffs against the latest. Omit to diff the two most recent snapshots (session-scoped).project_id(string, optional).
Returns DiffSinceLastSessionResult: diff (human-readable diff body) and optional cutoff_date_used (echoes the baseline timestamp for time-based lookups). Sentinel diff strings cover "not enough snapshots (need at least 2)" and "one snapshot covers the requested range" rather than erroring.
search_decisions (READ)
Searches all decisions by BM25 relevance over titles and rationale.
Inputs:
query(string, required, non-empty). Search text.limit(integer, optional, default10). Max results.include_superseded(boolean, optional, defaultfalse).project_id(string, optional).
Returns SearchDecisionsResult: results, a list of SearchHit rows (number, title, date?, status, relevance_snippet?, score), sorted by BM25 score descending and truncated to limit; error (kind="rejected") on an empty or whitespace query.
check_decision (READ)
Checks whether a proposed approach overlaps existing decisions without writing anything; returns related decisions and a deterministic assessment.
Inputs:
proposed_approach(string, required). The approach you are considering.context(string, optional). Extra context on why.project_id(string, optional).
Returns CheckDecisionResult: related_decisions, a list of RelatedDecision (id, title, score, status, date, rationale_preview) via Tier 1 plus Tier 2 BM25 retrieval; and assessment (a deterministic summary string).
The tool does not judge conflicts. The agent reads each related decision via get_decision before proposing.
propose_decision (WRITE)
Records an architectural decision; commits on Tier 1 structural validation (single-call), with Tier 2 BM25 similarity advisory (non-blocking).
Inputs:
title(string, required).rationale(string, required). Why, including constraints and tradeoffs.operation(string enumadd|update|supersede, optional, defaultadd).affected_decision_id(string, optional). Required when operation isupdateorsupersede(e.g.decision-042).rejected(array of{alternative, reason}objects, optional). Alternatives considered.confidence(string enumhigh|medium|low, optional, default effectivelymediumfor add/supersede).decision_type(string enumarchitecture|library_choice|pattern|refactor|api_design|infrastructure|data_model, optional).reversibility(string enumeasy|moderate|hard, optional).files_affected(array of strings, optional). Repo-relative paths.resolves_questions(array of strings, optional). Open-question ids to mark resolved.project_id(string, optional).
Returns ProposeDecisionResult: status (confirmed|rejected), tier (int), operation (add|update|supersede|reject), similar_decisions (advisory RelatedDecision hits), assessment, decision_id (on-disk file stem on confirm), touched_decisions (files rewritten), resolved_questions (ids moved under ## Resolved), and optional error.
On confirm the adapter captures a snapshot, regenerates AGENTS.md, and pushes to cloud.
flag_question (WRITE)
Flags an unresolved question for human review, or resolves existing questions against a decision (writes open-questions.md). Pass exactly one of question or resolved_by.
Inputs:
question(string, optional). The question to flag.context(string, optional). Why it matters.targets(array of strings, optional). Question ids; on flag, duplicate candidates that short-circuit if already resolved; on resolve, the entries to stamp (every id must exist).resolved_by(string, optional). Decision id that resolvestargets; when set, the call resolves instead of appending.project_id(string, optional).
Returns FlagQuestionResult: status (ok|rejected), num (minted id on the ok-flag path; unset when rejected), and error. On flag, the adapter may add a BM25 similarity hint pointing to an addressing decision (the question is still logged). On success it captures a snapshot and pushes.
The local stdio surface does not surface the structured envelope; it returns a short string ("Question flagged." / "Question(s) resolved." / a hint plus "The question has still been logged." / a rejection reason).
update_state (WRITE)
Updates the project's current state with a progress delta and triggers a snapshot.
Inputs:
delta(string, required). What changed, e.g."Deployed v0.2.0 to staging".project_id(string, optional).
Returns UpdateStateResult: status (ok wrote a new state body; noop means no existing state file to update), optional warning (keyword-overlap caution; the update is still applied), and optional error. On a non-noop the adapter captures a snapshot and pushes.
The local stdio surface returns a short string ("State updated." optionally with the warning appended).
list_projects (READ, remote-only)
Returns the projects this user has access to (the discovery entry point for picking a project_id).
Inputs: none (empty object schema).
Returns the projects accessible to the current user. It is not registered on the local stdio server, because local installs auto-resolve to the single project store, so the discovery tool is unnecessary; only the remote or hosted surface registers it. The return-shape detail is not defined in nauro_core.operations (it is handled by the private hosted server).
Source: packages/nauro-core/src/nauro_core/mcp_tools.py; packages/nauro/src/nauro/mcp/tools.py; packages/nauro-core/src/nauro_core/operations/results.py; packages/nauro/src/nauro/mcp/stdio_server.py.
Example tool calls (MCP JSON-RPC arguments)
The arguments below are passed in MCP tools/call requests, one example per common workflow.
// get_context at session start
{ "name": "get_context", "arguments": { "level": "L0" } }
// search the decision log
{ "name": "search_decisions", "arguments": { "query": "authentication", "limit": 10 } }
// check before adopting an approach (read-only, no write)
{ "name": "check_decision",
"arguments": { "proposed_approach": "Use Redis as the hot-read cache" } }
// record a decision (write)
{ "name": "propose_decision",
"arguments": {
"title": "Adopt Redis for hot-read cache",
"rationale": "In-memory cache for hot read paths; lower p99 than DB round-trips.",
"rejected": [{ "alternative": "Memcached", "reason": "Less feature-rich" }],
"files_affected": ["src/cache.py", "src/api.py"],
"confidence": "medium",
"decision_type": "infrastructure"
} }
Source: packages/nauro-core/src/nauro_core/mcp_tools.py (input schemas); README.md (CLI mirror of propose-decision).
Key facts
- One shared registry
nauro_core.mcp_tools.ALL_TOOLSholds 11 tools: 8 read, 3 write. Order: get_context, get_raw_file, list_decisions, get_decision, diff_since_last_session, search_decisions, check_decision, propose_decision, flag_question, update_state, list_projects. - Read tools (8): get_context, get_raw_file, list_decisions, get_decision, diff_since_last_session, search_decisions, check_decision, list_projects. Write tools (3): propose_decision, flag_question, update_state.
- The local stdio server registers 10 (7 read, 3 write);
list_projectsis remote-only, since local installs auto-resolve the single project store. - Local transport:
nauro serve --stdiorunsnauro.mcp.stdio_server.run_stdio(), aFastMCP("nauro")server over stdin and stdout.--stdiois a hidden no-op kept for back-compat; stdio is the only local transport. - Hosted endpoint:
https://mcp.nauro.ai/mcp(private repo); requiresnauro auth loginplusnauro link --cloudplusnauro sync. Enter the URL with no trailing slash. - All tools are
openWorldHint: False(closed-world); writes aredestructiveHint: False(additive, nothing deleted). Reads addreadOnlyHint: TrueplusidempotentHint: True; writes addreadOnlyHint: FalseplusidempotentHint: False. .mcp.jsonconnector entry shape (written bynauro adopt/nauro setup claude-code):mcpServers.nauro = {"command": "nauro", "args": ["serve", "--stdio"]}. Cursor uses.cursor/mcp.jsonwith the same shape; Codex uses~/.codex/config.toml.- Every tool except
list_projectsaccepts optionalproject_id(string);list_projectstakes an empty object schema.propose_decisionrequiresaffected_decision_idwhenoperationisupdateorsupersede;flag_questiontakes exactly one ofquestionorresolved_by. - Return shapes are typed Pydantic
*Resultmodels innauro_core.operations.results; each addsstore(and the local surface addsprojectidentity) at serialization time.
Open gaps
The exact return shape and fields of list_projects are not defined in the public nauro_core (the discovery tool is implemented only by the private hosted server). Its inputs (none) and purpose are documented above; the response schema is unconfirmable from the public repo.
The internal local-only cwd parameter present on the stdio tool signatures is not part of the published input_schema in mcp_tools.py; it is treated as an implementation detail.
The hosted HTTP server's JSON-RPC framing, auth flow internals, and envelope format live in a private repository and could not be read; only the public registry and operations behavior are documented here as ground truth.
The README states the hosted server is "8 read" overall (counting list_projects); the local-server docstring counts "10, 7 read, 3 write". Both are consistent (list_projects is the eighth read, remote-only); there is no conflict, noted here for clarity.