Skip to main content

MCP server: @oid4pay/oid4pay-mcp

The OID4Pay MCP server is a stdio-mode Model Context Protocol server that surfaces agent payments as model-callable tools. Wire it into Claude Desktop, Cline, Continue, or any MCP-aware client; the model can then initiate a payment against an enabled merchant without you implementing the protocol by hand. The tools reach the hosted Authorization Server over HTTP; the server never holds settlement logic of its own.

Twelve tools ship in @oid4pay/oid4pay-mcp 0.1.0, covering registration, payment, mandate verification, merchant discovery, wallet management, GDPR DSAR, and audit chain queries. These are MCP tools the model invokes through the MCP protocol; they are not direct JavaScript methods.

License: Apache-2.0.

Install

npm install -g @oid4pay/oid4pay-mcp

The package ships a single binary, oid4pay-mcp, with exactly two subcommands: register and serve.

Register

Register the agent once, using a one-shot setup token issued by the wallet. Registration mints a fresh DPoP keypair and a private_key_jwt keypair (if not already present) and calls the Authorization Server's /oauth/register endpoint. The resulting client_id and keys are persisted under ~/.local/share/oid4pay-mcp/ for the serve command to reuse.

oid4pay-mcp register --setup-token setup_4f6a2c1d8b9e3f70

Options:

FlagDefaultMeaning
--setup-token <code>requiredOne-shot setup token issued by the wallet.
--as-url <url>https://as.oid4pay.comAuthorization Server base URL.
--redirect-uri <url>http://127.0.0.1:8765/oid4ac/callbackRedirect URI the agent uses after consent.
--client-name <name>(unset)Human-readable client label.

To rotate, delete the persisted state under ~/.local/share/oid4pay-mcp/ and run register again with a fresh setup token.

Serve

Run the MCP server over stdio. The serve subcommand takes no options; it reads the persisted registration and keys and speaks the MCP protocol on stdin and stdout. MCP clients spawn this command and connect over those streams.

oid4pay-mcp serve

Wire into an MCP client

Claude Desktop

Add to ~/.config/Claude/claude_desktop_config.json (or the Windows / macOS equivalent):

{
  "mcpServers": {
    "oid4pay": {
      "command": "oid4pay-mcp",
      "args": ["serve"]
    }
  }
}

No environment keys are required; the server reads its registration and keys from ~/.local/share/oid4pay-mcp/. Run oid4pay-mcp register before starting the client.

Other MCP clients

Cline, Continue, and other MCP-aware toolchains follow the same pattern: point the client at the oid4pay-mcp command with the single argument serve. Any client that speaks stdio MCP can drive the twelve tools.

Token types

Each tool documents which token type the Authorization Server accepts on the wire:

Tools (table of contents)

The model invokes each tool by name through the MCP protocol. The raw JSON-RPC tools/call shape is shown per tool for reference; in practice the MCP client constructs that envelope for you.

ToolPurpose
agent_registerRFC 7591 Dynamic Client Registration.
agent_payment_initiateFull agent-payment chain (PAR : authorize : token : verify-mandate).
agent_verify_mandatePresent an SD-JWT VC mandate + KB-JWT at a merchant /verify-mandate.
discovery_list_merchantsDirectory query (country, currency, rail filters).
agent_browse_merchantSigned catalog preview for a single merchant.
agent_wallet_registerWallet-side registration with display metadata (client_name, purpose).
agent_wallet_list_agentsRead the principal's registered agents (wallet:read).
agent_wallet_revoke_agentCascade-revoke a registered agent (wallet:write).
agent_buy_cheapest_from_storeFetch catalog : pick cheapest : pay.
agent_dsar_initiateGDPR Article 15 (access) or Article 20 (portability) DSAR.
agent_audit_chain_queryForensic query against the principal's audit chain entries.
agent_payment_historyPayment-centric projection over the audit chain.
Convention. Every tool's input schema and output payload is pinned to the shipped 0.1.0 server. The MCP layer wraps the Authorization Server wire shapes; it does not invent shapes of its own. When this page disagrees with the code, the code wins and we fix the page.

agent_register

Generates or loads the agent's DPoP and private_key_jwt keypairs, then calls POST /oauth/register on the Authorization Server with the supplied setup token. Persists the resulting client_id. RFC 7591 Dynamic Client Registration; client authentication follows the private_key_jwt assertion. Subsequent tool calls reuse the persisted registration.

Input schema

{
  "type": "object",
  "properties": {
    "setup_token":   { "type": "string", "minLength": 1 },
    "as_url":        { "type": "string", "format": "uri" },
    "redirect_uris": { "type": "array", "minItems": 1,
                       "items": { "type": "string", "format": "uri" } },
    "client_name":   { "type": "string" }
  },
  "required": ["setup_token", "as_url", "redirect_uris"]
}

Output

