# Publish MCP server

> The remote MCP server at /mcp — connection, OAuth, and the six publishing tools.

Source: https://route402.dev/docs/publish-mcp

---

A remote MCP server that lets an agent publish an API end to end. No `npx`, no
local install, no config file editing.

## Connect

```
https://route402.dev/mcp
```

Paste that into any MCP client that supports remote servers. The client will:

1. `POST /mcp` without a token and receive `401` with
   `WWW-Authenticate: Bearer resource_metadata="…"`.
2. Fetch `/.well-known/oauth-protected-resource` (RFC 9728) to find the
   authorization server.
3. Fetch `/.well-known/oauth-authorization-server` (RFC 8414) for the endpoints.
4. Register itself at `/register` (RFC 7591) — no manually provisioned
   `client_id` needed.
5. Send you to a consent screen, then exchange the code with PKCE.

Copy-pasteable client config:

```json
{
  "mcpServers": {
    "route402": {
      "type": "http",
      "url": "https://route402.dev/mcp"
    }
  }
}
```

### Transport

Streamable HTTP. `POST /mcp` carries JSON-RPC and answers with
`application/json`; `GET /mcp` with `Accept: text/event-stream` opens the
server stream; `DELETE /mcp` ends the session. Protocol versions `2025-06-18`,
`2025-03-26` and `2024-11-05` are accepted.

### Scopes

| Scope | What it grants |
|---|---|
| `registry:read` | Browse public listings. The directory is public; nothing needs authorizing to read it. |
| `services:write` | Create services, store credentials, and publish under your account. |

Tokens are **audience-bound** to `https://route402.dev/mcp`. A token minted for
the wallet is rejected here, and vice versa. This server will never issue
`wallet:spend` — payment authority lives on a different resource server and is
consented separately.

Access tokens last one hour. Refresh tokens rotate on every use.

## Tools

Every tool returns JSON, never prose.

### `create_service`

Registers an upstream API. Requires `name`, `description` (≥ 20 characters) and
`upstream_base_url`. Optional: `tags`, `pay_to`, `network`, `icon_url`.

Returns `service_id`, `slug`, and the gateway base URL. The upstream must
resolve to a public address — private and reserved ranges are refused.

### `set_upstream_auth`

Stores the credential the gateway injects when calling your upstream. Requires
`service_id`, `placement` (`header`, `query`, or `bearer`) and `value`;
`param_name` is required unless placement is `bearer`.

**Write-only.** No tool returns this value, the web console cannot display it,
and there is no read API. The response carries a keyed `digest` — useful only
for answering "did this change?". Call the tool again to rotate.

### `add_endpoint`

Defines one path and its price. Requires `service_id`, `method`, `path`,
`description` (≥ 10 characters) and `price_usd`. Optional: `input_schema`,
`example_input`, `output_schema`, `output_example`.

Calling it again for the same method and path updates the definition **and
clears its test result** — the contract changed, so the old proof no longer
applies.

### `test_endpoint`

Calls the upstream once, exactly as the gateway will, with the stored
credential injected. Returns `status_code`, `latency_ms`, `content_type`, a
`body_preview`, and a `hint` when it fails.

Requires `service_id` and `endpoint_id`. `query_params` overrides the
endpoint's `example_input` for this call.

### `publish_service`

Registers every endpoint into the public Bazaar registry.

**Fails unless `test_endpoint` has passed for every endpoint.** A listing that
500s on its first call poisons the directory for everyone. When it cannot
publish, it returns `blockers` — a list of `{code, message}` — so the whole set
can be fixed in one turn instead of one at a time.

When the registry rejects a listing, `rejected_reason` carries its reason
verbatim.

**Publishing is all-or-nothing.** The service goes live only once every
endpoint is in the registry. If the registry rejects a listing or cannot be
reached, nothing is published — a service is never live but undiscoverable, and
an already-published service is not taken down because the registry hiccuped.
`publish_service` is idempotent, so retrying it is the whole recovery
procedure.

Prices are resolved here, not downstream. `price_usd` is converted to atomic
units of the network's settlement asset before it reaches the registry or the
gateway, which do no currency resolution of their own. A price with more
decimal places than the asset can express is refused rather than rounded, and a
network with no configured settlement asset is a blocker — a price nobody can
pay is not a listing.

### `get_service_status`

Health, listing state, endpoint test results, 30-day analytics, and any
outstanding blockers. Every endpoint carries its own `registry_status`
(`listed`, `rejected`, `unreachable`, `not_registered`) and `registry_reason`,
because the registry accepts or rejects each resource independently. Omit
`service_id` to list every service on the account.

## The five-turn flow

```
create_service      → svc_8f31 · acme-weather
set_upstream_auth   → stored, write-only
add_endpoint        → GET /current · $0.001
test_endpoint       → 200 in 198 ms ✓
publish_service     → bazaar.status = success
```

## Errors

Tool failures come back as a structured result with `isError: true` and a body
of `{ok: false, error, message, …}`. Common codes:

| Code | Meaning |
|---|---|
| `insufficient_scope` | The token lacks `services:write`. |
| `upstream_not_allowed` | The URL is malformed, or resolves to a private range. |
| `invalid_arguments` | Schema validation failed; the message names the field. |
| `not_publishable` | See `blockers` for the full list. |
| `temporarily_unavailable` | The platform database is unreachable. Nothing changed. |
