Skip to main content
Glama
ProofHoldings

@proof-holdings/mcp-server

Official

@proof-holdings/mcp-server

MCP (Model Context Protocol) server for the proof.holdings API. Exposes 176 tools for AI agents to create verifications, validate proofs, manage assets, and more.

Two ways to connect

Hosted — nothing to install. The same tools are served over HTTP, and the client authenticates in the browser when a tool first needs an account:

claude mcp add --transport http proof https://api.proof.holdings/mcp

Or as a config fragment:

{ "mcpServers": { "proof": { "type": "http", "url": "https://api.proof.holdings/mcp" } } }

⚠️ Versions before 1.1.0 predate the delegation tools and the keyless public mode and expose an older, smaller surface than this README describes. If a client is pinned to 1.0.0, upgrade it or use the hosted server above. GET /api/v1/mcp/connect always serves the current instructions.

Local — this package. Installs and runs as a stdio server:

npm install -g @proof-holdings/mcp-server

Or run directly with npx (no install needed):

npx @proof-holdings/mcp-server

Configuration

Variable

Required

Default

Description

PROOF_API_KEY

No

API key (pk_live_... or pk_test_...). Without it the server still starts in public mode: the keyless tools (account bootstrap, login, proof and delegation verification) work, and every other tool answers api_key_required.

PROOF_BASE_URL

No

https://api.proof.holdings

API base URL

Get your API key from the proof.holdings dashboard.

Client Setup

Add the following to your MCP client config file:

{
  "mcpServers": {
    "proof-holdings": {
      "command": "npx",
      "args": ["-y", "@proof-holdings/mcp-server"],
      "env": {
        "PROOF_API_KEY": "pk_live_your_key_here"
      }
    }
  }
}

Config file location by client:

Client

Config file

Claude Desktop (macOS)

~/Library/Application Support/Claude/claude_desktop_config.json

Claude Desktop (Windows)

%APPDATA%\Claude\claude_desktop_config.json

Cursor

.cursor/mcp.json in your project root

Claude Code

Run claude mcp add proof-holdings -- npx -y @proof-holdings/mcp-server

Windsurf

~/.codeium/windsurf/mcp_config.json

If installed globally (npm install -g @proof-holdings/mcp-server), use "command": "proof-mcp" and remove the "args" field.

Tools (176 tools)

Group totals are exact. The tables name the tools you are most likely to reach for rather than all of them — your MCP client's own tools/list is the complete, current list, and it is the one this server answers from.

Verifications & requests (28 tools)

Tool

Description

create_verification

Create a verification challenge

get_verification

Get verification status

submit_verification_code

Submit a verification code

trigger_verification

Trigger a verification check

wait_for_verification

Poll until it completes

create_multi_channel_verification

One phone, up to three channels, first completion wins

create_verification_request

Create a multi-asset request

get_request_by_reference

Look a request up by reference id

Domains & DNS (23 tools)

Tool

Description

add_domain

Add a domain to verify

verify_domain

Check the challenge record and mint the proof

connect_cloudflare

Connect Cloudflare so records are written for you

verify_domain_with_credentials

Prove control using stored credentials

setup_domain_email

Set up sending from the domain

Account, settings & billing (37 tools)

Tool

Description

get_platform_summary

One-call snapshot of the account

get_usage

Quota and usage for the period

search

Search across the account

create_account

Bootstrap a new account (no key needed)

create_api_key

Create a scoped API key

list_assets

List verified assets and their proof handles

Tool

Description

create_hitl

Create a human-approval configuration

create_confirmation

Send an approval request to a person

wait_for_confirmation

Poll until a person approves or denies

create_authorization

Ask a person to consent to being contacted

revoke_authorization

Withdraw a consent

Circles & Proof of Me (20 tools)

Tool

Description

create_circle

Create a circle of trusted contacts

add_circle_member

Add a contact

invite_circle_member

Send a single-use enrollment link

create_identity_challenge

Run a cross-channel identity check

Public profiles (16 tools)

Tool

Description

create_profile

Create a public profile

claim_username

Claim a public username

update_public_proofs

Choose which proofs a profile shows

Templates & webhooks (11 tools)

Tool

Description

list_templates

List message templates

render_template

Render a template with variables

list_webhook_deliveries