{
  "ok": true,
  "client_id": "agent_3ff8a1d2b9c4e7f0",
  "as_url": "https://as.oid4pay.com"
}

Errors

Operator-facing messageCause
agent_register failed: invalid_client_metadataAuthorization Server rejected the client_name or other RFC 7591 fields.
agent_register failed: invalid_redirect_uriOne of the redirect_uris uses a disallowed scheme.
agent_register failed: invalid_software_statementThe setup_token is expired, already used, or unbound to this Authorization Server.

Auth requirement

One-shot setup token issued by the wallet. The token binds the agent to a principal at issue time; the Authorization Server rejects reuse.

Invocation (raw JSON-RPC over the MCP transport)

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "agent_register",
    "arguments": {
      "setup_token": "setup_4f6a2c1d8b9e3f70",
      "as_url": "https://as.oid4pay.com",
      "redirect_uris": ["https://my-agent.example.com/callback"],
      "client_name": "acme-research-agent"
    }
  }
}

From the CLI this is the same operation as oid4pay-mcp register --setup-token setup_4f6a2c1d8b9e3f70.

agent_payment_initiate

Drives the full payment chain against the configured Authorization Server: PAR : authorize : token : verify-mandate. Returns the mandate id and (when issued) the signed receipt. Every leg follows its wire shape: PAR response, JWT-AT claim set, SD-JWT VC mandate, KB-JWT, DPoP, resource indicators, algorithm whitelist, and authorization code single-use. Settlement is performed by the Authorization Server, reached over HTTP.

Input schema

{
  "type": "object",
  "properties": {
    "merchant_url":        { "type": "string", "format": "uri" },
    "merchant_base_api":   { "type": "string", "format": "uri" },
    "merchant_domain":     { "type": "string", "minLength": 1 },
    "merchant_verify_url": { "type": "string", "format": "uri" },
    "amount": {
      "type": "object",
      "properties": {
        "currency":     { "type": "string", "length": 3 },
        "amount_minor": { "type": "integer", "minimum": 1 }
      },
      "required": ["currency", "amount_minor"]
    },
    "line_items": {
      "type": "array",
      "minItems": 1,
      "items": {
        "type": "object",
        "properties": {
          "sku":              { "type": "string", "minLength": 1 },
          "qty":              { "type": "integer", "minimum": 1 },
          "unit_price_minor": { "type": "integer", "minimum": 0 },
          "currency":         { "type": "string", "length": 3 }
        },
        "required": ["sku", "qty"]
      }
    },
    "offer_id":           { "type": "string" },
    "offer_digest":       { "type": "string" },
    "scope":              { "type": "string", "default": "openid oid4ac:payment" },
    "redirect_uri":       { "type": "string", "format": "uri" },
    "consent_mode_hint":  { "enum": ["auto", "dashboard", "scan"] },
    "consent_poll_timeout_seconds": { "type": "integer", "minimum": 1, "maximum": 900 }
  },
  "required": ["merchant_url", "amount", "line_items", "redirect_uri"]
}

Set merchant_base_api to the merchant's live payment API base (for example https://api.merchant.example); the agent fetches a single-use nonce and posts the DPoP-bound payment request. The legacy merchant_verify_url is used only when merchant_base_api is absent. Bind offer_digest so the Authorization Server mints it as a disclosable mandate claim; omitting it issues a mandate with no offer digest and the merchant rejects the payment with offer_digest_mismatch.

Output

The tool returns a PayResult. Always branch on the status discriminant: paid means the payment settled, step_up_required means the human consent poll timed out (re-poll or re-run with a longer consent_poll_timeout_seconds), setup_required means the principal must complete wallet setup first, and declined means no mandate was issued. On the merchant_base_api path the success result populates status, order_id, receipt, and settlement; on the legacy merchant_verify_url path it populates status, mandate_id, receipt_jws, and payment_provider_ref.

{
  "status": "paid",
  "order_id": "ord_7b2c4d6e8f0a1b3c",
  "receipt": { },
  "settlement": { }
}

Legacy merchant_verify_url success:

{
  "status": "paid",
  "mandate_id": "mandate_7b2c4d6e8f0a1b3c",
  "receipt_jws": "eyJhbGciOiJFZERTQSIsInR5cCI6InJlY2VpcHQrand0In0...",
  "payment_provider_ref": "ch_3OQ8Z2L8KZ4f5G2c0HiJkLmN"
}

Non-settled variants:

{
  "status": "step_up_required",
  "request_uri": "urn:ietf:params:oauth:request_uri:6esc_11ACC5bwc014ltc14eY22c"
}
{
  "status": "declined",
  "error": { "code": "consent_denied" }
}

Errors

