# MCP reference (/docs/ai/mcp/reference)



Two addresses are involved. Management endpoints live on the BSQAI API under `/v1/mcp`. MCP traffic goes to the MCP Gateway, which is a separate service with its own address.

Authenticate with a JWT or a BSQAI API key. Both are sent as `Authorization: Bearer ...`.

## Management endpoints [#management-endpoints]

| Method   | Path                                           | Who    | Result                                 |
| -------- | ---------------------------------------------- | ------ | -------------------------------------- |
| `POST`   | `/v1/mcp/servers`                              | Admin  | `200` with the server                  |
| `GET`    | `/v1/mcp/servers`                              | Member | `200` with `servers` and `count`       |
| `GET`    | `/v1/mcp/servers/{server_id}`                  | Member | `200` with the server                  |
| `PUT`    | `/v1/mcp/servers/{server_id}`                  | Admin  | `200` with the server                  |
| `DELETE` | `/v1/mcp/servers/{server_id}`                  | Admin  | `204`                                  |
| `GET`    | `/v1/mcp/servers/{server_id}/available-tools`  | Admin  | `200` with `tools` and `count`         |
| `PUT`    | `/v1/mcp/servers/{server_id}/credential`       | Member | `204`                                  |
| `DELETE` | `/v1/mcp/servers/{server_id}/credential`       | Member | `204`                                  |
| `POST`   | `/v1/mcp/servers/{server_id}/credential/oauth` | Member | `200` with `authorize_url` and `state` |
| `POST`   | `/v1/mcp/credential/oauth/callback`            | Member | `200` with `server_id`                 |

There is no `PATCH`. Updates use `PUT` and replace what they send.

`DELETE /v1/mcp/servers/{server_id}` is idempotent. An administrator gets `204` whether or not the server existed.

## Gateway endpoint [#gateway-endpoint]

| Method                  | Path                       |
| ----------------------- | -------------------------- |
| `GET`, `POST`, `DELETE` | `/servers/{server_id}/mcp` |

`POST` carries messages, `GET` opens the server-to-client stream, and `DELETE` ends a session. The transport is Streamable HTTP.

The full URL is on `gateway_url` in every server response, and on the server's detail page in the Portal.

## Fields on every server [#fields-on-every-server]

| Field            | Type                      | Rules                                                                            |
| ---------------- | ------------------------- | -------------------------------------------------------------------------------- |
| `runtime`        | `"managed"` or `"remote"` | Chooses the request shape. Cannot change after creation                          |
| `name`           | string                    | Lowercase DNS label, 1 to 63 characters, unique in the organization. Create only |
| `display_name`   | string                    | 1 to 200 characters                                                              |
| `description`    | string or `null`          | Up to 500 characters. Defaults to `null`                                         |
| `tool_allowlist` | array of strings          | Defaults to `[]`, which permits every tool                                       |

Unknown fields, and fields belonging to the other runtime, are rejected with `422`.

Sending `name` on an update is rejected. To rename what people read, change `display_name`.

## Managed Server fields [#managed-server-fields]

| Field       | Type              | Rules                                                                                        |
| ----------- | ----------------- | -------------------------------------------------------------------------------------------- |
| `image`     | string            | Required. Registry-qualified with a tag or digest. Up to 512 characters                      |
| `port`      | integer           | Required. 1 to 65535                                                                         |
| `path`      | string            | Defaults to `/mcp`. Must start with `/`                                                      |
| `arguments` | array of strings  | Defaults to `[]`. Replaces the image's default command                                       |
| `env`       | object of strings | Names match `^[A-Za-z_][A-Za-z0-9_]*$`. Write-only                                           |
| `resources` | object            | `cpu_request`, `memory_request`, `cpu_limit`, `memory_limit`. Each a string or `null`        |
| `security`  | object            | `run_as_non_root`, `run_as_user`, `run_as_group`, `read_only_root_filesystem`. Each nullable |

A `null` field in `resources` or `security` leaves the platform's default alone.

A Managed Server runs one replica.

### Environment semantics on update [#environment-semantics-on-update]

