# Letting the Model Write Code with CodeCall

> Why a long tool list costs a model, and how @frontmcp/plugin-codecall puts your tools behind a few tools of its own, so the model searches for the tools it needs, reads their schemas, and calls them one at a time or from a short script that runs on your server.

Source: https://frontmcp.dev/learn/letting-the-model-write-code-with-codecall

Every tool you add is one more name, description and schema that the model reads before it answers anything. At a dozen tools that's nothing. At a few hundred, the list crowds the model's context, similar tools blur together, and a task that takes five calls takes five round trips, with every result passing through the model. `@frontmcp/plugin-codecall` changes what the model is shown: instead of your tools, a few CodeCall tools that it uses to search yours, read the ones it needs, and call them, one at a time or from a short script that runs on your server and sends back only the answer.

**You will learn**
- What a long tool list costs a model
- How to put your tools behind CodeCall, and when it's worth it
- How the model finds tools with `codecall:search` and reads them with `codecall:describe`
- How it calls them, one with `codecall:invoke`, or several from a `codecall:execute` script
- What a script may do, and how to keep tools out of its reach

## A server with too many tools

The help desk has grown. It has seven areas now, tickets, customers, invoices, agents, macros, articles and SLA policies, each with five actions. That's 35 tools, written here with a loop to keep the example short. Open the **Tests** tab:

```ts tools.ts active
import { Tool, ToolContext, z } from "@frontmcp/sdk";

const areas = ["ticket", "customer", "invoice", "agent", "macro", "article", "sla policy"];

const actions: [string, (area: string) => string][] = [
  ["list", (area) => `List ${area}s, newest first. Filter by status and page through the results.`],
  ["get", (area) => `Get one ${area} by its id, with every field.`],
  ["create", (area) => `Create a new ${area}. Returns the new record with its id.`],
  ["update", (area) => `Update the fields of one ${area}. Fields you leave out keep their value.`],
  ["delete", (area) => `Delete one ${area}. This can't be undone.`],
];

export const helpDeskTools = areas.flatMap((area) =>
  actions.map(([action, describe]) => {
    @Tool({
      name: `${action}_${area.replace(" ", "_")}`,
      description: describe(area),
      inputSchema: {
        id: z.string().optional().describe(`The ${area}'s id`),
        status: z.enum(["open", "pending", "closed"]).optional().describe("Only records with this status"),
        page: z.number().int().min(1).optional().describe("Page number, from 1"),
        pageSize: z.number().int().min(1).max(100).optional().describe("Records per page, up to 100"),
      },
    })
    class AreaTool extends ToolContext {
      async execute(input: { id?: string; status?: "open" | "pending" | "closed"; page?: number; pageSize?: number }) {
        return { action, area, ...input };
      }
    }
    return AreaTool;
  }),
);
```

```ts help-desk.app.ts
import { App } from "@frontmcp/sdk";
import { helpDeskTools } from "./tools";

@App({ id: "help-desk", name: "Help Desk", tools: helpDeskTools })
export class HelpDeskApp {}
```

```ts list.test.ts
import { test, expect } from "@frontmcp/testing";

test("🚩 the model is sent all 35 tools, every schema included", async ({ mcp }) => {
  const tools = await mcp.tools.list();
  expect(tools).toHaveLength(35);
  expect(JSON.stringify(tools).length).toBeGreaterThan(19_000); // about 20 KB
});

test("🚩 and the tools look alike", async ({ mcp }) => {
  const names = (await mcp.tools.list()).map((t: { name: string }) => t.name);
  expect(names.filter((name: string) => name.endsWith("_ticket"))).toEqual([
    "create_ticket",
    "delete_ticket",
    "get_ticket",
    "list_ticket",
    "update_ticket",
  ]);
});
```

Nothing here is wrong, and a client lists all 35 tools. Open the **Capabilities** tab and scroll: that's what the model reads before every answer, about 20 KB of names, descriptions and schemas, and each new tool adds half a kilobyte more. Three problems grow with the list:

1. **Context.** The whole list is sent with every request to the model, whether the conversation needs one tool or none. At 200 tools it's over 100 KB.
2. **Choice.** The more tools look alike, the more often the model picks the wrong one, or calls two to be sure.
3. **Round trips.** "Which urgent tickets are open, and whose are they?" takes a search and then a customer lookup per ticket. Each call is a round trip through the model, and each result, every field of every ticket, lands in its context, where it stays.

## Putting the tools behind CodeCall

Install the plugin:

```bash
npm install @frontmcp/plugin-codecall
```

It needs Node 24 or later. Then register it on the app:

```ts help-desk.app.ts active
import { App } from "@frontmcp/sdk";
import { CodeCallPlugin } from "@frontmcp/plugin-codecall";
import { helpDeskTools } from "./tools";

@App({
  id: "help-desk",
  name: "Help Desk",
  tools: helpDeskTools,
  plugins: [CodeCallPlugin.init({ mode: "codecall_only" })],
})
export class HelpDeskApp {}
```

```ts tools.ts
import { Tool, ToolContext, z } from "@frontmcp/sdk";

const areas = ["ticket", "customer", "invoice", "agent", "macro", "article", "sla policy"];

const actions: [string, (area: string) => string][] = [
  ["list", (area) => `List ${area}s, newest first. Filter by status and page through the results.`],
  ["get", (area) => `Get one ${area} by its id, with every field.`],
  ["create", (area) => `Create a new ${area}. Returns the new record with its id.`],
  ["update", (area) => `Update the fields of one ${area}. Fields you leave out keep their value.`],
  ["delete", (area) => `Delete one ${area}. This can't be undone.`],
];

export const helpDeskTools = areas.flatMap((area) =>
  actions.map(([action, describe]) => {
    @Tool({
      name: `${action}_${area.replace(" ", "_")}`,
      description: describe(area),
      inputSchema: {
        id: z.string().optional().describe(`The ${area}'s id`),
        status: z.enum(["open", "pending", "closed"]).optional().describe("Only records with this status"),
        page: z.number().int().min(1).optional().describe("Page number, from 1"),
        pageSize: z.number().int().min(1).max(100).optional().describe("Records per page, up to 100"),
      },
    })
    class AreaTool extends ToolContext {
      async execute(input: { id?: string; status?: "open" | "pending" | "closed"; page?: number; pageSize?: number }) {
        return { action, area, ...input };
      }
    }
    return AreaTool;
  }),
);
```

```ts codecall.test.ts
import { test, expect } from "@frontmcp/testing";
import { create } from "@frontmcp/sdk";
import { CodeCallPlugin } from "@frontmcp/plugin-codecall";
import { helpDeskTools } from "./tools";