Operator-facing messageCause
agent_payment_initiate failed: PAR failed: 400PAR body rejected: missing authorization_details, bad resource indicator, or stale DPoP nonce.
agent_payment_initiate failed: token failed: 400 invalid_grantAuthorization code expired or reused. Re-run PAR.
agent_payment_initiate failed: token failed: 400 invalid_targetThe resource indicator does not match the PAR-bound audience (see resource indicators).
agent_payment_initiate failed: verify-mandate failed: 422Merchant rejected the SD-JWT VC presentation (audience mismatch, expired, status-revoked, or KB-JWT nonce wrong).
agent_payment_initiate failed: verify-mandate failed: 502Merchant upstream error; the mandate is still valid; retry with the same idempotency key.
agent_payment_initiate failed: declined: no mandate issuedPrincipal declined consent at the wallet; no access token was issued.

Auth requirement

Agent-registered client_id with a DPoP keypair. The server reads the persisted registration; agent_register must have run first.

Invocation (raw JSON-RPC over the MCP transport)

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "agent_payment_initiate",
    "arguments": {
      "merchant_url": "https://shop.alpacanica.com",
      "merchant_base_api": "https://api.alpacanica.com",
      "amount": { "currency": "EUR", "amount_minor": 1299 },
      "line_items": [{ "sku": "alpaca-sock-blue-43", "qty": 1 }],
      "redirect_uri": "https://my-agent.example.com/callback",
      "scope": "openid oid4ac:payment"
    }
  }
}

agent_verify_mandate

Posts a compact SD-JWT VC presentation (mandate JWT + selective disclosures + KB-JWT) to the merchant's /verify-mandate endpoint and returns the verifier's reply. Standalone helper for re-verification after a network failure. The presentation envelope follows the SD-JWT VC mandate shape; KB-JWT key binding enforces the cnf.jkt claim end-to-end.

Input schema

{
  "type": "object",
  "properties": {
    "merchant_verify_url": { "type": "string", "format": "uri" },
    "presentation":        { "type": "string", "minLength": 1 },
    "line_items":          { "type": "array",
                              "items": { "$ref": "#/definitions/LineItem" } },
    "offer_id":            { "type": "string" },
    "idempotency_key":     { "type": "string" }
  },
  "required": ["merchant_verify_url", "presentation", "line_items"]
}

Output

The tool returns the merchant verifier's reply verbatim. The SDK pins only two fields: ok (the verification result) and mandate_id. Any further fields are merchant-defined passthrough and typed as opaque; do not depend on them.

{
  "ok": true,
  "mandate_id": "mandate_7b2c4d6e8f0a1b3c"
}

Errors

Operator-facing messageCause
agent_verify_mandate failed: verify-mandate failed: 422 mandate_audience_mismatchMandate aud claim does not match merchant_verify_url origin.
agent_verify_mandate failed: verify-mandate failed: 422 mandate_kb_nonce_mismatchKB-JWT nonce does not match the merchant's challenge nonce.
agent_verify_mandate failed: verify-mandate failed: 422 mandate_status_revokedMandate revoked at the Authorization Server (cascade-revoke or principal-initiated).
agent_verify_mandate failed: verify-mandate failed: 422 mandate_expiredMandate exp claim in the past.

Auth requirement

None at the Authorization Server layer; the presentation carries its own cryptographic proof. The agent must hold the SD-JWT VC + KB-JWT (issued during agent_payment_initiate).

Invocation (raw JSON-RPC over the MCP transport)

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "agent_verify_mandate",
    "arguments": {
      "merchant_verify_url": "https://shop.alpacanica.com/verify-mandate",
      "presentation": "eyJhbGciOiJFZERTQSIs...~WyJzYWx0...~eyJhbGciOiJFZERTQSIs...",
      "line_items": [{ "sku": "alpaca-sock-blue-43", "qty": 1 }],
      "idempotency_key": "ik_4f6a2c1d8b9e3f70"
    }
  }
}

discovery_list_merchants

Calls GET /merchants on the OID4Pay discovery service and returns the paginated list of verified merchants. Filters: country (ISO 3166-1 alpha-2), currency (ISO 4217), rail (card, ideal, sepa_debit, etc.), free-text search against the business name. Pagination via page + page_size (server caps page_size at 100). Returns merchant identities only (id, audience, business name, country, accepted currencies and rails); no inventory.

Input schema

{
  "type": "object",
  "properties": {
    "country":   { "type": "string", "length": 2 },
    "currency":  { "type": "string", "length": 3 },
    "rail":      { "type": "string" },
    "q":         { "type": "string" },
    "page":      { "type": "integer", "minimum": 1 },
    "page_size": { "type": "integer", "minimum": 1, "maximum": 100 }
  }
}

Output

