MCP

Your box (the container running your workspace, on your laptop under stackbone dev or deployed in your cloud) serves the Model Context Protocol at <box-url>/mcp. Point Claude Code, Codex or any other MCP client at it and the agent gets four tools that reach the box's own HTTP API: find an operation, read its exact shape, call a read, call a write. Every call re-enters the box as an ordinary request, so the role check and the audit trail are the ones Studio and the CLI already go through. Nothing passes through Stackbone.

Four tools, not one per route

Tool What it does
list_operations Lists the operations this box exposes, as compact entries: id, method, path, tag, summary, and whether it is a read or a write. Filter with query, tag or kind.
describe_operation One operation in full: its parameters, its JSON request body and its JSON responses, with only the schema components it references. Says whether the operation asks for your confirmation before it runs.
call_read_operation Calls one read operation by id, with its declared parameters and an optional JSON body.
call_write_operation Calls one write operation by id, the same way. Some writes pause for your confirmation first (see below).

The catalog is a projection of the same OpenAPI document the box serves at /api/openapi.json. The box keeps an operation out when it cannot project it safely: a route that answers a credential (revealing a secret), one that streams (the chat wires, the event stream, the log tail), one that takes a file upload, or a write with no role check on it. The agent gets exactly the operations left. A route added without meeting those rules does not appear.

The list is also filtered per caller. An operation your role cannot reach is absent from list_operations and unknown to describe_operation. Naming its id anyway reaches the route and gets the route's own 403.

Reads and writes are two tools

An MCP client lets you approve tools by name. With one call_operation covering everything, approving it once to read a log would approve deleting a trigger. So the box splits the call in two: call_read_operation is marked read-only, call_write_operation is marked destructive, and you can allow the first without the second.

Whether an operation is a read or a write comes from the permission its route checks, not from the HTTP verb. POST /api/db/query checks a read permission, so it is a read. Listing secrets, API keys, dynamic config, prompts or connections checks a write permission on this box, so those GET routes are writes and only call_write_operation reaches them. list_operations tells you which is which. Calling an operation through the wrong tool is refused with wrong_operation_kind before anything runs.

Some writes ask you first

Deleting something, rotating a secret, revoking an API key, cancelling a run, starting an eval run: the box does not run these on the first call. call_write_operation answers with a question instead, the client shows you what is about to happen, and only your acceptance runs it. Decline or cancel and nothing happens.

The box checks the call before it asks. Parameters, body, size and encoding are all validated first, so a call it was going to refuse anyway comes back as that refusal instead of as a question. The sentence you are shown then names the arguments that passed those checks, which is the call the box would send.

The mechanism is MCP elicitation. The box keeps no state between the question and the answer. Everything the retry needs travels in an opaque token bound to you and to the exact call, and it expires after 90 seconds. A token from another user, another call or an earlier minute is refused.

A client the box cannot ask does not get to call these operations at all. The answer is confirmation_unavailable and nothing runs. Two kinds of client land there: one that does not support elicitation, and one connected on the older MCP revision, where the box keeps nothing open to carry your answer back on. Either way the operation is refused rather than run unasked, so make that one change from Studio or the CLI. Claude Code supports elicitation and shows the dialog on its own. describe_operation reports the requirement in advance, in the confirm field, so the agent can tell you before it calls.

Connect a client

The address is your box address plus /mcp. The credential depends on where the box runs:

Where your box runs Address Credential
On your laptop, under stackbone dev <tunnel address>/mcp Your Stackbone account, as on a deployed box. The tunnel address is new on every stackbone dev, so the client signs in again after each restart. With no tunnel, /mcp answers not ready.
Self-hosted, with the Docker Compose package https://<your-box>/mcp The operator token, sent as Authorization: Bearer <STUDIO_STANDALONE_TOKEN>. The same value Studio takes on that box.
Deployed in your cloud and registered in Studio https://<your-box>/mcp Your Stackbone account. The client discovers the sign-in from the box, you sign in and grant it, and it receives a five-minute token that is valid at this box's /mcp and nowhere else. Your role's permissions apply on every call.

On a box deployed in your cloud you hand the client the address and nothing else. It reads from the box where to sign in, identifies itself, and sends you through sign-in and consent. There is no client id or secret for you to obtain, paste or keep in sync. Approve it once, and the client holds a short-lived token that works at that box's /mcp and nowhere else.

In Claude Code:

# a local box
claude mcp add --transport http my-box http://127.0.0.1:4242/mcp

# a self-hosted box
claude mcp add --transport http my-box https://<your-box>/mcp \
  --header "Authorization: Bearer <STUDIO_STANDALONE_TOKEN>"

In Cursor (.cursor/mcp.json) and most clients that read a JSON config:

{
  "mcpServers": {
    "my-box": {
      "type": "http",
      "url": "https://<your-box>/mcp",
      "headers": { "Authorization": "Bearer <STUDIO_STANDALONE_TOKEN>" }
    }
  }
}

