MCP reference
Endpoints, server fields, status values, and error codes for MCP Servers and the MCP Gateway.
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
| 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
| 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
| 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
| 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
| 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
| 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
{
"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
A Managed Server with a reported status carries this shape.
{
"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
Link or replace:
{ "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:
{ "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:
{ "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
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
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
The Gateway answers in JSON-RPC 2.0. The platform code appears on error.data.code.
{
"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.