Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/docs/content/1.getting-started/2.installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ This will give you access to prompts like `setup-mcp-server`, `create-tool`, `cr
- Node.js 18.x or higher
- A package manager (npm, pnpm, yarn, or bun)

If you enable Code Mode, that feature specifically requires Node.js `>=18.16.0`.

## Installation

::steps
Expand Down
2 changes: 2 additions & 0 deletions apps/docs/content/2.core-concepts/5.handlers.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ export default defineMcpHandler({
})
```

Code Mode requires `secure-exec` and Node.js `>=18.16.0`.

::callout{icon="i-lucide-book-open" color="primary"}
See the [Code Mode guide](/advanced/code-mode) for full documentation, security details, and configuration options.
::
Expand Down
83 changes: 81 additions & 2 deletions apps/docs/content/3.advanced/8.code-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ bun add secure-exec

::

::callout{icon="i-lucide-info" color="info"}
Code Mode requires `secure-exec` and Node.js `>=18.16.0`. The rest of the module still supports Node.js 18.x, but code mode depends on `AsyncLocalStorage.snapshot()` to preserve request context.
::

### 2. Enable on a handler

Add `experimental_codeMode` to any handler:
Expand Down Expand Up @@ -194,6 +198,10 @@ export default defineMcpHandler({
memoryLimit: 64,
cpuTimeLimitMs: 10_000,
maxResultSize: 102_400,
maxRequestBodyBytes: 1_048_576,
maxToolResponseSize: 1_048_576,
wallTimeLimitMs: 60_000,
maxToolCalls: 200,
progressive: false,
description: undefined,
},
Expand All @@ -219,6 +227,30 @@ export default defineMcpHandler({
Maximum result size in bytes before truncation. Large results are intelligently truncated — arrays by number of items, objects by number of keys.
::

::field{name="maxRequestBodyBytes" type="number"}
Default: `1048576` (1 MB)

Maximum bytes accepted in a single RPC request body from the sandbox. Returns HTTP 413 if exceeded. Prevents memory exhaustion from oversized payloads.
::

::field{name="maxToolResponseSize" type="number"}
Default: `1048576` (1 MB)

Maximum bytes for individual tool RPC responses. Large results are truncated using the same strategy as `maxResultSize`.
::

::field{name="wallTimeLimitMs" type="number"}
Default: `60000` (60 seconds)

Wall-clock timeout for the entire execution. Unlike `cpuTimeLimitMs` which only covers CPU time in the isolate, this caps total elapsed time including host-side tool calls. Returns HTTP 408 on timeout.
::

::field{name="maxToolCalls" type="number"}
Default: `200`

Maximum number of tool RPC calls per execution. Prevents runaway loops that repeatedly invoke expensive tools. Returns HTTP 429 when exceeded.
::

::field{name="progressive" type="boolean"}
Default: `false`

Expand All @@ -228,10 +260,32 @@ export default defineMcpHandler({
::field{name="description" type="string"}
Default: built-in template

Custom description for the `code` tool. Supports `{{types}}` and `{{count}}` placeholders.
Custom description for the `code` tool. Supports `{{types}}`, `{{count}}`, and `{{example}}` placeholders.
::
::

## Annotation Surfacing

MCP tool annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`) are automatically surfaced as tags in code mode type signatures and search results:

```typescript
export default defineMcpTool({
name: 'delete-runbook',
description: 'Delete a runbook',
inputSchema: { id: z.string() },
annotations: { destructiveHint: true },
handler: async ({ id }) => { /* ... */ },
})
```

This produces a type signature like:

```typescript
delete_runbook: (input: { id: string }) => Promise<unknown>; // [destructive] Delete a runbook
```

Available tags: `[read-only]`, `[destructive]`, `[idempotent]`. Tags are shown when the tool has annotations set.

## Progressive Mode

When your server exposes many tools (50+), embedding all type definitions in the `code` tool description becomes expensive in tokens. Progressive mode solves this by splitting into two tools:
Expand All @@ -247,6 +301,23 @@ export default defineMcpHandler({
})
```

When tools have a `group` field, search results are grouped by section with annotation tags:

```txt
search({ query: "runbook" }) →

Found 3/6 tools matching "runbook":

## workspace

codemode.create_runbook: (...) => Promise<{ ok: boolean; data: { id: string } }>;
codemode.delete_runbook: (input: { id: string }) => Promise<unknown>; // [destructive] Irreversible

## public

codemode.search_public: (input: { q: string }) => Promise<Runbook[]>; // [read-only] Full-text search
```

The LLM workflow becomes:

::code-group
Expand Down Expand Up @@ -287,7 +358,7 @@ Always combine related operations into a single code block.`,
})
```

The `{{types}}` placeholder is replaced with the generated TypeScript definitions. The `{{count}}` placeholder is replaced with the number of available tools.
Available placeholders: `{{types}}` (TypeScript definitions), `{{count}}` (tool count), `{{example}}` (usage example).

In progressive mode, `{{types}}` is not available since types are discovered via the `search` tool.

Expand Down Expand Up @@ -328,8 +399,12 @@ This prevents other local processes from calling your MCP tools through the RPC
| Resource | Default | Configurable | Protection |
|----------|---------|-------------|------------|
| **CPU time** | 10 seconds | `cpuTimeLimitMs` | Sandbox is killed on timeout — prevents infinite loops |
| **Wall-clock time** | 60 seconds | `wallTimeLimitMs` | Caps total elapsed time including tool calls |
| **Memory** | 64 MB | `memoryLimit` | V8 isolate hard limit — prevents OOM crashes |
| **Result size** | 100 KB | `maxResultSize` | Intelligent truncation (arrays by items, objects by keys) |
| **Tool response size** | 1 MB | `maxToolResponseSize` | Per-call truncation before response delivery |
| **Request body size** | 1 MB | `maxRequestBodyBytes` | HTTP 413 early rejection — prevents memory exhaustion |
| **Tool calls per execution** | 200 | `maxToolCalls` | HTTP 429 rate limit — prevents runaway loops |
| **Log entries** | 200 | No | Console output capped — prevents `console.log` flooding |

### Input Validation
Expand All @@ -340,6 +415,10 @@ Tool names are interpolated into the sandbox code template. To prevent code inje
- **Sanitization** — Names are sanitized upstream (`get-user` → `get_user`), but a second validation layer at the template level ensures defense in depth.
- **Rejection** — If a name fails validation, the execution throws immediately — no partial injection.

### Error Sanitization

Infrastructure errors (file paths, stack traces) are sanitized before being returned to the sandbox or the MCP client. Full error details are logged server-side with the `[nuxt-mcp-toolkit]` prefix for debugging.

### Summary

::callout{icon="i-lucide-shield" color="info"}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export async function execute(
+ '(requires Node.js: secure-exec, node:http). Use a Node.js deployment target '
+ 'or disable experimental_codeMode.',
logs: [],
durationMs: 0,
}
}

Expand Down
Loading
Loading