{
  "merchants": [
    {
      "id": "merch_alpacanica",
      "merchant_audience": "https://shop.alpacanica.com",
      "business_name": "Alpacanica Outfitters",
      "country": "NL",
      "accepts_currency": ["EUR", "USD"],
      "accepts_rail": ["card", "ideal", "sepa_debit"],
      "verification_status": "verified",
      "verified_at": "2026-04-01T00:00:00Z"
    },
    {
      "id": "merch_bristleandslate",
      "merchant_audience": "https://shop.bristleandslate.com",
      "business_name": "Bristle and Slate",
      "country": "NL",
      "accepts_currency": ["EUR"],
      "accepts_rail": ["card", "ideal"],
      "verification_status": "verified",
      "verified_at": "2026-04-12T00:00:00Z"
    }
  ],
  "page": 1,
  "page_size": 20,
  "total": 2
}

Errors

Operator-facing messageCause
discovery_list_merchants failed: 400 invalid_filterFilter value rejected (e.g. country not ISO 3166-1, currency not ISO 4217).
discovery_list_merchants failed: 500 server_errorDiscovery service upstream error.
discovery_list_merchants failed: fetch failedTransport-level failure (ECONNREFUSED, DNS, TLS).

Auth requirement

None. The discovery service is unauthenticated for read; results are filtered to verified, in-good-standing merchants. The base URL is read from the OID4PAY_DISCOVERY_URL environment variable when set; otherwise it defaults to the production discovery service.

Invocation (raw JSON-RPC over the MCP transport)

{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "tools/call",
  "params": {
    "name": "discovery_list_merchants",
    "arguments": {
      "country": "NL",
      "currency": "EUR",
      "rail": "ideal",
      "page": 1,
      "page_size": 20
    }
  }
}

agent_browse_merchant

Calls GET /merchants/{id}/catalog-preview on the OID4Pay discovery service. The discovery service proxies the merchant's RFC 9421 signed catalog (first page only); the response carries the merchant's signature, signature_input, and content_digest fields. The server rejects responses missing any signed field, refusing to act on an unsigned catalog; that is where the HTTP message signature is enforced.

Input schema

{
  "type": "object",
  "properties": {
    "merchant_id": { "type": "string", "minLength": 1 }
  },
  "required": ["merchant_id"]
}

Output

{
  "@context": "https://oid4pay.com/ns/catalog/v1",
  "@type": "SignedCatalog",
  "merchant": "https://shop.alpacanica.com",
  "catalog_url": "https://shop.alpacanica.com/oid4ac/catalog",
  "signature": ":MEUCIQDt...:",
  "signature_input": "sig1=(\"@method\" \"@target-uri\" \"content-digest\");keyid=\"merch_alpacanica_2026Q2\";alg=\"ed25519\";created=1715750488;expires=1715750548",
  "content_digest": "sha-256=:bdaTFvfksp...:",
  "items": [
    {
      "sku": "alpaca-sock-blue-43",
      "title": "Alpaca wool sock, sky blue, size 43"
    }
  ]
}

Item entries pass through opaque: the SDK pins the top-level @context, @type, merchant, catalog_url, signature, signature_input, content_digest, and items keys, but does not pin per-item fields beyond what the merchant publishes.

Errors

Operator-facing messageCause
agent_browse_merchant rejected upstream catalog (missing signed field "signature")Upstream catalog response omitted a signed field; the server refuses to act on an unsigned catalog.
agent_browse_merchant failed: 404 not_foundNo merchant with that id in the directory.
agent_browse_merchant failed: 429 rate_limitedDiscovery service rate-limit. Backoff before retrying.
agent_browse_merchant failed: 502 bad_gatewayUpstream merchant catalog endpoint unreachable.

Auth requirement

None. The server verifies the merchant's RFC 9421 signature before returning.

Invocation (raw JSON-RPC over the MCP transport)

{
  "jsonrpc": "2.0",
  "id": 5,
  "method": "tools/call",
  "params": {
    "name": "agent_browse_merchant",
    "arguments": {
      "merchant_id": "merch_alpacanica"
    }
  }
}

agent_wallet_register

RFC 7591 Dynamic Client Registration against the Authorization Server, persisting the new client_id and DPoP keypair locally. Differs from agent_register in that it ships a client_name and purpose that the wallet renders in its agents list (the user-facing labels). Returns the bound principal id alongside the issued client id.

Input schema

{
  "type": "object",
  "properties": {
    "setup_token":   { "type": "string", "minLength": 1 },
    "as_url":        { "type": "string", "format": "uri" },
    "redirect_uris": { "type": "array", "minItems": 1,
                       "items": { "type": "string", "format": "uri" } },
    "client_name":   { "type": "string", "minLength": 1 },
    "purpose":       { "type": "string", "minLength": 1 },
    "scope":         { "type": "string", "default": "wallet:read wallet:write" }
  },
  "required": ["setup_token", "as_url", "redirect_uris", "client_name", "purpose"]
}

Output

{
  "ok": true,
  "client_id": "agent_3ff8a1d2b9c4e7f0",
  "principal_id": "principal_8a2c1d8b9e3f70a4",
  "client_name": "acme-research-agent",
  "purpose": "Find and book research papers under EUR 50/month.",
  "scope": "wallet:read wallet:write",
  "as_url": "https://as.oid4pay.com"
}

