Sign InGet Started
Back to Blog

From a question to a signed quote, in two MCP servers

@payxor/mcp-client reads balances and entitlements with no key. @payxor/mcp-admin creates products and reads revenue with a pxk_ key.

October 15, 2026•PayXor Team

An agent can ask PayXor the same questions the SDK already answers. Two MCP servers expose those questions over stdio. @payxor/mcp-client talks to the public API. @payxor/mcp-admin talks to the admin API with a key you create in the dashboard. The wallet still signs the token approval and the payment.

Two frosted-glass panels on a dark blue field, one open and one sealed, joined by a line of cyan light.

Two processes, one config file

Both packages ship a binary and speak the Model Context Protocol on stdin and stdout. Node 18 or newer is enough to run them. PAYXOR_API_URL defaults to https://api.payxor.xyz on each server. Set it when you want the servers pointed at another backend.

@payxor/mcp-client starts with no secret. It builds a PayXorClient from that API URL and registers nine tools, three prompts, a chains resource, a stablecoin resource for every supported chain, and an entitlements guide. The binary name is payxor-mcp-client.

@payxor/mcp-admin exits on startup unless PAYXOR_API_KEY or PAYXOR_AUTH_TOKEN is set. It prefers the API key and sends that key as X-API-Key. A session token is sent as a bearer token. Create the key under API Keys. Give it a name, a scope of account or app, and a permission of read or read_write. The full value, pxk_ plus 64 hex characters, is shown once. The binary name is payxor-mcp-admin.

{
  "mcpServers": {
    "payxor-client": {
      "command": "npx",
      "args": ["@payxor/mcp-client"]
    },
    "payxor-admin": {
      "command": "npx",
      "args": ["@payxor/mcp-admin"],
      "env": {
        "PAYXOR_API_KEY": "pxk_your_api_key_here"
      }
    }
  }
}

In Cursor that file is .cursor/mcp.json in the project root. A read key that calls a write route receives API key does not have write permission. An app-scoped key that names another app receives API key does not have access to this app. Creating an app needs a key scoped to the full account. An app-scoped key cannot call create_app.

What the client server can ask

The client tools take the bytes32 ids your app already uses in the SDK.

  1. get_product_info loads the public product from appId and productId.
  2. get_app_stablecoins lists the tokens that app accepts on chainId.
  3. check_balance reads an ERC-20 balance for tokenAddress, owner, and chainId.
  4. check_allowance reads that owner's allowance for the PayXor contract on the same chain.
  5. get_quote requests a quote bound to appId, productId, chainId, and payer. Pass tokenAddress to select the token. Pass amount, in the token's smallest unit, only for a receipt product that allows a custom amount.
  6. confirm_transaction posts txHash and quoteId after the payment transaction has been submitted, so the backend can mark the quote used.
  7. check_session calls isSessionActive for that payer, app, product, and chain.
  8. check_feature calls isFeatureUnlocked. It takes entitlementId, the bytes32 stored on the product.
  9. check_pass calls hasPass for a passId.
A row of frosted-glass icons joined by a cyan line, from a wallet and a tag through coins, a sealed scroll, and a shield.

Three resources sit beside the tools. payxor://chains lists mainnets and testnets from @payxor/types. payxor://chains/{chainId}/stablecoins is that chain's token catalog. The catalog is the set of known tokens. get_app_stablecoins is the shorter list that one app has enabled. payxor://entitlements is a short guide. The live checks are check_session, check_feature, and check_pass.

Three prompts package those steps. payment_flow walks through the product, the balance, the allowance, and the quote, then tells the agent to call confirm_transaction after the payer has submitted the transaction. verify_payment_readiness stops at the balance and the allowance. check_entitlements takes entitlementType of session, feature, or pass, and the id that matches that type.

The quote is signed here. The payment is signed in the wallet.

get_quote returns the quote and sigBackend. The backend sets the price and signs the quote with EIP-712. That rule is the one in Backend-Authoritative Pricing. The client server exposes no pay tool and no approve tool, so the payer still signs those two transactions from a wallet. The wallet half of the flow is From wallet connect to entitlement, in two React hooks.