test("tools/list shows CodeCall's tools instead of the app's", async ({ mcp }) => {
  expect((await mcp.tools.list()).map((t: { name: string }) => t.name)).toEqual([
    "codecall:describe",
    "codecall:execute",
    "codecall:invoke",
    "codecall:search",
    "codecall:searchKnowledge",
    "codecall:searchSkills",
  ]);
});

test("the list is the same size, however many tools are behind it", async ({ mcp }) => {
  const small = await create({
    info: { name: "help-desk", version: "1.0.0" },
    tools: helpDeskTools.slice(0, 5),
    plugins: [CodeCallPlugin.init({ mode: "codecall_only" })],
  });
  const fiveTools = (await small.listTools()).tools;
  await small.dispose();
  expect(JSON.stringify(await mcp.tools.list()).length).toBe(JSON.stringify(fiveTools).length);
});

test("CodeCall can use all 35 tools, and finds them", async ({ mcp }) => {
  const { tools, totalAvailableTools } = (await mcp.tools.call("codecall:search", { queries: ["update ticket", "list invoices"] })).json();
  expect(totalAvailableTools).toBe(35);
  expect(tools.slice(0, 2).map((t: { name: string }) => t.name).sort()).toEqual(["list_invoice", "update_ticket"]);
});
```

Open **Capabilities** again: the app's 35 tools are gone from the list, and CodeCall's six are there instead. The **Call** tab shows the model searching for two of the tools it can no longer see. The model now works in steps:

| Tool | What the model does with it |
| --- | --- |
| `codecall:search` | Finds tools for short phrases, like `"close ticket"`. |
| `codecall:describe` | Reads the input and output schemas of the tools it picked. |
| `codecall:invoke` | Calls one tool, and gets its result. |
| `codecall:execute` | Runs a short script that calls several tools, and gets what the script returns. |
| `codecall:searchSkills`, `codecall:searchKnowledge` | Search the server's [skills](https://frontmcp.dev/learn/teaching-the-model-skills). |

Each has a long description written for the model, which explains that flow. Every tool call still goes through FrontMCP's normal flow, with the caller's credentials: validation, hooks and [authorization](https://frontmcp.dev/learn/authorizing-calls) all apply, as for a direct call.

`mode: "codecall_only"` is the default, and the usual choice: CodeCall's tools are listed, yours aren't. `"codecall_opt_in"` lists every tool as before and lets CodeCall use only the ones marked `codecall: { enabledInCodeCall: true }`, which is a way to try CodeCall on a server whose clients already use the tools directly. In `codecall_only`, a client can't call your tools directly any more: a `tools/call` for one that isn't listed gets `Tool "…" not found`. [Modes](https://frontmcp.dev/reference/plugins/codecall#modes) lists all three.

> **Pitfall: CodeCall's own tools aren't small**
Those six descriptions and schemas come to about 21 KB, what 35 to 40 small tools like these take, and it stays that size however many tools are behind them. For a server with a dozen tools, list them directly: CodeCall costs the model more than it saves. It pays off when your own list is bigger than that, or when the model's tasks need several calls in a row.

> **Note**
It makes no difference whether you register CodeCall on one `@App` or on `@FrontMcp`: either way it hides every app's tools from `tools/list`, and its tools search and call every app's tools. [`appIds`](https://frontmcp.dev/reference/plugins/codecall#several-apps-on-one-server) limits the hiding to the apps you name.

## Finding tools: search, then describe

The rest of this lesson uses a smaller help desk, with real tickets and customers, so there's something to find. `codecall:search` takes one or more short phrases, searches for each on its own, and merges the results, best first:

```ts tools.ts active
import { PublicMcpError, Tool, ToolContext, z } from "@frontmcp/sdk";

const tickets = [
  { id: "T-1", title: "Cannot log in", status: "open", priority: "high", customerId: "C-1" },
  { id: "T-2", title: "Invoice shows the wrong total", status: "open", priority: "low", customerId: "C-2" },
  { id: "T-3", title: "Export times out", status: "open", priority: "high", customerId: "C-2" },
  { id: "T-4", title: "Password reset email never arrives", status: "closed", priority: "high", customerId: "C-1" },
];

const customers = [
  { id: "C-1", name: "Acme Corp", plan: "enterprise" },
  { id: "C-2", name: "Globex", plan: "starter" },
];

@Tool({
  name: "search_tickets",
  description: "Search support tickets by status and priority",
  inputSchema: { status: z.enum(["open", "closed"]).optional(), priority: z.enum(["low", "high"]).optional() },
})
export class SearchTickets extends ToolContext {
  async execute({ status, priority }: { status?: "open" | "closed"; priority?: "low" | "high" }) {
    return { tickets: tickets.filter((t) => (!status || t.status === status) && (!priority || t.priority === priority)) };
  }
}

@Tool({
  name: "get_ticket",
  description: "Get one support ticket by its id",
  inputSchema: { id: z.string().describe("A ticket id, like T-1") },
  examples: [{ description: "Look up the ticket about logging in", input: { id: "T-1" } }],
})
export class GetTicket extends ToolContext {
  async execute({ id }: { id: string }) {
    const ticket = tickets.find((t) => t.id === id);
    if (!ticket) this.fail(new PublicMcpError(`There's no ticket ${id}.`));
    return ticket;
  }
}

@Tool({ name: "close_ticket", description: "Close a support ticket", inputSchema: { id: z.string() } })
export class CloseTicket extends ToolContext {
  async execute({ id }: { id: string }) {
    return { id, status: "closed" };
  }
}

@Tool({ name: "get_customer", description: "Get a customer account by its id", inputSchema: { id: z.string() } })
export class GetCustomer extends ToolContext {
  async execute({ id }: { id: string }) {
    return customers.find((c) => c.id === id) ?? this.fail(new PublicMcpError(`There's no customer ${id}.`));
  }
}

@Tool({ name: "refund_invoice", description: "Refund an invoice in full", inputSchema: { invoiceId: z.string() } })
export class RefundInvoice extends ToolContext {
  async execute({ invoiceId }: { invoiceId: string }) {
    return { invoiceId, status: "refunded" };
  }
}
```

```ts help-desk.app.ts
import { App } from "@frontmcp/sdk";
import { CodeCallPlugin } from "@frontmcp/plugin-codecall";
import { CloseTicket, GetCustomer, GetTicket, RefundInvoice, SearchTickets } from "./tools";

