Restore an MCP session
Restart Chio's HTTP MCP edge in front of the pricing server and keep using a client on its saved session, under the authority in force now.
On this page
A pricing client that survives a restart of the edge in front of it: the same session, the quotes it already holds, and only the authority the current policy grants.
Larkfield's pricing functions run as an ordinary stdio MCP server with two tools: quote, which records each quote it issues, and discount, which only computes. Chio's authenticated HTTP MCP edge sits in front of it. The client saves its negotiated session ID and protocol, the edge persists sessions, authority, and receipts, and the server keeps the quotes. Each owner persists its own part.
The saved session continues under current policy
The client reconnects with its saved MCP-Session-Id and protocol, without initializing again. quote records the quote, and the result carries a chioReceipt with its own receipt ID. MCP edge.
tools/call on a saved MCP session after the edge restarts. Messages and status codes come from the captured exchange; initialization and the trust view are left out.A restored session carries its identity forward, never yesterday’s authority. After a restart, the kernel still checks every call against the current policy and revocations, and an expired session stops at the edge before any call.
1. Start the persistent edge
Starts from chio new pricing-desk --example pricing-desk, or the same project as a download. Run these in pricing-desk. The client and the pricing server use only Python's standard library, so this chapter needs no Rust build.
python3 mcp_client.py setup
export CHIO_AUTH_TOKEN="$(cat state/mcp/auth-token.txt)"
chio --receipt-db state/mcp/receipts.db \
--session-db state/mcp/sessions.db \
--authority-seed-file state/mcp/authority.seed \
mcp serve-http --policy mcp-policy.yaml \
--server-id pricing --listen 127.0.0.1:4313 \
-- python3 mcp_pricing.pyReady: state/mcp. Credential: state/mcp/auth-token.txt
You should see the setup line, then the edge listening on 127.0.0.1:4313. Keep terminal 1 running. The credential, session store, authority seed, receipts, and quotes all live under state/mcp.
Before you begin
Install the CLI with curl -fsSL https://chio.computer/install.sh | sh (see Installation) and use Python 3.10 or newer. --session-db and --authority-seed-file are global flags; --policy, --server-id, and --listen belong to chio mcp serve-http.
2. Connect and request a quote
In a second terminal in the same directory, connect, quote sku-118 at 10 percent, and keep a copy of the saved session.
python3 mcp_client.py connect
python3 mcp_client.py quote sku-118 10
cp state/mcp/client-session.json session-before-restart.jsonConnected. Saved session: 6194…50ee
{
"result": {
"_meta": {
"chioReceipt": {
"receiptId": "36e0…4bb9",
"requestId": "mcp-edge-req-ec01…c29a"
}
},
"content": [{ "text": "{\"id\": \"cba1391a-…\", \"sku\": \"sku-118\", \"percent\": 10.0, \"price\": \"21.60\", \"currency\": \"USD\"}" }],
"isError": false
}
}You should see the saved session ID and a result whose chioReceipt names the receipt for this one call. The client writes state/mcp/client-session.json after initialization, and the quote lands in state/mcp/quotes.
3. Restart the edge and reconnect
Stop terminal 1 with Ctrl+C and run the same command again, keeping the databases, authority seed, policy, and credential. Then continue in terminal 2 on the existing session.
cmp session-before-restart.json state/mcp/client-session.json
python3 mcp_client.py quote sku-204 25{
"result": {
"_meta": {
"chioReceipt": {
"receiptId": "08ff…f8f3"
}
},
"content": [{ "text": "{\"id\": \"2f80a481-…\", \"sku\": \"sku-204\", \"percent\": 25.0, \"price\": \"43.50\", \"currency\": \"USD\"}" }],
"isError": false
}
}You should see cmp print nothing and a second quote with its own receipt. An unchanged session file with a successful call shows reconnection to the saved session, and both quotes sit in state/mcp/quotes. The client never initializes again or substitutes a new session. Each part has one owner:
- Client:
client-session.jsonandauth-token.txt, the negotiated ID and protocol plus valid authentication. - Edge:
sessions.dbandauthority.seed, eligible session state and consistent authority across restarts. - Evidence:
receipts.db, the signed call records; a saved session file alone keeps no receipts. - Pricing server: the quotes
mcp_pricing.pyrecords understate/mcp/quotes, independent of session lifecycle. - Operator:
mcp-policy.yaml, which decides what new calls may do after a restart.
How the client reconnects
headers["Authorization"] = "Bearer " + token
headers["MCP-Session-Id"] = saved_session["id"]
headers["MCP-Protocol-Version"] = saved_session["protocol"]
# Each tools/call gets a fresh JSON-RPC request ID.4. Narrow the policy under the same session
Stop the edge, remove the quote grant so the default capability keeps only discount, and restart it.
capabilities:
default:
tools:
- server: pricing
tool: discount
operations: [invoke]
ttl: 300python3 mcp_client.py quote sku-301 80
python3 mcp_client.py discount sku-301 80
cmp session-before-restart.json state/mcp/client-session.json{
"result": {
"_meta": { "chioReceipt": { "receiptId": "11f3…88d2" } },
"content": [{ "text": "requested tool quote on server pricing is not in capability scope" }],
"isError": true
}
}
{
"result": {
"_meta": { "chioReceipt": { "receiptId": "4f2b…0807" } },
"content": [{ "text": "{\"sku\": \"sku-301\", \"percent\": 80.0, \"price\": \"28.000\", \"currency\": \"USD\"}" }],
"isError": false
}
}You should see the quote refused with a receipt ID, discount still answering, and the session file unchanged. The refused quote is never recorded. The client exits unsuccessfully on HTTP errors and on tool results marked as errors, and never replaces its session on its own.
5. Revoke the capability and verify the refusal
The local credential also holds operator access to this development edge. Revoke the saved session’s capability, then call again.
python3 mcp_client.py revoke
python3 mcp_client.py discount sku-301 80{
"capabilities": [
{ "capabilityId": "cap-01a08e2f-4288-7913-92e6-c8dd71529990", "revoked": true }
],
"lifecycle": { "state": "ready" }
}
{
"result": {
"_meta": { "chioReceipt": { "receiptId": "9915…f556" } },
"content": [{ "text": "capability has been revoked: cap-01a08e2f-4288-7913-92e6-c8dd71529990" }],
"isError": true
}
}You should see the session ready with its capability revoked, and the next call refused with its own receipt ID. The refusal holds after another edge restart: session availability and permission to act are separate checks. A replacement session is a separate authorization decision, and the client refuses to overwrite its session file. Your receipt and capability IDs differ from these.
Complete source: mcp_client.py, mcp_pricing.py, mcp-policy.yaml, all in the project download.
Expired, drained, and revoked sessions fail differently
An expired or drained session cannot reconnect by presenting its saved ID. To watch idle expiry, use a separate extracted project and a fresh session, add CHIO_MCP_SESSION_IDLE_EXPIRY_MILLIS=5000 and CHIO_MCP_SESSION_REAPER_INTERVAL_MILLIS=100 to the edge command, and leave the client idle for more than five seconds. The next call returns HTTP 410, “MCP session expired and must be re-initialized”, and the client keeps the old record for inspection. Keep this tuning away from a service that must tolerate longer disconnects.
A missing session file means the client cannot present its identity; a missing seed or session store means a different edge configuration. The stores preserve session state, authority, admission records, and receipts, never a running tool invocation. No expiry or refusal authorizes replaying a call that may have completed: investigate the original request and its effect first.