Errors

Operator-facing messageCause
agent_wallet_register failed: invalid_client_metadataclient_name empty or violates the Authorization Server validators.
agent_wallet_register failed: invalid_software_statementSetup token expired, single-use, or unbound.
agent_wallet_register failed: invalid_redirect_uriOne of the redirect_uris rejected (disallowed scheme, mismatched host).

Auth requirement

One-shot setup token issued by the wallet QR scan.

Invocation (raw JSON-RPC over the MCP transport)

{
  "jsonrpc": "2.0",
  "id": 6,
  "method": "tools/call",
  "params": {
    "name": "agent_wallet_register",
    "arguments": {
      "setup_token": "setup_4f6a2c1d8b9e3f70",
      "as_url": "https://as.oid4pay.com",
      "redirect_uris": ["https://my-agent.example.com/callback"],
      "client_name": "acme-research-agent",
      "purpose": "Find and book research papers under EUR 50/month.",
      "scope": "wallet:read wallet:write"
    }
  }
}

agent_wallet_list_agents

Runs a wallet:read OAuth dance against the Authorization Server to mint an access token scoped wallet:read, then GET /wallet/agents. Returns the principal's agents (the Authorization Server filters on the access token sub claim resolved by the wallet session, so an agent cannot read another principal's agents). Errors with insufficient_scope if the Authorization Server refuses the requested scope.

Input schema

{
  "type": "object",
  "properties": {
    "as_url":       { "type": "string", "format": "uri" },
    "resource":     { "type": "string", "format": "uri" },
    "redirect_uri": { "type": "string", "format": "uri" }
  },
  "required": ["as_url", "resource", "redirect_uri"]
}

Output

{
  "agents": [
    {
      "id": "wa_7c1e9b04d8f24a6e",
      "client_id": "agent_3ff8a1d2b9c4e7f0",
      "label": "acme-research-agent",
      "purpose": "Find and book research papers under EUR 50/month.",
      "created_at": "2026-05-01T09:14:22Z",
      "last_used_at": "2026-05-15T05:21:28Z",
      "revoked": false,
      "scopes": ["wallet:read", "wallet:write"]
    }
  ]
}

Errors

Operator-facing messageCause
agent_wallet_list_agents: insufficient_scope (need wallet:read); ...Authorization Server refused the wallet:read scope (the principal has not granted it).
agent_wallet_list_agents failed (401 invalid_token): ...Access token expired or DPoP proof invalid.
agent_wallet_list_agents failed (403 forbidden): ...Token valid but principal mismatch; the Authorization Server rejected the cross-principal request.

Auth requirement

Wallet-session access token, scope wallet:read.

Invocation (raw JSON-RPC over the MCP transport)

{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tools/call",
  "params": {
    "name": "agent_wallet_list_agents",
    "arguments": {
      "as_url": "https://as.oid4pay.com",
      "resource": "https://as.oid4pay.com",
      "redirect_uri": "https://my-agent.example.com/callback"
    }
  }
}

agent_wallet_revoke_agent

Runs a wallet:write OAuth dance against the Authorization Server to mint an access token scoped wallet:write, then POST /wallet/agents/{agent_client_id}/revoke. The Authorization Server cascade-revokes every active token family for the agent (mandates, refresh tokens, access tokens), as set out in authorization code single-use and /oauth/revoke. Errors with insufficient_scope if the Authorization Server refuses the requested scope.

Input schema

{
  "type": "object",
  "properties": {
    "as_url":          { "type": "string", "format": "uri" },
    "agent_client_id": { "type": "string", "minLength": 1 },
    "resource":        { "type": "string", "format": "uri" },
    "redirect_uri":    { "type": "string", "format": "uri" }
  },
  "required": ["as_url", "agent_client_id", "resource", "redirect_uri"]
}

Output

{
  "ok": true,
  "client_id": "agent_3ff8a1d2b9c4e7f0",
  "revoked_at": "2026-05-15T05:21:28Z",
  "families_revoked": 3
}

Errors

Operator-facing messageCause
agent_wallet_revoke_agent: insufficient_scope (need wallet:write); ...Authorization Server refused the wallet:write scope.
agent_wallet_revoke_agent failed (404 not_found): ...No agent with that client_id bound to this principal.
agent_wallet_revoke_agent failed (409 conflict): ...Agent already revoked; the cascade is idempotent but the Authorization Server reports the prior state.
agent_wallet_revoke_agent failed (503 server_error): ...Authorization Server transient failure; retry safe (revoke is idempotent).

Auth requirement

Wallet-session access token, scope wallet:write.

Invocation (raw JSON-RPC over the MCP transport)

