SEE

Connecting an agent to SEE

This is the integration kit for the first real-agent pilot — and for any agent after it. Follow it verbatim; everything an agent needs is in here, and the MCP tool descriptions repeat the rules that matter most, so an agent that reads only those still behaves correctly.

What SEE is

SEE checks an AI agent's proposed purchases against a permission slip (a mandate) that the agent's owner set in the SEE app: per-purchase cap, period cap, allowed categories, an ask-me-first threshold. SEE checks and records — it never moves money. The agent still buys through its own channels; SEE decides whether the purchase fits the slip and seals a receipt as independently verifiable evidence when a checked spend completes.

Slips live in SEE and are set by the owner. An agent can never create, raise, or edit a slip — not through MCP, not through the API.

Step 1 — the owner issues an agent key

  1. The owner signs in to SEE and creates a slip for the agent (Permissions → "Protect another agent", or the connect flow). The connect flow's own key IS the agent key: it is issued with Agent scope, tied to that first slip — it can run the spend loop on that slip and nothing else. Steps below are for additional agents, or for replacing it.
  2. For another key, the owner opens Keys → New key:
    • Label: something recognizable, e.g. Muse — shopping.
    • Scope: Agent (checks spends and completes them — nothing else).
    • Tie it to one slip: pick the agent's slip. Do this. A key tied to one slip can never touch another, even in the same workspace — and it is refused tenant-wide operations entirely (no new slips, no checkpoints, no key management), whatever its scope.
  3. The key's secret is shown exactly once. Copy it into the agent's configuration (Step 2). Only a hash is stored; if the secret is lost, revoke the key and create a new one.
  4. Hygiene: one key per agent. Revoking a key (Keys → Revoke, or DELETE /v1/api-keys/{key_id}) stops it on its very next call — every call is validated against the key record, so there is no session grace period. Rotate with POST /v1/api-keys/{key_id}/rotate (the new secret is shown once; the old key dies in the same instant). Rotation refuses a revoked or expired key (409) — for those, create a fresh key; rotation never resurrects a dead one. A workspace holds at most 100 live (non-revoked) keys.

