Local chain
Paid MCP tool
A paid resource reachable only as an MCP tool — no HTTP route at all.
A paid resource exposed only as an MCP tool. With protocols.http.enabled: false there is no invoke route at all — the shape for “only an MCP-speaking agent should be able to call this”.
version: 1
merchant:
id: paid-mcp-tool-example
name: Paid MCP Tool Example
publicBaseUrl: ${GATEWAY_PUBLIC_BASE_URL:-http://localhost:8080}
server:
port: ${GATEWAY_PORT:-8080}
host: 0.0.0.0
storage:
receipts:
driver: sqlite
path: ${RECEIPT_STORE_PATH:-./data/receipts.sqlite}
protocols:
# HTTP resource routes are off entirely: this resource is reachable only
# as an MCP tool call, never as `POST /api/resources/:id/invoke`.
http:
enabled: false
mcp:
enabled: true
mountPath: /mcp
resources:
market_report_tool:
name: Get market report
description: Latest premium market analysis, callable only as an MCP tool. 0.01 USDC per call.
input:
type: object
properties: {}
additionalProperties: false
backend:
type: http
method: GET
url: ${MERCHANT_API_BASE_URL:-http://localhost:3000}/api/report
timeoutMs: 10000
pricing:
type: fixed
amount: "0.01"
currency: USDC
expose: [mcp]
payments: [x402]
payments:
x402:
enabled: true
network: ${X402_NETWORK:-eip155:84532}
rpcUrl: ${X402_RPC_URL:-http://127.0.0.1:8545}
# Placeholder — replace with the deployed MockUSDC address from
# `.deploy/local.json` after `npm run chain:deploy`.
asset: ${X402_ASSET:-0x000000000000000000000000000000000000dEaD}
assetName: ${X402_ASSET_NAME:-MockUSDC}
assetVersion: ${X402_ASSET_VERSION:-2}
assetDecimals: ${X402_ASSET_DECIMALS:-6}
# Merchant-controlled settlement destination. NEVER a gateway-owned wallet.
# LOCAL DEVELOPMENT ONLY - DO NOT FUND (Anvil's well-known account #1).
payTo: ${MERCHANT_WALLET:-0x70997970C51812dc3A010C7d01b50e0d17dc79C8}
maxTimeoutSeconds: 120
facilitator:
mode: local
# LOCAL DEVELOPMENT ONLY - DO NOT FUND (Anvil's well-known account #0).
signerPrivateKey: ${X402_FACILITATOR_PRIVATE_KEY:-0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d}What it demonstrates
- HTTP resource routes are off entirely, not merely unused.
expose: [http]here would fail validation withCONFIG_INVALID(“exposed via 'http' but protocols.http.enabled is false”).- The id doubles as the tool name, so it is limited to
A-Z a-z 0-9 . _ -.
Run it
From a clone of the repository:
- Local chain and mock USDC
bash npm run chain:start npm run chain:deploy - The “existing backend” this example fronts
bash npm run dev:merchant - The gateway, with this example's config
bash AGENT_COMMERCE_CONFIG=examples/paid-mcp-tool/config.yaml \ X402_ASSET=$(node -p "require('./.deploy/local.json').asset") \ npx tsx src/gateway/main.ts - Verify
bash npm run agent-commerce -- validate --config examples/paid-mcp-tool/config.yaml npm run agent-commerce -- doctor --config examples/paid-mcp-tool/config.yaml - Call it
bash curl -s http://localhost:8080/mcp \ -H 'content-type: application/json' \ -H 'accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' # -> "market_report_tool" appears; there is no HTTP resource route
The config validates standalone with no environment set: every
${VAR:-default} falls back to a value that works against the local demo stack.Source: examples/paid-mcp-tool