{
  "jsonrpc": "2.0",
  "id": 8,
  "method": "tools/call",
  "params": {
    "name": "agent_wallet_revoke_agent",
    "arguments": {
      "as_url": "https://as.oid4pay.com",
      "agent_client_id": "agent_3ff8a1d2b9c4e7f0",
      "resource": "https://as.oid4pay.com",
      "redirect_uri": "https://my-agent.example.com/callback"
    }
  }
}

agent_buy_cheapest_from_store

Fetches the merchant's /.well-known/oid4ac-catalog, picks the cheapest in-stock item matching the optional category and shipping filters under max_amount_minor, and pays for it through the full payment flow. Returns the chosen item and the payment result. Settlement is performed by the Authorization Server.

Input schema

{
  "type": "object",
  "properties": {
    "store_url":          { "type": "string", "format": "uri" },
    "merchant_verify_url":{ "type": "string", "format": "uri" },
    "max_amount_minor":   { "type": "integer", "minimum": 1 },
    "currency":           { "type": "string", "length": 3, "default": "EUR" },
    "category":           { "type": "string" },
    "ships_to_country":   { "type": "string" },
    "redirect_uri":       { "type": "string", "format": "uri" }
  },
  "required": ["store_url", "merchant_verify_url", "max_amount_minor", "redirect_uri"]
}

Output

{
  "chosen": {
    "sku": "alpaca-sock-blue-43",
    "title": "Alpaca wool sock, sky blue, size 43",
    "unit_price_minor": 1299,
    "currency": "EUR",
    "in_stock": true,
    "category": "apparel"
  },
  "result": {
    "status": "paid",
    "mandate_id": "mandate_7b2c4d6e8f0a1b3c",
    "receipt_jws": "eyJhbGciOiJFZERTQSIsInR5cCI6InJlY2VpcHQrand0In0...",
    "payment_provider_ref": "ch_3OQ8Z2L8KZ4f5G2c0HiJkLmN"
  }
}

Errors

Operator-facing messageCause
agent_buy_cheapest_from_store: no in-stock matching items under the capNo item satisfies the category, currency, max_amount_minor, and ships_to_country filters.
agent_buy_cheapest_from_store failed: catalog 404The merchant does not serve a catalog at the expected well-known path.
agent_buy_cheapest_from_store failed: catalog 502Upstream merchant catalog endpoint unreachable.
agent_buy_cheapest_from_store failed: PAR failed: 400PAR rejected after the pick; same causes as agent_payment_initiate.
agent_buy_cheapest_from_store failed: verify-mandate failed: 502Mandate was issued but merchant verifier upstream errored; the mandate is still valid; retry verify with the same idempotency key.

Auth requirement

Agent-registered client_id with a DPoP keypair (same preconditions as agent_payment_initiate).

Invocation (raw JSON-RPC over the MCP transport)

{
  "jsonrpc": "2.0",
  "id": 9,
  "method": "tools/call",
  "params": {
    "name": "agent_buy_cheapest_from_store",
    "arguments": {
      "store_url": "https://shop.alpacanica.com",
      "merchant_verify_url": "https://shop.alpacanica.com/verify-mandate",
      "max_amount_minor": 2500,
      "currency": "EUR",
      "category": "apparel",
      "redirect_uri": "https://my-agent.example.com/callback"
    }
  }
}

agent_dsar_initiate

Initiates a GDPR Article 15 (access) or Article 20 (portability) Data Subject Access Request on behalf of the principal. The agent presents the principal's wallet-session access token as proof of consent. Article 15 dumps deliver asynchronously to the principal's verified email; Article 20 dumps stream synchronously as JSON in the response. Agent-only tokens are refused (DSAR requires a wallet-session principal). Rate-limit is 10 per day per principal per endpoint.

Input schema

{
  "type": "object",
  "properties": {
    "right":            { "enum": ["access", "portability"] },
    "principal_token":  { "type": "string", "minLength": 1 },
    "delivery_preference": {
      "enum": ["email", "synchronous_json"],
      "default": "email"
    }
  },
  "required": ["right", "principal_token"]
}

Output (Article 15 access, asynchronous email)

{
  "status": "queued",
  "request_id": "dsar_4f6a2c1d8b9e3f70",
  "estimated_delivery": "2026-05-15T05:51:28Z"
}

Output (Article 20 portability, synchronous JSON)

{
  "schema_version": 1,
  "request_id": "dsar_4f6a2c1d8b9e3f70",
  "principal": {
    "principal_id": "principal_8a2c1d8b9e3f70a4",
    "wallet_operator": "https://wallet.oid4pay.com"
  },
  "agent_clients": [...],
  "audit_chain_entries": [...]
}

Errors