@App({
  id: "help-desk",
  name: "Help Desk",
  tools: [SearchTickets, GetTicket, CloseTicket, GetCustomer, RefundInvoice],
  plugins: [CodeCallPlugin.init({ mode: "codecall_only" })],
})
export class HelpDeskApp {}
```

```ts find.test.ts
import { test, expect } from "@frontmcp/testing";

const search = async (mcp: any, queries: string[]) => (await mcp.tools.call("codecall:search", { queries })).json();

test("each phrase finds its tool, best first", async ({ mcp }) => {
  const { tools } = await search(mcp, ["close ticket", "refund invoice"]);
  expect(tools[0]).toMatchObject({ name: "close_ticket", matchedQueries: ["close ticket"] });
  expect(tools[1]).toMatchObject({ name: "refund_invoice", matchedQueries: ["refund invoice"] });
});

test("🚩 search matches words, not meaning", async ({ mcp }) => {
  expect((await search(mcp, ["give money back"])).tools).toEqual([]);
});

test("codecall:describe returns the JSON Schema, with each field's description", async ({ mcp }) => {
  const { tools } = (await mcp.tools.call("codecall:describe", { toolNames: ["get_ticket"] })).json();
  expect(tools[0].inputSchema.properties.id).toEqual({ type: "string", description: "A ticket id, like T-1" });
  expect(tools[0].usageExamples).toEqual([
    {
      description: "Look up the ticket about logging in",
      code: 'const result = await callTool(\'get_ticket\', {\n  "id": "T-1"\n});\nreturn result;',
    },
  ]);
});

test("without examples, CodeCall writes one from the tool's schema", async ({ mcp }) => {
  const { tools } = (await mcp.tools.call("codecall:describe", { toolNames: ["search_tickets"] })).json();
  expect(Object.keys(tools[0].inputSchema.properties)).toEqual(["status", "priority"]);
  expect(tools[0].usageExamples).toEqual([
    {
      description: "Search for search_tickets",
      code: 'const results = await callTool(\'search_tickets\', {\n  "status": "open"\n});\nreturn results.items || results;',
    },
  ]);
});
```

- **Search matches words**, from each tool's name (split on `_`, `-`, `:` and `.`), its description, its `tags`, the names of its input fields and its `examples`. Built-in synonyms help (`add` also finds `create`), but words aren't reduced to their stem, so `ticket` doesn't find `tickets`, and a phrase in other words, like `give money back`, finds nothing. Write descriptions with the words a model would search for.
- **Each result** has the tool's `name`, `description`, `appId`, a `relevanceScore` from 0 to 1, and `matchedQueries`, the phrases that found it. `totalAvailableTools` counts the tools CodeCall may use.
- **`codecall:describe`** takes the names the model picked and returns each tool's input and output schema as JSON Schema, with the text of each field's `.describe()`, and some usage examples. A name CodeCall doesn't have comes back in `notFound`.

CodeCall caches the results of `codecall:search` and `codecall:describe` for 60 seconds per signed-in caller, with the [Cache plugin](https://frontmcp.dev/learn/caching-results) it brings along. After you change a tool, a caller who asked about it in the last minute gets the old answer until then. Anonymous callers, like the Playground's, aren't cached.

> **Note**
`codecall:describe` gives a tool's own [`examples`](https://frontmcp.dev/reference/sdk/tool#options) as its usage examples, and shows only those. A tool without any gets one that CodeCall writes from its name and input schema: it uses only properties the schema declares, like `status`, with the first value of the enum, for `search_tickets`. That's a valid call, but a guess at what the model wants. Give a tool an example of the call you'd make, and that's the one the model copies.

## Calling one tool: `codecall:invoke`

Once the model knows a tool's schema, `codecall:invoke` calls it and returns the tool's own result, errors included:

```ts help-desk.app.ts active
import { App } from "@frontmcp/sdk";
import { CodeCallPlugin } from "@frontmcp/plugin-codecall";
import { CloseTicket, GetCustomer, GetTicket, SearchTickets } from "./tools";

@App({
  id: "help-desk",
  name: "Help Desk",
  tools: [SearchTickets, GetTicket, CloseTicket, GetCustomer],
  plugins: [CodeCallPlugin.init({ mode: "codecall_only" })],
})
export class HelpDeskApp {}
```

```ts tools.ts
import { PublicMcpError, Tool, ToolContext, z } from "@frontmcp/sdk";

const tickets = [
  { id: "T-1", title: "Cannot log in", status: "open", priority: "high", customerId: "C-1" },
  { id: "T-2", title: "Invoice shows the wrong total", status: "open", priority: "low", customerId: "C-2" },
  { id: "T-3", title: "Export times out", status: "open", priority: "high", customerId: "C-2" },
  { id: "T-4", title: "Password reset email never arrives", status: "closed", priority: "high", customerId: "C-1" },
];

const customers = [
  { id: "C-1", name: "Acme Corp", plan: "enterprise" },
  { id: "C-2", name: "Globex", plan: "starter" },
];

@Tool({
  name: "search_tickets",
  description: "Search support tickets by status and priority",
  inputSchema: { status: z.enum(["open", "closed"]).optional(), priority: z.enum(["low", "high"]).optional() },
})
export class SearchTickets extends ToolContext {
  async execute({ status, priority }: { status?: "open" | "closed"; priority?: "low" | "high" }) {
    return { tickets: tickets.filter((t) => (!status || t.status === status) && (!priority || t.priority === priority)) };
  }
}

@Tool({ name: "get_ticket", description: "Get one support ticket by its id", inputSchema: { id: z.string().describe("A ticket id, like T-1") } })
export class GetTicket extends ToolContext {
  async execute({ id }: { id: string }) {
    const ticket = tickets.find((t) => t.id === id);
    if (!ticket) this.fail(new PublicMcpError(`There's no ticket ${id}.`));
    return ticket;
  }
}

@Tool({ name: "close_ticket", description: "Close a support ticket", inputSchema: { id: z.string() } })
export class CloseTicket extends ToolContext {
  async execute({ id }: { id: string }) {
    return { id, status: "closed" };
  }
}

@Tool({ name: "get_customer", description: "Get a customer account by its id", inputSchema: { id: z.string() } })
export class GetCustomer extends ToolContext {
  async execute({ id }: { id: string }) {
    return customers.find((c) => c.id === id) ?? this.fail(new PublicMcpError(`There's no customer ${id}.`));
  }
}
```

```ts invoke.test.ts
import { test, expect } from "@frontmcp/testing";