Drop headers for a local box. The field names differ by client; the coding agents page lists the differences for the clients the CLI knows.

Turning MCP on and off

MCP is on unless somebody turned it off. Studio's MCP screen carries the switch beside the address, and only an owner or an admin can move it — the same seats that hold the other settings a member is kept away from.

Off means off for everyone. A client that already holds a valid credential — your account's token, the operator bearer, or nothing at all on a local box — is refused the same way as one that holds none:

{ "code": "mcp_disabled" }

with HTTP 503, and no sign-in challenge: the credential is not the problem, so signing in again will not help. Ask an operator to turn MCP back on. It takes effect on the next call — nobody has to restart the agent — and it changes nothing else: Studio, the assistant and your own API calls keep working while MCP is off.

"Enabled" describes the setting and nothing more. It is not a claim that the box is reachable or that your credential will be accepted; a box that was never registered and armed is enabled and still answers mcp_not_ready.

Three things to know about the wire:

  • The box serves two MCP revisions on the same address: the current 2026-07-28, and the older initialize handshake most shipped clients still open with. Your client negotiates one of them, so there is nothing to configure. A client that asks for a revision newer than 2026-07-28 gets one of these two back: the current transport refuses it with a 400, the older handshake answers with the newest revision the box does speak. Neither revision opens a session, so every call stands on its own credential. On the older one the box cannot ask you to confirm, which is the limit described above.
  • A request from a browser carries an Origin. One outside the box's CORS allowlist is refused with 403 before anything else. Non-browser clients send no Origin and are not affected.
  • /mcp takes POST only. Anything else is 405.

Limits

What Limit When you cross it
Request body 1 MiB 413 at the door, before the body is read. One operation's JSON body has the same ceiling (request_too_large), so over /mcp the door always answers first
Response of one operation 256 KiB response_too_large. The answer is refused whole, never truncated.
One list_operations answer 256 operations or 64 KiB catalog_too_large. Narrow it with query, tag or kind.
Bodies JSON in, JSON out An operation with any other body type is not in the catalog
Request headers you may set accept-language, if-none-match, if-modified-since, when the operation declares them header_not_allowed
Response headers you get back content-type, etag, last-modified, retry-after, location Everything else is dropped

The box never follows a redirect or walks an operation's pages for you. An operation that pages answers one page, with whatever cursor the route returns.

What the box answers

A call comes back as one JSON envelope: the operation id, the inner HTTP status, the safe response headers, the content type and the body. An inner 4xx or 5xx keeps the envelope and is flagged as an error result, so the agent sees the route's own answer (the 403 for a permission you lack, the 404 for a run that is gone).

When the box refuses before the route runs, the result carries a code:

Code Meaning
operation_not_found No such id on this box. describe_operation also answers it for an id your role cannot reach, and list_operations leaves that id out of its answer. A call to that id reaches the route and gets the route's own 403
wrong_operation_kind A read called through call_write_operation, or the reverse. The message names the right tool
invalid_parameter, missing_parameter, undeclared_parameter A parameter that fails its schema, is required and absent, or is not declared by the operation
invalid_body, missing_body, body_not_allowed The JSON body fails its schema, is required and absent, or was sent to an operation that takes none
header_not_allowed A header outside the list above
confirmation_unavailable The operation needs your confirmation and this client cannot be asked. details.reason says which: no_elicitation (your client declared none) or legacy_protocol (the older MCP revision, where the box keeps nothing open to carry your answer back)
confirmation_declined, confirmation_expired, confirmation_invalid, confirmation_mismatch You declined, the 90 seconds passed, the token does not verify, or it belongs to a different call or user
confirmation_unreadable The retry carried the token but no answer the box could read. Call the operation again and answer the new prompt
request_too_large See Limits
catalog_too_large See Limits
response_too_large, unsupported_response_content_type, invalid_response_json The route answered more than 256 KiB, something other than JSON, or JSON that does not parse

At the door, before any tool runs: 403 invalid_origin for a browser origin the box does not allow, 413 for an oversized request, 401 invalid_token for a missing or wrong credential, and three 503s that mean different things and should be read carefully:

Code Meaning
mcp_disabled An operator turned MCP off for this installation. Your credential is fine; ask them to turn it back on
mcp_not_ready A deployed box that has not been registered and armed yet. Retry once it has; do not sign in again
mcp_unavailable The box could not read whether MCP is enabled, so it refused rather than guess. Retry shortly; do not sign in again

None of the three carries a sign-in challenge.

What is not there

  • MCP resources and prompts. The box serves tools only.
  • The chat wires, the event stream and the log tail. They stream, and the catalog projects bounded JSON only. Use them as described on the API page.
  • File uploads and binary downloads.
  • A tool per route, or a separate permission model. The role you hold decides, the same as in Studio.

What's next

BUILT WITH ❤️ FROM CANADA AND SPAIN