Operator-facing messageCause
agent_dsar_initiate failed: DSAR requires a wallet-session JWT-AT; agent-only tokens are refusedAuthorization Server returned 401 invalid_token; the principal token was an agent-only token.
agent_dsar_initiate failed: rate limited; DSAR caps at 10/day per principal per endpointAuthorization Server returned 429 rate_limited.
agent_dsar_initiate failed: insufficient_scope (need privacy:dsar); ...Wallet-session token does not carry the privacy:dsar scope.
agent_dsar_initiate failed: agent is not registered yet; call agent_register firstNo persisted registration.

Auth requirement

Wallet-session access token (the principal's), passed in the principal_token input. Agent-only tokens are refused.

Invocation (raw JSON-RPC over the MCP transport)

{
  "jsonrpc": "2.0",
  "id": 10,
  "method": "tools/call",
  "params": {
    "name": "agent_dsar_initiate",
    "arguments": {
      "right": "access",
      "principal_token": "eyJhbGciOiJFZERTQSIs...",
      "delivery_preference": "email"
    }
  }
}

agent_audit_chain_query

Forensic query against the audit chain for events involving the authenticated principal. The agent presents the principal's wallet-session access token as proof of consent. Returns the audit entries within the requested time window, scoped to the principal (the Authorization Server filters the chain to the principal, so an agent cannot read another principal's chain). Rate-limit is 30/min per principal. Hard cap of 200 results per call.

Input schema

{
  "type": "object",
  "properties": {
    "principal_token": { "type": "string", "minLength": 1 },
    "from_iso":        { "type": "string", "format": "date-time" },
    "to_iso":          { "type": "string", "format": "date-time" },
    "event_types": {
      "type": "array",
      "items": {
        "enum": [
          "oid4ac.mandate.issued",
          "oid4ac.payment.succeeded",
          "oid4ac.payment.failed",
          "oid4ac.payment.disputed",
          "oid4ac.token.revoked",
          "oid4ac.privacy.access",
          "oid4ac.privacy.erasure"
        ]
      }
    },
    "limit": {
      "type": "integer",
      "minimum": 1,
      "maximum": 200,
      "default": 50
    }
  },
  "required": ["principal_token"]
}

Output

{
  "principal_id": "principal_8a2c1d8b9e3f70a4",
  "from_iso": "2026-05-01T00:00:00Z",
  "to_iso": null,
  "event_types": ["oid4ac.payment.succeeded"],
  "limit": 50,
  "entries": [
    {
      "seq": 4231,
      "ts": "2026-05-15T05:21:28Z",
      "event": "oid4ac.payment.succeeded",
      "tenant_id": "tenant_oid4pay_prod",
      "actor": {
        "kind": "agent",
        "id": "agent_3ff8a1d2b9c4e7f0"
      },
      "payload": {
        "mandate_id": "mandate_7b2c4d6e8f0a1b3c",
        "payment_intent_id": "pi_3OQ8Z2L8KZ4f5G2c1AbCdE",
        "amount_minor": 1299,
        "currency": "EUR",
        "merchant_audience": "https://shop.alpacanica.com"
      }
    }
  ]
}

Errors

Operator-facing messageCause
agent_audit_chain_query failed: audit chain query requires a wallet-session JWT-ATAuthorization Server returned 401 invalid_token (agent-only token).
agent_audit_chain_query failed: principal_id mismatchAuthorization Server returned 403 principal_mismatch.
agent_audit_chain_query failed: rate limited; audit chain caps at 30/min per principalAuthorization Server returned 429 rate_limited.
agent_audit_chain_query failed: insufficient_scope (need audit:read); ...Wallet-session token does not carry the audit:read scope.
agent_audit_chain_query failed: agent is not registered yet; call agent_register firstNo persisted registration.

Auth requirement

Wallet-session access token, scope audit:read. Agent-only tokens are refused.

Invocation (raw JSON-RPC over the MCP transport)

{
  "jsonrpc": "2.0",
  "id": 11,
  "method": "tools/call",
  "params": {
    "name": "agent_audit_chain_query",
    "arguments": {
      "principal_token": "eyJhbGciOiJFZERTQSIs...",
      "from_iso": "2026-05-01T00:00:00Z",
      "to_iso": "2026-05-15T23:59:59Z",
      "event_types": ["oid4ac.payment.succeeded", "oid4ac.payment.disputed"],
      "limit": 100
    }
  }
}

agent_payment_history

Payment-centric projection over the audit chain. Filters the chain to oid4ac.payment.succeeded (and optionally oid4ac.payment.disputed), then projects each entry into a payment row shape suitable for direct rendering in a chat-style agent UI. Returns at most 100 payments per call; paginate older payments via from_iso. Inherits the same wallet-session access token requirement and principal scoping as agent_audit_chain_query.

Input schema

{
  "type": "object",
  "properties": {
    "principal_token":  { "type": "string", "minLength": 1 },
    "from_iso":         { "type": "string", "format": "date-time" },
    "to_iso":           { "type": "string", "format": "date-time" },
    "include_disputed": { "type": "boolean", "default": true },
    "limit": {
      "type": "integer",
      "minimum": 1,
      "maximum": 100,
      "default": 50
    }
  },
  "required": ["principal_token"]
}

