Docs menu
Docs The MCP surface
The MCP surface
The three tools a code-mode profile exposes, what the model's code can and cannot do inside the sandbox, how approvals travel over MCP, and the default limits.
A code-mode profile exposes exactly three tools. Nothing else is always loaded, so the fixed cost of the surface, tool definitions and server instructions together, has a target of 1,500 tokens or fewer. The server targets MCP 2026-07-28 (stateless requests, multi-round-trip input) and falls back for older clients.
search
Annotations: read-only, idempotent.
Find the user's tools and skills by what you want to do, for example "create a GitHub issue" or "recent Slack messages". Returns tool addresses with one-line TypeScript signatures, best match first. Read full types with
describebefore writing code. Passnamespaceto search or list one source.
| Field | Type | Notes |
|---|---|---|
query |
string, 1–200 | Empty lists a namespace |
namespace |
string | Optional |
limit |
integer, 1–25 | Default 8 |
It returns plain text, best match first. A tool marked [asks] needs approval under your current policy, and tools your policy denies are hidden, with a count.
github (connections: personal*, work)
github.createIssue(input: { owner: string; repo: string; title: string; body?: string; labels?: string[] }): Promise<Issue> [asks]
Create an issue in a repository.
github.searchIssues(input: { q: string; perPage?: number; page?: number }): Promise<IssueSearchResult>
Search issues and pull requests.
3 more matches. Narrow the query or pass namespace.Ranking is BM25F over tool names, descriptions and parameter names, and never over connection names.
describe
Annotations: read-only, idempotent.
Get exact TypeScript declarations for tools: an address (
github.createIssue), a whole namespace (github, budget permitting), or a type named in an earlier declaration (github.Issue). Writeexecutecode against these types.
It takes targets, a list of 1 to 10 strings, and returns one declare const <namespace> block per namespace, with required fields first and each field's description as JSDoc. Output stays within a token budget, 2,500 by default; deeper types become named interfaces you can describe on their own.
declare const github: {
/** Search issues and pull requests. */
searchIssues(input: {
/** Search query, GitHub syntax. */
q: string;
perPage?: number;
page?: number;
}, options?: CallOptions): Promise<github.IssueSearchResult>;
};execute
Annotations: not read-only, open world. It has an output schema.
Run JavaScript in a sandbox where each of the user's sources is a global object of async functions:
await github.createIssue({ owner, repo, title }). TypeScript annotations are allowed and stripped. Write the body of an async function andreturnwhat you need; run independent calls in parallel withPromise.all. There is no network or filesystem besides these functions. If a call needs the user's approval or a connection, the result says so and a page opens on the user's screen; tell the user, then continue with{ "resume": "<runId>" }after they decide. Never ask the user for passwords or tokens in chat.
Send exactly one of code (up to 64 KB) or resume (a run id). The result is an envelope, as structured content plus one compact line of text. Abridged, without the M2 fields:
interface ExecuteEnvelope {
runId: string;
status: "completed" | "failed" | "awaiting_approval" | "awaiting_connection" | "cancelled";
result?: unknown;
error?: { code: string; message: string; hint?: string };
logs?: string[];
calls: { address: string; ok: boolean; ms: number }[];
pending?: { id: string; kind: "approval" | "connection"; address?: string; summary: string; message?: string }[];
truncated?: { result?: boolean; logs?: boolean };
notices?: string[];
}The hub forgives common code shapes. It takes the body of a fenced block, unwraps export default, calls a lone function, and returns a final expression, and each rewrite adds one line to notices so the model learns what changed.
const bugs = await github.searchIssues({ q: "repo:acme/app is:open label:bug" });
const top = bugs.items.slice(0, 3).map((i) => `• ${i.title} (#${i.number})`);
await slack.postMessage({ channel: "#eng", text: top.join("\n") });
return top;Inside the sandbox
Both sandbox drivers load the same guest prelude, so this list holds everywhere.
- Your sources as frozen global objects, one per namespace. Calling an unknown tool raises a
TypeErrorthat names the closest tools. hub.search(query)andhub.describe(target), the same lookups from inside code.ToolErrorfor expected tool failures, withcode,address,status,hintandretryable.console, captured within a log budget.- Determinism:
Dateruns on the run's logical clock, andMath.random,crypto.randomUUIDandcrypto.getRandomValuesdraw from a per-run seed. That is what lets a resumed run replay exactly. setTimeoutup to 30 seconds, plusstructuredClone,TextEncoder,TextDecoder,atobandbtoa.
Not available: fetch, import and require, process, Buffer, eval and the Function constructor. URL and URLSearchParams are not in M1. A second connection is picked per call with { connection: "work" }.
Approvals over MCP
When a call needs a person, the run records a pending input and stops. What the client receives depends on what it declared:
- A 2026-07-28 client with form elicitation gets an input request with the call's address, connection, a redacted preview of the arguments and the reason, and four choices. The answer is accepted only with the state the server signed for that exact pending call, and only once.
- Every other client gets
status: "awaiting_approval". A local hub opens its approval page in your browser and tells the model only that a page opened, withcapabilities hub approve <runId>as the fallback. It never hands the model a URL or a token. - Always:
capabilities hub approve <runId>from a terminal.
The model then calls execute({ resume: runId }). Until a person decides, resume returns the same pending envelope and runs nothing.
Default limits
These are targets that prototypes and perf tests check. Hosts may lower them but not raise them.
| Limit | Default | Meaning |
|---|---|---|
codeBytes |
64 KB | Size of the code in one execute |
computeMs |
10,000 | Guest execution time, paused while tool calls are in flight |
wallMs |
90,000 | Ceiling for one attempt, tool calls included |
memoryBytes |
64 MB | Guest heap |
maxCalls |
500 | Tool calls per run, all attempts together |
maxConcurrent |
8 | Tool calls in flight at once |
inlineResultBytes |
24 KB | Result and logs returned inline, across every channel |
upstreamTimeoutMs |
30,000 | One upstream call |
approvalTtlMs |
24 h | How long a pending approval waits |
Arriving in M2
M1 serves MCP sources, static credentials, the default code-mode profile and the search and describe helpers. M2 adds skills over MCP (the Skills extension, plus describe("skill:<name>")), large results as handles read with hub.read, OpenAPI sources, OAuth connections, named profiles and direct mode, where each tool is exposed as its own MCP tool for clients and models that do better that way.
Updated 2026-11-06