MCP
Your box (the container running your workspace, on your laptop under
stackbone devor 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 olderinitializehandshake 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 than2026-07-28gets one of these two back: the current transport refuses it with a400, 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 with403before anything else. Non-browser clients send noOriginand are not affected. /mcptakesPOSTonly. Anything else is405.
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
- API: the routes the tools project.
- API reference: the OpenAPI document behind the catalog.
- Security and auth: how each caller proves itself to the box.
- Studio copilot: the same tools, in a panel beside every Studio screen.
- Coding agents: wire the same agent to this documentation.