test("it returns the tool's own result", async ({ mcp }) => {
  const result = await mcp.tools.call("codecall:invoke", { tool: "get_ticket", input: { id: "T-1" } });
  expect(result.json()).toMatchObject({ id: "T-1", title: "Cannot log in", status: "open" });
});

test("and the tool's own error", async ({ mcp }) => {
  const result = await mcp.tools.call("codecall:invoke", { tool: "get_ticket", input: { id: "T-9" } });
  expect(result).toBeError();
  expect(result).toHaveTextContent("There's no ticket T-9.");
});

test("a tool CodeCall doesn't have is refused, with a hint", async ({ mcp }) => {
  const result = await mcp.tools.call("codecall:invoke", { tool: "delete_everything", input: {} });
  expect(result).toHaveTextContent('Tool "delete_everything" is not available. Use codecall:search to discover available tools.');
});
```

```ts script.test.ts
import { test, expect } from "@frontmcp/testing";

const run = async (mcp: any, script: string) => (await mcp.tools.call("codecall:execute", { script })).json();

test("a script joins tools and returns only the answer", async ({ mcp }) => {
  const script = [
    'const { tickets } = await callTool("search_tickets", { status: "open", priority: "high" });',
    "const out = [];",
    "for (const t of tickets) {",
    '  const customer = await callTool("get_customer", { id: t.customerId });',
    "  out.push({ ticket: t.id, title: t.title, customer: customer.name });",
    "}",
    "return out;",
  ].join("\n");
  expect(await run(mcp, script)).toEqual({
    status: "ok",
    result: [
      { ticket: "T-1", title: "Cannot log in", customer: "Acme Corp" },
      { ticket: "T-3", title: "Export times out", customer: "Globex" },
    ],
  });
});

test("parallel() fetches the customers at once", async ({ mcp }) => {
  const script = [
    'const { tickets } = await callTool("search_tickets", { status: "open" });',
    'const owners = await parallel(tickets, (t) => callTool("get_customer", { id: t.customerId }));',
    "return tickets.map((t, i) => `${t.id}: ${owners[i].name}`);",
  ].join("\n");
  expect(await run(mcp, script)).toEqual({ status: "ok", result: ["T-1: Acme Corp", "T-2: Globex", "T-3: Globex"] });
});

test("a script that reads and then deletes is stopped by the sandbox", async ({ mcp }) => {
  const script = 'const customer = await callTool("get_customer", { id: "C-2" });\nreturn await callTool("delete_customer", { id: customer.id });';
  expect(await run(mcp, script)).toEqual({
    status: "runtime_error",
    error: {
      source: "script",
      message: "Suspicious pattern detected: Delete operation after data access (potential cover-up) [DELETE_AFTER_ACCESS]",
      name: "Error",
    },
  });
});

test("a failed call throws, and the script's error isn't the tool's", async ({ mcp }) => {
  const caught = 'try {\n  return await callTool("get_ticket", { id: "T-9" });\n} catch (error) {\n  return { failed: error.message };\n}';
  expect(await run(mcp, caught)).toEqual({ status: "ok", result: { failed: 'Tool "get_ticket" execution failed' } });
  expect(await run(mcp, 'return await callTool("get_ticket", { id: "T-9" });')).toEqual({
    status: "tool_error",
    error: { source: "tool", toolName: "get_ticket", message: 'Tool "get_ticket" execution failed', code: "EXECUTION" },
  });
});
```

That's the same result the model would get from calling `get_ticket` directly, so for a single call nothing changes but the path. Try `T-9` in the Call tab: the tool's error message comes through too.

## Several calls in one script: `codecall:execute`

"Which urgent tickets are open, and whose are they?" needs a search and then one customer lookup per ticket. With `codecall:execute`, the model writes those calls as a short JavaScript program and sends it in one request. The script runs on your server, and only what it returns goes back to the model:

```js
const { tickets } = await callTool("search_tickets", { status: "open", priority: "high" });
const out = [];
for (const t of tickets) {
  const customer = await callTool("get_customer", { id: t.customerId });
  out.push({ ticket: t.id, title: t.title, customer: customer.name });
}
return out;
```

Here are the tools above again, with `codecall:execute` already called with that script. It's in the **Call** tab's `script` field: change it and send it again.

`codecall:execute` answers:

```json
{
  "status": "ok",
  "result": [
    { "ticket": "T-1", "title": "Cannot log in", "customer": "Acme Corp" },
    { "ticket": "T-3", "title": "Export times out", "customer": "Globex" }
  ]
}
```

Three tool calls, one round trip, and the model reads two short lines instead of every field of every ticket and customer. The script is the body of an `async` function: it uses `await` at the top level and `return`s its answer.

- **`callTool(name, input)`** calls a tool through the same flow as a direct call, with the caller's credentials, and returns the object the tool returned.
- **`parallel(items, fn)`** runs calls at once, like `Promise.all`, for up to 100 items.
- **`mcpLog(level, message)`** adds a line to the result's `logs`, since there's no `console`.
- **`status`** says how the script ended: `ok` with a `result`, or `syntax_error`, `illegal_access`, `tool_error`, `runtime_error` or `timeout`, with an `error`. `codecall:execute` answers with such an object whatever happens to the script, so the model can read what went wrong and try again. Only input that doesn't fit its schema, like a script under 23 characters, is an error result.

The same question with `parallel()`, which fetches the customers at once:

```js
const { tickets } = await callTool("search_tickets", { status: "open" });
const owners = await parallel(tickets, (t) => callTool("get_customer", { id: t.customerId }));
return tickets.map((t, i) => `${t.id}: ${owners[i].name}`);
// { "status": "ok", "result": ["T-1: Acme Corp", "T-2: Globex", "T-3: Globex"] }
```

> **Note**
A server runs a script in a sandbox built on Node's `vm` module, which a browser doesn't have, so the Playground runs it in Enclave's browser sandbox: two nested iframes with no network and no `eval`. The checks and the tool calls are the same, and the tests on this page pass on Node and in the browser. A few things differ, like `getTool()`, which only answers for names written in the script. [In the Playground](https://frontmcp.dev/reference/plugins/codecall#in-the-playground) lists them.

## What a script may do

A script is code a model wrote, so CodeCall treats it as untrusted. It runs a subset of JavaScript called **AgentScript**, and checks each script before running any of it. Anything outside the subset is refused with `illegal_access`, and nothing in the script runs, not even the tool calls before the refused line. Checking works in the Playground too, so the tests here try some refused scripts. Add your own:

```ts refused.test.ts active
import { test, expect } from "@frontmcp/testing";
import { closed } from "./tools";

