Skip to content
Conduit

mcp server

state-canon-mcp

A state-grounded RAG that puts verified current state in the agent's path, so it reasons from ground

rm-w3kufe/state-canon-mcpcommunitylistedunreviewed

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, 2026

state-canon — verified ground truth for agents

CI Python 3.10+ License: Apache-2.0 MCP Compatible No Dependencies

An agent grounded in verified canon does more careful work than one running on recall — and burns fewer tokens. state-canon keeps 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

  1. 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.
  2. 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.
  3. 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:

tooldoes
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_onboard once.
  • "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

ScenarioCall pattern
Agent session start — "where am I?"state://digest resource injected, or one state_onboard
"Is service X ok?" mid-sessionstate_query(services, {name: X}) — ~80 tokens
Reviewing another agent's (or person's) reportstate_verify(domain, filter, expect) → holds/mismatches + evidence
Post-deploy sanitystate_reconcile() → live drift list (orphans, missing, rule violations)
CI gaterun 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)

piecestate
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.

↑↓ navigate openesc close