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.
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: onstellar:testnet 0.10 USDC per call and 1.00 USDC per 24-hour window by default, or tighter values you
set.
~/.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 withisError: 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.
./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 withtool_rate_limited. - Per minute: 30
pay_and_call, 120search_stellar_resources, 120get_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.