Chio/Docs

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.

Guide chio@5f63e6e

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.

Start
Session outcomesChoose an outcome.

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.

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

Terminal 1 · pricing-deskbash
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.py
Output
Ready: 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.

Terminal 2 · pricing-deskbash
python3 mcp_client.py connect
python3 mcp_client.py quote sku-118 10
cp state/mcp/client-session.json session-before-restart.json
Output
Connected. 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.

Terminal 2 · pricing-deskbash
cmp session-before-restart.json state/mcp/client-session.json
python3 mcp_client.py quote sku-204 25
Output
{
  "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.json and auth-token.txt, the negotiated ID and protocol plus valid authentication.
  • Edge: sessions.db and authority.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.py records under state/mcp/quotes, independent of session lifecycle.
  • Operator: mcp-policy.yaml, which decides what new calls may do after a restart.
How the client reconnects
mcp_client.py · reconnect without reinitializingpython
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.

mcp-policy.yaml · read-only default capabilityyaml
capabilities:
  default:
    tools:
      - server: pricing
        tool: discount
        operations: [invoke]
        ttl: 300
Terminal 2 · pricing-deskbash
python3 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
Output
{
  "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.

Terminal 2 · pricing-deskbash
python3 mcp_client.py revoke
python3 mcp_client.py discount sku-301 80
Output
{
  "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.

Next: rotate keys and revoke authority