Chio/Docs

Admin API

Operate a running MCP edge over its token-gated admin routes, from the first health read to a session drain.

Deployment chio@5f63e6e

On this page

An operator holding the admin token can inspect a running MCP edge, rotate its issuing key, revoke capabilities, and drain sessions over the same listener that serves /mcp.

The /admin/* routes belong to chio mcp serve-http. The Trust Control Plane is a separate service with its own routes and credentials.

Start
Admin routesChoose a mode.
chio mcp serve-http/admin/*/mcpauthority, receipts, budgets, revocationsooperatoraagent sessiongadmin token, Origingsession tokenKkernelssessionsρreceiptsσauthorityκtrust control planechio mcp serve-http/admin/*/mcpforwardedooperatoraagentgadmin tokengsession tokenKkernelssessionsρreceiptsσauthorityκcontrol plane

Both doors share one listener and one kernel

Agent sessions arrive at /mcp with the session credential. The operator arrives at /admin/* with CHIO_ADMIN_TOKEN, a separate value. Admin reads and writes reach the edge’s own receipt, authority, and session stores. Trust Control Plane.

One remote MCP edge with both doors on its listener. The three stores stand for the receipt, authority, and session databases the edge opens; budget records and OAuth are left out.

Agent sessions present the session credential at /mcp; the operator presents the admin token at /admin/*. The admin token permits every admin operation, reads and writes alike, with no read-only role, so restrict the listener at the network boundary. Admin routes share the listener's exposure and run outside the MCP request rate limiter.

1. Give the edge its own admin token

Set CHIO_ADMIN_TOKEN to a value distinct from the MCP session credential in CHIO_AUTH_TOKEN, then start the edge with its three databases in a private state directory.

Terminalbash
mkdir -p .chio/admin && chmod 700 .chio .chio/admin
export CHIO_AUTH_TOKEN="$(openssl rand -hex 32)"
export CHIO_ADMIN_TOKEN="$(openssl rand -hex 32)"
chio mcp serve-http \
  --policy policy.yaml --server-id pricing \
  --listen 127.0.0.1:8931 \
  --receipt-db .chio/admin/receipts.db \
  --authority-db .chio/admin/authority.db \
  --session-db .chio/admin/session.db \
  -- python mcp_server.py &
CHIO_ADMIN_PID=$!

You should see the edge listening on 127.0.0.1:8931; stop it later with kill "$CHIO_ADMIN_PID". Without CHIO_ADMIN_TOKEN, the admin door accepts the static session token, and startup refuses a configuration with neither token. The durable admission authority behind --session-db owns revocation and budget state, so give it no separate revocation or budget database flags.

2. Read the node's health

Send the admin token as Authorization: Bearer <admin-token>. The admin door also checks Origin when a request carries one, allowing localhost, 127.0.0.1, and ::1; a server-side client can omit the header. This request selects four fields from an edge with no connected sessions.

GET /admin/healthtranscript
$ curl --fail-with-body -sS \
  -H "Authorization: Bearer $CHIO_ADMIN_TOKEN" \
  http://127.0.0.1:8931/admin/health \
  | jq '{ok, serverId: .server.serverId, authMode: .auth.mode, activeSessions: .sessions.activeCount}'
{
  "ok": true,
  "serverId": "demo-server",
  "authMode": "static_bearer",
  "activeSessions": 0
}
exit 0

You should see ok: true, the server id, the static_bearer auth mode, and zero active sessions. ok confirms that this handler answered. The store flags in the full response describe configuration and say nothing about whether a store is reachable.

Missing admin bearertranscript
$ curl -sS -w '\nHTTP %{http_code}\n' http://127.0.0.1:8931/admin/health
missing or invalid bearer token
HTTP 401
exit 0

Without the bearer, the edge answers 401 before any handler runs.

3. Rotate the issuing key

POST to /admin/authority with an empty body.

Terminalbash
curl -sS -X POST \
  -H "Authorization: Bearer $CHIO_ADMIN_TOKEN" \
  http://127.0.0.1:8931/admin/authority

You should see the new public key in the response. Rotation applies to sessions opened afterward; existing sessions keep the capabilities they hold, and rotation revokes none of them. An edge with no authority backend answers this request with 409.

4. Revoke a capability

POST the capability id as JSON to /admin/revocations.

Terminalbash
curl -sS -X POST \
  -H "Authorization: Bearer $CHIO_ADMIN_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"capability_id": "cap-01a8…31f2"}' \
  http://127.0.0.1:8931/admin/revocations

You should see newlyRevoked, which tells a new revocation from a repeat. To revoke everything one session holds, POST to /admin/sessions/{session_id}/trust, then read each capability's revoked field; the top-level flag and the newly-revoked count alone leave a failed attempt unreported.

5. Drain a session

Drain stops a session admitting new work, removes its resumable state, and sets a deadline. Shutdown closes the session and removes it at once. Both take an empty body and act on one session.

Terminalbash
curl -sS -X POST \
  -H "Authorization: Bearer $CHIO_ADMIN_TOKEN" \
  "http://127.0.0.1:8931/admin/sessions/$SESSION_ID/drain"

You should see lifecycle.state report the drain. Cleanup later moves the drained session out of the active map. See Session Lifecycle for the states.

With a control URL, authority state lives in the trust control plane

Start the edge with --control-url and --control-token, and authority, receipt, budget, and revocation operations go to the trust control plane. Health keeps local configuration and session data but loads authority remotely. Session trust keeps local session data and resolves each capability's revocation state remotely.

Session listing, drain, shutdown, and metrics stay on the edge. A URL without a token returns 409 on every affected route, health included. Forwarded responses follow the control-plane API: optional fields can be omitted, and budget records carry additional cost data.

A status code names the check that refused

Handler errors carry plain-text bodies, so check the status before parsing a response as JSON. Request extraction can reject a malformed query or JSON body before the handler's authentication checks run.

StatusCauseAction
401Missing or wrong admin bearerSend the admin token; the response carries a Bearer challenge
403Origin outside localhost, 127.0.0.1, ::1Fix Origin; it is checked before the bearer
404Unknown session idCheck the id and tombstone retention
409Missing store, authority backend, or control tokenConfigure the missing piece
500Storage, receipt verification, forwarding, or lifecycle errorRead the edge log for the failing operation
A refused bearer
Incorrect admin bearertranscript
$ curl -sS -w '\nHTTP %{http_code}\n' \
  -H 'Authorization: Bearer wrong-token' \
  http://127.0.0.1:8931/admin/health
missing or invalid admin bearer token
HTTP 401
exit 0

The parser requires the exact prefix Bearer and a nonempty value. The comparison takes the same time for any two tokens of equal length; token length stays observable.

Admin endpoints

RequestPurpose
Inspect configuration and session counts
Read the issuing key
Rotate the key for future sessions
List tool-call receipts
List client-callback receipts
Read capability usage
Read revocation state
Revoke a capability
List active and terminal sessions
Inspect one session's trust
Revoke a session's capabilities
Begin draining one session
Close one session
Scrape Prometheus metrics

Health and configuration

The full response contains ok, server, auth, controlPlane, stores, sessions, authority, federation, oauth: the auth mode, store configuration, session counts, and lifecycle timings. Loading the authority backend can fail the whole request.

The edge has no unauthenticated readiness route. Use an authenticated health request or a TCP probe; see Health & Readiness.

Authority

GET resolves the configured control plane first, then an authority database, then a seed file. The response names the backend, the current public key, and the trusted keys; database and control-plane responses also carry the generation and rotation time. With no authority backend, GET reports configured: false and POST returns 409. See Authority & Rotation.

Receipts

Both lists return the newest records first, in a receipts array with backend, kind, count, and filter metadata. They are administrative reads across tenants. Every record is verified on read, and a corrupt signature or action hash fails the whole request.

KindOptional query keys
Tool calls
capability_id, tool_server, tool_name, decision, limit
Client callbacks
session_id, parent_request_id, request_id, operation_kind, terminal_state, limit

Keys are snake_case. Unknown keys are ignored, so a misspelled filter returns a broader list. The server clamps the requested limit and offers no cursor or offset; an omitted limit uses the default below. To transfer a complete selected record set, use evidence export.

crates/protocol/chio-mcp-remote/src/remote_mcp/session_core.rs113-114rust
const DEFAULT_ADMIN_LIST_LIMIT: usize = 50;
const MAX_ADMIN_LIST_LIMIT: usize = 200;

Budgets and revocations

Both GET routes accept capability_id and limit. A local budget record holds the capability, grant index, invocation count, and update time. Monetary exposure and realized-spend fields appear in the forwarded response; for local costs, read receipts.

A capability filter makes the revocation response report that capability's status. Without a filter, the local status is null and a forwarded response omits it. Revocation POST requires a JSON body with capability_id, and newlyRevoked tells a new revocation from a repeat.

Sessions

The session list returns full active and terminal records with counts and lifecycle timings, with no filters or pagination. Session and health reads perform due cleanup, so a read can advance expiry, finish a drain, or purge an old tombstone.

The trust GET adds per-capability revocation state to authentication, lifecycle, and ownership data. A terminal session stays inspectable while its tombstone is retained; an unknown session id returns 404. The trust POST attempts to revoke every capability the session holds, then reads status again.

Capability records in the session list use id; the trust response uses capabilityId and adds revoked. Auth method fields use snake_case names such as token_fingerprint, and ownership fields use camelCase. The static-bearer fingerprint is a digest of the session credential.

Drain and shutdown

Both POST routes take an empty body and act on one session. Read lifecycle.state for the result. The draining and shutdown booleans also appear when the request targets a session that is already terminal.

Metrics

The route returns Prometheus text with Content-Type: text/plain; version=0.0.4, aggregated across the process with no per-session breakdown. Configure the scraper with the admin bearer; Node Observability covers collector setup.

Next: know what the node keeps on disk