High-level discovery for the MPP Router service catalog. Lets agents find and call paid APIs on Stellar mainnet without hardcoding URLs.
MPP Router is a Stellar-native 402 proxy. It takes USDC payments on stellar:pubnet and forwards your request to upstream merchant APIs — Parallel.ai, Exa, Firecrawl, OpenRouter, and more. You pay once in Stellar USDC and get back whatever the upstream merchant returns. No need to hold USDC on Base/Tempo.
Mainnet only. The router does not run on testnet.
mpprouter or mpp router by nameDiscover — GET https://apiserver.mpprouter.dev/v1/services/catalog returns the live service list.
Match — pick the service whose id or category best fits the user's intent. Ask the user if ambiguous.
Read docs — check the matched service's docs field:
docs.llms_txt exists → fetch it and read the API schema to build the correct request body. The router forwards bodies as-is — it does not transform or validate them.docs.api_reference → read that instead.docs.homepage → browse it for the API format.Invoke — {method} {base_url}{public_path} with the correct request body. Use the method field from the catalog entry — most services are POST, but a few are GET; do not assume. Expect 402 Payment Required with a Stellar challenge in WWW-Authenticate: Payment request=....
Pay — hand off to pay-per-call with the 402 challenge; it produces a signed credential.
Retry — re-POST the same body with Authorization: Payment <credential>. Receive the upstream response + Payment-Receipt header.
The catalog is live and large (~500 services). Always call
/v1/services/catalog fresh — never hardcode a list, never cache for
more than a minute.
The catalog exposes a verified_mode field per service. The discover
skill reads it and tags each record with a payment_mode string:
charge ( ✓ verified charge) — end-to-end verified to work with
this skill's Stellar charge flow. Safe to call. Known-good as of
2026-08-10 (12): anthropic_chat_completions, coingecko_simple_price,
deepgram_deepgram_list-models, deepseek_chat, exa_search,
firecrawl_scrape, grok_grok_chat, groq_chat, mistral_mistral_chat,
parallel_search, perplexity_perplexity_chat, storage_upload
(plus tavily_tavily_search, first paid-verified 2026-08-10).
Every service on this list was verified with a real paid call through
the production Router. For the live view, read the catalog's
charge_rozo_verified / charge_rozo_verified_at fields — newly
verified services appear there in real time — and the tx-hash audit
trail of verification payments is published in
rozo-mpprouter/docs/verified-runs.json.session ( ⚠ session-only) — upstream merchant is session-mode
only. The router now correctly advertises stellar.intents: ["channel"]
(no charge) for these services. To use them, the agent must have a
registered Stellar channel contract — see the stellar-agent-wallet
root SKILL.md "Out of scope" section. Affected:
anthropic_messages, openai_chat, openrouter_chat, tempo_rpc.
Charge-mode clients cannot call these services.
( gemini_generate is currently payment_status: unavailable — the
upstream merchant's Google API key is invalid; do not offer it.)unverified ( · unverified) — the router hasn't labeled
verified_mode (or labeled it as the literal string "false", which
is a router-side catalog-generator bug). ~97% of the catalog is in
this bucket. Most of these work but we can't prove it without a paid
probe. Treat as cautiously optimistic: acceptable to call on user
request, but warn the user that it's not formally verified.When the user asks to call a paid API via MPP Router:
Fetch the catalog fresh.
Find the best service match.
Check its payment_mode:
charge → proceed to pay-per-call. This is still a real-money
mainnet payment: pay-per-call's own confirmation gate applies (it
prompts before every mainnet payment unless the user has set a
session --max-auto ceiling). "Verified" describes the service
record, not permission to spend without the user seeing it.session → do not call it by default. Tell the user: "This
service is currently in session-only mode on MPP Router. Paying via
Stellar charge mode would succeed but the upstream would reject the
receipt, so you'd lose the fee with no result. Waiting on a
router-side fix. Alternative service?" The ONE exception: the user,
after reading exactly that warning, explicitly says to proceed
anyway and accepts the fee is likely lost — then proceed. There is
no other path to calling a session service.unverified → proceed (through the same confirmation gate) but
surface a brief caveat in your response ("this service isn't
formally verified for Stellar charge mode; proceeding anyway — let
me know if the call fails after payment").Discover enriches each record with a payment_mode field (one of
charge, session, unverified) derived from verified_mode. This
is the field the LLM should branch on — it normalizes the router's
raw verified_mode string (which can be missing or literally
"false") into a fixed vocabulary.
json
{
"id": "exa_search",
"name": "Exa – Search the web",
"category": "search",
"description": "Search the web",
"public_path": "/v1/services/exa/search",
"method": "POST",
"price": "$0.005/request",
"payment_method": "stellar",
"network": "stellar-mainnet",
"asset": "USDC",
"status": "active",
"methods": { "stellar": { "intents": ["charge", "channel"] } },
"docs": {
"homepage": "https://docs.exa.ai",
"llms_txt": "https://docs.exa.ai/llms.txt"
},
"verified_mode": "charge",
"payment_mode": "charge"
}
docs field — upstream API documentation#The router forwards request bodies as-is without transformation. Clients
must know each upstream API's request format. The docs field provides
the upstream's own documentation:
Sub-fieldWhat it isHow the agent should use itllms_txtLLM-readable docs (69/88 providers)Read this first. Fetch it and parse the API schema to construct the correct request body.api_referenceFull API reference (8/88 providers)Detailed endpoint docs — use when llms_txt is missing or insufficient.homepageDocs homepage (82/88 providers)Fallback — browse or scrape if neither of the above is available.
Decision rule: if docs.llms_txt exists, fetch and read it before
calling the service. If not, check docs.api_reference. If neither
exists, warn the user that you may need them to supply the request body
format, or try docs.homepage.
19 providers have no llms_txt — these include Google Gemini, Google
Maps, Nansen, Parallel, Allium, AgentMail, and several Tempo-proxied
third-party APIs.
bash
# List all services
./node_modules/.bin/tsx skills/discover/run.ts
# Filter by category
./node_modules/.bin/tsx skills/discover/run.ts --category search
# Match a free-text intent (uses simple keyword match)
./node_modules/.bin/tsx skills/discover/run.ts --query "web search"
# JSON output for piping
./node_modules/.bin/tsx skills/discover/run.ts --json
bash
# Discover which service to use — capture path, method, and the
# catalog-advertised payment expectations so pay-per-call can refuse
# a 402 that tries to redirect funds or inflate the price.
SERVICE_JSON=$(./node_modules/.bin/tsx skills/discover/run.ts --query "web search" --pick-one --json)
SERVICE=$(echo "$SERVICE_JSON" | jq -r '.public_path')
METHOD=$(echo "$SERVICE_JSON" | jq -r '.method')
EXPECT_AMT=$(echo "$SERVICE_JSON" | jq -r '.expect.amount_usdc // empty')
EXPECT_TO=$(echo "$SERVICE_JSON" | jq -r '.expect.pay_to // empty')
# Call it — pay-per-call handles the 402 → pay → retry loop.
# The --expect-* flags cross-check the 402 challenge against the
# catalog. Any drift aborts before signing.
./node_modules/.bin/tsx skills/pay-per-call/run.ts "https://apiserver.mpprouter.dev$SERVICE" \
--method "$METHOD" \
--body '{"query": "Summarize https://stripe.com/docs"}' \
${EXPECT_AMT:+--expect-amount "$EXPECT_AMT"} \
${EXPECT_TO:+--expect-pay-to "$EXPECT_TO"}