| What you send       | Effect                                                 |
| ------------------- | ------------------------------------------------------ |
| No `env` field      | Keep the stored environment                            |
| `"env": null`       | Keep the stored environment                            |
| `"env": {"A": "1"}` | Replace the whole environment with exactly those names |
| `"env": {}`         | Remove every environment variable                      |

Values are never returned, so a read gives you nothing to resend. Omission preserving the environment is what makes a read, edit, and write cycle safe.

## Remote Server fields [#remote-server-fields]

| Field     | Type   | Rules                                                                                    |
| --------- | ------ | ---------------------------------------------------------------------------------------- |
| `address` | string | Required. Absolute `http://` or `https://` URL including the path, up to 2048 characters |

The address is stored exactly as sent and is never normalized. It must carry no username or password, and its host must be publicly resolvable.

A Remote Server accepts none of the Managed fields.

## Server response [#server-response]

```json
{
  "id": "2ddb7c76-4fb1-41bc-bf04-c0ae2b4ee4f3",
  "organization_id": "...",
  "name": "linear",
  "display_name": "Linear",
  "description": null,
  "runtime": "remote",
  "address": "https://mcp.linear.app/mcp",
  "namespace": null,
  "image": null,
  "port": null,
  "path": null,
  "arguments": [],
  "env": [],
  "resources": { "cpu_request": null, "memory_request": null, "cpu_limit": null, "memory_limit": null },
  "security": { "run_as_non_root": null, "run_as_user": null, "run_as_group": null, "read_only_root_filesystem": null },
  "tool_allowlist": ["search_issues"],
  "created_at": "2026-08-28T09:14:22Z",
  "updated_at": "2026-08-28T09:14:22Z",
  "credential_mode": "per_user",
  "linked": true,
  "credential_last_used_at": "2026-08-28T10:02:11Z",
  "status": null,
  "gateway_url": "https://mcp-gateway.<platform-domain>/servers/2ddb7c76-4fb1-41bc-bf04-c0ae2b4ee4f3/mcp"
}
```

| Response field            | Meaning                                                                   |
| ------------------------- | ------------------------------------------------------------------------- |
| `env`                     | Names only, each with `configured: true`. Values are never returned       |
| `namespace`               | Where the platform runs a Managed Server. `null` for a Remote Server      |
| `credential_mode`         | Whose identity reaches the Upstream. Servers report `per_user`            |
| `linked`                  | Whether the calling user has linked a credential. Yours alone             |
| `credential_last_used_at` | When your credential was last used, or `null`                             |
| `status`                  | Live workload status, or `null`                                           |
| `gateway_url`             | The address an MCP client uses. `null` when the deployment publishes none |

## Status [#status]

A Managed Server with a reported status carries this shape.

```json
{
  "ready": true,
  "replicas": 1,
  "ready_replicas": 1,
  "url": "...",
  "conditions": [
    {
      "type": "Ready",
      "status": "True",
      "reason": "...",
      "message": "...",
      "last_transition_time": "2026-08-28T09:15:03Z"
    }
  ]
}
```

`ready: true` means the workload is up and the platform completed an MCP handshake with it. That is stricter than the container running.

`status` is `null` for a Remote Server, always. It is also `null` for a Managed Server the cluster has not reported on yet. The Portal renders these as **Not reported** and **Status unknown**.

`status.url` is the internal workload address. It is not reachable from outside the cluster. Use `gateway_url`.

## Credential endpoints [#credential-endpoints]

Link or replace:

```json
{ "credential": "<upstream token>" }
```

The value is 1 to 4096 characters and may not be blank. The response is `204` with no body.

Start OAuth Linking with `POST /v1/mcp/servers/{server_id}/credential/oauth`. It takes no body and answers:

```json
{ "authorize_url": "https://...", "state": "..." }
```

Send the browser to `authorize_url`. The upstream system returns the browser to the Portal with `code` and `state`, which the Portal posts to `/v1/mcp/credential/oauth/callback`:

```json
{ "state": "...", "code": "..." }
```

A `state` is valid once and for ten minutes. It is bound to the user and organization that started the flow, and is consumed before the code is exchanged, so a repeated post fails.

