Migrate from MCP to Chio
You already have an MCP server and an MCP-speaking agent. Chio is designed to slot between them without asking either side to change. The agent still makes tools/list and tools/call requests. The server still answers them. The wire between them just gained policy enforcement and signed receipts. This page is the shortest path from plain MCP to governed MCP.
Who this guide is for
What Doesn't Change
The design goal of the adapter is that both ends of the conversation remain MCP-native. Nothing you have already built is thrown away.
- Your agent stays unchanged. It still speaks MCP. The same client library, the same config file, the same
tools/listandtools/callrequest shapes, the same response format. - Your server stays unchanged. It runs as a subprocess behind Chio exactly as it would run on its own. Same binary, same arguments, same stdio transport.
- Tool definitions are preserved. Names, input schemas, output schemas, descriptions, and annotations are forwarded verbatim from
tools/list. Whatever your server exposes, the agent sees. - Stdio transport is preserved. If you launch MCP servers via stdio today, you keep doing that. Chio just happens to be the process the client launches.
Practical consequence: zero client rewrite, zero server rewrite. The migration is a config change plus a policy file.
What Does Change
What the agent experiences is still MCP, but the semantics get richer:
- Signed receipts. Every allow and every deny produces a
chio.receipt.v1signed with Chio's Ed25519 key. You can verify receipts offline and feed them into auditing pipelines. - Policy-enforced denials. Calls that were previously unconditional can now be blocked by your policy. A filesystem write against
.envreturns a structured error instead of executing. - Optional cost metering. If you configure tool pricing, each call exposes a per-call price and the agent can surface budget state. Opt-in; disabled by default.
- Structured deny reasons. A denied call carries the guard that failed and a human-readable reason. Agents that handle errors well can recover or re-plan.
Before and After
Before Chio, the agent talks directly to the MCP server. After Chio, the agent talks to Chio, Chio talks to the MCP server, and the agent-facing interface is identical.
Notice that the agent side of both diagrams is the same set of MCP messages. The change is entirely inside the middle hop.
Step 1: Wrap Your Existing MCP Server
The Wrap an MCP Server guide is the full walkthrough. For migration, you only need two things: a policy file and a one-line launcher change.
First, drop in a starter policy. The code-agent preset is a safe, opinionated baseline for file / shell / git workflows:
$ chio init my-governed-mcp
$ cd my-governed-mcp
# produces a runnable policy.yaml with the code-agent defaultsSecond, change your MCP client config so it launches chio instead of the raw server. Everything after -- is the literal command Chio runs as the subprocess, so copy your original command verbatim:
{
"mcpServers": {
"filesystem": {
"command": "chio",
"args": [
"mcp", "serve",
"--policy", "./policy.yaml",
"--server-id", "fs",
"--",
"npx", "-y",
"@modelcontextprotocol/server-filesystem", "./workspace"
]
}
}
}That is the whole wrap. Restart the client. The agent sees the same tools, same schemas, same responses, and now every call is mediated by Chio's kernel.
Migrate one server at a time
--server-id, and the unwrapped ones keep working. There is no big-bang migration; you can run a mixed fleet indefinitely.Step 2: Write Your First Policy
The Write a Policy guide is the reference. For migration, start with the safest possible shape: deny every tool by default, then allow exactly one. That gives you a visible, verifiable baseline before you open anything up.
hushspec: "0.1.0"
name: migration-baseline
rules:
tool_access:
enabled: true
default: block
allow:
- read_file
forbidden_paths:
enabled: true
patterns:
- "**/.env"
- "**/.env.*"
- "**/.ssh/**"
- "**/*.pem"
- "**/*.key"
- "**/credentials*"
exceptions: []
secret_patterns:
enabled: true
velocity:
enabled: true
max_invocations: 100
window_seconds: 60With this in place, your agent can call read_file on anything that does not match a forbidden pattern, and literally every other tool is denied. That might feel aggressive — it is — but it gives you a receipt for every tool your agent wanted to call so you know exactly what to add to the allow list.
Exercise the policy with chio check before you point an agent at it:
# ALLOW: read in workspace
$ chio check --policy ./policy.yaml \
--tool read_file \
--params '{"path": "./workspace/README.md"}'
verdict: ALLOW
tool: read_file
server: *
receipt_id: rcpt-019dbbf8-33db-7f21-81c7-aab0427616c8
policy: a40c24d0930d773e060fac86dd77e24e68af4cb0a59b1b836759ed63fbaa23b8
source: d14550004f854d4131839bd2388b3ec9aa3784c898a47f261d434bffbc88d799
# DENY: write_file not in allow list
$ chio check --policy ./policy.yaml \
--tool write_file \
--params '{"path": "./workspace/output.txt", "content": "hi"}'
verdict: DENY
tool: write_file
server: *
reason: requested tool write_file on server * is not in capability scope
receipt_id: rcpt-019dbbf8-33ef-7dc3-9143-25968ddd18e9
policy: a40c24d0930d773e060fac86dd77e24e68af4cb0a59b1b836759ed63fbaa23b8
source: d14550004f854d4131839bd2388b3ec9aa3784c898a47f261d434bffbc88d799
# DENY: .env is a forbidden path, even for reads
$ chio check --policy ./policy.yaml \
--tool read_file \
--params '{"path": "./workspace/.env"}'
verdict: DENY
tool: read_file
server: *
reason: guard denied the request: guard "forbidden-paths" denied the request: path matches forbidden pattern **/.env
receipt_id: rcpt-019dbbf8-3405-74b2-86b5-07ac94779b39
policy: a40c24d0930d773e060fac86dd77e24e68af4cb0a59b1b836759ed63fbaa23b8
source: d14550004f854d4131839bd2388b3ec9aa3784c898a47f261d434bffbc88d799Fail-closed by default
Step 3: Check Receipts
Run your agent. Make it do real work. Then pull the receipts and look at what happened. This is where the migration pays for itself: you now have a cryptographic record of every tool call your agent attempted.
Make sure you pointed chio mcp serve at a receipt database (for example --receipt-db ./receipts.sqlite). Then list receipts with the same database. Default output is JSON Lines (one receipt per line):
$ chio --receipt-db ./receipts.sqlite receipt list \
--tool-server fs --limit 3
{"id":"rcpt-019dbbf8-4cfe-...","timestamp":1776975105,"capability_id":"cap-019dbbf8-4cdd-...","tool_server":"fs","tool_name":"read_file","action":{"parameters":{"path":"./workspace/README.md"},"parameter_hash":"a3c8a200..."},"decision":{"verdict":"allow"},"content_hash":"42e9fd40...","policy_hash":"a40c24d0...","metadata":{...},"kernel_key":"25403c1e...","signature":"7be63cdb..."}
{"id":"rcpt-019dbbf8-4d78-...","timestamp":1776975105,"capability_id":"cap-019dbbf8-4d6a-...","tool_server":"fs","tool_name":"write_file","action":{"parameters":{...},"parameter_hash":"..."},"decision":{"verdict":"deny","reason":"requested tool write_file on server fs is not in capability scope","guard":"kernel"},...}
{"id":"rcpt-019dbbf8-4d90-...","timestamp":1776975106,"capability_id":"cap-...","tool_server":"fs","tool_name":"read_file","action":{"parameters":{"path":"./workspace/.env"},"parameter_hash":"..."},"decision":{"verdict":"deny","reason":"guard \"forbidden-paths\" denied the request",...},...}Pipe to jq for summaries (decision counts, denied tools, cost histograms) and wire the same stream into downstream audit pipelines.
For offline verification, export an evidence package and verify it without contacting the kernel:
$ chio --receipt-db ./receipts.sqlite evidence export --output ./pkg
$ chio evidence verify --input ./pkg
# evidence package verified
# tool_receipts: 10
# checkpoint_equivocations: 0
# capability_lineage: 3
# verified_files: 8The package bundles receipts, capability lineage, checkpoints, and inclusion proofs. Any gap or mutation fails the verification with a non-zero exit. See Query and Audit Receipts for the full query surface (filter by server, tool, outcome, capability, time range) and Verify Receipts Offline for the air-gapped verification workflow.
Compatibility Matrix
The adapter is a thin translation layer; most MCP features pass through unchanged. The table below is grounded in what chio-mcp-adapter actually implements today.
| MCP feature | Status | Notes |
|---|---|---|
tools/list discovery | Supported | Adapter queries the upstream once and builds a governed manifest; schemas and annotations are preserved verbatim. |
tools/call invocation | Supported | Every call runs through the guard pipeline before dispatch; every decision is a signed receipt. |
| stdio transport | Supported | Canonical migration path. Your client spawns chio instead of the raw server. |
| Streamable HTTP transport | Supported via chio mcp serve-http | Session contract follows MCP spec: initialize on POST /mcp, session id in response, GET /mcp for notifications and replay. |
| Server notifications | Forwarded | Drained from the upstream and delivered to the client in order; not subject to guard evaluation. |
resources/list, resources/read | Passthrough with opt-in scoping | Default is allow-through. Enforce with resource_grants in a custom policy if you want per-URI control. |
resources/templates/list | Passthrough | Templates are surfaced to the client; no template-level guard today. |
prompts/list, prompts/get | Passthrough with opt-in scoping | Similar to resources. Use prompt_grants for per-prompt control; receipts still cover every fetch. |
Argument completion (completion/complete) | Supported | Completion requests for prompts and resource URIs forward to the upstream; the result is returned unchanged. |
Sampling (sampling/createMessage) | Passthrough | Nested sampling is proxied unchanged. For governance on nested calls, enable allow_sampling_tool_use in the policy kernel block. |
| Elicitation (including URL elicitation) | Supported | URL-mode elicitations are parsed and surfaced as structured operations, so the agent can prompt the user to open an auth page. |
| OAuth2 / OIDC on the HTTP edge | Supported | Available only on the HTTP edge, not on stdio. --preset is stdio-only today. |
| Windows native stdio | Untested | Use WSL. macOS and Linux are the supported platforms. |
Common Migration Surprises
Most migrations are boring in the best way. These are the edges that bite people often enough that they are worth calling out.
- Large tool results go through guards. Tools that return many megabytes (log tailers, file readers, search aggregators) see every byte evaluated for secret patterns before it reaches the agent. For stdio-wrapped servers this can dominate latency on large responses. Heavy streamers should move to
chio mcp serve-http, which exposes backpressure controls, or have their responses sliced server-side into smaller chunks. - Unusual tool names. Tool names become identifiers in policy files. Names with colons, dots, or whitespace are legal in MCP but awkward to target in YAML. If your server exposes a tool called
fs:write, quote it in the allow list:- "fs:write". If you control the server, prefer plain snake_case. - Long-running tools and timeouts. Chio does not add its own tool timeout — it will wait as long as the upstream takes. But your MCP client may have one, and the agent-facing error on a client-side timeout is indistinguishable from a deny at first glance. Check the receipt log: a real deny has a
decision: denyentry; a timeout leaves only an open call. - Prompts and resources are allow-through by default. The starter preset governs
tools/call. MCP resources and prompts are proxied untouched unless you addresource_grantsorprompt_grantsto a custom policy. Receipts still record every fetch, so nothing is hidden — but the guard pipeline is not enforcing here until you ask it to. - Receipts are not retroactive. Chio can only sign what passed through it. Tool calls your agent made before the migration are gone; the audit trail starts the first time the client talks to
chio mcp serve.
Summary
The three-step loop is the whole migration:
- Wrap. Point your MCP client at
chio mcp servewith the original server command after--. - Policy. Start with a deny-by-default rule and one allowed tool. Expand from there using receipts to see what the agent actually wants.
- Audit. Query and verify receipts to confirm the policy is doing what you think it is.
Everything else — capability tokens, delegation, tool pricing, HTTP edges, custom guards — is layered on top of that baseline. You can stop after step three and still have more governance than plain MCP offers.
Next Steps
- Wrap an MCP Server · the full walkthrough with every flag and option covered
- Write a Policy · author HushSpec policies that match your real workflows
- Query and Audit Receipts · filter, export, and verify the audit trail
- Architecture · how the kernel, guards, and adapters fit together