Chio/Docs

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

You have an MCP client (Claude Desktop, Cursor, a custom agent) pointed at an MCP server (filesystem, postgres, github, your own) and you want audit trails, policy, and denials for unsafe calls. You do not want to rewrite either side. You are in the right place.

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/list and tools/call request 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.v1 signed 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 .env returns 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.

rendering…
Before: plain MCP. The agent speaks MCP straight to the server. Every call executes; nothing is written down.
rendering…
After: governed MCP. Chio is the process the client launches; the agent speaks the same MCP it spoke before. The server runs unchanged as a subprocess.

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:

bash
# No policy file required -- the preset is built in.
$ chio mcp serve --preset code-agent --server-id fs \
    -- npx -y @modelcontextprotocol/server-filesystem ./workspace

There 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:

mcp-client-config.json
{
  "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

If you have multiple MCP servers configured, wrap them one by one. Each wrapped server gets its own --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

If your coding agent is a Python process, you can skip the CLI wrap and embed the SDK directly: 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.

policy.yaml
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: 60

With 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:

bash
# 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:     2f6b8d1c9e04a7538bd1e26f4c90a83bd57e14f9a2c60831be47d15fca9082c3

Fail-closed by default

Chio denies anything the policy does not explicitly allow. That is a deliberate inversion of the plain-MCP default. Expect the first run of your agent to hit denials for tools that were silently allowed before — that is the whole point. Add them to the allow list one at a time, with the guards you need, without broadly enabling tools.

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):

bash
$ 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:

bash
$ 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:         8

The 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 featureStatusNotes
tools/list discoverySupportedAdapter queries the upstream once and builds a governed manifest; schemas and annotations are preserved verbatim.
tools/call invocationSupportedEvery call runs through the guard pipeline before dispatch; every decision is a signed receipt.
stdio transportSupportedCanonical migration path. Your client spawns chio instead of the raw server.
Streamable HTTP transportSupported via chio mcp serve-httpSession contract follows MCP spec: initialize on POST /mcp, session id in response, GET /mcp for notifications and replay.
Server notificationsForwardedDrained from the upstream and delivered to the client in order; not subject to guard evaluation.
resources/list, resources/readPassthrough with opt-in scopingDefault is allow-through. Enforce with resource_grants in a custom policy if you want per-URI control.
resources/templates/listPassthroughTemplates are surfaced to the client; no template-level guard today.
prompts/list, prompts/getPassthrough with opt-in scopingSimilar to resources. Use prompt_grants for per-prompt control; receipts still cover every fetch.
Argument completion (completion/complete)SupportedCompletion requests for prompts and resource URIs forward to the upstream, which returns its response unchanged.
Sampling (sampling/createMessage)PassthroughNested sampling is proxied unchanged. For governance on nested calls, enable allow_sampling_tool_use in the policy kernel block.
Elicitation (including URL elicitation)SupportedURL-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 edgeSupportedAvailable only on the HTTP edge, not on stdio. --preset is stdio-only today.
Windows native stdioUntestedUse 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: deny entry; 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_grants or prompt_grants to 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 serve with 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