BuildConnect
Migrate from MCP to Chio
Add signed receipts and policy enforcement to an existing MCP client-server connection without changing the agent or server.
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. Existing client and server implementations remain in use.
- 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.
The migration requires a client configuration change and a policy file, not a client or server rewrite.
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 has the complete walkthrough. For migration, you need two things: a policy file and a one-line launcher change.
First, pick a starting policy. The code-agent preset is a safe, opinionated baseline for file / shell / git workflows, and it needs no policy file at all — pass --preset code-agent and Chio applies the bundled deny-by-default guard set directly:
# No policy file required -- the preset is built in.
$ chio mcp serve --preset code-agent --server-id fs \
-- npx -y @modelcontextprotocol/server-filesystem ./workspaceThere is no command that writes the preset out to a file for editing. When you outgrow it, hand-author a HushSpec policy (Step 2) and swap --preset code-agent for --policy ./policy.yaml. Note that chio init <path> is a different tool: it scaffolds a standalone runnable demo project — its own toy tool server plus a smoke runner — not a drop-in policy you can point at an existing filesystem, postgres, or github server.
Second, 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"
]
}
}
}Restart the client after this change. The agent receives the same tools, schemas, and responses, with each call 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.Python-first? Embed the SDK instead
pip install chio-code-agent chio-sdk-python, then from chio_code_agent import CodeAgent and from chio_sdk import ChioClient. The import is chio_sdk (from the PyPI package chio-sdk-python), not a top-level chio. The file-backed HushSpec plus chio mcp serve stays the default operator workflow.Step 2: Write Your First Policy
The Write a Policy guide is the reference. For migration, begin with a deny-by-default policy that allows one tool. This provides a baseline before you add more tools.
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_per_window: 100
window_secs: 60With this in place, your agent can call read_file on anything that does not match a forbidden pattern; all other tools are denied. The resulting receipts show which tools 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-019dbc0a-71a2-7c48-b90d-3f61e2a8d114
policy: 7c19f4a2e0d38b6154fac09d2e77b41a6c8ef35b1a24d9078e6fbc3d5091a2e4
source: 2f6b8d1c9e04a7538bd1e26f4c90a83bd57e14f9a2c60831be47d15fca9082c3
# 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-019dbc0a-71b5-7e02-a4c7-6d9024fb3e77
policy: 7c19f4a2e0d38b6154fac09d2e77b41a6c8ef35b1a24d9078e6fbc3d5091a2e4
source: 2f6b8d1c9e04a7538bd1e26f4c90a83bd57e14f9a2c60831be47d15fca9082c3
# 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-path" denied the request: path matches forbidden pattern **/.env
receipt_id: rcpt-019dbc0a-71c9-7a91-8f3b-12ce70a5b4d0
policy: 7c19f4a2e0d38b6154fac09d2e77b41a6c8ef35b1a24d9078e6fbc3d5091a2e4
source: 2f6b8d1c9e04a7538bd1e26f4c90a83bd57e14f9a2c60831be47d15fca9082c3Fail-closed by default
Step 3: Check Receipts
Run the agent through representative work, then inspect the receipts. They provide a cryptographic record of the tool calls it 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. A local read fails closed unless you pass exactly one of --tenant <id> or --admin-all, so the boundary is always explicit. Output is JSON Lines (one receipt per line):
$ chio --receipt-db ./receipts.sqlite receipt list \
--tool-server fs --limit 3 --admin-all
{"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":"7c19f4a2...","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-path\" 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, which returns its response 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 require only the configuration change. Watch for these common edge cases.
- 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 policy denial 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 migration has three steps:
- 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.
Capability tokens, delegation, tool pricing, HTTP edges, and custom guards build on that baseline. After step three, Chio evaluates calls and records signed receipts.
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