# MCP Servers API

> Browse the public MCP connector directory, register workspace-owned MCP servers, and call tools on connected servers.

**URL:** https://inference.sh/docs/api/rest/mcp-servers
**Last updated:** 2026-10-31

---

Browse the public MCP connector directory, register workspace-owned MCP servers, and call tools on connected servers.

Three surfaces work together:

| Surface | Endpoints | Purpose |
|---------|-----------|---------|
| **Public directory** | `/mcps`, `/mcps/{slug}` | Discover featured connectors (Linear, Slack, GitHub, …) |
| **Workspace registrations** | `/mcp-servers` | Register custom MCP server URLs for your workspace |
| **Tool proxy** | `/mcps/{slug}/tools` | List and invoke tools after OAuth or API-key connect |

Connecting (OAuth, API keys) uses `POST /credentials` with `provider: "mcp"` (requires `credentials:write`). This page covers directory browsing, workspace CRUD, discovery, and tool calls.

See [REST overview — response format](/docs/api/rest/overview#response-format).

→ [Browsing & connecting](/docs/connectors/browsing): CLI and web app workflows  
→ [Using connector tools](/docs/connectors/using-tools) — `belt mcp tools` and agent tool setup

---

## Public directory

No API key required.

### List MCP servers

`GET /mcps` or `POST /mcps/list`

Returns approved store listings sorted by rank. Accepts standard cursor-list parameters (`cursor`, `limit`, `filters`, `sort`, `search`) on POST.

**Response** (`PublicMCPServerDTO`):

| Field | Description |
|-------|-------------|
| `id` | Server ID |
| `slug` | Short identifier (used in URLs and `belt mcp connect {slug}`) |
| `name` | Stable identifier (often matches `slug`) |
| `title` | Display name; falls back to `name` when empty |
| `description` | Short summary |
| `icon_url` | Server icon |
| `category` | `developer`, `productivity`, `data`, `communication`, `ai`, or `other` |
| `server_url` | MCP endpoint URL |
| `auth_type` | `oauth`, `api_key`, or `none` |
| `default_scopes` | OAuth scopes requested on connect |
| `documentation_url` | Vendor docs link |
| `namespace` | Publisher namespace |
| `is_featured`, `rank` | Store placement |
| `installs`, `uses` | Usage counters |
| `connection_status` | `connected` when you or your workspace have a connected MCP credential for this `server_url`. Omitted otherwise. The match compares `server_url` with the credential's `account_identifier`. |

Send an API key or session on list and get calls to receive `connection_status`. Unauthenticated directory requests omit it.

```bash
curl "https://api.inference.sh/mcps?limit=20"
```

### Get by slug

`GET /mcps/{slug}`

Returns one public listing. Responds `404` when the slug is not in the directory.

```bash
curl https://api.inference.sh/mcps/linear
```

---

## Discover server metadata

`GET /mcps/discover?url={url}`

Proxy for MCP remote discovery. Use this from custom UIs when browser CORS blocks direct fetches to `/.well-known/mcp-server-card` or OAuth metadata endpoints.

No API key required. The API tries, in order:

1. `GET {origin}/.well-known/mcp-server-card` (MCP server card)
2. OAuth protected-resource metadata on the URL

Returns the JSON document with `Cache-Control: public, max-age=300`. Responds `404` when neither document is found.

| Query | Required | Description |
|-------|----------|-------------|
| `url` | Yes | `http` or `https` URL (typically a bare origin like `https://mcp.example.com`) |

```bash
curl "https://api.inference.sh/mcps/discover?url=https://mcp.example.com"
```

The same resolution runs automatically when you connect via CLI, the web app, or `POST /credentials` with a bare `server_url` origin.

---

## Workspace MCP server registrations

Manage servers your workspace publishes or connects privately. Requires API key scopes `credentials:read` (list, get) and `credentials:write` (create, update, delete).

### List workspace servers

`GET /mcp-servers` or `POST /mcp-servers/list`

Cursor-paginated list of MCP servers owned by the authenticated workspace. Same pagination shape as [Skills](/docs/api/rest/skills#list-skills). Returns `MCPServerDTO` objects (see below).

### Get workspace server

`GET /mcp-servers/{id}`

Returns `MCPServerDTO` for one registration.

### `MCPServerDTO` fields

| Field | Description |
|-------|-------------|
| `id` | Server ID |
| `user_id`, `team_id`, `visibility` | Ownership and sharing |
| `slug` | URL-safe identifier |
| `name` | Stable identifier (often matches `slug`) |
| `title` | Display name; falls back to `name` when empty |
| `description`, `icon_url` | Listing metadata |
| `server_url` | MCP endpoint URL |
| `auth_type` | `oauth`, `api_key`, or `none` |
| `oauth_client_id` | BYOK OAuth client ID when set |
| `default_scopes` | OAuth scopes to request on connect |
| `headers` | Static HTTP headers your workspace admins set on this registration. The platform sends them on every MCP request for this `server_url` (for example Azure DevOps `X-MCP-Toolsets`, `X-MCP-Readonly`). Non-secret only — never put tokens here. |
| `setup` | Optional `MCPServerSetup` hints from the public directory for this server's host (see below). Omitted when the directory has no setup row for the host. |
| `documentation_url` | Vendor docs link |
| `connection_status` | `connected` when you or your workspace have a connected MCP credential for this `server_url`. Omitted otherwise. The match compares `server_url` with the credential's `account_identifier`. |
| `connection_scope` | When `connection_status` is `connected`, who owns that login: `user`, `team`, `org`, or `platform`. Omitted when not connected. |
| `credentials` | The credential the server declares it needs, same shape as an app's `credentials:` (`[{slug, scopes, capabilities, …}]`, written as a map keyed by slug or a list). Each `slug` must name an [auth provider](/docs/api/rest/auth-providers) your workspace can connect: a catalog slug, your team's own scheme, or an MCP server's provider such as `<server-slug>-mcp`. When set, `connection_status` means the caller has a connected login for that slug, and tool calls send that login's token (or API key, per the slug's [auth method](/docs/api/rest/auth-providers#auth-method)) instead of a server-specific MCP login. Empty for servers that still sign in the MCP-specific way. |
| `credential_slug` | The first declared slug, for linking to the vault entry (`/credentials/{slug}`). Empty when the server declares nothing. |

List and get responses set `connection_status` and `connection_scope` from your MCP credentials, so a connect/disconnect UI does not need to merge `GET /credentials/configs`.

#### `setup` (`MCPServerSetup`)

Directory maintainers store setup metadata per MCP host (not per workspace registration). When you list or get a workspace server whose `server_url` shares a host with a directory entry, the API attaches that host's `setup` object so connect UIs can show vendor-specific guidance without hard-coding servers in the app.

| Field | Description |
|-------|-------------|
| `resource_app_id` | Application ID at the server's authorization server. Organizations need this before they can approve access (for Microsoft Entra: the application ID a tenant uses when creating a service principal). |
| `recommended_headers` | Least-privilege static headers the vendor documents (same validation rules as workspace `headers`). The web app offers these as presets in the headers editor; [`GET /credentials/mcp/app`](/docs/api/rest/credentials#get-mcp-oauth-app-status) also returns `recommended_headers` for MCP connect flows. |

Python types: `MCPServerSetup` on `MCPServerDTO` in `inferencesh.types` (**inferencesh ≥ v0.14.3**).

### Create workspace server

`POST /mcp-servers`

Register a custom MCP server URL. The API enriches missing fields from the remote server card and OAuth discovery metadata, then declares the server's sign-in the way connect does: it creates or reuses the auth provider for the server's URL and sets `credentials` from what discovery found. A body that already sends `credentials` keeps them. A server that cannot be reached saves with no provider; set `credentials` later with update or [refresh](#refresh-workspace-server-metadata).

**Body** (common fields):

| Field | Required | Description |
|-------|----------|-------------|
| `server_url` | Yes | MCP endpoint or bare origin (resolved via discovery when needed) |
| `name` | No | Stable name (defaults from server card or hostname) |
| `title` | No | Display title |
| `slug` | No | URL-safe identifier (auto-generated from `name` when omitted) |
| `description` | No | Short summary (max [200 characters](/docs/api/rest/overview#description-length-limits); a description discovered from the server is clipped to the limit when the server is registered) |
| `auth_type` | No | `oauth` (default), `api_key`, or `none` |
| `oauth_client_id` | No | OAuth client ID when you bring your own client |
| `default_scopes` | No | OAuth scopes to request |
| `headers` | No | Static, non-secret HTTP headers on every MCP request (for example `X-MCP-Toolsets`, `X-MCP-Readonly`). At most 16 headers; values up to 1024 characters. Cannot override auth or MCP transport headers (`Authorization`, `Mcp-*`, and similar). |
| `credentials` | No | The credential the server needs, as a map keyed by slug (`{"aikido-mcp": {"scopes": ["issues:read"]}}`) or a list of entries. Usually omitted on create, since discovery declares it. Validated like an app's `credentials:`, and each slug must name a provider your workspace can connect (see `credentials` on `MCPServerDTO` above). On update, a present value replaces the declaration and `[]` clears it. |
| `documentation_url` | No | Link to vendor docs |
| `visibility` | No | `team` (default) or `public` — `team` keeps the registration in your workspace; `public` lists it in the MCP directory |

**Response**: `MCPServerCreateResponse`:

| Field | Description |
|-------|-------------|
| `id`, `slug`, `name`, … | Created `MCPServerDTO` |
| `discovery` | Optional hints for the connect UI (`ready_to_connect`, `needs_credentials`, `cross_domain`, `cross_domain_issuer`) |

**Errors:**

| HTTP | Code | Cause |
|------|------|-------|
| `409` | `already_exists` | Duplicate `slug` within the workspace |
| `400` | `validation_error` | Invalid field values (`credentials`, `headers`, `slug`, …); `detail` names the field |
| `400` | `invalid_request` | A `credentials` slug names no provider; `detail` names it and adds `leave the credential slug empty to set up sign-in from the server` |
| `400` | `create_failed` | Any other discovery or input failure (for example a missing `server_url`), with the error text in `detail` |
| `402` / `403` | entitlement or refusal | Connector limit reached on your plan, a missing scope, or no `manage_vault` |

```bash
curl -X POST https://api.inference.sh/mcp-servers \
  -H "Authorization: Bearer inf_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "server_url": "https://mcp.example.com",
    "name": "Example MCP",
    "visibility": "team"
  }'
```

**Register vs connect:** Creating a server only registers its URL and who can **see** it. It does not share your OAuth login. Each person connects their own account afterward — see [Browsing & connecting — custom MCP servers](/docs/connectors/browsing#custom-mcp-servers).

After create, connect with `POST /credentials` (`provider: "mcp"`, `metadata.server_url`) or `belt mcp connect` — see [Browsing & connecting](/docs/connectors/browsing#connect).

### Update workspace server

`PUT /mcp-servers/{id}`

Send only fields to change. Same field names as create.

For `headers`:

| Request body | Effect |
|--------------|--------|
| Field omitted | Stored headers stay as they are |
| `"headers": { … }` | Replaces the whole set (validated) |
| `"headers": {}` | Clears all static headers |

**Errors:** the same codes as [create](#create-workspace-server) (`validation_error`, `invalid_request`, refusals, `404` for an unknown server). Other failures answer `400` `update_failed` with the error text in `detail`. Nothing is saved when validation fails.

### Update workspace server visibility

`POST /mcp-servers/{id}/visibility`

Changes who can **see** a workspace registration (not who can use your connection): your workspace only, or the public MCP directory. Requires **`credentials:write`** and the workspace **`manage_vault`** capability (same as create and update). Public servers get a store listing without sharing anyone's OAuth login — only the server registration is shared.

```json
{
  "visibility": "public"
}
```

| Value | Description |
|-------|-------------|
| `team` | Only your workspace (default) |
| `public` | Listed in the MCP directory; others can connect with their own credentials |

**Response:** Updated `MCPServerDTO`.

```bash
curl -X POST "https://api.inference.sh/mcp-servers/mcp_abc123/visibility" \
  -H "Authorization: Bearer inf_your_key" \
  -H "Content-Type: application/json" \
  -d '{"visibility":"public"}'
```

You can still set `visibility` on [create](#create-workspace-server); this endpoint updates an existing registration.

### Refresh workspace server metadata

`POST /mcp-servers/{id}/refresh`

Re-runs MCP server card and OAuth discovery against the stored `server_url`, then persists updated fields. Requires `credentials:write`.

Use this when a registration has stale metadata — for example a hostname used as the display name, or an incorrect `auth_type` from before automatic discovery was wired in. The same enrichment logic runs on create; refresh applies it again without recreating the server.

CLI equivalent: `belt mcp refresh <slug>` (resolves the public directory slug to the server ID).

**Response**: same shape as [Create workspace server](#create-workspace-server): updated `MCPServerDTO` plus optional `discovery` hints.

**Errors:** the same codes as [create](#create-workspace-server) (`validation_error`, `invalid_request`, refusals, `404` for an unknown server). Other discovery or save failures answer `400` `refresh_failed` with the error text in `detail`.

```bash
curl -X POST "https://api.inference.sh/mcp-servers/mcp_abc123/refresh" \
  -H "Authorization: Bearer inf_your_key"
```

### Delete workspace server

`DELETE /mcp-servers/{id}`

Returns `{ "deleted": true }`.

---

## List and call tools

Requires authentication and an active credential for the server (`credentials:read` to list, `credentials:write` to call).

You must connect first — `GET /mcps/{slug}/tools` returns `400 mcp_error` with `not connected to {name}` when no credential exists for that server's URL.

### List tools

`GET /mcps/{slug}/tools`

Returns an array of MCP tool definitions:

| Field | Description |
|-------|-------------|
| `name` | Tool identifier |
| `title` | Display title (optional) |
| `description` | What the tool does |
| `inputSchema` | JSON Schema for arguments. Per MCP (SEP-1613), schemas without `"$schema"` use the **2020-12** dialect; an explicit `"$schema"` (including draft-07) is respected. |
| `outputSchema` | JSON Schema for results (optional) |
| `annotations` | Optional MCP hints (`title`, `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`). Values come from the connected MCP server as returned by `tools/list`. |

```bash
curl https://api.inference.sh/mcps/linear/tools \
  -H "Authorization: Bearer inf_your_key"
```

### Call tool

`POST /mcps/{slug}/tools/{tool}`

**Body** — JSON object of tool arguments (empty object when the tool takes no input).

**Success** — MCP tool result (content blocks and structured output). When the remote tool declares an MCP Apps page ([SEP-1865](https://spec.modelcontextprotocol.io/specification/2025-06-18/basic/ui/)), the response may also include:

| Field | Description |
|-------|-------------|
| `ui.resource_uri` | Resource URI from the tool's `_meta.ui` |
| `ui.html` | Fetched HTML document (`text/html;profile=mcp-app`) |
| `ui.csp` | Content-Security-Policy for the sandboxed iframe |
| `ui.prefers_border` | When `true`, hosts may draw a border around the page |
| `ui.domain` | Optional postMessage domain hint |
| `ui.page_hash` | When the HTML is stored once in `mcp_ui_pages`, the inline `ui.html` is omitted and this SHA-256 key names the page (see [Stored MCP Apps pages](#stored-mcp-apps-pages)) |

Recorded runs (`GET /mcp-tool-calls/{id}`) keep the same `ui` payload (or `ui.page_hash` plus a fetch) so permalinks can reopen the page with the stored input and output. Nested tool calls from the page go back through the MCP proxy on the same connection — by directory slug when one exists, or by `credential_id` when it does not (see below).

**Tool-level error** — HTTP `200` with `{ "error": true, "content": "..." }` when the remote server reports `isError`.

**Connection errors** — `400 not_connected` or `502 mcp_error` when the credential is missing or the remote call fails.

```bash
curl -X POST https://api.inference.sh/mcps/linear/tools/list_issues \
  -H "Authorization: Bearer inf_your_key" \
  -H "Content-Type: application/json" \
  -d '{"teamId": "TEAM-123", "limit": 10}'
```

### Call tool by connection (`credential_id`)

`POST /mcp-credentials/{credential_id}/tools/{tool}`

Same request body, scopes (`credentials:write`), audit row, usage policy, and response shape as [Call tool](#call-tool). Use this when the MCP server is **not** in the public directory (no resolvable `/mcps/{slug}`) but you have a connected MCP credential — for example an MCP Apps page in chat that must proxy follow-up `tools/call` on the same connection.

The caller must be allowed to use that connection: their own, their workspace's, their org's, or a platform-wide MCP credential. A teammate's personal connection is never used directly; the API substitutes the caller's own connection to the same `server_url` when possible, or responds `403` when they must connect their own account.

```bash
curl -X POST "https://api.inference.sh/mcp-credentials/cred_abc123/tools/list_issues" \
  -H "Authorization: Bearer inf_your_key" \
  -H "Content-Type: application/json" \
  -d '{"limit": 10}'
```

### Read MCP Apps resource by connection

`POST /mcp-credentials/{credential_id}/resources/read`

Proxies MCP `resources/read` to the server behind the credential. Only `ui://` URIs are accepted (MCP Apps pages and their parts). Requires `credentials:read`. The body matches `MCPResourceReadRequest`:

```json
{ "uri": "ui://vendor.example/widget.html" }
```

Responses follow MCP `resources/read` (contents array). Total payload size is capped at **2 MiB**, the same limit as tool-result UI fetch. Blocked servers are rejected under the same usage policy as tool calls (even though no tool-call row is written). Unknown credentials, non-MCP credentials, and unreachable connections respond `404` with a generic not-found message so credential IDs cannot be probed.

---

## Stored MCP Apps pages

Large MCP Apps HTML is stored once under a content hash (SHA-256 over HTML plus framing metadata). Tool-call records and chat widgets reference the page with `ui.page_hash` instead of repeating `ui.html`.

### Get stored page

`GET /mcp-ui-pages/{hash}`

Returns `MCPUIPageDTO` (`html`, `csp`, `prefers_border`, `domain`, `created_at`). Requires `credentials:read`. You may read a page only when you can see at least one MCP tool-call record that rendered with that hash; otherwise the API responds `404` (same response for unknown hashes, so hashes cannot be enumerated). Responses are cacheable (`Cache-Control: private, max-age=31536000, immutable`) because a hash always names the same document.

Chat embeds that already carry inline HTML do not need this route; it is for permalinks, audit views, and clients that received only `page_hash` on the tool call.

---

## JavaScript SDK

```typescript
import { inference } from '@inferencesh/sdk';

const client = inference({ apiKey: process.env.INFERENCE_API_KEY });

// Public directory
const directory = await client.mcpServers.list({ limit: 20 });
const linear = await client.mcpServers.get('linear');

// Team-owned registrations
const owned = await client.mcpServers.listOwned();
const created = await client.mcpServers.create({
  server_url: 'https://mcp.example.com',
  name: 'Example MCP',
});

// Tools (requires connect first)
const tools = await client.mcpServers.listTools('linear');
const result = await client.mcpServers.callTool('linear', 'list_issues', { limit: 10 });
```

→ [JavaScript SDK](/docs/api/sdk-javascript)

---

## MCP tool call history

`GET /mcp-tool-calls` (cursor-paginated) returns `MCPToolCallDTO` records for connector tool invocations. Requires `credentials:read`.

When a remote tool declares an MCP Apps page in `_meta.ui.resourceUri`, the platform fetches that `ui://` resource at call time (up to 2 MiB), stores the document in `mcp_ui_pages`, and attaches a reference on the record:

| Field | Description |
|-------|-------------|
| `ui.resource_uri` | The `ui://` URI from the tool definition or result |
| `ui.html` | Fetched HTML when the record was written before hash storage, or on live responses before persistence |
| `ui.page_hash` | Content hash when the HTML is stored once; fetch with [`GET /mcp-ui-pages/{hash}`](#get-stored-page) |
| `ui.csp`, `ui.domain`, `ui.prefers_border` | Framing hints from the resource `_meta.ui` block |

Chat and audit clients use this to render connector widgets the same way an MCP host would after `tools/call`. Follow-up tool calls and `resources/read` from the page use [`POST /mcp-credentials/{credential_id}/tools/{tool}`](#call-tool-by-connection-credential_id) and [`POST /mcp-credentials/{credential_id}/resources/read`](#read-mcp-apps-resource-by-connection) when no directory slug applies. See [inference shell as an MCP server — MCP Apps](/docs/connectors/mcp-server#mcp-apps-interactive-uis).

---

## Related APIs

| Need | Endpoint / guide |
|------|------------------|
| OAuth connect and disconnect | `POST /credentials` with `provider: "mcp"` — [Browsing & connecting](/docs/connectors/browsing#connect) |
| Tool call audit history | `GET /mcp-tool-calls` (same `credentials:read` scope) |
| Add tools to agents | [Connector tools](/docs/connectors/agent-tools) |
| CLI workflows | [Browsing & connecting](/docs/connectors/browsing) |
