mcp server
state-canon-mcp
A state-grounded RAG that puts verified current state in the agent's path, so it reasons from ground
Set up
Runs on your computer. Pick your agent and operating system.
This project does not publish an install method we can turn into a config. Follow the README instead.
README
synced Oct 26, 2026state-canon — verified ground truth for agents
An agent grounded in verified canon does more careful work than one running on recall — and burns fewer tokens.
state-canonkeeps a canonical, reconciled record of the system — the authoritative version of the world state, written on every change and reconciled against reality — and puts it in the agent's path, so the agent reasons from ground truth instead of stale memory or costly re-derivation. State you can query, verify, and govern.
Status: early / exploratory — the thesis and the method are battle-tested in daily use; the public tooling is being extracted and the token-savings claim is being measured, not proclaimed. Full status below. Stdlib-only, no dependencies. The method itself is a separate read: METHODOLOGY.md.
The bet
The industry is racing toward multi-agent swarms on the premise that more agents = better work. We take the opposite bet: the bottleneck was never how many agents — it's whether each decision is grounded in verified canon instead of the agent's own recall. Fix that, and a single agent goes a long way, at lower token cost.
- One agent, grounded in canon — it designs, executes, and verifies its own claims against ground truth, not against its memory of what it did.
- A human — sets policy and holds the ceiling: irreversible / outward-facing changes need approval.
The audit property that matters — what verifies a claim is not what produced it — holds with one agent because the canon is external to the agent's reasoning. The agent doesn't check its work by re-reading its own recall; it reconciles against a canonical store that reality writes to. The full pattern — the single-agent loop, boundaries, the real case study — is in METHODOLOGY.md.
flowchart LR
D["agent:<br/>design + spec"] --> F["framed task<br/>with stop-points"]
F --> E["agent:<br/>build / deploy / test"]
E --> R["claim: done"]
R --> V{"verify LIVE<br/>against canon,<br/>not the claim"}
V -- defect found --> C["finding<br/>written to canon"]
C --> F
V -- verified --> G{"blast radius<br/>gate"}
G -- pilot passes --> N["next stage<br/>wider rollout"]
G -- human approval --> H["human decides"]
Why it works
- Grounded, not re-derived. The current, reconciled state of the system is injected into the agent's path. It doesn't re-explore the world each session; it reads verified ground truth. This is what this tool provides.
- Verify against canon, not recall. The agent never trusts a report — its own or anyone's — over the live state ("is the service actually running?", not "did I say it is?"). Canon is the authority; recall is a lead to check.
- Compact, machine-primary artifacts. State and handoffs are terse and structured, not prose — the token-cost lever.
What it is — a canon layer, exposed over MCP
A canonical store written on every change, a reconciler that keeps it ≡ reality (drift / orphan detection), and an onboard step that injects the current state into the model's context. The agent queries it or has the digest front-loaded — either way it reads reconciled ground truth, not its own recall. The discipline in one line: recall is not canon — verify against state.
Exposed over the Model Context Protocol so any agent can plug in:
| tool | does |
|---|---|
state_onboard() | the whole current picture in one call (services + status, open drift, rules, last decision) |
state_query(domain, filter) | a narrow slice, on demand |
state_verify(claim) | ★ don't trust the report — check it against ground truth → {holds, actual, evidence} |
state_reconcile(domain) | model≡reality → the drift / orphan report |
state_journal_mark(session_id?, drifts?) | opt-in (--journal PATH) — save a session snapshot for later diff/trend |
state_journal_diff(from_id?, to_id?) | opt-in — what changed between two snapshots |
state_journal_history(limit?) | opt-in — recent snapshots |
state_focus_mark(ref, status?, note?) | opt-in (--focus PATH) — upsert a focus entry by ref (create or update status/note) |
state_focus_close(ref, note?) | opt-in — mark a focus entry as done; state_query('focus') reads back |
Plus resources (state://digest, state://schema, state://rules, state://handoff) for clients that
inject context up front. The split is deliberate — resources feed the front-load (inject the digest at
session start); tools feed the lazy path (query only what you need).
It's domain-agnostic. You register your own StateProvider (SQLite, JSON, Git, an API — anything) and
a Reconciler (how to observe reality for each domain). Full abstraction, reference instances, the Git/VCS
instance, remote mode, and composing with other context sources: INTERFACE.md.
It composes. state-canon is one instrument, not the whole rack: attach it over MCP side-by-side with your content RAG, your memory server, your other tools. The discipline that keeps the ensemble honest is authority ordering — for current truth, reconciled state outranks memory and documents.
Install
Requirements: Python 3.10+. Nothing else — the server is stdlib-only, there is no pip install.
git clone https://github.com/rm-w3kufe/state-canon-mcp.git state-canon
python3 state-canon/tests/test_state_canon.py # optional: 28 checks, ~1s
The server is one command (stdio; your MCP client spawns it):
python3 /abs/path/state-canon/mcp_server.py --state /abs/path/your-state.json
python3 /abs/path/state-canon/mcp_server.py --sqlite /abs/path/your-state.db # any SQLite DB
python3 /abs/path/state-canon/mcp_server.py --git /abs/path/your-repo # a git working tree
python3 /abs/path/state-canon/mcp_server.py --microstack /abs/path/state-canon/corpus/microstack # demo
python3 /abs/path/state-canon/mcp_server.py --instance /abs/path/your-instance.py:arg # bring-your-own
Architecture note: the server runs locally, beside your agent (stdio). Your state lives where your agent lives. It is not a network service and does not phone anywhere. (Remote state is supported at the provider layer — see INTERFACE.md.)
Per-client configuration
All stdio MCP clients need the same three facts: command: python3, args: [launcher, --flag, path].
Use absolute paths everywhere.
Claude Code (CLI):
claude mcp add state-canon -- python3 /abs/path/state-canon/mcp_server.py --state /abs/path/state.json
or per-project in .mcp.json / Claude Desktop in claude_desktop_config.json:
{ "mcpServers": { "state-canon": {
"command": "python3",
"args": ["/abs/path/state-canon/mcp_server.py", "--state", "/abs/path/state.json"] } } }
OpenCode (opencode.json, 0.x shape):
{ "mcp": { "state-canon": {
"type": "local",
"command": ["python3", "/abs/path/state-canon/mcp_server.py", "--state", "/abs/path/state.json"] } } }
Cursor (.cursor/mcp.json), Cline (cline_mcp_settings.json), Windsurf
(~/.codeium/windsurf/mcp_config.json) — all use the same mcpServers shape as Claude Desktop above.
Verify the install (no client needed)
printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' \
| python3 /abs/path/state-canon/mcp_server.py --microstack /abs/path/state-canon/corpus/microstack
You should see one JSON line with "serverInfo": {"name": "state-canon"}.
Updating
state-canon-mcp is a plain git clone, not a package — updating is a git pull:
cd state-canon && git pull
Check what changed: CHANGELOG.md, or git log --oneline v0.2.0..HEAD
against the version your initialize response currently reports.
Your own customizations are safe across an update: a --state JSON file, a
--sqlite/--git target, or a full --instance module all live outside this repo
and are loaded at runtime — a git pull never touches them. The only thing that could
break a custom instance is a breaking change to the StateProvider / Reconciler
interface itself, and that would always be called out under ### Changed in the
changelog, never buried in ### Added.
Quickstart — the 60-second demo
The repo ships a tiny fictional deployment (corpus/microstack/) with three seeded drifts: a service
declared active but actually stopped, a rule violation, and an orphan process.
1. Ask for the whole picture:
cd state-canon
printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"state_onboard","arguments":{}}}' \
| python3 mcp_server.py --microstack corpus/microstack
→ one compact digest: 9 services, the 3 drifts (recomputed live from the raw files, not read from a snapshot), the rules, the last recorded decision.
2. Catch a lie — claim that cache is running, against ground truth:
printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"state_verify","arguments":{"domain":"services","filter":{"name":"cache"},"expect":{"actual":"running"}}}}' \
| python3 mcp_server.py --microstack corpus/microstack
→ "holds": false with evidence: declared active, observed stopped. That is the whole philosophy in
one call: don't trust the report — verify against state.
3. Combined demo — full agent canon with session journaling, per-ref focus tracking, and a VSM task provider:
python3 /abs/path/state-canon/mcp_server.py \
--instance /abs/path/instances/tasks_provider.py:/abs/path/your-TASKS.vsm \
--journal /abs/path/sessions/journal.db \
--focus /abs/path/current_focus.json
This enables state_onboard/state_query/state_reconcile (task and focus
reconcilers), state_journal_* (snapshot/diff/history), and
state_focus_mark/state_focus_close (per-agent focus tracking) — the full
canon loop with no external dependencies.
4. With your agent attached, ask it:
- "What's the current state of the system? Any drift?" → it calls
state_onboardonce. - "Is the cache service consistent with what's declared?" → it calls
state_query/state_verify.
The usage pattern that measured best (see Measured): inject the digest
once at session start (cheapest for broad questions), then use targeted state_query for follow-ups
(cheapest for narrow ones). Cold re-exploration is always the most expensive.
Onboarding your system — three steps
Step 1 — point the server at your state
JSON (quickest start). Any JSON document whose top-level keys hold lists of records:
{ "system": "my-stack",
"services": [ {"name": "api", "state": "running", "version": "1.2.0"} ],
"rules": [ "api and worker versions must match" ],
"last_decision": {"id": "D-1", "text": "approved cache upgrade"} }
python3 mcp_server.py --state /abs/path/my-state.json
SQLite. Every table/view becomes a queryable domain automatically — or pass an explicit mapping (see
instances/sqlite_ops.py for a real production example). Opened read-only by construction (mode=ro):
from state_canon.sqlite_provider import SqliteStateProvider
provider = SqliteStateProvider("ops.db",
domains={"services": "services", "blockers": "v_open_blockers"},
meta={"system": "my-stack"})
Git. --git /path/to/repo — the working tree becomes a live drift report (see INTERFACE.md).
An API / anything. Implement two methods (this is remote mode — the server stays local, the provider crosses the wire; an SSH-channel provider is the same shape):
from state_canon.provider import StateProvider
import json, urllib.request
class ApiStateProvider(StateProvider):
def __init__(self, base_url): self.base = base_url
def list_domains(self): return ["services", "incidents", "meta"]
def query(self, domain, filter=None):
if domain == "meta": return [{"system": "my-stack"}]
with urllib.request.urlopen(f"{self.base}/{domain}") as r: records = json.load(r)
if filter: records = [x for x in records if all(x.get(k) == v for k, v in filter.items())]
return records
Step 2 — shape the digest (what matters at onboard)
Raw dumps don't belong in a model's context (our first real-canon digest weighed 44k chars; the policy cut
it to 12k). Declare what matters per domain — detail is never lost, it stays one state_query away:
DIGEST_POLICY = {
"services": {"fields": ["name", "state", "version"], "max": 60},
"deploys": {"fields": ["component", "result"], "max": 5, "last": True},
"audit_log": {"skip": True}, # queryable, but not onboard material
}
Step 3 (optional but recommended) — add a reconciler
This is where canon earns its keep: declared vs observed → typed drift, computed live.
from state_canon.reconcile import Reconciler
class MyReconciler(Reconciler):
domain = "services"
def declared(self): # the model: your manifest / DB / config
return load_manifest()
def observe(self): # the reality: ps, systemctl, an API probe...
return probe_running_processes()
The generic diff yields declared_but_missing, orphan, and your registered rule_violations — exactly
the three drift kinds seeded in the demo. Wire it in an instance module (instances/microstack.py is the
40-line reference).
Use cases
| Scenario | Call pattern |
|---|---|
| Agent session start — "where am I?" | state://digest resource injected, or one state_onboard |
| "Is service X ok?" mid-session | state_query(services, {name: X}) — ~80 tokens |
| Reviewing another agent's (or person's) report | state_verify(domain, filter, expect) → holds/mismatches + evidence |
| Post-deploy sanity | state_reconcile() → live drift list (orphans, missing, rule violations) |
| CI gate | run the reconcile in a script; fail the pipeline if drift ≠ expected |
Measured, not proclaimed
We refuse to ship a token-savings number we haven't earned. EXPERIMENT.md defines a reproducible experiment: the same tasks run cold (agent re-explores) vs onboard (state injected) vs MCP (state queried), on a small controlled corpus, same model, ≥5 trials, scored for correctness (savings that produce a wrong answer are disqualified). The honest finding is a trade-off — onboard wins broad tasks, lazy MCP wins narrow ones, cold always loses — reported that way in RESULTS.md, caveats and all.
Status (honest)
| piece | state |
|---|---|
| Thesis + method | ✅ in daily use |
| MCP server (stdlib-only) | ✅ 28/28 checks + end-to-end stdio smoke |
| Controlled corpus + experiment | ✅ trade-off characterized (onboard wins broad, lazy query wins narrow, cold always loses; correctness intact) |
| Second living instance (SQLite over a real production canon) | ✅ 14/14 structural checks; digest policy born from a real 44k→12k lesson |
| Git/VCS instance | ✅ GitStateProvider + reconciler (--git); the three drift kinds fall out of git status; read-only; 17/17 checks |
| SSH SQLite instance | ✅ SshSqliteStateProvider — remote SQLite over SSH subprocess; read-only by construction; shlex.quote() for shell metacharacters; 20/20 checks |
| Remote mode (remote state, local server) | ✅ by design at the provider layer; remote MCP transport = open integration point, not shipped |
| Session journal (snapshot / diff / trend) | ✅ opt-in via --journal; 28/28 checks incl. degrade-to-zero when the instance-specific JOURNAL_STATS_FN/JOURNAL_RAG_FN hooks aren't supplied |
| VSM task-file provider + Focus (read/write) | ✅ tasks_provider: 68/68 checks (VSM parser + TaskSessionReconciler catches stale tasks + FocusTaskReconciler catches stale focus entries: stale_focus_task_done, stale_focus_session_resolved; provider + both reconcilers reload on mtime change, bind_focus_tracker keeps focus reads and writes on the same live store). FocusTracker: 42/42 checks (--focus flag, state_focus_mark/close, atomic write, dogfooded on own focus.json) |
| End-to-end smoke (subprocess stdio) | ✅ 27/27 checks — spawns real MCP server with --instance + --focus + --journal, sends JSON-RPC over stdio, asserts reconcile patterns, focus mark/close, journal persist, onboard digest |
| FreshnessReconciler | ✅ state_canon/freshness.py — generic core reconciler: flags missing or stale files by mtime vs max_age. Two drift kinds (missing, stale). Domain-configurable. Stdlib-only, 6 pytest tests. |
| Provider-contract safeguard (state_verify vs declared/observed) | ✅ state_verify cross-checks the provider against any registered reconciler's observe() and surfaces a warning on disagreement; 22/22 checks (tests/test_housekeeping_fixes.py, also covers duplicate-key drift detection, focus.py concurrent-write locking, JsonStateProvider mtime reload + unknown-filter parity with SqliteStateProvider, digest.py fallback labeling) |
| Skills (9) + agent spec (1) | ✅ the disciplines and the single-agent spec, installable |
| Publication-grade token counters (real MCP attach + billed usage) | ⬜ pending |
| Realistic corpus + published numbers | ⬜ after validation |
Legend: ✅ built and tested · ⬜ declared/roadmap. METHODOLOGY.md, skills/ and agents/ are practice
documentation (the pattern we run daily), not orchestration code — nothing here pretends to be what it isn't.
Going deeper
- METHODOLOGY.md — the canon-grounded agent: roles, the four boundaries where quality is made, the real four-round case study, and the honest limits.
- INTERFACE.md — extending & integrating: the provider abstraction, reference instances, the Git/VCS instance, remote mode, and composing with other context sources.
- skills/ — the nine disciplines as portable, installable skills (each with its scar).
- agents/ — the agent as an adoptable spec (
agent.md). - corpus/microstack/ — the measurement corpus, experiment design, and results.
- CHANGELOG.md — what changed, by version.
Repository layout
state-canon/
├── README.md ← this file (what it is · install · usage · status)
├── METHODOLOGY.md ← the canon-grounded agent (the method behind the tool)
├── INTERFACE.md ← extending & integrating (abstraction · git · remote · composition)
├── CHANGELOG.md ← what changed, by version
├── LICENSE
├── skills/ ← the disciplines as portable skills (9 · installable in Claude Code)
├── agents/ ← the agent as an adoptable spec
├── mcp_server.py ← one-file launcher for any MCP client
├── state_canon/ ← the library (stdlib)
│ ├── provider.py ← StateProvider ABC + JsonStateProvider
│ ├── server.py ← MCP stdio server (JSON-RPC dispatch, 9 tools, 4 resources)
│ ├── digest.py ← compact onboard digest
│ ├── reconcile.py ← Reconciler ABC + Drift dataclass
│ ├── journal.py ← StateJournal (snapshot/diff/trend over SQLite)
│ ├── focus.py ← FocusTracker (atomic read-write, flock)
│ ├── freshness.py ← FreshnessReconciler (stale/missing files by mtime)
│ ├── sqlite_provider.py ← SqliteStateProvider (read-only)
│ ├── ssh_sqlite_provider.py ← SshSqliteStateProvider (remote SQLite over SSH)
│ └── git_provider.py ← GitStateProvider + GitReconciler
├── instances/ ← reference instances
│ ├── microstack.py ← demo (manifest vs processes)
│ ├── sqlite_ops.py ← production ops DB
│ ├── ssh_sqlite_ops.py ← remote SSH variant
│ └── tasks_provider.py ← VSM task/session parser + reconcilers
├── tests/ ← test suites (254 check() calls + 6 pytest tests)
└── corpus/microstack/ ← controlled measurement corpus
├── raw/ ← what a COLD agent faces (must synthesize)
├── synthesized/ ← the reconciled state (the canon's product)
├── EXPERIMENT.md · RESULTS.md · GROUND_TRUTH.md · TASKS.md
Lineage & philosophy
Built on Stafford Beer's Viable System Model and the Cybersyn project (Chile, 1971) — a system is viable when it can be described, governed, and audited. "Don't trust, verify" isn't a slogan here; it's the reconciler and the verify-at-every-boundary discipline made mechanical. Community-first, and deliberately from the Global South — the heir to Cybersyn's bet that good cybernetics serves people.
License
Code: Apache-2.0 (LICENSE) · Docs: CC-BY-4.0.
This module is deliberately more permissive than the AGPL core it was extracted from — it is meant to be adopted, embedded, and improved by the community. Improvements can flow back; the module stays clean of AGPL code by construction.