const run = async (mcp: any, script: string) => (await mcp.tools.call("codecall:execute", { script })).json();

test("while loops are refused; use for…of", async ({ mcp }) => {
  const result = await run(mcp, "let page = 1;\nwhile (page < 3) { page++; }\nreturn callTool('search_tickets', {});");
  expect(result.status).toBe("illegal_access");
  expect(result.error.message).toContain("FORBIDDEN_LOOP (line 2): Only for-of loops are allowed (vm.allowLoops is false) (while loop)");
});

test("so are counting for loops, in the default preset", async ({ mcp }) => {
  const result = await run(mcp, "let n = 0;\nfor (let i = 0; i < 3; i++) { n += i; }\nreturn callTool('search_tickets', {});");
  expect(result.error.message).toContain("FORBIDDEN_LOOP (line 2): Only for-of loops are allowed (vm.allowLoops is false) (for loop)");
});

test("function declarations are refused; use arrow functions", async ({ mcp }) => {
  const result = await run(mcp, "function isOpen(t) { return t.status === 'open'; }\nreturn callTool('search_tickets', {});");
  expect(result.error.message).toContain("NO_USER_FUNCTION_DECLARATION");
});

test("console doesn't exist; use mcpLog()", async ({ mcp }) => {
  const result = await run(mcp, "console.log('hi');\nreturn callTool('search_tickets', {});");
  expect(result.error.message).toContain("DISALLOWED_IDENTIFIER (line 1): console is not available in CodeCall scripts; use mcpLog(level, message)");
});

test("eval, process and constructor tricks are refused", async ({ mcp }) => {
  for (const script of [
    "return eval(\"callTool('search_tickets', {})\");",
    "const env = process.env;\nreturn callTool('search_tickets', {});",
    "return [].constructor.constructor('return process')();",
  ]) {
    expect((await run(mcp, script)).status).toBe("illegal_access");
  }
});

test("a refused script has no effect at all", async ({ mcp }) => {
  const result = await run(mcp, "await callTool('close_ticket', { id: 'T-2' });\nwhile (true) {}\nreturn 1;");
  expect(result.status).toBe("illegal_access");
  expect(closed).toEqual([]); // close_ticket never ran
});

test("a script shorter than 23 characters is an error result", async ({ mcp }) => {
  expect(await mcp.tools.call("codecall:execute", { script: "return 1 + 1" })).toBeError("INVALID_INPUT");
});
```

```ts tools.ts
import { Tool, ToolContext, z } from "@frontmcp/sdk";

export const closed: string[] = [];

@Tool({ name: "search_tickets", description: "Search support tickets by status", inputSchema: { status: z.enum(["open", "closed"]).optional() } })
export class SearchTickets extends ToolContext {
  async execute(_input: { status?: "open" | "closed" }) {
    return { tickets: [{ id: "T-1", status: "open" }, { id: "T-2", status: "open" }] };
  }
}

@Tool({ name: "close_ticket", description: "Close a support ticket", inputSchema: { id: z.string() } })
export class CloseTicket extends ToolContext {
  async execute({ id }: { id: string }) {
    closed.push(id);
    return { id, status: "closed" };
  }
}
```

```ts help-desk.app.ts
import { App } from "@frontmcp/sdk";
import { CodeCallPlugin } from "@frontmcp/plugin-codecall";
import { CloseTicket, SearchTickets } from "./tools";

@App({ id: "help-desk", name: "Help Desk", tools: [SearchTickets, CloseTicket], plugins: [CodeCallPlugin.init({ mode: "codecall_only" })] })
export class HelpDeskApp {}
```

| A script may use | It may not use |
| --- | --- |
| `const`, `let`, `if`, `for…of` (with `break` and `continue`), `try`/`catch`, `throw` | `for (…; …; …)`, unless the server sets `vm.allowLoops`; `while`, `do…while`, `for…in` |
| Arrow functions, also `async` | `function` declarations and expressions, `class` |
| `callTool()`, `parallel()`, `getTool()`, `mcpLog()`, `mcpNotify()` | `console`, `fetch`, `setTimeout`, `process`, `require`, `import()`, `globalThis` |
| `Math`, `JSON`, `Object`, `Array`, `String`, `Number`, `Date`, and their methods | `eval`, `Function`, `.constructor`, `__proto__`, regular expressions, `Promise`, `Map`, `Set`, `Error` |

A tool's name in `callTool()` must be a string literal: `callTool(name, …)` with a variable is refused. To throw an error of your own, `throw "message"`. [AgentScript](https://frontmcp.dev/reference/plugins/codecall#agentscript) lists every rule.

A script that passes the checks runs in a fresh sandbox, within limits set by the `vm` option's preset (`secure` by default):

| Limit | `secure` | When it's reached |
| --- | --- | --- |
| Computing time | 3.5 seconds | The script ends with `timeout`. Time spent waiting for tools doesn't count. |
| Tool calls | 5,000 per script | `runtime_error`: `Maximum tool call limit exceeded (5000). …` |
| Loop iterations | 10,000 per loop, in every preset | `runtime_error`: `Maximum iteration limit exceeded (10000). This limit prevents infinite loops.` |
| Items in `parallel()` | 100 | `runtime_error`: `parallel() is limited to 100 items` |

[`vm`](https://frontmcp.dev/reference/plugins/codecall#vm) lists the other presets.

> **Pitfall: The sandbox stops scripts that look like an attack**
While a script runs, the sandbox watches the order of its tool calls by tool name, and stops patterns an attacker would use: a `delete…` call soon after a read, a `send…` or `export…` call after a `list…`, and a few more. They match parts of names, so ordinary scripts trip them too. This script, which looks up a customer and deletes it,

```js
const customer = await callTool("get_customer", { id: "C-2" });
return await callTool("delete_customer", { id: customer.id });
```

ends with `{ "status": "runtime_error", "error": { "source": "script", "message": "Suspicious pattern detected: Delete operation after data access (potential cover-up) [DELETE_AFTER_ACCESS]", "name": "Error" } }`. CodeCall has no option to turn this off. Split such work over two requests, or make the second call with `codecall:invoke`. [Limits](https://frontmcp.dev/reference/plugins/codecall#limits) lists every pattern.

**Deep dive: When a tool fails inside a script**
A failed call throws, and a script can catch it. The error has a `message`, but not the tool's: to the script, every failure reads `Tool "get_ticket" execution failed` (or `was not found`, `timed out`, `Access denied`):

```js
try {
  return await callTool("get_ticket", { id: "T-9" });
} catch (error) {
  return { failed: error.message };
}
// { "status": "ok", "result": { "failed": "Tool \"get_ticket\" execution failed" } }
```

The tool said `There's no ticket T-9.`, and that doesn't reach the script. Without the `try`, the script ends with `{ "status": "tool_error", "error": { "source": "tool", "toolName": "get_ticket", "message": "Tool \"get_ticket\" execution failed", "code": "EXECUTION" } }`. When the model needs to know why, have the tool return an answer instead of failing, like `{ found: false }`, or call it with `codecall:invoke`, which returns the tool's own error. `callTool(name, input, { throwOnError: false })` returns `{ success, data }` or `{ success, error }` instead of throwing, with the same message.