List webhook deliveries

retry_webhook_delivery

Retry a failed delivery

Delegations & proofs (10 tools)

Tool

Description

create_delegation

Authorize an artifact from a domain you have proven

revoke_delegation

Revoke a delegation

verify_delegation

Check whether an artifact is authorized by the domain it claims

validate_proof

Verify a signed proof token (no key needed)

get_proof_status

Read a proof's status by its public handle

list_revoked_proofs

Read the revocation list (no key needed)

Sign-in & sessions (9 tools)

Tool

Description

start_login

Begin a sign-in (no key needed)

wait_for_login

Poll until sign-in completes

get_current_user

Who the current session belongs to

render_auth_link

Render a sign-in link for the user to open

Test Mode

Use a test-mode API key (pk_test_*) to interact with the API without creating real verifications. Test-mode keys are available in your dashboard.

Transport

Two transports ship in this package:

  • stdio (default, mcp-server / proof-mcp) — the server communicates over stdin/stdout. One user per process; this is what a client launches locally.

  • Streamable HTTP (node dist/remote.js) — a remote server that many users connect to over the network by URL, with no install. A connection starts ANONYMOUS — the keyless surface (account bootstrap, login, proof and delegation verification) works with no credential at all — and a tool that needs an account answers 401 with a WWW-Authenticate challenge naming the authorization server, which is what a standards-compliant client follows to sign in. The 401 lands on the TOOL CALL and never on a bare initialize or tools/list FOR AN ANONYMOUS CONNECTION: measured against live clients, refusing an anonymous handshake reads to the user as a connection timeout rather than as an invitation to log in. Three shapes are refused at the handshake instead — a presented token that does not resolve (there the 401 is what makes a client refresh), a request whose credential does not match the session it names, and an opening batch that smuggles a keyed tool call alongside initialize. A signed-in client sends the API key it was granted in the Authorization header — the only place a credential is read, never a query parameter — and each connection gets its own server and HTTP client, so one user's key or session can never reach another. PORT (default 3100), MCP_MAX_SESSIONS (default 100), MCP_SESSION_TTL_MS (default 30 min, counted from the last POST the server ACCEPTED — one it answered below 400. A POST refused before any work happens does not postpone it, whether the refusal is ours (body over 4MB) or the transport's (unparseable or empty body, unsupported mcp-protocol-version, a second initialize); and an open event stream is a connection, not activity, so a session whose only traffic is that stream ages out. The official client does not recover from this on its own: measured against SDK 1.27.1, the stream's reconnect gives up after two attempts and the next tool call fails with unknown_session until the host reconnects the server. Size the TTL with that in mind — it is a memory bound paid for in reconnects, not a transparent one); /healthz reports the live session count.

Delegation (_meta)

The server card (server.json) can carry a Proof of Delegation publication under the namespaced _meta key holdings.proof/delegation:

{
  "_meta": {
    "holdings.proof/delegation": { "token": "<delegation JWT>" }
  }
}

The token is an ES256 JWT minted by proof.holdings attesting exactly one thing: the controller of the principal domain authorized the delegate artifact for the listed scopes. It is not a statement that the server is safe, audited, or endorsed. A verifier checks the signature against the issuer JWKS, then compares principal and delegate to facts it resolved itself — a token copied into another package fails that comparison, because its delegate names the genuine artifact. Details: Delegations — API reference.

When the card is published through the official MCP registry, the same entry is nested under _meta["io.modelcontextprotocol.registry/publisher-provided"] — readers should check both locations.

Maintainers: the entry is written by the fail-closed publish tool, never by hand — from the repository root, after the delegation is minted for pkg:npm/@proof-holdings/mcp-server:

npm run delegation:publish -- --target mcp --token <jwt>
# or mint + publish in one step (needs PROOF_API_KEY):
npm run delegation:publish -- --target mcp --mint \
  --control-proof ph_ctl_<32hex> --scope proof-verification

The tool refuses any token whose claims do not name this exact package with principal: proof.holdings (the same check --target a2a performs against the A2A agent card's own url before regenerating /.well-known/agent-card.json). For --target a2a, if the card-regeneration step fails after the source file is written, just re-run the command — the token is already validated and the regeneration is idempotent.

Verifying someone else's delegation (verify_delegation)

This server also checks delegations, not just publishes one. The verify_delegation tool runs the reference verifier (@proof-holdings/delegation-verifier) over another server's card and needs no API key — verification runs against public surfaces only.

{
  "card": { /* the MCP server.json or A2A agent card you fetched */ },
  "delegate": { "type": "purl", "value": "pkg:npm/postmark-mcp" },
  "expected_principal": "postmarkapp.com"
}

Both pins are required, and they close different attacks:

  • delegate must be the artifact identity you resolved — the package you are about to install, the endpoint you are about to call. Never copy it out of the card being checked: a published token is a bearer artifact, so comparing it against a field of the same card would bless a token pasted in from somewhere else.

  • expected_principal is the domain you expect to stand behind it. An issuer binds the artifact to nothing, so any domain owner can mint a genuine, signature-valid delegation naming someone else's package. Without this pin a verdict would only mean "some domain claims this".

Re-checking everything you already trust (verify_delegations)

verify_delegation answers "is this one good, right now, because you asked". If an agent has already resolved and verified thirty-five artifacts, re-verifying them one call at a time does not scale — verify_delegations batch-checks up to 50 in a single call, also with no API key.

{
  "items": [
    { "card": { /* ... */ }, "delegate": { "type": "purl", "value": "pkg:npm/postmark-mcp" }, "expected_principal": "postmarkapp.com" },
    { "token": "<jwt>", "delegate": { "type": "url", "value": "https://example.com/mcp" }, "expected_principal": "example.com" }
  ]
}

Each item takes exactly the shape verify_delegation requires (card XOR token, delegate, expected_principal, optional required_scopes) and is verified independently — no cross-item state, nothing persisted, and one item failing never affects another item's result. Revocation is always checked (there is no check_status: false on this tool — the entire point of a batch re-check is to see what changed).

Two things this tool deliberately does NOT do:

  • It does not discover what you have installed. You must already hold each artifact's card or token. It has no DNS-pointer resolution and fetches no caller-supplied URL — the same trust boundary verify_delegation already draws, kept narrow on purpose (see src/services/delegationPointer/resolve.ts's documented gaps in the main repository, which this tool stays outside of).

  • It does not run continuously. Each call is a single point-in-time check. There is no cadence, no scheduler, no push notification — call it again whenever you want a fresh answer.

Each result in results[] carries an outcome, one of four buckets:

Outcome

Meaning

confirmed_valid

The delegation verified — same meaning as verify_delegation's verified: true.

confirmed_invalid

A genuine negative verdict: revoked, suspended, expired, a mismatched principal or delegate, an ungranted scope, or a malformed/untrusted/badly-signed token.

no_claim_found

The artifact publishes no delegation at all. An absence, never an accusation.

unconfirmed

We could not reach the issuer or otherwise get a confident answer right now (e.g. its JWKS or status endpoint is unreachable). Never treat this as a bad verdict — it means "ask again later", not "revoked".

A result also carries checked_at — the ISO timestamp of the moment that item's own check completed, not one timestamp shared across the whole call — so "established locally" is never presented as "established by reaching us, at this moment" without saying which.

⚠️ Release order (maintainers)

package.json declares @proof-holdings/delegation-verifier as a runtime dependency, so the range it names has to be resolvable on the registry before this package is uploaded:

@proof-holdings/delegation-verifier is published BEFORE @proof-holdings/mcp-server, on every release that moves the range. This server is launched via npx by every documented client, so a release whose dependency the registry cannot resolve makes npx @proof-holdings/mcp-server fail with E404 for everyone until the verifier lands. A run of .github/workflows/publish-packages.yml with target: all enforces the order by its step order; a publish by hand from a terminal has nothing enforcing it but docs/runbooks/npm-release.md.

Working on the verifier and the server together does not need a publish. Link the sibling instead:

npm run verifier:link   # cd mcp && npm install ../packages/delegation-verifier --no-save

The test suite needs no link at all: it resolves the verifier's SOURCE through a vitest alias rather than node_modules, so it is green on a fresh clone and picks up an uncommitted verifier change without a build.

Requirements

  • Node.js >= 18.0.0

  • A proof.holdings API key for the keyed tools. The server starts and answers the keyless ones without it — see Configuration above.

License

MIT

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ProofHoldings/mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server