Skip to content

AI assistants (MCP) ​

Overview ​

@klappay/mcp is an open-source Model Context Protocol server that lets an AI assistant — Claude Code, Claude Desktop, Cursor, or any MCP client — call the Klap API on your behalf: look up a charge and its timeline, check which webhook deliveries failed, run a metrics query, or create a test charge and push it through the sandbox.

It runs on your own machine, started by the MCP client over stdio with npx. Nothing is hosted by Klap: the server talks straight to https://api.klappay.com/v1 with your own API key, exactly like an integration built on @klappay/node would — every tool is a thin wrapper around one SDK method, so the same scopes, rate limits and errors apply.

This is different from llms.txt: llms.txt lets an agent read these docs; the MCP server lets it act on your account. The source is at github.com/klappay/klap-mcp (MIT), and its own reference lives at mcp.klappay.com.

Install ​

Requires Node.js 24+. Store the key once with the Klap CLI so it lives in ~/.klap/config.json (created with 0600 permissions) instead of in your MCP client's config file, which is often synced, shared or committed:

sh
npx @klappay/cli login --api-key - --base-url https://api.klappay.com/v1

Paste a klap_test_... key on stdin and press Ctrl-D — --api-key - keeps it out of your shell history. A live key can be stored the same way later; test and live keys sit in separate slots of the same file.

Then add one server per environment. Keeping test and live as separate servers means the assistant always knows which one it's talking to, and you can remove live without touching test.

sh
claude mcp add klap-test -- npx -y @klappay/mcp@1.0.0
claude mcp add klap-live -e KLAP_ENV=live -- npx -y @klappay/mcp@1.0.0
json
{
  "mcpServers": {
    "klap-test": {
      "command": "npx",
      "args": ["-y", "@klappay/mcp@1.0.0"]
    },
    "klap-live": {
      "command": "npx",
      "args": ["-y", "@klappay/mcp@1.0.0"],
      "env": { "KLAP_ENV": "live" }
    }
  }
}

Claude Desktop reads claude_desktop_config.json, Cursor reads ~/.cursor/mcp.json — same shape. Pin an exact version as above rather than @latest: the server runs with your API key, so upgrade it on purpose, after reading its changelog.

On startup the server prints one line to the client's MCP log — klap-mcp: test → api.klappay.com — and never the key. If it refuses to start, the same log has a one-line reason (see Configuration).

Then ask things like:

  • "Which Klap environment are you connected to?"
  • "Create a 25 USD test charge payable in USDC on Base, expiring in 15 minutes."
  • "Mark that charge as confirmed in the sandbox, then show me its timeline."
  • "Which of my webhooks had failed deliveries this week?"

Test vs. live ​

test (default)livelive + KLAP_MCP_ALLOW_LIVE_WRITES=1
Read toolsyesyesyes
charges_create, charges_check, webhooks_retry_deliveryyesnoyes
sandbox_triggeryesnono

Live is read-only by default. Write tools aren't hidden behind a prompt or a confirmation — in live they're simply never registered, so an assistant can't call them whatever it's told (including by text it reads back from the API, like a charge's metadata or a webhook URL). Only add KLAP_MCP_ALLOW_LIVE_WRITES=1 (exactly 1) if you really want the assistant creating live charges or re-sending live webhook deliveries.

A dedicated key with only the scopes the assistant needs — e.g. charges:read + webhooks:read + metrics:read for a live, read-only server — caps what it can do at the API itself, independently of the server's own rules.

Tools ​

Every result is JSON carrying an environment field (test/live). API responses are parsed through the published @klappay/types schemas before the assistant sees them: unknown fields are dropped, and a response that doesn't match becomes an unexpected_response error instead of being passed through.

ToolSDK methodRESTNotes
klap_status——Returns { environment, host }. No API call.
charges_getcharges.get(id)GET /charges/{id}metadata is left out unless includeMetadata: true — it's free-form merchant data that may hold customer details. See Charges.
charges_timelinecharges.getTimeline(id)GET /charges/{id}/timelineSee Audit trail.
webhooks_listwebhooks.list()GET /webhooksEach url is reduced to origin + path (credentials, query string and fragment removed). Secrets are never returned, only the hint.
webhooks_list_deliverieswebhooks.listDeliveries(id, page)GET /webhooks/{id}/deliverieslimit 1–100 (default 20), cursor from the previous page.
networks_getnetworks.get()GET /networksThe (token, network) pairs this environment accepts. See Networks & tokens.
metrics_querymetrics.query(query)POST /metrics/queryTakes a MetricsQuery. See Metrics.
charges_createcharges.create(input)POST /chargesCreateCharge without escrow and redirectUrl. Pass idempotencyKey to make a retry safe — without it, every call creates a new charge.
charges_checkcharges.check(id, input?)POST /charges/{id}/checkOptional txHash + network. See Forcing an on-chain re-check.
webhooks_retry_deliverywebhooks.retryDelivery(id, deliveryId)POST /webhooks/{id}/deliveries/{deliveryId}/retryThe receiving endpoint processes that event again.
sandbox_triggersandbox.trigger(id, event, amount?)POST /sandbox/charges/{id}/triggercharge.confirmed, .partially_paid, .overpaid, .expired, .underpaid, .settled or .settlement_failed. See Sandbox.

Ids are validated before any request is sent (ch_..., wh_..., ev_...). A failed call returns isError: true with the API's own code/status/message — the same codes listed in Errors.

Not exposed on purpose ​

Not availableWhy
Escrow release / refundThey move funds out of a charge — that stays a deliberate human action. See Releasing or refunding an escrow.
Webhook create / delete / rotate secretPointing deliveries elsewhere, or rotating a secret, can silently break or redirect your integration.
Recipient changesThey decide where money goes. See Recipients.
Live event streamsA long-lived SSE stream doesn't fit a request/response tool.
QR codes and swap quotesPayer-facing features with no use to an assistant.

For any of these, use the Dashboard, the CLI or the SDK directly.

Configuration ​

VariableDefaultMeaning
KLAP_ENVtesttest or live, exactly. Anything else refuses to start.
KLAP_API_KEYunsetUse this key instead of ~/.klap/config.json. Requires KLAP_BASE_URL; its prefix must match KLAP_ENV.
KLAP_BASE_URLstored base URLRequired with KLAP_API_KEY. With a stored key it must equal the stored base URL.
KLAP_MCP_ALLOW_LIVE_WRITESunsetExactly 1 registers the live write tools. No effect in test.

The server refuses to start, rather than guess, whenever the setup is ambiguous: a klap_live_ key with KLAP_ENV unset, a stored key paired with a different KLAP_BASE_URL (a stored key is only ever sent to the host it was stored with), or a plain http:// base URL on anything but localhost. Unlike the CLI, it never picks live on its own — if only a live key is stored, set KLAP_ENV=live.

Passing KLAP_API_KEY + KLAP_BASE_URL directly in the client's env works for CI or a throwaway setup, but puts the key in that config file; prefer klap login on a workstation. The full list of refusals and how to fix each is at mcp.klappay.com/configuration.

See also ​

  • Authentication — key format, scopes, rotation and revocation; revoke the key the server uses the same way as any other.
  • Sandbox — what sandbox_trigger does to a charge, and which events can't be triggered.
  • mcp.klappay.com — the server's own docs, with llms-full.txt.

Klap API, Node.js SDK and types reference.