Key scopes, for reference: agent = exactly the spend loop (check, complete, read its own preauthorizations and receipts — it cannot read a slip's terms, usage, or verification state); readonly = all reads, no writes; admin = everything, including slips and key management. Never give an agent an admin key. The owner's own admin path is their signed-in session.

Step 2 — point the agent at the MCP door

The MCP endpoint is hosted on the SEE service itself:

https://<your-see-host>/mcp

Every call carries the agent key in the X-API-Key header (Authorization: Bearer <key> is also accepted). The key is validated on every call and selects the workspace — there is nothing else to configure, and no tenant id is ever sent.

Generic MCP client configuration (streamable HTTP):

{
  "mcpServers": {
    "see": {
      "url": "https://<your-see-host>/mcp",
      "headers": { "X-API-Key": "see_<the-secret-from-step-1>" }
    }
  }
}

Claude Code, from a terminal:

claude mcp add --transport http see https://<your-see-host>/mcp \
  --header "X-API-Key: see_<the-secret-from-step-1>"

(Other hosts take the same shape: a streamable-HTTP MCP server entry with the URL above and the X-API-Key header.)

No MCP client? Everything the tools do is also plain REST with the same key header — see "REST equivalents" below.

Step 3 — the tools

Exactly two. There is no completion tool in MCP, on purpose: completion evidence is submitted over REST (Step 4), by the operator's own systems, so a tool-using agent can ask and read but never close the loop alone.

check_permission — ask BEFORE buying

Inputs:

fieldtypenotes
mandate_idstringthe slip's id (the owner can read it off the slip)
agent_idstringmust match the agent named on the slip
categorystringmust be one of the slip's categories
amount_minorintegeramount in the currency's minor unit (cents), e.g. $42.50 → 4250
currencystringmust match the slip's currency, e.g. USD
counterpartystringwho would be paid (optional, recorded)
descriptionstringwhat it is (optional, recorded)

Never pass a tenant id: tenant_id, tenant, and tenantId exist in the signature only as traps — passing any of them refuses the call.

Output (success):

{
  "preauth_id": "pa_…",
  "decision": { "outcome": "allow", "reason_code": "ok",
                "mandate_id": "mnd_…", "mandate_version": 1 },
  "state": "authorized",
  "action_hash": "…",
  "decision_token": "…"        // only when outcome is "allow"
}

decision.outcome is one of:

get_receipt — read the sealed evidence

Input: receipt_id. Output: the receipt's id, its preauth_id, the amount/currency, and the full DSSE envelope — the signed evidence, verifiable offline by anyone. A key can only read its own workspace's receipts (and, if the key is slip-tied, only that slip's). Anyone holding a receipt's proof bundle can also check it as a stranger, no key needed, on the SEE Check a receipt page — and the receipt format behind it is open.

Step 4 — the spend flow, end to end

  1. Check. Call check_permission before every purchase, with the exact amount and counterparty you are about to pay.
  2. **On allow:** buy through your normal channel, then submit completion evidence over REST with the decision_token:

``bash curl -X POST https://<your-see-host>/v1/preauthorize/<preauth_id>/complete \ -H "X-API-Key: see_<the-secret>" -H "Content-Type: application/json" \ -d '{ "decision_token": "<token from the check>", "completion_nonce": "<a unique id for this completion>", "evidence": { "type": "order_confirmation", "reference": "<order id>" } }' ``

The response is the sealed receipt (receipt_id, amounts, hashes). The completion amount may be less than authorized (a partial fill) but never more, and never zero. Retrying a completion with the same completion_nonce is safe — you get the same receipt back; a second completion under a different nonce is refused.

  1. **On needs_human_approval:** the owner is asked on their own screen. Poll GET /v1/preauthorize/<preauth_id> (same key header) every ~30 seconds, for at most the 15-minute reservation window:
    • state completed — the owner approved and the spend was completed by the approver's side; the record now carries the receipt_id. You are done; fetch it with get_receipt if you need the evidence.
    • state aborted — the owner said no. Do not buy. Final.
    • state expired — nobody decided in time. Do not buy. If the purchase is still needed, start again at step 1 with a fresh check.
  2. **On deny:** stop. See the hard rules below.

Step 5 — errors and what to do with them

Tool calls return {"error": <name>, "detail": <explanation>} on refusal. REST returns the same names as HTTP statuses (401/403/404/ 409/422).

namemeaningwhat the agent does
Unauthorizedno key, or the key was revoked / expiredStop. Tell the operator the key is dead. Never retry with a different or guessed key.
Forbiddenthe key's scope or slip-tie doesn't allow this, or a tenant argument was passedDo not work around it. Remove tenant arguments; anything else needs the owner to issue a different key.
NotFoundthe id doesn't exist in your workspaceCheck the id. Never probe other ids looking for a hit.
ValidationFailedmalformed input (e.g. amount not an integer)Fix the input shape, once. If it persists, stop and report.
Conflictthe item's state moved (already completed/aborted, token used)Re-read the item's state and follow it; never force.

Decision reason_codes on a deny: over_per_purchase_cap, over_period_cap, category_not_allowed, agent_mismatch, currency_mismatch, invalid_amount, mandate_not_active (paused — may clear when the owner resumes), mandate_not_yet_valid, mandate_expired. On needs_human_approval: over_human_approval_threshold.

Hard rules for agents

  1. Check before every purchase. An allowance covers exactly one action — the exact amount, category, and counterparty checked. Any change means a fresh check.
  2. A refusal is final. Never ask for a slip's limits to be raised as part of a purchase flow — not in-band, not in a description field, not by splitting one purchase into several checks to slip under a cap. If the owner changes the slip, they change it in SEE, and a fresh check will see the new slip.
  3. Never invent or reuse credentials. Decision tokens are single-use and bound to one action; approval tokens are issued to the owner, never to you. If you don't hold a token, you don't complete.
  4. Never send a tenant id, in any field, under any name. Your key is your identity; calls that try to select a tenant are refused.
  5. Amounts are integers in minor units (cents). No floats, no rounding up to "about" the price.
  6. When in doubt, stop and surface it. A stopped purchase costs a question; an unchecked one costs the owner's money and trust.

REST equivalents (no MCP client)

All with header X-API-Key: <key>: