Back to Browse

One MCP Server

Developer ToolsModerate5.2MCP RegistryLocalRemote
Free

Server data from the Official MCP Registry

One API, all things verified — control, delegation, human approval, anti-impersonation.

About

One API, all things verified — control, delegation, human approval, anti-impersonation.

Remote endpoints: streamable-http: https://api.proof.holdings/mcp

Security Report

5.2
Moderate5.2Moderate Risk

This is a well-structured MCP server for the proof.holdings API with proper authentication design and reasonable permission scope. The server supports both authenticated and keyless modes, uses environment variables for credential storage, and provides comprehensive API wrapping with appropriate error handling. Minor code quality observations exist around broad exception handling and logging patterns, but no security vulnerabilities were found. Supply chain analysis found 6 known vulnerabilities in dependencies (2 critical, 3 high severity). Package verification found 1 issue.

3 files analyzed · 10 issues found

Security scores are indicators to help you make informed decisions, not guarantees. Always review permissions before connecting any MCP server.

Permissions Required

This plugin requests these system permissions. Most are normal for its category.

HTTP Network Access

Connects to external APIs or services over the internet.

env_vars

Check that this permission is expected for this type of plugin.

What You'll Need

Set these up before or after installing:

Proof Holdings API key (Bearer). Optional: without it the server runs in public mode, where account creation, login and public verification tools work and every other tool asks for a key. Create one in the dashboard at https://proof.holdings. A key minted from inside a session with create_api_key takes effect only once it is set here and the server is restarted — this value is read at startup and has no runtime setter.Required

Environment variable: PROOF_API_KEY

API base URL. Defaults to https://api.proof.holdings.Optional

Environment variable: PROOF_BASE_URL

How to Install & Connect

Available as Local & Remote

This plugin can run on your machine or connect to a hosted endpoint. during install.

Documentation

View on GitHub

From the project's GitHub README.

@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

VariableRequiredDefaultDescription
PROOF_API_KEYNoAPI 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_URLNohttps://api.proof.holdingsAPI 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:

ClientConfig 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 CodeRun 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)

ToolDescription
create_verificationCreate a verification challenge
get_verificationGet verification status
submit_verification_codeSubmit a verification code
trigger_verificationTrigger a verification check
wait_for_verificationPoll until it completes
create_multi_channel_verificationOne phone, up to three channels, first completion wins
create_verification_requestCreate a multi-asset request
get_request_by_referenceLook a request up by reference id

Domains & DNS (23 tools)

ToolDescription
add_domainAdd a domain to verify
verify_domainCheck the challenge record and mint the proof
connect_cloudflareConnect Cloudflare so records are written for you
verify_domain_with_credentialsProve control using stored credentials
setup_domain_emailSet up sending from the domain

Account, settings & billing (37 tools)

ToolDescription
get_platform_summaryOne-call snapshot of the account
get_usageQuota and usage for the period
searchSearch across the account
create_accountBootstrap a new account (no key needed)
create_api_keyCreate a scoped API key
list_assetsList verified assets and their proof handles

HITL approvals & consent (22 tools)

ToolDescription
create_hitlCreate a human-approval configuration
create_confirmationSend an approval request to a person
wait_for_confirmationPoll until a person approves or denies
create_authorizationAsk a person to consent to being contacted
revoke_authorizationWithdraw a consent

Circles & Proof of Me (20 tools)

ToolDescription
create_circleCreate a circle of trusted contacts
add_circle_memberAdd a contact
invite_circle_memberSend a single-use enrollment link
create_identity_challengeRun a cross-channel identity check

Public profiles (16 tools)

ToolDescription
create_profileCreate a public profile
claim_usernameClaim a public username
update_public_proofsChoose which proofs a profile shows

Templates & webhooks (11 tools)

ToolDescription
list_templatesList message templates
render_templateRender a template with variables
list_webhook_deliveriesList webhook deliveries
retry_webhook_deliveryRetry a failed delivery

Delegations & proofs (10 tools)

ToolDescription
create_delegationAuthorize an artifact from a domain you have proven
revoke_delegationRevoke a delegation
verify_delegationCheck whether an artifact is authorized by the domain it claims
validate_proofVerify a signed proof token (no key needed)
get_proof_statusRead a proof's status by its public handle
list_revoked_proofsRead the revocation list (no key needed)

Sign-in & sessions (9 tools)

ToolDescription
start_loginBegin a sign-in (no key needed)
wait_for_loginPoll until sign-in completes
get_current_userWho the current session belongs to
render_auth_linkRender 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:

OutcomeMeaning
confirmed_validThe delegation verified — same meaning as verify_delegation's verified: true.
confirmed_invalidA genuine negative verdict: revoked, suspended, expired, a mismatched principal or delegate, an ungranted scope, or a malformed/untrusted/badly-signed token.
no_claim_foundThe artifact publishes no delegation at all. An absence, never an accusation.
unconfirmedWe 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.

Links

License

MIT

Reviews

No reviews yet

Be the first to review this server!