## Keeping tools away from CodeCall

CodeCall may use every tool of the server by default, so a script can call a destructive tool as easily as a harmless one, as often as the limits allow. Keep such tools away from it, with `codecall: { enabledInCodeCall: false }` on the tool, or an `includeTools` rule, which can read the tool's annotations:

```ts help-desk.app.ts active
import { App } from "@frontmcp/sdk";
import { CodeCallPlugin } from "@frontmcp/plugin-codecall";
import { DeleteCustomer, GetTicket, PurgeClosedTickets } from "./tools";

@App({
  id: "help-desk",
  name: "Help Desk",
  tools: [GetTicket, DeleteCustomer, PurgeClosedTickets],
  plugins: [
    CodeCallPlugin.init({
      mode: "codecall_only",
      // ✅ No tool marked destructive, whoever wrote it
      includeTools: (tool) => !tool.annotations?.destructiveHint,
    }),
  ],
})
export class HelpDeskApp {}
```

```ts tools.ts
import { Tool, ToolContext, z } from "@frontmcp/sdk";

@Tool({ name: "get_ticket", description: "Get one support ticket by its id", inputSchema: { id: z.string() } })
export class GetTicket extends ToolContext {
  async execute({ id }: { id: string }) {
    return { id, title: "Cannot log in", status: "open" };
  }
}

@Tool({
  name: "delete_customer",
  description: "Delete a customer and all their tickets",
  inputSchema: { id: z.string() },
  codecall: { enabledInCodeCall: false }, // ✅ never through CodeCall
})
export class DeleteCustomer extends ToolContext {
  async execute({ id }: { id: string }) {
    return { deleted: id };
  }
}

@Tool({
  name: "purge_closed_tickets",
  description: "Delete every closed ticket",
  inputSchema: {},
  annotations: { destructiveHint: true },
})
export class PurgeClosedTickets extends ToolContext {
  async execute() {
    return { purged: 12 };
  }
}
```

```ts away.test.ts
import { test, expect } from "@frontmcp/testing";

test("codecall:describe only knows get_ticket", async ({ mcp }) => {
  const result = await mcp.tools.call("codecall:describe", { toolNames: ["get_ticket", "delete_customer", "purge_closed_tickets"] });
  expect(result.json().tools.map((t: { name: string }) => t.name)).toEqual(["get_ticket"]);
  expect(result.json().notFound).toEqual(["delete_customer", "purge_closed_tickets"]);
});

test("codecall:invoke refuses the other two", async ({ mcp }) => {
  const result = await mcp.tools.call("codecall:invoke", { tool: "purge_closed_tickets", input: {} });
  expect(result).toBeError();
  expect(result).toHaveTextContent('Tool "purge_closed_tickets" is not available.');
});

test("a client that knows the name can't call them directly either", async ({ mcp }) => {
  const result = await mcp.tools.call("delete_customer", { id: "C-1" });
  expect(result).toBeError("TOOL_NOT_FOUND");
  expect(result).toHaveTextContent('Tool "delete_customer" not found');
});
```

A tool CodeCall may not use isn't found by search, is in `notFound` for `codecall:describe`, is refused by `codecall:invoke`, and gets `Access denied for tool "delete_customer"` in a script. The model can't tell whether such a tool exists. The reverse option, `codecall: { visibleInListTools: true }`, keeps a tool in `tools/list` next to CodeCall's, for one the model should always see, like a health check.

In `codecall_only`, as here, a client that sends `tools/call` for `delete_customer` by name gets `Tool "delete_customer" not found` too, as the last test shows: CodeCall takes it out of `tools/list`, and FrontMCP answers a direct call of a tool CodeCall hides as if it didn't exist. Your own tools can still call it with `this.callTool()`.

