Skip to content

Write an integration

This page wires a real agent to a scoped TOVIO session: Cursor, Claude Code, or your own custom MCP client. The pattern is always the same — issue a token, start the workspace MCP package with that token in its environment, point the client at it — so once you have done one, the rest are small variations.

Phase 2 server and contract are implemented

The MCP server lives in the workspace as tovio-mcp-server. The configurations below target the implemented tool contract. The package is not yet published to npm, so integrations currently run it from a built workspace checkout (pnpm --filter tovio-mcp-server build).

The shape of every integration

Every MCP-based agent integration has the same three moving parts:

  1. A capability token issued by a human, scoping what the agent may touch.
  2. The MCP server — the built tovio-mcp-server package (node …/packages/tovio-mcp-server/dist/index.js, or tovio mcp serve from inside the repository), which the client spawns over stdio.
  3. The client's MCP config, telling it how to launch the server and present the token.
human ── tovio agent new ──▶ token ─┐
                                    ├─▶ client launches `tovio-mcp-server` with `TOVIO_TOKEN`
editor / agent (MCP client) ────────┘            │
        ▲                                         ▼
        └──────── JSON tool calls ◀────── tovio-core (validates every call)

The key idea: the client never touches the repository directly. It only calls MCP tools, and every one of those calls is re-validated against the token by the core. You are not trusting the editor — you are trusting the token.

Step 1 — issue a scoped token

tovio agent new issues the safe default described below --- broad access to clear code, structurally excluded from every policy-protected path --- and always sets a deadline (--expires-in, in hours, defaulting to 24):

$ tovio agent new cursor-cgm \
    --model anthropic:claude-sonnet \
    --task "stabilize the CGM integration" \
    --expires-in 8 \
    --json
{
  "token_id": "cap_7r4qy9m2x8k3v6bd0n1p5h",
  "object": "3f9c…",
  "agent": "did:tovio:agent/cursor-cgm",
  "model": "anthropic:claude-sonnet",
  "authorized_by": "did:tovio:dustin",
  "path_scope": ["**"],
  "denied_path_scope": ["config/production/**", "secrets/**"],
  "branch_scope": ["agent/cursor-cgm/**"],
  "secret_clearance": false,
  "issued_at": 1782482400,
  "expires_at": 1782511200,
  "revoked": false,
  "signature_valid": null
}

Capture the token_id; the client config references it. See capability tokens for what each field means.

CLI narrowing is still a gap

agent new currently issues the §10 default: broad clear-code access with every protected policy path in denied_path_scope. There is no --scope flag yet. To narrow, issue the root token with --can-delegate and mint a narrower leaf with the issue_sub_token tool; the leaf's hex is the sub-agent's own TOVIO_TOKEN.

Step 2 — wire up a client

Each MCP client has its own config file but the same essentials: a command to launch the server and the token to present.

Add a TOVIO entry to Cursor's MCP settings (.cursor/mcp.json in the project, or your global MCP config):

{
  "mcpServers": {
    "tovio": {
      "command": "node",
      "args": ["/absolute/path/to/tovio-checkout/packages/tovio-mcp-server/dist/index.js"],
      "env": {
        "TOVIO_REPO": "/absolute/path/to/your-repo",
        "TOVIO_TOKEN": "cap_7r4qy9m2x8k3v6bd0n1p5h"
      }
    }
  }
}

Cursor spawns the server over stdio and discovers its tool catalog — the same 18 tools for every session; scope is enforced on each call, not by hiding tools. The agent then uses read_file, commit, diff, and the rest as ordinary tools.

Register the server in the project's .mcp.json (or with claude mcp add):

{
  "mcpServers": {
    "tovio": {
      "type": "stdio",
      "command": "node",
      "args": ["/absolute/path/to/tovio-checkout/packages/tovio-mcp-server/dist/index.js"],
      "env": {
        "TOVIO_REPO": "/absolute/path/to/your-repo",
        "TOVIO_TOKEN": "cap_7r4qy9m2x8k3v6bd0n1p5h"
      }
    }
  }
}

Claude Code negotiates MCP over stdio. The effective_scope tool echoes the session's scope, and enforcement revalidates the complete capability chain on every call.