Output

{
  "payments": [
    {
      "payment_intent_id": "pi_3OQ8Z2L8KZ4f5G2c1AbCdE",
      "merchant_audience": "https://shop.alpacanica.com",
      "amount_minor": 1299,
      "currency": "EUR",
      "processed_at_iso": "2026-05-15T05:21:28Z",
      "status": "succeeded",
      "mandate_id": "mandate_7b2c4d6e8f0a1b3c",
      "agent_client_id": "agent_3ff8a1d2b9c4e7f0",
      "receipt_url": "https://as.oid4pay.com/receipts/r_4f6a2c1d8b9e.json"
    },
    {
      "payment_intent_id": "pi_3OQ8Z2L8KZ4f5G2c2XyZ12",
      "merchant_audience": "https://shop.bristleandslate.com",
      "amount_minor": 4500,
      "currency": "EUR",
      "processed_at_iso": "2026-05-12T11:08:01Z",
      "status": "disputed_pending",
      "mandate_id": "mandate_9c3d5e7f1a2b4c6d",
      "agent_client_id": "agent_3ff8a1d2b9c4e7f0"
    }
  ],
  "total_count": 2,
  "has_more": false
}

Status values

StatusSource event
succeededoid4ac.payment.succeeded
disputed_pendingoid4ac.payment.disputed with dispute_status=pending
disputed_wonoid4ac.payment.disputed with dispute_status=won
disputed_lostoid4ac.payment.disputed with dispute_status=lost

Errors

Operator-facing messageCause
agent_payment_history failed: payment history query requires a wallet-session JWT-ATAuthorization Server returned 401 invalid_token (agent-only token).
agent_payment_history failed: principal_id mismatchAuthorization Server returned 403 principal_mismatch.
agent_payment_history failed: rate limited; payment history caps at 30/min per principalAuthorization Server returned 429 rate_limited.
agent_payment_history failed: insufficient_scope (need audit:read); ...Wallet-session token does not carry the audit:read scope.
agent_payment_history failed: agent is not registered yet; call agent_register firstNo persisted registration.

Auth requirement

Wallet-session access token, scope audit:read. Agent-only tokens are refused.

Invocation (raw JSON-RPC over the MCP transport)

{
  "jsonrpc": "2.0",
  "id": 12,
  "method": "tools/call",
  "params": {
    "name": "agent_payment_history",
    "arguments": {
      "principal_token": "eyJhbGciOiJFZERTQSIs...",
      "from_iso": "2026-05-01T00:00:00Z",
      "to_iso": "2026-05-15T23:59:59Z",
      "include_disputed": true,
      "limit": 50
    }
  }
}

Library mode

Most integrators wire the server into an MCP client and let the model call the twelve tools. If you instead want to drive the Authorization Server from a Node program without going through the MCP layer, the package exports a single programmatic client: the OID4PayClient class, imported from the @oid4pay/oid4pay-mcp/lib/oid4ac subpath. It is constructed with the Authorization Server URL plus the DPoP and client assertion keys, and exposes methods such as .register(). There is no separate discovery or wallet client export; discovery and wallet operations are reached through the MCP tools above.

import { OID4PayClient } from "@oid4pay/oid4pay-mcp/lib/oid4ac";

const client = new OID4PayClient({
  asUrl: "https://as.oid4pay.com",
  dpopKey,            // a stored Ed25519 key
  clientAssertionKey, // a stored Ed25519 key
});

const reg = await client.register({
  setupToken: "setup_4f6a2c1d8b9e3f70",
  redirectUris: ["https://my-agent.example.com/callback"],
  clientName: "acme-research-agent",
});
console.log(reg.client_id);

Permissions and consent

The server prompts for principal consent through the wallet on every new merchant audience. Mandates are scoped per merchant; the agent cannot silently extend a mandate from one merchant to another. The wallet:read + wallet:write scopes gate the wallet-management tools; audit:read gates the audit-chain and payment-history tools; privacy:dsar gates the DSAR tool. Scopes that the principal has not granted come back as insufficient_scope at the Authorization Server.

Algorithm whitelist

The server enforces the algorithm whitelist and refuses to call any endpoint that returns a JWT or RFC 9421 signature using a rejected algorithm. EdDSA only for JWT-AT / SD-JWT VC / KB-JWT signatures; ed25519 or ecdsa-p256-sha256 for RFC 9421 HTTP message signatures. alg=none, HMAC for DPoP, and RS256 outside the legacy window are refused.

Source

The server lives at oid4pay-mcp/ in the OID4Pay repo. Run oid4pay-mcp --help for the CLI reference, or npm test in the package directory to run the test suite. The machine-readable tool spec lives at oid4pay-mcp/docs/tool-spec.json. License: Apache-2.0.