---
name: mpprouter-discover
description: Discover paid API services available via MPP Router (apiserver.mpprouter.dev) that accept Stellar USDC payments. Triggers on prompts like "summarize X using parallel.ai via MPPRouter", "search with exa via mpp", "find a service for <task>", "list mpp services", "what APIs can I pay for with stellar". Fetches the live service catalog, picks a matching service, and hands off to the pay-per-call sub-skill to actually invoke it.
---

# discover-mpprouter

High-level discovery for the MPP Router service catalog. Lets agents find and call paid APIs on Stellar mainnet without hardcoding URLs.

## What is MPP Router?

[MPP Router](https://www.mpprouter.dev) 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.

## When to trigger

- "Summarize https://stripe.com/docs using parallel.ai search via MPPRouter"
- "Search for X via exa through mpp"
- "Scrape this page with firecrawl — use stellar to pay"
- "List available MPP services"
- "What paid APIs can I call with stellar USDC?"
- User mentions `mpprouter` or `mpp router` by name

## Flow

1. **Discover** — `GET https://apiserver.mpprouter.dev/v1/services/catalog` returns the live service list.
2. **Match** — pick the service whose `id` or `category` best fits the user's intent. Ask the user if ambiguous.
3. **Read docs** — check the matched service's `docs` field:
   - If `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.**
   - If only `docs.api_reference` → read that instead.
   - If only `docs.homepage` → browse it for the API format.
   - If no docs at all → ask the user for the request body format, or try a minimal probe.
4. **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=...`.
5. **Pay** — hand off to `pay-per-call` with the 402 challenge; it produces a signed credential.
6. **Retry** — re-POST the same body with `Authorization: Payment <credential>`. Receive the upstream response + `Payment-Receipt` header.

## Service catalog

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.

### Payment-mode labels (IMPORTANT for the LLM)

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`](https://github.com/mpprouter/rozo-mpprouter/blob/main/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.

### Decision rule for the LLM

When the user asks to call a paid API via MPP Router:

1. Fetch the catalog fresh.
2. Find the best service match.
3. 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").

## Service record shape

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-field | What it is | How the agent should use it |
|---|---|---|
| `llms_txt` | LLM-readable docs (69/88 providers) | **Read this first.** Fetch it and parse the API schema to construct the correct request body. |
| `api_reference` | Full API reference (8/88 providers) | Detailed endpoint docs — use when `llms_txt` is missing or insufficient. |
| `homepage` | Docs 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.

## How to run

```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
```

## Full end-to-end example

```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"}
```

## Anti-patterns

- ❌ Don't hardcode service paths — the catalog is the source of truth.
- ❌ Don't pay twice for one call — credentials are single-use and HMAC-bound to amount/currency/recipient.
- ❌ Don't use testnet — MPP Router is mainnet-only.
- ❌ Don't cache the catalog for more than a minute — prices and service availability can change.
