| title | Usage |
|---|---|
| description | The three tools that code-mode gives the model, how a program calls MCP tools, and what comes back. |
You do not call the tools yourself. Ask the model for a task that needs several MCP calls. The model then finds the
tools with search_tools and writes one program for run_code.
Ask, for example:
Find my open Jira issues that have no update in the last 14 days, and give me their keys and titles.
Without code-mode, the model calls the Jira tools one by one, and each result goes into the context. With code-mode, the model writes one program. The program does the loop and the filter, and only the final list goes into the context.
| Tool | What it does |
|---|---|
mcp__code-mode__search_tools |
Finds MCP tools by keywords. For each tool, it gives the name, one line of description and, when known, the argument types, the argument names and the result shape. It also gives the hints for the servers that it finds. An empty query lists all MCP tools. |
mcp__code-mode__run_code |
Runs one program in the sandbox and gives back its return value. |
mcp__code-mode__add_hint |
Proposes a hint for a server. The proposal has no effect until you approve it. See Hints. |
mcp__code-mode__remove_hint |
Hides a wrong hint in the current session and proposes to remove it from its hint file. See Remove a hint that is wrong. |
mcp__code-mode__edit_hint |
Hides a wrong hint in the current session and proposes a corrected hint in its place. See Change a hint that is wrong. |
mcp__code-mode__save_function |
Proposes a part of a program as a saved function, or new code for a saved function. The proposal has no effect until you approve it. See Saved functions. |
search_tools and run_code load in every session. add_hint, remove_hint, edit_hint and save_function load
only when the model needs them.
A program is the body of an async JavaScript function. In the program:
await call("mcp__<server>__<tool>", args)calls an MCP tool and gives its result.await tools["<server>"]["<tool>"](args)is the same call in a different form.await sleep(ms)waits for the given number of milliseconds. Use it to poll a slow job.await saveFile(name, data)writes data to a file and gives its path. See Files.await readFile(path)gives the text of a file. See Files.await resources(server)andawait readResource(server, uri)read MCP resources. See MCP resources.await lib.<name>(args)calls a saved function. See Saved functions.console.log(...)adds a line to the console output.return valuesets the result. The model gets the value as JSON.
An example program:
const [resources, user] = await Promise.all([
call("mcp__claude_ai_Jira__getAccessibleAtlassianResources", {}),
call("mcp__claude_ai_Jira__atlassianUserInfo", {}),
])
return { resourceCount: resources.length, hasAccountId: Boolean(user.account_id) }A program that polls a job until it is done:
let job = await call("mcp__ci__start_build", { branch: "main" })
while (job.status === "running") {
await sleep(5000)
job = await call("mcp__ci__get_build", { id: job.id })
}
return { status: job.status, url: job.url }The timeoutSeconds limit of the run includes the time in sleep. See Configuration.
At most 8 nested calls of one run wait for a reply at the same time. If a program starts more, for example with
Promise.all over 300 ids, the other calls wait in a queue. Thus a program does not send all its calls to a server
at once.
What a call gives:
- If the tool gives structured content, the call gives that object.
- If the tool gives text that is JSON, the call gives the parsed value.
- An integer that a number does not hold exactly arrives as text. Its digits stay exact.
- Else the call gives the text.
- If the call fails or a permission rule denies it, the call throws an
Error. The program can catch it.
A large result works the same way. If a tool result is too large for the context, Claude Code saves it to a file.
The model then gets only a note with the path. In a program, the plugin reads that file and the call gives the full
result. The plugin reads only files in the tool-results folder of the current session, up to 4 MiB. If the file
is larger, or Claude Code saved only part of the result, the call throws an Error. Then ask the tool for less
data, for example one page or fewer fields.
A program can call only MCP tools. It cannot call built-in tools such as Bash or Read, or the code-mode tools.
It has no require, fetch, process or setTimeout. It reads and writes files only with readFile and
saveFile.
Use saveFile when the data is too large to return, for example an export of 2,000 issues. The data then goes to
a file, not into the context. Only the path goes into the context:
const { issues } = await call("mcp__claude_ai_Jira__searchJiraIssuesUsingJql", { cloudId: "example.atlassian.net", jql: "project = OPS" })
const path = await saveFile("ops-issues.csv", issues.map(i => ({ key: i.key, status: i.fields.status.name })))
return { path, count: issues.length }saveFile(name, data) writes the file in ~/.claude/code-mode/files/<session id>/:
| Data | File |
|---|---|
| Text | The text, as it is. |
An array, and the name ends in .csv |
CSV. Objects get a header line with all their keys. |
An array, and the name ends in .jsonl |
One JSON value on each line. |
| Other data | Formatted JSON. |
- The name must be a file name with no folder, for example
issues.csv. A file with the same name is replaced. - The plugin does not write in the project. To move a file into the project, the model uses
Bash, with its normal permission check. - A file can have at most 16 million characters.
readFile(path) gives the text of a file, for example a list of ids to look up. A relative path starts at the
working folder of the session. The plugin reads the file only in these cases:
- The file is from
saveFilein this session. - Your permission rules let Claude Code read the file with no prompt. Files in the project are in this group by
default. A
Readallow rule adds other files.
Else readFile throws an error, and you get no prompt. A program can send what it reads to an MCP tool. Thus
the plugin reads no file that needs your approval. A file can have at most 4 MiB.
The model gets one text with these parts:
- The shown text is the return value as formatted JSON, at most 20,000 characters. The plugin cuts a longer shown text, and the note tells the model to return less data.
- The console lines, if there are any, at most 4,000 characters.
- One line for each MCP call that the program made, with its size and its shape. See Output projection.
- A request to propose a hint or a saved function, in the conditions that Hints and Saved functions give.
- The hints for the servers whose calls failed, if there are any.
If the program throws, the first part is the error and the lines of the program where it occurred.
Output projection is on by default. To turn it off, set the option projection to false.
The program is the filter: the model writes it for the task, so it keeps the exact values. A filter can miss the data that the model needs, because the model wrote it before it saw the data. Output projection lets the model see what it removed, and fix the filter without a new MCP call.
The last part of the result has one line for each MCP call:
--- 3 MCP calls: 184k characters in, 2k out ---
#4 mcp__slack__search 181k {messages: [312 × {ts, text, user, …+5}], next_cursor}
- mcp__slack__user failed
- mcp__slack__history did not finish
await recall(n) returns result #n again, with no new call.
#4is the number of the result in the session.181kis the size of the result, in characters of JSON.- The shape shows the keys and the array lengths, not the values.
did not finishshows a call that had no reply when the run ended. Usually the run stopped at its time limit.
The shape shows the keys of an object only if they are field names, such as status or next_cursor. If the keys
are data, for example emails, ids or the names of services, the shape shows only the number of keys. Then the data
stays out of the context, as the program intended.
The plugin keeps the newest 50 results of the session in memory, up to 8 million characters. A larger result is not
kept. The results stay until the session ends. A later program can read one again with await recall(4). Then the
line says how old the result is, for example recalled: #4 (12 min old). If the data changes often, the model must
call the tool again.
A call to recall makes no MCP call and no permission check, because the call that made the result was approved.
This is also true with approval set to per-call. A failed call is not kept. If the plugin cannot read the session
id, it keeps the results only until the run ends.
The line also tells the model these facts:
- A call is the same as an earlier call, with the same tool and arguments.
- The program returned nothing, but its calls returned data.
- The shown text was cut. Then the plugin keeps the whole value, and the note gives its number for
recall.
To measure the experiment, set the option metrics to true. The plugin then writes one line for each run to
~/.claude/code-mode/metrics/<session id>.jsonl. A line has sizes and counts only: no data, no arguments and no
program. To compare sessions with projection on and off, run this command:
node scripts/projection-report.mjsWhen a nested call works, the plugin saves the shape of its result for that MCP tool. search_tools then shows the
shape under the argument types, also in later sessions:
### mcp__slack__search
Search messages
args: { query: string; count?: number }
returns (earlier call): {messages: [{ts, text, user, …+5}], next_cursor}
Thus the first program of a session can use the correct fields. Without a shape, the model must guess the fields, and a wrong guess costs one more run.
- The shape has the same rules as the shape in the output projection lines. It shows field names, not values. It has no array lengths.
- A result that is text has the shape
text. - An empty result does not change a known shape.
- The plugin keeps the newest 500 shapes in
~/.claude/code-mode/shapes.json. It loads only the shapes that have field names and punctuation. It ignores all other text in the file. - A server can change the shape of its results. Then the program fails one time, and the next call saves the new shape.
Result shapes are part of output projection. If the option projection is false, the plugin does not save or show
shapes.
Some servers give data as resources, not as tools. A program reads them with two functions:
const list = await resources("docs")
const readme = list.find(r => r.name === "README")
const [content] = await readResource(readme.server, readme.uri)
return content.text.split("\n").slice(0, 20)resources(server)gives the resources of a server. Each resource has itsserver,uriandname. Without a server, it gives the resources of all servers.readResource(server, uri)gives the contents of the resource: a list of items withuri,mimeTypeandtext.
The plugin uses the resource tools of Claude Code, ListMcpResourcesTool and ReadMcpResourceTool. Each read gets
the normal permission check of these tools. Your approval of run_code does not cover it. See
Permissions. If no server in the session has resources, the call throws an error.
search_tools shows argument types only if Claude Code wrote a types file. Claude Code does not write it for a
plugin from a marketplace. Thus the plugin also learns argument names. When a nested call works, the plugin saves
the names of its arguments, not their values. search_tools then shows them for the tools that have no types:
### mcp__claude_ai_Jira__searchJiraIssuesUsingJql
Search Jira issues using JQL
args: names from earlier calls: {cloudId, jql, maxResults}
If a tool in the result has no types, the result says one time, after its first line, that ToolSearch shows the schema. It does not say it again for each tool.
- The list has only the names that earlier calls used. It does not show the types, or which arguments are required.
- Some servers ignore an argument with a wrong name, and the call still works. Then the wrong name goes into the list too.
- The plugin keeps the names of the newest 500 tools in
~/.claude/code-mode/args.json. It loads only entries that look like argument names.
Before the sandbox starts, the plugin checks each call("<tool>", { ... }) in the program. If a name is wrong, the
program does not run, and no call occurs. Thus a write call at the start of a program does not run if a later
call has a wrong name.
| The plugin knows | The program does not run when |
|---|---|
| The list of MCP tools | A tool name is not a connected MCP tool. If its server is connected, a deny rule can hide the tool. |
| The types of the tool | An argument has a name that the types do not have, or a required argument is missing. |
| Only the argument names of earlier calls | An argument name is 1 or 2 letters different from a known name, for example maxResult and maxResults. |
The result names each wrong name, its line and, when it can, the correct name.
The check reads only a literal tool name and an object literal. It does not check call(name, args) with
variables, or an object with ...spread. If a name is correct but the check refuses it, the model passes the
arguments as a variable.
When a session starts, the plugin tells the model to use run_code for MCP calls. The plugin tells it again after
/clear and after a compaction. The model calls an MCP tool directly only when run_code fails with an error that
a changed program cannot fix.
To stop all direct MCP calls, turn on the option blockDirectMcp. Then a direct MCP call gets a denial that tells
the model to use run_code. See Configuration.