nornir MCP tools — authoritative reference
nornir-mcp is an MCP (Model Context Protocol) server over stdio that exposes nornir's whole API surface as tools. It is the same surface as the nornir CLI, presented for LLM agents (Claude Code/Desktop, Copilot CLI, …).
Total tools: 70. Every #[tool] in src/bin/nornir-mcp.rs is listed below, grouped by area, with its input args (name : type — ? = optional/defaulted) and output shape. This file is generated-by-hand from the source and kept in lock-step with the functional coverage test (tests/support/mcp_harness.rs, run via --test mcp_matrix): every tool here has a coverage row, and the test goes RED if a tool is added without one.
Modes (read first)
Every warehouse-touching tool has two code paths:
- Embedded (no
NORNIR_SERVER): opens a local Iceberg/redb warehouse directly. Single-writer redb, so per-call telemetry is disabled in this mode.
- Client / proxy (
NORNIR_SERVER set): relays the call to a running nornir-server over gRPC (bearer auth via NORNIR_SERVER_TOKEN, workspace via NORNIR_WORKSPACE or the workspace_use tool). The server owns the warehouse; the MCP never opens it locally.
Some tools are server-only (no embedded path): workspace_register, sync_now, index_status. Offline they return a clear "needs NORNIR_SERVER" error.
Output shape notation: most tools return a single text content block holding pretty-printed JSON (the ok_json helper); a few return a plain-text table or an SVG string. "→ JSON {…}" / "→ JSON […]" below describes that payload.
repos / workspace (7)
| Tool |
Purpose |
Input args |
Output |
repos_list |
List repos declared in nornir.toml (or the active workspace's members in server mode). |
— |
→ JSON ["holger", …] |
workspaces_list |
List the workspaces this MCP can target + the active one (server mode). |
— |
→ JSON {active, workspaces:[…]} (embedded: {active, note}) |
workspace_use |
Switch the active workspace for subsequent calls (server mode), no restart. Empty name clears the override. |
name : string |
→ JSON {active} |
workspace_register |
Server-only. Register a workspace so nornir tracks its git members + rebuilds the warehouse on change. |
name : string, descriptor : string, mode? : string (default monitored), poll? : string |
→ JSON {registered, mode, members:[…]} |
sync_now |
Server-only. Force an immediate git fetch + warehouse republish for a workspace (skip the poll loop). |
workspace? : string (empty = active) |
→ JSON {workspace, fetched, changed, snapshot, errors} |
index_status |
Server-only. Workspace freshness: current snapshot + each git member's last SHA, last-synced time, sync state. |
workspace? : string |
→ JSON {workspace, current_snapshot, updated_at, members:[…]} |
crate_published |
Is a crate published on crates.io? Queries the sparse index directly (no cargo/API key). |
name : string |
→ JSON {name, published, max_version, versions_count, yanked_latest} |
(workspaces_list, workspace_use, repos_list are the agent's orientation entry points; workspace_register/sync_now/index_status are the write/ freshness side, server-only.)
dependency Mímir (cross-repo graph) (10)
| Tool |
Purpose |
Input args |
Output |
deps_of |
Repos <repo> depends on. transitive=false: direct edges with the via crates; transitive=true: full forward closure. |
repo : string, transitive? : bool |
→ JSON edges/closure |
dependents_of |
Repos that depend ON <repo> — the BLAST RADIUS. Direct (via) or full reverse closure. |
repo : string, transitive? : bool |
→ JSON edges/closure |
affected_by_change |
Given changed repos, the invalidation set (changed + transitive dependents) in build order. |
repos : string[] |
→ JSON build-ordered list |
build_order |
Full workspace build order (deps before dependents). Errors on a cycle. |
— |
→ JSON ordered repo list |
dep_path |
Shortest dependency path from→to, annotated with via crates. Null if unreachable. |
from : string, to : string |
→ JSON path |
external_dep_users |
Workspace repos that consume external crate <crate> (e.g. "who uses serde?"). |
crate : string |
→ JSON [repos] |
dep_graph_svg |
Render the cross-repo graph as a static SVG flowchart (edges labelled with crates; no JS, no build step). |
— |
→ SVG text |
changed_since_last_release |
Diff each repo's HEAD vs last-released SHA, expand moved repos through the graph into the build-ordered re-run set. |
— |
→ JSON change set |
repo_overview |
One-shot orientation: <repo>'s deps + dependents (with via), build-order position, and a knowledge digest (symbol/call counts + sample names). |
repo : string |
→ JSON overview |
regression_trace |
Deterministic regression time-bisect: last GREEN release, first RED, suspect commit range, full timeline. |
repo : string, workspace? : string |
→ JSON trace |
guard (tamper evidence) (3)
| Tool |
Purpose |
Input args |
Output |
guard_status |
Report writable state of every [guard].forbidden path. |
— |
→ text table (exists/writable/changed/path) |
guard_apply |
chmod -w every forbidden path, record a tamper-evidence manifest (sha256 + mode), export guard-policy.json. |
— |
→ text table |
guard_verify |
Verify every forbidden path against the recorded manifest (per-path drift). Read-only. |
— |
→ JSON {intact, recorded_at, paths:[…]} |
Note: the unlock (chmod +w) tool guard_release is intentionally NOT exposed to agents (see the agent_tool_surface_excludes_unlock_* unit test).
bench / release gates (8)
| Tool |
Purpose |
Input args |
Output |
bench_history |
Read bench_history.jsonl for <repo> (one BenchRun per line). |
repo : string |
→ JSON [BenchRun, …] |
test_history |
<repo>'s recorded test-run history — one RunSummary per run, newest-first (the SAME per-run data nornir test history <repo> prints). Embedded (own warehouse) or via the server (Viz.TestResults) when NORNIR_SERVER is set. |
repo : string, limit? : usize |
→ JSON [{run_id,repo,ts_micros,passed,failed,ignored,stalled,skipped,listed,total,green}, …] |
release_gate_path_patches |
Gate: no [patch.crates-io] path patches in <repo>'s Cargo.toml. |
repo : string |
→ "ok: …" / error |
release_gate_nexus_floor |
Gate: holger_ops_sec ≥ nexus_ops_sec for the latest BenchRun. |
repo : string |
→ "ok: …" / error |
release_gate_no_regression |
Gate: latest BenchRun vs same-machine history; fail if a metric drops > max_regression_pct. |
repo : string |
→ "ok: …" / error |
release_gate_docs_fresh |
Gate: README.md generated sections in sync with the latest BenchRun. |
repo : string |
→ "ok: …" / error |
release_gate_all |
Run every gate enabled in [repo.<name>.gates] (incl. the heavy tests matrix gate when [gates].tests = true). |
repo : string |
→ JSON {repo, passed:[…], failed:[{name,error}]} |
release_doctor |
Advisory release doctor report across the whole constellation — dirty trees (real local git status), external-dependency version skew (target vs each repo with bump hints), path-override safety, promotability, blast radius. Read-only; the "see it + get hints first" tier before the gates. Embedded/local only (reads the local checkouts). |
forbid? : [string] (e.g. ["arrow=56"]), json? : bool |
→ JSON / text doctor report |
docs (7)
| Tool |
Purpose |
Input args |
Output |
docs_init |
Scaffold .nornir/ for <repo> (migrate existing README/CHANGELOG into it). |
repo : string |
→ JSON {repo, nornir_dir/sources, …} |
docs_render |
Render every managed doc from .nornir/ (full rewrite, chmod-aware). |
repo : string |
→ JSON {repo, reports:[…]} |
docs_check |
Dry-run: every artifact matches its .nornir/ source. |
repo : string |
→ "ok: …" / drift error |
docs_history |
List historized exports from the Iceberg doc_exports table (newest first). |
repo : string, doc? : string, version? : string, format? : string, limit? : usize |
→ JSON {repo, rows:[{doc,version,format,path,exported_at,size_bytes}]} |
docs_book |
Render the WHOLE doc set into one typst book → <repo>/docs/book.<ext>, historize in doc_exports. Needs docs-export feature. |
repo : string, format? : string (pdf|html|md) |
→ JSON {repo, format, sources, bytes, out, sha256, git_sha, export_id} |
docs_export |
Export the assembled README → <repo>/docs/README.<ext>, historize. Needs docs-export feature. |
repo : string, format? : string |
→ JSON {repo, format, bytes, out, sha256, git_sha, export_id} |
docs_install_hooks |
Install a tracked pre-commit hook for <repo> (.githooks/pre-commit + core.hooksPath) that re-renders managed markdown (markdown-only, never blocks) on every commit. Local-only; idempotent. |
repo : string |
→ JSON install report |
index / search (1)
| Tool |
Purpose |
Input args |
Output |
search |
Full-text BM25 search over indexed corpora (Tantivy). Run nornir index build first. |
query : string, corpus? : string (docs|code|bench_history|changelog|config), repo? : string, limit? : usize |
→ JSON [{id,corpus,repo,path,score,snippet,title}] |
introspect — DWARF over a live binary (5)
| Tool |
Purpose |
Input args |
Output |
symbol_lookup |
Extract every function symbol from a built binary, filter by name substring. |
binary : string, pattern : string, limit? : usize |
→ JSON [{name,name_demangled,name_mangled,file,line,size_bytes,krate}] |
defined_in |
Symbols defined in source files whose path ends with file. |
binary : string, file : string, limit? : usize |
→ JSON [Symbol] |
callers_of |
Inline-callgraph: functions that call name. (Inlined edges only.) |
binary : string, name : string |
→ JSON [names] |
callees_of |
Inline-callgraph: functions called by name. |
binary : string, name : string |
→ JSON [names] |
path_between |
Shortest inline-call chain from→to (BFS). Null if none. |
binary : string, from : string, to : string |
→ JSON path / null |
knowledge — persisted syn graph, no binary (5)
Reads the latest nornir knowledge scan --persist snapshot from Iceberg.
| Tool |
Purpose |
Input args |
Output |
knowledge_symbol_lookup |
Symbols in <repo> whose item name contains arg. |
repo : string, arg : string, limit? : usize |
→ JSON [{crate_name,module_path,item_kind,item_name,visibility,file,line,doc_lines,signature}] |
knowledge_defined_in |
Symbols defined in files whose path ends with arg. |
repo : string, arg : string, limit? : usize |
→ JSON [Symbol] |
knowledge_callers |
Call edges invoking name (bare or path-qualified last segment). |
repo : string, name : string, limit? : usize |
→ JSON [{crate_name,caller_path,callee_ident,call_kind,file,line}] |
knowledge_callees |
Call edges emitted from a caller whose path ends with name. |
repo : string, name : string, limit? : usize |
→ JSON [Call] |
knowledge_call_path |
Shortest call chain from→to (BFS, last-segment match). Array or null. |
repo : string, from : string, to : string |
→ JSON path / null |
DWARF — persisted warehouse facts, no binary (5)
Reads the historized dwarf_* tables (nornir introspect symbols --persist), with optional time-travel by git sha.
| Tool |
Purpose |
Input args |
Output |
dwarf_symbol_lookup |
Persisted function symbols for <repo> (or _workspace) whose demangled name contains arg. |
repo : string, arg : string, sha? : string, limit? : usize |
→ JSON [Symbol] |
dwarf_defined_in |
Persisted symbols defined in a file ending with arg. |
repo : string, arg : string, sha? : string, limit? : usize |
→ JSON [Symbol] |
dwarf_callers |
Persisted inline callers of arg. |
repo : string, arg : string, sha? : string |
→ JSON [names] |
dwarf_callees |
Persisted inline callees of arg. |
repo : string, arg : string, sha? : string |
→ JSON [names] |
dwarf_call_path |
Persisted shortest inline-call chain from→to. Null if none. |
repo : string, from : string, to : string, sha? : string |
→ JSON path / null |
funnel — ideas → plans → nodes (planning DAG) (7)
A persistent DAG that survives agent context loss. Call funnel_show + funnel_next after any restart to find what to work on.
| Tool |
Purpose |
Input args |
Output |
funnel_submit_idea |
Submit a new idea into the intake funnel. |
text : string, source? : string |
→ idea id (i-NNN) |
funnel_create_plan |
Create + auto-activate a plan refining an idea. |
idea_id : string, summary : string |
→ plan id (p-NNN) |
funnel_add_node |
Add a node to a plan (verb kind, optional needs deps). |
plan_id : string, kind : string, title? : string, prompt? : string, targets? : string[], needs? : string[] |
→ node id (n-NNN) |
funnel_link |
Add a dependency edge: to becomes ready only once from is done. |
plan_id : string, from : string, to : string |
→ "ok …" |
funnel_next |
Ready PlanNodes (deps satisfied) across active plans, in topo order. |
— |
→ JSON [PlanNode] |
funnel_status |
Flip a node's status (ready|active|blocked|done|abandoned). |
plan_id : string, node_id : string, status : string, why? : string |
→ "ok …" |
funnel_show |
Dump the whole funnel (ideas, plans, nodes, edges). |
— |
→ text summary |
vector — semantic code search (1)
| Tool |
Purpose |
Input args |
Output |
vector_search |
Semantic (jina-v2-base-code) vector search over a repo's materialized embeddings; time-travel by sha. Repo must be vectorized via nornir vector index <repo>. Needs embed-tract/embed-ort feature (or a server that has one). |
query : string, repo : string, sha? : string, limit? : usize |
→ JSON {repo, hits:[{score,file,start_line,end_line}]} |
completeness gate — autonom n-005 (2)
| Tool |
Purpose |
Input args |
Output |
test_coverage |
The autonom completeness gate (n-005): discover the testable surface (viz tabs × {thin,fat}, CLI subcommands, MCP tools, and unreached functions = symbol_facts − test-reachable(call_edges)), difference it against the covered set + .nornir/autonom-allow.toml, and report the gap. Reads the latest persisted gate run; computes one live if none. |
repo? : string (default = workspace name) |
→ JSON {total, covered, allowlisted, gap, green, missing:[…]} |
release_gate_coverage |
Release gate wrapping n-005 for <repo>'s workspace — fails (HARD zero) when the surface has any uncovered+un-allowlisted node OR any stale allowlist entry. Embedded/local only. |
repo : string |
→ {ok} / error with the gap+stale list |
architecture wiring — EPIC ARCH (3)
Reads the latest historized architecture_wiring graph (generate first with nornir arch generate <repo>).
| Tool |
Purpose |
Input args |
Output |
arch_svg |
The historized component/gRPC/core/table wiring graph for <repo> as a static skade circuit-board SVG (no JS, no build step) — the code-derived version of .nornir/architecture-wiring.md §2. |
repo : string |
→ SVG text (or a hint if none historized) |
arch_graph |
The same wiring graph as JSON — nodes are Component (viz pane) / gRPC verb / core fn / warehouse table; edges are the call/access facts. |
repo : string |
→ JSON {nodes:[{id,label,kind}], edges:[{from,to,kind}]} |
arch_trace |
The focused sub-board of everything reachable downstream from <entrypoint> (a UI pane like TestTab, a gRPC verb like Viz.Timeline, or a crate::module bucket; case-insensitive substring match). |
repo : string, entrypoint : string |
→ JSON focused sub-graph |
airgap — Skíðblaðnir (4)
Drives the sibling skidbladnir crate (../Skidbladnir, path dep). Zero network (the airgap law). CLI parity with nornir airgap ….
| Tool |
Purpose |
Input args |
Output |
airgap_pack |
Pack a staging tree into an airgap bundle (*.tar.zst) + write the sidecar version file. |
staging : string, out : string, version? : string (default today YYYYMMDD) |
→ text ✓ packed N entries → out (root sha256 …) |
airgap_verify |
Verify a received bundle against its sidecar manifest — recompute the manifest root hash AND every entry's content SHA-256 by extraction; refuse a tampered bundle. |
bundle : string, sha256? : string (out-of-band whole-file hash) |
→ text verify report (manifest_root_ok/bundle_hash_ok/bad_entries) |
airgap_unfold_plan |
The unfold provisioning PLAN for a target: kvm → qemu argv, terraform → nornir_component HCL, redfish → ComputerSystem.Reset body. Plan is real; provisioning is a feature-gated seam. |
backend : string (kvm|terraform|redfish), target : string, disk? : string (kvm) |
→ text plan |
airgap_events |
Read back the airgap_events bring-up DAG from the warehouse JSONL sink (one row per product×op×phase, with depends_on edges). |
— |
→ JSON [AirgapEvent, …] (or a hint when none written) |
viz — live Urðr Threads UI (2)
Talks to a running nornir viz over its control channel ($NORNIR_VIZ_CMD) — LAW #6 "see what the user sees as data".
| Tool |
Purpose |
Input args |
Output |
viz.state |
What is the viz showing RIGHT NOW? The full UI-state mirror: selected workspace + picker options, active tab + full tab list, and per-tab the rendered rows/nodes/cells. |
— |
→ JSON UI-state mirror |
viz.click |
Drive the viz: switch the active TAB / WORKSPACE / app-wide PALETTE, set a named form field, or click a named action; writes a one-shot command the running viz applies on its next frame. |
tab? : string, workspace? : string, palette? : string, set_field? : string, click_id? : string, wait_ms? : u64 (default 1200) |
→ JSON state after apply |
Count by area
| Area |
Tools |
| repos / workspace |
7 |
| dependency Mímir |
10 |
| guard |
3 |
| bench / release gates |
8 |
| completeness gate (n-005) |
2 |
| docs |
7 |
| index / search |
1 |
| introspect (DWARF, live binary) |
5 |
| knowledge (syn graph) |
5 |
| DWARF (persisted) |
5 |
| funnel |
7 |
| vector |
1 |
| architecture wiring (EPIC ARCH) |
3 |
| airgap (Skíðblaðnir) |
4 |
| viz (live Urðr Threads UI) |
2 |
| Total |
70 |
How this is verified
tests/support/mcp_harness.rs (the shared body of --test mcp_matrix and --test mcp_tools) enumerates the registered tool set from the live server's tools/list and exercises every tool with real arguments against a seeded warehouse, asserting structured output. The per-tool verdicts land in tests/mcp_tool_coverage.json. Current surface: 70 tools — the 3 server-only tools (workspace_register, sync_now, index_status) skip in the embedded harness; the rest are exercised and asserted.