Any MCP-capable client connects the same way. Spawn the server over stdio with TOVIO_REPO and TOVIO_TOKEN in its environment (a cap_… id, or the hex leaf a delegating agent handed out), perform the standard MCP initialization, and call tools:

// process environment
{
  "TOVIO_REPO": "/absolute/path/to/your-repo",
  "TOVIO_TOKEN": "cap_7r4qy9m2x8k3v6bd0n1p5h"
}

A client that prefers a socket can start the server with tovio mcp serve --port 7744 instead and send the bearer the readiness line prints — see the MCP server. Branch your logic on the error envelope's code, never on prose — see the machine contract.

Step 3 — verify the session is bounded

A good first call is status: it returns the repository state filtered to the token's scope, which confirms the agent sees only its own working set.

// → status
// ← RepositoryStatus (relay ahead/behind appears only while a fresh authenticated sync observation exists)
{
  "branch": "agent/cursor-cgm/dexcom-backoff",
  "change": { "changeId": "chg:a3f7b2", "message": "Add Dexcom G7 reconnect backoff" },
  "modified": [
    { "path": "src/integrations/cgm/dexcom.ts", "status": "M", "encrypted": false }
  ],
  "openConflicts": []
}

Then prove the boundary by reaching for something out of scope — the session refuses it cleanly, before policy is even consulted. The denial is the error envelope, returned as the tool result's text and flagged isError:

// → read_file  { "path": "config/production/api-keys.env" }
// ← isError (note: no policy_requires — policy was never read)
{
  "code": "TVO-TOKEN-001",
  "title": "Agent token is not scoped to this path",
  "cause": "The read targeted `config/production/api-keys.env`, outside the token's path_scope (minus denied_path_scope); it is rejected before any policy or envelope is consulted (§7 step 2).",
  "context": { "path": "config/production/api-keys.env" },
  "exit_code": 13
}

That clean refusal is the whole point: the agent is bounded by the token, not by hoping it behaves.

Step 4 — handle expiry and renewal

A short task fits inside one token. A long one will outlive its token, so handle the deadline:

  • Track the deadline yourself: expires_at is in the token (tovio agent show <id> --json, Unix seconds). The server does not yet attach an approaching-expiry signal to tool responses.
  • Renew proactively (around 80% of the token's TTL) so the active call always runs under a valid token.
  • If a call returns TVO-TOKEN-002 (expired) or TVO-TOKEN-003 (revoked), the session is closed. Request a fresh token from the human authorizer and reconnect — do not retry blindly.
// pattern: branch on the structured code, never the message text
const envelope = result.isError ? JSON.parse(result.content[0].text) : null;
if (envelope?.code === "TVO-TOKEN-002") {
  // token expired — renew and reconnect
} else if (envelope?.code === "TVO-TOKEN-001") {
  // out of scope — the path is not this agent's concern
}

Orchestrating sub-agents

If you are building an orchestrator that fans work out to sub-agents, issue the orchestrator's token with tovio agent new … --can-delegate, then call issue_sub_token per sub-agent with a narrower scope and hand the returned hex leaf to that sub-agent as its own TOVIO_TOKEN. The flow gives each sub-agent its own chain-verified session and makes parent revocation cascade to every child. The narrowing rules are in capability tokens.

Delegated sessions preserve the authorization chain

A delegated session persists and reconstructs the canonical root-to-leaf chain, rejects leaf mutation, revalidates every ancestor and revocation, and records the complete chain plus the tool-manifest hash in commit provenance.

Checklist

  • [ ] Token issued with --expires-in; its broad-clear default is acceptable or a narrower sub-token is used.
  • [ ] Client config launches the workspace MCP package with TOVIO_REPO and TOVIO_TOKEN.
  • [ ] First status call returns only in-scope state.
  • [ ] An out-of-scope call is cleanly refused (TVO-TOKEN-001).
  • [ ] The client branches on the envelope's code, not prose.
  • [ ] Long-running jobs renew before expiry.

Where this connects

Last reviewed September 9, 2026

Suggest an improvement to this page Not for security reports — see disclosure