# MCP Server Registry

The MCP Gateway serves a read-only
[MCP Server Registry API](https://github.com/modelcontextprotocol/registry)
(v0.1 freeze) that advertises every MCP route on the gateway. MCP clients and
mirroring subregistries can discover the gateway's registered servers without
authentication, per the registry specification.

The registry is enabled by default and mounted at `/__zuplo/mcp-registry` (with
the default `basePath`). Configure or disable it through the
[`registry` option](../programmable-api/mcp-gateway-plugin.mdx#registry) on
`McpGatewayPlugin`.

## What gets listed

The registry projects one entry per MCP-serving route — both:

- **Gateway virtual servers** — routes that carry MCP gateway policies
  (`mcp-oauth-inbound` / `mcp-token-exchange-inbound`) and use
  [`McpProxyHandler`](./code-config/mcp-proxy-handler.mdx) to proxy to an
  upstream MCP server.
- **Standalone MCP servers** — routes that use the built-in `mcpServerHandler`
  or a bare `McpProxyHandler` without gateway policies.

Each entry includes:

| Field         | Source                                                                                                            |
| ------------- | ----------------------------------------------------------------------------------------------------------------- |
| `name`        | `<reverse-DNS of request host>/<operationId>` (for example, `com.example.gateway/linear-mcp-server`)              |
| `remotes`     | The gateway route URL, using the `streamable-http` transport.                                                     |
| `description` | OpenAPI operation `description` or `summary`, clamped to 100 code points per the spec.                            |
| `title`       | `mcpServerHandler` options or the OpenAPI operation `summary`.                                                    |
| `version`     | `mcpServerHandler` options or upstream connection config. Range-like values (`^1.2.3`, `1.x`) project as `0.0.0`. |
| `websiteUrl`  | Upstream connection config, canonicalized to a valid URI.                                                         |
| `icons`       | Upstream connection config. Only HTTPS URLs are included; `src` is clamped to 255 characters.                     |

:::note

Server names are derived from the request host — the same posture as the OAuth
issuer and Protected Resource Metadata. Names vary with the hostname the gateway
is reached on, so a custom domain produces different names than the default
`*.zuplo.dev` origin.

:::

The projection never throws. Every spec constraint is enforced by sanitizing or
omitting: names longer than 200 characters are truncated with a hash suffix,
descriptions are clamped, non-HTTPS icons are dropped, and version ranges are
replaced with `0.0.0`. One malformed route can't take down discovery for the
rest.

Parameterized routes (for example, `/mcp/{server}`) are omitted from the
directory — a templated path is not a concretely connectable remote.

## Endpoints

All paths are relative to the registry mount (default: `/__zuplo/mcp-registry`).
Discovery GETs are unauthenticated and CORS-permissive
(`Access-Control-Allow-Origin: *`).

### `GET {path}/v0.1/servers`

Lists all MCP servers registered on the gateway.

| Query parameter | Type    | Default | Description                                                                                     |
| --------------- | ------- | ------- | ----------------------------------------------------------------------------------------------- |
| `cursor`        | string  | —       | Opaque pagination cursor from a previous response.                                              |
| `limit`         | integer | `30`    | Maximum servers to return. Clamped to `100`.                                                    |
| `search`        | string  | —       | Case-insensitive substring filter on server name.                                               |
| `version`       | string  | —       | Filter to servers matching a specific version.                                                  |
| `updated_since` | string  | —       | RFC 3339 date-time. Validated but never filters — every entry conservatively counts as changed. |

Response:

```json
{
  "servers": [
    {
      "name": "com.example.gateway/linear-mcp-server",
      "description": "Linear MCP proxy",
      "title": "Linear",
      "version": "1.0.0",
      "remotes": [
        {
          "type": "streamable-http",
          "url": "https://gateway.example.com/mcp/linear-v1"
        }
      ],
      "websiteUrl": "https://linear.app",
      "icons": []
    }
  ],
  "metadata": { "count": 1 }
}
```

When `cursor` is present in the response, more results are available. Pass it
back as the `cursor` query parameter on the next request.

### `GET {path}/v0.1/servers/{serverName}/versions`

Lists versions for a single server. With the current read-only projection, every
server has exactly one version entry.

`{serverName}` is URL-encoded — the slash in the reverse-DNS name is sent as
`%2F` (for example, `com.example.gateway%2Flinear-mcp-server`).

### `GET {path}/v0.1/servers/{serverName}/versions/{version}`

Returns a single server version. The special alias `latest` resolves to the
server's current version.

### Write endpoints (refused)

The registry is read-only. Write endpoints are refused per the spec:

| Endpoint                                                     | Method   | Status | Reason                                                                                       |
| ------------------------------------------------------------ | -------- | ------ | -------------------------------------------------------------------------------------------- |
| `{path}/v0.1/publish`                                        | `POST`   | `501`  | Spec marks publishing optional and documents `501`.                                          |
| `{path}/v0.1/servers/{serverName}/versions/{version}`        | `PUT`    | `501`  | Spec marks version update optional.                                                          |
| `{path}/v0.1/servers/{serverName}/versions/{version}`        | `DELETE` | `501`  | Spec marks version deletion optional.                                                        |
| `{path}/v0.1/servers/{serverName}/versions/{version}/status` | `PATCH`  | `403`  | Spec defines no `501` for status operations; returns `403` with a permissions error message. |
| `{path}/v0.1/servers/{serverName}/status`                    | `PATCH`  | `403`  | Same as above.                                                                               |

All error bodies use the spec's `{"error": "..."}` shape — not RFC 7807 problem
details — so off-the-shelf registry clients can parse them. A catch-all under
the mount ensures even unrecognized requests get the spec-shaped `404`.

## Configuration

The registry is configured through the `McpGatewayPlugin` constructor. See the
[`registry` option](../programmable-api/mcp-gateway-plugin.mdx#registry) for the
full options reference.

```ts title="modules/zuplo.runtime.ts"
import { RuntimeExtensions } from "@zuplo/runtime";
import { McpGatewayPlugin } from "@zuplo/runtime/mcp-gateway";

export function runtimeInit(runtime: RuntimeExtensions) {
  runtime.addPlugin(
    new McpGatewayPlugin({
      registry: {
        enabled: true, // default
        path: "/mcp-registry", // default: "{basePath}/mcp-registry"
      },
    }),
  );
}
```

## Limitations

- **No publishing.** The registry is a read-only projection of the gateway's
  live route configuration. There is no write path, no namespace-ownership
  model, and no stored `publishedAt` or `updatedAt` timestamps.
- **`updated_since` validates but never filters.** Because the registry is
  derived from route config with no stored timestamps, every entry
  conservatively counts as changed. This keeps mirroring subregistries correct
  at the cost of re-reading everything.
- **No deployment-pinned server identity.** Server names are derived from the
  request host, not from a stable deployment-level identifier.

## Related

- [`McpGatewayPlugin` reference](../programmable-api/mcp-gateway-plugin.mdx) —
  the full plugin options, including `basePath` and `registry`.
- [Gateway reference](./reference.mdx) — the full URL catalog, including the
  registry mount.
- [How the MCP Gateway works](./how-it-works.mdx) — architecture and request
  lifecycle.
- [MCP Server Registry specification](https://github.com/modelcontextprotocol/registry)
  — the v0.1 freeze the gateway implements.