> **Pitfall: In the other modes, a listed tool can be called directly**
`codecall_opt_in` and `metadata_driven` leave tools in `tools/list`, and a client can call a listed tool by name, even one CodeCall refuses. To decide who may call a tool, use [`authorities`](https://frontmcp.dev/learn/authorizing-calls#declaring-who-may-call-a-tool), which apply to direct calls and to CodeCall's alike. (Before FrontMCP 1.9, a client could call any tool by name, in every mode: hiding it was the only protection.)

The [CRM with CodeCall](https://frontmcp.dev/examples/crm-with-codecall) example puts 47 tools behind CodeCall, keeps the destructive ones out of reach, and runs one script that combines eight calls.

## Recap

- Every listed tool is read by the model before every answer. A long list costs context and accuracy, and multi-step tasks cost a round trip per call.
- `CodeCallPlugin.init({ mode: "codecall_only" })` lists CodeCall's six tools instead of yours. They're about 21 KB, whatever is behind them, so CodeCall pays off for large servers, not small ones.
- The model finds tools with `codecall:search` (words, not meaning), reads their schemas with `codecall:describe`, and calls one with `codecall:invoke`. Give tools good descriptions and `examples`.
- `codecall:execute` runs an AgentScript program on the server that calls several tools and returns only the answer. Scripts are checked before they run, and run within limits. The Playground runs them in a browser sandbox.
- Keep destructive tools away with `enabledInCodeCall: false` or `includeTools`. In `codecall_only`, a client can't call them by name either; in the other modes, use `authorities` for that.
- Every option, AgentScript's rules and the sandbox's limits are in the [CodeCall reference](https://frontmcp.dev/reference/plugins/codecall).

## Try some challenges

Each challenge runs hidden checks against your code. Edit the code, then press **Check**.

### Challenge: Put the help desk behind CodeCall
The help desk lists its tools directly. Put them behind CodeCall, so the model sees CodeCall's tools and reaches the help desk's through them.

```ts help-desk.app.ts active
import { App } from "@frontmcp/sdk";
import { CloseTicket, GetCustomer, GetTicket, SearchTickets } from "./tools";

@App({
  id: "help-desk",
  name: "Help Desk",
  tools: [SearchTickets, GetTicket, CloseTicket, GetCustomer],
})
export class HelpDeskApp {}
```

```ts help-desk.app.ts solution
import { App } from "@frontmcp/sdk";
import { CodeCallPlugin } from "@frontmcp/plugin-codecall";
import { CloseTicket, GetCustomer, GetTicket, SearchTickets } from "./tools";

@App({
  id: "help-desk",
  name: "Help Desk",
  tools: [SearchTickets, GetTicket, CloseTicket, GetCustomer],
  plugins: [CodeCallPlugin.init({ mode: "codecall_only" })],
})
export class HelpDeskApp {}
```

```ts tools.ts
import { PublicMcpError, Tool, ToolContext, z } from "@frontmcp/sdk";

const tickets = [
  { id: "T-1", title: "Cannot log in", status: "open", customerId: "C-1" },
  { id: "T-2", title: "Invoice shows the wrong total", status: "open", customerId: "C-2" },
];

@Tool({ name: "search_tickets", description: "Search support tickets by status", inputSchema: { status: z.enum(["open", "closed"]).optional() } })
export class SearchTickets extends ToolContext {
  async execute({ status }: { status?: "open" | "closed" }) {
    return { tickets: tickets.filter((t) => !status || t.status === status) };
  }
}

@Tool({ name: "get_ticket", description: "Get one support ticket by its id", inputSchema: { id: z.string() } })
export class GetTicket extends ToolContext {
  async execute({ id }: { id: string }) {
    return tickets.find((t) => t.id === id) ?? this.fail(new PublicMcpError(`There's no ticket ${id}.`));
  }
}

@Tool({ name: "close_ticket", description: "Close a support ticket", inputSchema: { id: z.string() } })
export class CloseTicket extends ToolContext {
  async execute({ id }: { id: string }) {
    return { id, status: "closed" };
  }
}

@Tool({ name: "get_customer", description: "Get a customer account by its id", inputSchema: { id: z.string() } })
export class GetCustomer extends ToolContext {
  async execute({ id }: { id: string }) {
    return { id, name: id === "C-1" ? "Acme Corp" : "Globex" };
  }
}
```

```ts codecall.test.ts hidden
import { test, expect } from "@frontmcp/testing";

test("`tools/list` shows CodeCall's tools, not the help desk's", async ({ mcp }) => {
  const tools = await mcp.tools.list();
  expect(tools).toContainTool("codecall:search");
  expect(tools).toContainTool("codecall:execute");
  expect(tools).not.toContainTool("get_ticket");
});

test("`codecall:describe` knows `get_ticket`", async ({ mcp }) => {
  const result = await mcp.tools.call("codecall:describe", { toolNames: ["get_ticket"] });
  expect(result).toBeSuccessful();
  expect(result.json().tools[0].name).toBe("get_ticket");
});

test("`codecall:invoke` calls it", async ({ mcp }) => {
  const result = await mcp.tools.call("codecall:invoke", { tool: "get_ticket", input: { id: "T-1" } });
  expect(result.json()).toMatchObject({ id: "T-1", title: "Cannot log in" });
});
```

**Hint:**
CodeCall is a plugin. Remember to call `init()`.

**Solution:**
`plugins: [CodeCallPlugin.init({ mode: "codecall_only" })]` registers CodeCall on the app. `tools/list` now shows its six tools, and the four help desk tools are reached through `codecall:search`, `codecall:describe`, `codecall:invoke` and `codecall:execute`. `CodeCallPlugin.init()` works too, since `"codecall_only"` is the default; `plugins: [CodeCallPlugin]` without `init()` starts, but every CodeCall tool then fails with `Provider "CodeCallConfig" is not available`.

### Challenge: Keep destructive tools out of scripts
CodeCall can reach `delete_customer` and `purge_closed_tickets`, so a model's script could call them in a loop. Keep every tool annotated `destructiveHint` away from CodeCall, including ones added later, and leave the rest available.

```ts help-desk.app.ts active
import { App } from "@frontmcp/sdk";
import { CodeCallPlugin } from "@frontmcp/plugin-codecall";
import { CloseTicket, DeleteCustomer, GetTicket, PurgeClosedTickets } from "./tools";

@App({
  id: "help-desk",
  name: "Help Desk",
  tools: [GetTicket, CloseTicket, DeleteCustomer, PurgeClosedTickets],
  plugins: [CodeCallPlugin.init({ mode: "codecall_only" })],
})
export class HelpDeskApp {}
```

```ts help-desk.app.ts solution
import { App } from "@frontmcp/sdk";
import { CodeCallPlugin } from "@frontmcp/plugin-codecall";
import { CloseTicket, DeleteCustomer, GetTicket, PurgeClosedTickets } from "./tools";

@App({
  id: "help-desk",
  name: "Help Desk",
  tools: [GetTicket, CloseTicket, DeleteCustomer, PurgeClosedTickets],
  plugins: [
    CodeCallPlugin.init({
      mode: "codecall_only",
      includeTools: (tool) => !tool.annotations?.destructiveHint,
    }),
  ],
})
export class HelpDeskApp {}
```

```ts tools.ts
import { Tool, ToolContext, z } from "@frontmcp/sdk";

@Tool({ name: "get_ticket", description: "Get one support ticket by its id", inputSchema: { id: z.string() } })
export class GetTicket extends ToolContext {
  async execute({ id }: { id: string }) {
    return { id, title: "Cannot log in", status: "open" };
  }
}

@Tool({ name: "close_ticket", description: "Close a support ticket", inputSchema: { id: z.string() } })
export class CloseTicket extends ToolContext {
  async execute({ id }: { id: string }) {
    return { id, status: "closed" };
  }
}

@Tool({
  name: "delete_customer",
  description: "Delete a customer and all their tickets",
  inputSchema: { id: z.string() },
  annotations: { destructiveHint: true },
})
export class DeleteCustomer extends ToolContext {
  async execute({ id }: { id: string }) {
    return { deleted: id };
  }
}

@Tool({
  name: "purge_closed_tickets",
  description: "Delete every closed ticket",
  inputSchema: {},
  annotations: { destructiveHint: true },
})
export class PurgeClosedTickets extends ToolContext {
  async execute() {
    return { purged: 12 };
  }
}
```

```ts away.test.ts hidden
import { test, expect } from "@frontmcp/testing";
import { App, FrontMcpInstance, Tool, ToolContext } from "@frontmcp/sdk";
import { HelpDeskApp } from "./help-desk.app";

test("`delete_customer` and `purge_closed_tickets` are out of CodeCall's reach", async ({ mcp }) => {
  const result = await mcp.tools.call("codecall:describe", { toolNames: ["delete_customer", "purge_closed_tickets"] });
  expect(result.json().notFound).toEqual(["delete_customer", "purge_closed_tickets"]);
  const invoked = await mcp.tools.call("codecall:invoke", { tool: "delete_customer", input: { id: "C-1" } });
  expect(invoked).toHaveTextContent('Tool "delete_customer" is not available.');
});

test("`get_ticket` and `close_ticket` are still available", async ({ mcp }) => {
  const result = await mcp.tools.call("codecall:describe", { toolNames: ["get_ticket", "close_ticket"] });
  expect(result.json().notFound).toBeUndefined();
  expect(await mcp.tools.call("codecall:invoke", { tool: "close_ticket", input: { id: "T-1" } })).toBeSuccessful();
});

test("so is a destructive tool added later", async () => {
  @Tool({ name: "wipe_macros", description: "Delete every macro", inputSchema: {}, annotations: { destructiveHint: true } })
  class WipeMacros extends ToolContext {
    async execute() {
      return { wiped: 3 };
    }
  }
  @App({ id: "macros", name: "Macros", tools: [WipeMacros] })
  class MacrosApp {}

  const server = await FrontMcpInstance.createDirect({ info: { name: "help-desk", version: "1.0.0" }, apps: [HelpDeskApp, MacrosApp] });
  const asReviewer = { authContext: { user: { sub: "reviewer" } } };
  const result = await server.callTool("codecall:describe", { toolNames: ["get_ticket", "wipe_macros"] }, asReviewer);
  await server.dispose();
  expect((result.structuredContent as { notFound?: string[] }).notFound).toEqual(["wipe_macros"]);
});
```

**Hint:**
Both tools are already annotated. One CodeCall option decides which tools it may use, and it gets each tool's annotations.

**Solution:**
`includeTools` is called with each tool, including its `annotations`, and CodeCall only uses the tools it returns `true` for. Returning `!tool.annotations?.destructiveHint` keeps out both tools, and any tool annotated the same way later. `codecall: { enabledInCodeCall: false }` on each tool would keep these two out, but not the next one someone adds. Either way, a client can still call them by name: use [`authorities`](https://frontmcp.dev/learn/authorizing-calls) for that.

### Challenge: Show the model the call you'd make
When the model describes `search_tickets`, the example it reads is one CodeCall wrote from the schema: open tickets, with no priority. The help desk's most common search is for open tickets with high priority. Give the tool an example of its own for that search, so it's the call the model sees, and CodeCall's guess isn't listed next to it.

```ts tools.ts active
import { Tool, ToolContext, z } from "@frontmcp/sdk";

const tickets = [
  { id: "T-1", title: "Cannot log in", status: "open", priority: "high" },
  { id: "T-2", title: "Invoice shows the wrong total", status: "open", priority: "low" },
  { id: "T-4", title: "Password reset email never arrives", status: "closed", priority: "high" },
];

@Tool({
  name: "search_tickets",
  description: "Search support tickets by status and priority",
  inputSchema: { status: z.enum(["open", "closed"]).optional(), priority: z.enum(["low", "high"]).optional() },
})
export class SearchTickets extends ToolContext {
  async execute({ status, priority }: { status?: "open" | "closed"; priority?: "low" | "high" }) {
    return { tickets: tickets.filter((t) => (!status || t.status === status) && (!priority || t.priority === priority)) };
  }
}
```

```ts tools.ts solution
import { Tool, ToolContext, z } from "@frontmcp/sdk";

const tickets = [
  { id: "T-1", title: "Cannot log in", status: "open", priority: "high" },
  { id: "T-2", title: "Invoice shows the wrong total", status: "open", priority: "low" },
  { id: "T-4", title: "Password reset email never arrives", status: "closed", priority: "high" },
];

@Tool({
  name: "search_tickets",
  description: "Search support tickets by status and priority",
  inputSchema: { status: z.enum(["open", "closed"]).optional(), priority: z.enum(["low", "high"]).optional() },
  examples: [{ description: "Open tickets with high priority", input: { status: "open", priority: "high" } }],
})
export class SearchTickets extends ToolContext {
  async execute({ status, priority }: { status?: "open" | "closed"; priority?: "low" | "high" }) {
    return { tickets: tickets.filter((t) => (!status || t.status === status) && (!priority || t.priority === priority)) };
  }
}
```

```ts help-desk.app.ts
import { App } from "@frontmcp/sdk";
import { CodeCallPlugin } from "@frontmcp/plugin-codecall";
import { SearchTickets } from "./tools";

@App({ id: "help-desk", name: "Help Desk", tools: [SearchTickets], plugins: [CodeCallPlugin.init({ mode: "codecall_only" })] })
export class HelpDeskApp {}
```

```ts example.test.ts hidden
import { test, expect } from "@frontmcp/testing";

const usageExamples = async (mcp: any) =>
  (await mcp.tools.call("codecall:describe", { toolNames: ["search_tickets"] })).json().tools[0].usageExamples;

test("the example asks for open tickets with high priority", async ({ mcp }) => {
  const [example] = await usageExamples(mcp);
  expect(example.code).toContain('"status": "open"');
  expect(example.code).toContain('"priority": "high"');
});

test("CodeCall's own guess isn't listed next to it", async ({ mcp }) => {
  const examples = await usageExamples(mcp);
  expect(examples).toHaveLength(1);
  expect(examples[0].code).not.toContain("results.items");
});

test("its input finds the ticket", async ({ mcp }) => {
  const result = await mcp.tools.call("codecall:invoke", { tool: "search_tickets", input: { status: "open", priority: "high" } });
  expect(result.json().tickets.map((t: { id: string }) => t.id)).toEqual(["T-1"]);
});
```

**Hint:**
`@Tool` takes `examples`: a list of `{ description, input }`.

**Solution:**
`examples: [{ description: "Open tickets with high priority", input: { status: "open", priority: "high" } }]` gives `codecall:describe` a usage example of your own, and it shows yours alone: when a tool has examples, CodeCall doesn't write one next to them. Without it, the model would have copied the guess, which asks for open tickets and leaves out the priority. The examples are indexed for `codecall:search` too, so their descriptions help the model find the tool.
