Skip to main content
rail402-mcp (package @rail402.dev/mcp) is an MCP server for agents that pay for what they use. It searches the Rail402 Bazaar in plain language and pays HTTP endpoints and paid MCP tools on Stellar through the buyer SDK, so every payment passes the same spending policy and signing guard. It serves stdio by default and, optionally, Streamable HTTP on a loopback address.
The paying account’s key stays in a key file that the server reads; the agent never sees it. Create one and fund it on the testnet with the CLI:

Add it to an MCP host

The server is configured through its environment. Without a key it only searches; with one it pays under the caps: on stellar:testnet 0.10 USDC per call and 1.00 USDC per 24-hour window by default, or tighter values you set.
Most hosts, including Cursor (~/.cursor/mcp.json) and desktop MCP clients, read an mcpServers object like the first one; Codex reads ~/.codex/config.toml. Hosts that add servers from their own command line take the same command (rail402-mcp) and variables. A leading ~/ in RAIL402_SECRET_KEY_FILE is expanded by the server, because hosts pass values verbatim. examples/mcp-agent in the repository is an MCP client that starts rail402-mcp over stdio, searches for “air quality for a city”, pays the first callable result within the policy through its listing and prints the transaction hash and explorer link. It knows no seller endpoint in advance.

Tools

Every tool declares an input and an output JSON Schema in tools/list. Results come back as structuredContent, and content[0].text carries the same JSON for clients that read text only.

search_stellar_resources

Input: query (required, 1-500 characters), and optionally type (http or mcp), maxPrice ({ amount, asset? }, a hard filter), payTo, limit (1-20, default 5) and cursor. Each result has listingId (pass it to pay_and_call), type, resource, method or toolName, prices (decimal amount, asset, payTo, feesSponsored, receivable, withinPolicy), callable, withinPolicy, trust, settlements, the seller’s description, and the listing’s input schema and example. Seller text is marked as untrusted data in the schema.

pay_and_call

A success has paid, replayed, the target, the payment receipt (status, scheme, authorized and settled amounts, payer, payTo, transaction, explorerUrl, verifiedOnChain), the seller’s response and the budget after the call. pay_and_call pays upto resources too. Their price is a cap: spendingCap must cover it, the budget reserves it when the authorization is signed, and the receipt’s settled amount is what the seller charged (from zero up to the cap). The rest of the cap returns to the budget once the chain proves the settlement, before the tool answers. get_budget shows the schemes in force, the pinned uptoContracts and the allowed uptoFacilitators.

Errors

A refusal is a tool result with isError: true and structuredContent: { ok: false, error: { code, reason, retryable, paid, details? } }. paid tells the agent whether money may have moved (none, not_settled, settled, unknown). The tool descriptions tell the agent to retry anything but none only with the same idempotencyKey. Such a retry never pays twice:
  • It first checks the earlier call’s own payment on chain.
  • While that payment’s outcome is open, it returns the stored outcome.
  • Once the chain shows the payment was consumed, it reports it as paid (paid: "settled", with the receipt).
  • Once the chain shows the authorization expired unused, it pays again.
get_budget with reconcile: true settles or releases open payments from on-chain proof in the same way before it shows the budget. It never pays. Arguments that do not match the tool’s schema are tool_input_invalid, never a protocol error. A request that never reaches a tool is a JSON-RPC error whose data carries { code, reason } like a tool’s error: tool_unknown (-32602) for an unknown tool, rpc_params_invalid (-32602) for a tools/call without a tool name or with arguments that are not an object, and rpc_method_unknown (-32601) for a method the server does not serve. Every code is in the error-code reference.

Use the tools in your own code

@rail402.dev/mcp/tools exports the three tools without the server around them: createTools({ buyer }) returns their definitions (definitions, with input and output schemas) and call(name, args), which returns the same structuredContent and errors the server returns. Any buyer works, including one from @rail402.dev/sdk/browser; nothing in this entry imports a Node module. The Playground’s agent scenario calls these tools in the browser.
The ./tools entry is in @rail402.dev/mcp from version 0.4.0 on.

Limits

  • One paid call runs at a time per server (RAIL402_MAX_INFLIGHT, 1-8); up to 4 more wait, and further calls are refused with tool_rate_limited.
  • Per minute: 30 pay_and_call, 120 search_stellar_resources, 120 get_budget.
  • Request bodies and tool arguments sent to sellers are at most 64 KiB; seller responses at most 1 MiB, of which 64 KiB are returned inline.

Configuration

Invalid or unsafe configuration stops the server at startup with one JSON line on stderr carrying a code (for example payment_policy_invalid with details.setting) and exit status 1. On stellar:pubnet the server needs RAIL402_RPC_URL, RAIL402_BAZAAR_URL and, with a key, both caps; see choosing the network.

HTTP transport

rail402-mcp --http [--host 127.0.0.1] [--port 3402] serves Streamable HTTP at /mcp for a single operator. It listens on a loopback address only, checks the Host header (DNS rebinding) and browser Origins (RAIL402_MCP_ALLOWED_ORIGINS, localhost by default), and requires Authorization: Bearer <token> whenever a key is configured. The token comes from RAIL402_MCP_TOKEN, must be at least 43 characters of random data (for example openssl rand -base64 32), is compared in constant time and is removed from the environment once read. Request bodies are limited to 1 MiB.