MCP server and connectors directory
The MCP server assistants read verdicts and evidence from, the OAuth flow, the tool list with its annotations, and the Claude connectors directory listing.
Last changed Markdown versionllms.txt
The agent may declare its intent. It may read the verdict. It must never control the independent facts, the governing contract, or the final evidence.
Two pieces, built on that line:
- The evidence server, inside the API at
${API_URL}/mcp: an assistant (Claude, ChatGPT, Cursor, an agent framework) reads actions, verdicts, reasons and evidence pointers, contracts, coverage and usage; validates, dry-runs and drafts contracts without saving; and, with theactions:recordscope, declares an action before it happens. - The recorder,
recorder/pythonandrecorder/js: a stdio proxy in front of any MCP server an agent uses, which records a mapped tool call as an action before forwarding it. See the READMEs there.
Nothing on either surface writes a fact, saves or activates a contract, overrides a verdict, deletes anything, or reads a credential. No scope grants any of these.
Transport and auth
Streamable HTTP, stateless, JSON responses (json_response=true), mounted by app/api/main.py at the root so the
OAuth metadata documents sit at the issuer. Bearer tokens of three kinds are accepted (app/mcp/auth.py):
| Token | Who | Scopes |
|---|---|---|
spk_… API key |
A service, an agent runtime, a local MCP client | The key's scopes (Settings → API keys; keys from before scopes carry all) |
cla_… OAuth access token |
A remote client a member approved on the consent page | The grant, capped to the member's current role on every call |
| Session JWT | The web app itself | The member's role |
Unauthenticated requests get 401 with WWW-Authenticate: Bearer resource_metadata=…/.well-known/oauth-protected-resource/mcp,
which is how an OAuth-capable client discovers the authorization server.
Scopes (app/mcp/scopes.py)
actions:read, actions:record, facts:write, contracts:read, contracts:validate, contracts:draft,
reconcile:read, connectors:read, usage:read. Presets: recorder (actions:record, actions:read),
evidence (facts:write, actions:read, connectors:read, bound to named sources), reader (every read scope
plus validate and draft), full (all). Owners, admins and analysts may hold every scope; viewers only the read
scopes. A model-facing principal (an OAuth token, any bearer on this server) never holds facts:write: the
model may declare its intent and read the verdict; the evidence arrives by another credential. Over REST an API
key acts as an analyst when it may write and as a viewer otherwise, and each write endpoint checks its scope.
Tools
| Tool | Scope | What it returns |
|---|---|---|
get_action(action_ref) |
actions:read | The action with both lenses, status, reason codes, checks, evidence pointers |
list_actions(contract_id?, workflow_id?, verdict?, period?, limit) |
actions:read | Actions with their latest verdict, newest first |
pending_actions(limit) |
actions:read | Provisional actions, what they wait on, when they settle |
workflow_summary(period?) |
actions:read | Per-contract counts for a month |
record_action(contract_id, subject_id, …) |
actions:record | The provisional verification and the allowance; channel: mcp |
verify_hash(hash) |
actions:read | Whether a statement / verdict / dispute hash exists and its chain holds |
connector_health() |
connectors:read | Connections with status, trust, provides, last sync, watermark; never credentials |
usage() |
usage:read | Plan, limits, features, usage, allowance |
list_contracts(), get_contract(contract_id, version?) |
contracts:read | Summaries; YAML, compiled rules, plain language, versions |
validate_contract(yaml) |
contracts:validate | Errors, compiled rules, plain language, weakening warnings |
dry_run_verify(contract_id, period, yaml?, version?) |
contracts:validate | Counts per lens, verified amount, reasons; nothing persisted |
draft_contract(action_type? | contract_id?, …) |
contracts:draft | Proposed YAML with statistics and warnings; nothing saved |
list_reconciliations(vendor?, period?, limit), get_reconciliation(id) |
reconcile:read | Reconciliation views (coverage, claims, run, statement totals) |
Resources: commitlayer://reason-codes (every code with label and explanation), commitlayer://actions/{ref}.
Prompts: investigate_failed_action(action_ref), review_contract_change(contract_id, yaml).
Every tool carries a title and the standard annotations (0.9.12). Fourteen are readOnlyHint: true, so a client
such as Claude runs them without asking the person before each call; record_action is readOnlyHint: false,
destructiveHint: false, idempotentHint: false (recording without an action_id twice is two actions).
openWorldHint is false throughout. The hint must say what the scope enforces: tests/test_mcp.py fails when a
tool's readOnlyHint disagrees with its _call scope, and the exported product-catalog/mcp-tools.yaml derives
read_only and destructive from the annotations.
Errors are returned as content, never as a stack trace: {"error": {"code", "message", …}} with codes
insufficient_scope (plus required_scope), plan_required (plus upgrade_to), not_found, invalid_request,
invalid_contract (plus errors), or an engine code. Every call is audited as mcp.tool_called with
object_id = tool name, the actor (apikey:<prefix>, <email> via <client_id>, or the email) and
metadata {…args, ok, code}.
OAuth 2.1 (app/mcp/auth.py, app/api/routers/oauth.py)
The API is the authorization server. Endpoints (served by the MCP SDK): /.well-known/oauth-authorization-server,
/.well-known/oauth-protected-resource/mcp, POST /register (dynamic client registration, open), GET /authorize
(PKCE S256 required), POST /token (authorization_code, refresh_token), POST /revoke.
Flow: the client registers → /authorize redirects the browser to ${WEB_URL}/oauth/consent?req=<signed request, 10 min> → the signed-in member sees the client, the workspace and the requested scopes (GET /v1/oauth/consent),
unticks what they do not want, approves or denies (POST /v1/oauth/consent {req, approve, scopes?}) → the answer
carries redirect_to (the client's redirect URI with code and state, or error=access_denied) → the client
exchanges the code with its verifier at /token.
Codes are single use and live 10 minutes; access tokens 1 hour; refresh tokens 30 days. A refresh rotates the pair
and the old pair dies. Tokens are stored hashed (oauth_tokens), bound to the member and the workspace they chose;
a member who leaves the workspace loses every token, and a token never outranks the member's current role. Client
secrets are stored envelope-encrypted because the token endpoint compares them in full. Audit:
oauth.approved {scopes, client_name}, oauth.denied.
The Claude connectors directory
The server meets the directory's published requirements (checked against the live API on 2026-11-02): remote
Streamable HTTP over HTTPS, a 401 with resource_metadata on unauthenticated calls, protected-resource metadata
naming /mcp as the resource, authorization-server metadata with PKCE S256 and open dynamic client registration
(a client that registers as a public client gets no secret and is accepted), authorization-code and refresh
grants, per-tool errors as content, titles and annotations on every tool. Submission is at
claude.ai/directory/manage (Submit new → MCP connector) from an account on a paid Claude plan. What the form
asks for, and the answers:
- Connection:
https://api.commitlayer.ai/mcp, a single URL. - Listing. Name: CommitLayer. One-liner (≤200): Read what your AI agents actually did: PASS, FAIL or
UNKNOWN verdicts with the evidence, from the systems the action touched. Description (≤2000): the first
paragraph of
docs/mcp.mdin plain words: CommitLayer verifies agent actions against independent systems of record (Stripe, Shopify, CRMs, your own events) and returns a verdict with the step that did not land; through this connector Claude reads recorded actions and their verdicts, reasons and evidence pointers, lists what is still pending and what each is waiting on, reads contracts, validates and dry-runs contract changes without saving, drafts a contract from recorded actions, checks connector health and the workspace's plan and usage, and, when the token carriesactions:record, records an action before it is performed. It never writes a fact, changes a verdict, activates a contract or deletes anything. Categories: Developer tools, Productivity. Documentation URL:https://commitlayer.ai/mcp. Privacy policy URL:https://commitlayer.ai/legal/privacy. Support:support@commitlayer.ai. Icon:stateprf-ui/public/logo/icon-512.png. Slug:commitlayer(permanent once published). - Use cases, three prompts that work on the demo workspace: "Which of my agent's refunds failed this month,
and why?" (list_actions with verdict FAIL, then get_action); "What is still waiting on a system of record,
and when does it settle?" (pending_actions); "Draft an action contract from the refunds recorded in the last
30 days and tell me what limits it would set." (draft_contract). Prerequisites: a CommitLayer workspace (free
tier). The connector reads; it writes only through
record_action, on a token that carries that scope. - Authentication: required; OAuth with dynamic client registration (
oauth_dcr); the person signs in to CommitLayer and approves the client for one workspace with the scopes they choose. - Data handling: the API is our own; no health data, no sponsored content.
- Test & launch: a reviewer account with sample data. Create a member of the synthetic demo workspace (181
verified refund-workflow actions, connectors, contracts) with a password sign-in, enter its email and password
in the portal's test-account card, and write: Add the URL as a custom connector, sign in with the credentials
above when CommitLayer's consent page opens, approve the default scopes, then run
list_actionswithverdict: FAIL,get_actionon one of the results, andpending_actions. Confirm every tool was run through MCP Inspector or as a custom connector first. - Compliance: the seven acknowledgments; the server collects nothing from the conversation, stores no conversation content, and calls no API it does not own.
A Community listing follows the automatic policy scan; a Verified one, where reviewers run every tool, is
Anthropic's call later. Escalations: mcp-review@anthropic.com.
Testing
tests/test_mcp.py starts the API under uvicorn and drives it with the MCP Python client: tool catalog, every
read tool, resources, audit rows, a read-only key refused record_action over MCP and POST /v1/actions over REST,
a recorder key recording, a revoked key refused, metadata documents, and the full OAuth flow (registration,
authorize, consent describe / deny / approve with trimmed scopes, wrong verifier, single-use code, refresh rotation,
revocation).