After the transaction is mined, confirm_transaction reports the hash and the quoteId from the quote. Read the on-chain entitlement before you treat the purchase as complete. check_session, check_feature, and check_pass are those reads. A receipt check stays on the SDK, as verifyReceipt(txHash, quoteId, chainId).

What the admin server can change

A cyan key hovering above a closed glass ledger on a dark blue field.

create_app takes name, payee, supportedNetworks, and supportedStablecoins. supportedNetworks is an array of chain ids, such as 84532 for Base Sepolia. supportedStablecoins maps each chain id to token addresses. Set isTesting when the app is for testnets. domain is optional. The response includes id, the UUID, and app_id, a random bytes32, along with payee, supported_networks, and isTesting.

create_product takes that UUID as appId, plus productName, mode, and price. Mode is 0 for a session, 1 for a feature, 2 for a receipt, and 3 for a pass. price is a digit string in the token's smallest unit, such as 1000000 for 1 USDC at 6 decimals. Pass duration in seconds for a session product. allowsCustomAmount is accepted only when the mode is 2. The API returns allowsCustomAmount can only be enabled for RECEIPT mode (mode 2) products otherwise. A receipt that should take a custom amount uses price 0 and allowsCustomAmount: true.

update_app can set status to active or paused. delete_app and delete_product remove the row. list_apps, get_app, and list_products read them back. get_app, update_app, and delete_app take the UUID.

list_quotes filters by product, by status (issued, used, or expired), by chain, and by fromDate and toDate, with limit and offset. get_stats returns quote counts, revenue, fees, net revenue, conversion rate, revenue by chain, and the unique payers and top payers. Pass a bytes32 product id when the numbers should cover one product.

Five further tools return documentation and do their work off the payment path. getSdkOverview summarizes the SDK. getSdkUsage takes a method name such as getQuote, pay, checkBalance, approveToken, isSessionActive, getProductInfo, or confirmTransaction. getReactComponentUsage takes a component name such as DropdownBuyButton, ProductCard, StablecoinSelector, Button, StatusMessage, or StatusBadge. generateIntegrationSnippet targets nextjs-app-router, nextjs-pages-router, vite-react, or remix, and can fill in a product when you pass the product's bytes32 and the app UUID. listRecommendedFlows returns five integration writeups, from a single-product checkout through a receipt marketplace.

App, product, quote, and stats tools use snake_case names. Those five documentation tools use camelCase names.

Prompts on this server are create_app, setup_product, view_analytics, complete_app_setup, and review_quotes. create_app is both a tool and a prompt. setup_product is the prompt that leads into the create_product tool.

Copy the id the tool returned

A small glass hex tile beside a taller glass folder card, both lit by cyan light on a dark blue field.

create_app returns two identifiers. id is the UUID. app_id is a random bytes32. Admin tools take the UUID. create_product, list_products, get_stats, and list_quotes all name that field appId, and the API responds with appId must be a UUID when it receives the hex string.

The product response carries its own UUID in id, the app's bytes32 in app_id, and product_id. That product_id is the keccak256 of the app name, a colon, and the product name. Client tools take those bytes32 values. get_quote, check_session, and get_app_stablecoins want app_id and product_id. check_feature wants entitlement_id when the product has one.

list_quotes and get_stats take both kinds. Their appId is the UUID. Their optional productId filter is the bytes32.

Call list_apps or get_app when you need the pair again.

A loop you can run on a testnet

  1. With a full-account key, ask the admin server to create an app on Base Sepolia, chain id 84532, with isTesting set, and a session product with a duration in seconds.
  2. Copy app_id and product_id over to the client server. Keep id for the admin calls.
  3. Ask the client server for the product, the stablecoins on chain 84532, the payer's balance, and the allowance.
  4. Ask for a quote. Pass sigBackend to the wallet flow. The agent does not submit the transaction.
  5. After the transaction is mined, call confirm_transaction with the hash and quote.quoteId.
  6. Call check_session for that payer, app, product, and chain.
  7. Call get_stats with the app UUID.

Create the key from API Keys. Leave quote signing on the backend. The documentation places these servers next to the SDK and the API reference.