Cookbook
Sign with Claude
Authorize an agent to sign on your behalf within a scoped policy. Claude proposes, the policy decides, the human is the audit trail.
Last updated
Authorize an agent to sign on your behalf within a scoped policy. Claude proposes, the policy decides, the human is the audit trail.
Trigger
An agent is empowered to act on routine paperwork without paging a human every time.
Call sequence
1. Define the policy
POST /v1/agent_policies { tier: 3, kinds: ['nda','grant'], ceiling: 50000 }2. Issue agent token bound to the policy
POST /v1/agent_tokens { policy, ttl: '8h' }3. Agent signs within scope
POST /v1/documents/{doc}/sign Authorization: Bearer tok_…Idempotency
Policy creation idempotent on `(account, name)`.
Webhooks
| Event | Description |
|---|---|
agent_policy.created | Policy is live. |
document.signed | Per signing. |
agent_token.revoked | On TTL or manual revoke. |
Errors
| Status | Code | Description |
|---|---|---|
403 | policy_violation | Agent attempted an out-of-scope action. |
Related
This recipe is for in-chat signing — a human is still the signer, the model is just the relay surface. The model never holds long-lived credentials and never synthesises the words the human typed.
Install the Matter MCP server
Follow the install guide at /mcp/install. The short version:
# Claude Desktop
claude mcp add matter --command "npx" --args "@matter/mcp@latest"Restart the client. The Matter tools appear under matter_* in the MCP picker.
First-run OAuth
The first time any signing tool is invoked, the MCP server opens your default browser to a Clerk-hosted authorisation page. You sign in with your Matter credentials, approve the requested scopes, and Clerk redirects back to a loopback URL.
The harness writes the refresh token to your OS keychain. The model never sees it. Every subsequent tool call exchanges the refresh token for a short-lived access token inside the harness; only the access token's bearer header reaches the API.
~/.config/matter-mcp/credentials (macOS keychain entry on darwin)
└── refresh_token: encrypted, host-bound, never read by the modelIf your Clerk session expires (default 7 days), the next signing tool
call returns 403 reauthentication_required and the harness re-opens the
browser.
In-chat signing walkthrough
Ask Claude what's pending, read the disclosure, type the confirmation verbatim, sign.
Human: What do I need to sign?
Claude: (matter_list_pending_signatures) — returns 83(b) election + board consent
Human: Show me the 83(b).
Claude: (matter_get_disclosure_packet) — renders disclosure + summary
Human: Jane Smith, I confirm
Claude: (matter_request_signing_session) — mints sig_… token (TTL 15min)
(matter_sign_document_with_intent) — applies signature with intent_text
Claude: Signed. PDF available at …The signing session pins the packet_hash the human just read and mints
a signature_token good for exactly one signature on exactly one document:
# What the harness sends (the model never sees the bearer header).
curl -X POST https://api.mattermode.com/v1/signing_sessions \
-H "Authorization: Bearer $CLERK_ACCESS_TOKEN" \
-H "Matter-Version: 2026-05-01" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"document_id": "doc_83b_2026Q2_F0und3rCEO",
"packet_hash": "sha256:9f1c…",
"intent_method": "in_chat_text"
}'The token is single-use, expires in 15 minutes, and is bound to the
document_id and packet_hash recorded at session creation.
The MCP tool description for matter_sign_document_with_intent
instructs the model: "Pass intent_text exactly as the human typed
it in the chat. Do not paraphrase, complete partial names, or
synthesise text the human did not type." This guardrail is enforced
at the tool layer, not just by prompt — the API rejects intent_text
that fails to satisfy the disclosure's requirements with 422 intent_text_insufficient.
MCP tool descriptions
The descriptions below are the verbatim text the Matter MCP server registers with the client. Models receiving these descriptions are bound by them.
| Tool | Description |
|---|---|
matter_list_pending_signatures | "List documents awaiting the authenticated human's signature. Read-only. Returns document_id, kind, subject, deadline, and a summary_url for each." |
matter_get_disclosure_packet | "Fetch the ESRA disclosure packet plus a plain-English document summary for one document. Read-only. The returned packet_hash is required when minting a signing session." |
matter_request_signing_session | "Mint a single-use signature_token for one document. The packet_hash returned by matter_get_disclosure_packet must be passed back unchanged. Token TTL is 15 minutes and is consumed by exactly one signing call." |
matter_sign_document_with_intent | "Apply the human's signature to one document using a signature_token from matter_request_signing_session. Pass intent_text exactly as the human typed it in the chat. Do not paraphrase, complete partial names, or synthesise text the human did not type. If the human typed only a partial confirmation, ask them to retype the full required text rather than completing it for them." |
What lands on the document
{
"id": "doc_83b_2026Q2_F0und3rCEO",
"status": "signed",
"signatures": [
{
"stakeholder_id": "stk_F0und3rCEO",
"signed_at": "2026-04-26T15:21:18Z",
"legal_basis": "esra_consent",
"method": "signing_session",
"intent_text": "Jane Smith, I confirm",
"signing_session_id": "sigsess_4Vy7nPq2",
"consent_record": {
"consent_id": "cnst_7M1qDvL",
"disclosure_version": "esra-2026-01",
"packet_hash": "sha256:9f1c…",
"captured_at": "2026-04-26T14:02:11Z"
},
"agent_authority": null
}
]
}legal_basis is esra_consent — same legal footing as the magic-link
rail because the signer is the human, the chat is the surface.
agent_authority is null because no agent authority was exercised; the
agent only relayed bytes the human dictated.
Failure modes
| Code | What happened | Recovery |
|---|---|---|
403 reauthentication_required | Clerk session has expired. | Harness opens the browser; the human re-authenticates; the tool call is retried automatically. |
403 human_oauth_required | The signing tools were invoked outside an OAuth-bound MCP context (e.g. from a server-side bot with only an sk_live_… key). | These tools are gated to MCP + OAuth. Use the magic-link rail or the agent rail instead. |
422 esra_consent_required | The signer has no active ESRA consent. | The MCP server walks the human through POST /v1/consents/esra and retries. |
422 disclosure_changed_resign_required | The disclosure version was bumped between matter_get_disclosure_packet and matter_sign_document_with_intent — packet_hash no longer matches. | Harness re-fetches the packet, re-presents it to the human, and asks for a fresh confirmation. The signing session is invalidated. |
410 signing_token_consumed | The signature_token was already used. Tokens are single-use. | Mint a fresh session. This usually indicates a bug — the model attempted a second sign on the same token. |
422 intent_text_insufficient | The text the human typed does not satisfy the disclosure's signature requirement. | Tool returns the requirement string back to the model, which prompts the human to retype. |
Why this is defensible
E-SIGN §7001 and UETA §2(8) define an electronic signature as "an
electronic sound, symbol, or process, attached to or logically associated
with a record and executed or adopted by a person with the intent to
sign the record." The chat surface satisfies "executed or adopted by a
person" because the intent_text is the verbatim string the human typed.
The signature_token binds the act of signing to a specific document and
a specific disclosure version, captured at the moment the human read it.
This is materially different from the agent rail
(sign as a tier-3 agent), where the agent
itself is the signer under UETA §14 and the legal basis is
ueta_electronic_agent.