No endpoint returns a credential.

## Agent integration [#agent-integration]

`mcp_server_ids` lists the MCP Servers an Agent may reach. On `POST /v1/agents`, omit the field or send `[]` to create an Agent without MCP Servers. On `PUT /v1/agents/{config_id}`, the update semantics are:

| What you send             | Effect                |
| ------------------------- | --------------------- |
| No `mcp_server_ids` field | Keep the current list |
| `null`                    | Keep the current list |
| `[]`                      | Remove every server   |
| A list of identifiers     | Replace the list      |

Every identifier must name a server in the caller's organization. An identifier that does not returns `agent_mcp_server_not_in_organization`, naming every invalid entry.

At run time the agent reaches only the servers the running user has linked and may use. The rest contribute no tools.

## Error codes [#error-codes]

Management endpoints answer with the platform error envelope. Request validation failures answer `422` without an error code.

| Status | Code                                   | Meaning                                                                   |
| ------ | -------------------------------------- | ------------------------------------------------------------------------- |
| `400`  | `mcp_server_address_invalid`           | The Remote address broke a rule the message names                         |
| `400`  | `mcp_server_image_invalid`             | The image reference is not one the cluster would accept                   |
| `400`  | `mcp_server_runtime_mismatch`          | The update used the other runtime's shape                                 |
| `400`  | `mcp_server_does_not_use_oauth`        | The server presented no OAuth challenge                                   |
| `400`  | `mcp_oauth_state_invalid`              | The state is unknown, expired, already used, or another user's            |
| `400`  | `agent_mcp_server_not_in_organization` | An Agent referenced a server outside the organization                     |
| `403`  | `mcp_server_access_denied`             | The server is absent, another organization's, or not yours to use         |
| `403`  | `mcp_server_management_denied`         | The caller is not a Platform Admin                                        |
| `403`  | `mcp_credential_not_linked`            | No credential is linked for this server                                   |
| `403`  | `mcp_credential_rejected`              | The upstream system refused the linked credential                         |
| `403`  | `mcp_credential_expired`               | An OAuth credential that can no longer be renewed                         |
| `409`  | `mcp_server_name_conflict`             | Another server in the organization has that name                          |
| `503`  | `mcp_server_environment_not_stored`    | The environment could not be written, so nothing was recorded             |
| `503`  | `mcp_server_not_ready`                 | The Managed workload is not serving yet                                   |
| `503`  | `mcp_server_unreachable`               | The server did not answer or did not complete an MCP handshake            |
| `503`  | `mcp_oauth_upstream_error`             | OAuth discovery, registration, or exchange failed. Retrying is the remedy |
| `503`  | `mcp_oauth_not_configured`             | The deployment publishes no OAuth return address                          |

A missing server and an inaccessible one both return `mcp_server_access_denied`, so probing for identifiers reveals nothing.

### Gateway errors [#gateway-errors]

The Gateway answers in JSON-RPC 2.0. The platform code appears on `error.data.code`.

```json
{
  "jsonrpc": "2.0",
  "id": null,
  "error": {
    "code": -32003,
    "message": "No credential is linked for this MCP Server.",
    "data": { "code": "mcp_credential_not_linked" }
  }
}
```

| JSON-RPC code | Platform code               |
| ------------- | --------------------------- |
| `-32002`      | `mcp_server_access_denied`  |
| `-32003`      | `mcp_credential_not_linked` |
| `-32004`      | `mcp_credential_rejected`   |
| `-32005`      | `mcp_credential_expired`    |

A call to a tool outside the allowlist is the one exception. It answers HTTP `200` with JSON-RPC `-32602` and the message `Unknown tool: <name>`, carrying no `data`, so it is indistinguishable from a tool the server never had.

The three credential refusals also carry a `WWW-Authenticate` bearer challenge whose `error` parameter matches the code in the body.

## Related pages [#related-pages]

* [How MCP works on the platform](/docs/ai/mcp/how-it-works)
* [Add an MCP Server](/docs/ai/mcp/add-a-server)
* [Connect an MCP client to the Gateway](/docs/ai/mcp/connect-a-client)
