Skip to content

Code Mode#533

Open
0xKoller wants to merge 7 commits intocanaryfrom
code-mode-xmcp-415
Open

Code Mode#533
0xKoller wants to merge 7 commits intocanaryfrom
code-mode-xmcp-415

Conversation

@0xKoller
Copy link
Copy Markdown
Collaborator

Code Mode: Tool Introspection & Inter-Tool Calling

Adds two new primitives to xmcp's extra parameter, enabling the Code Mode hundreds of tools are exposed through just 2 meta-tools (search + execute), reducing context window token usage by 99%+.

Core changes

  • extra.listTools() - Returns cached metadata and JSON Schema for all registered tools
  • extra.callTool(name, args) - Invokes another tool by name with validation, error handling, and auth propagation. Bypasses middleware (direct handler invocation). Returns CallToolResult with isError: true on failure, never throws.
  • internal annotation - Tools marked internal: true are hidden from MCP's tools/list (saving tokens) but remain accessible via callTool and listTools
  • ToolRegistry class - Internal registry populated during addToolsToServer(), caches Zod-to-JSON-Schema conversion at startup using MCP SDK's toJsonSchemaCompat()

@linear
Copy link
Copy Markdown

linear bot commented Mar 26, 2026

XMCP-415 Code Mode

@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Mar 26, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
xmcp-website Ready Ready Preview, Comment Mar 26, 2026 3:58pm

@0xKoller 0xKoller marked this pull request as ready for review March 26, 2026 15:43
@0xKoller 0xKoller requested a review from valebearzotti as a code owner March 26, 2026 15:43
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 26, 2026

Rspack Bundle Analysis

Build Assets Total Size (MB) Build Time
Main Compiler 4 6.33 5.10s
Runtime Compiler 6 5.36 5.90s

Main Compiler

Source: stats-main.json

Asset Chunk Size (KB) Size (MB)
cli.js cli 6016.37 5.88
index.js index 368.80 0.36
cloudflare.js cloudflare 81.70 0.08
detached-flush.js detached-flush 15.18 0.01

Total emitted JS: 6.33 MB

Runtime Compiler

Source: stats-runtime.json

Asset Chunk Size (KB) Size (MB)
http.js http 1307.18 1.28
adapter-nestjs.js adapter-nestjs 1283.46 1.25
adapter-nextjs.js adapter-nextjs 1277.49 1.25
adapter-express.js adapter-express 1275.76 1.25
stdio.js stdio 344.82 0.34
headers.js headers 1.33 0.00

Total emitted JS: 5.36 MB

Package Footprint (npm pack + npm install)

Item Size (KB) Size (MB)
Tarball (.tgz) 4627.02 4.52
dist/ 12026.12 11.74
node_modules/ 101215.46 98.84
dist + node_modules 113241.58 110.59

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 26, 2026

Greptile Summary

This PR introduces the Code Mode pattern — two new primitives (extra.listTools() and extra.callTool()) backed by a new ToolRegistry class, plus an internal annotation to hide tools from tools/list while keeping them reachable via meta-tools. The overall design is sound and well-documented, but there are two functional issues and a performance nit worth addressing before merging.\n\n- JSON.parse unguarded in execute tool — both in examples/code-mode-http/src/tools/execute.ts and the quickstart snippet in code-mode.mdx, JSON.parse(args) is called without a try/catch. A malformed JSON string from an agent will throw an uncaught SyntaxError rather than returning a clean isError: true response, contradicting the "never throws" guarantee stated in the PR description.\n\n- Nested callTool does not propagate augmented extra — in tools.ts, the callTool closure passes the original MCP extra (not augmentedExtra) to registry.call(). Tools invoked via callTool receive raw extra without listTools/callTool methods, silently breaking the tool-chaining pattern the PR explicitly advertises. One-line fix: pass augmentedExtra instead of extra.\n\n- transformToolHandler re-created on every registry.call() invocation — minor performance nit; the wrapped handler could be cached at registration time.

Confidence Score: 3/5

Two P1 bugs — unguarded JSON.parse and broken augmented-extra propagation — should be fixed before merging.

The core design is solid but the unguarded JSON.parse will throw a runtime SyntaxError on malformed agent input, and the nested callTool limitation silently breaks the tool-chaining use case the PR explicitly advertises. Both are straightforward fixes, so the PR is close to merge-ready once addressed.

packages/xmcp/src/runtime/utils/tools.ts and examples/code-mode-http/src/tools/execute.ts / apps/website/content/docs/guides/code-mode.mdx

Important Files Changed

Filename Overview
packages/xmcp/src/runtime/utils/tool-registry.ts New ToolRegistry class; transformToolHandler recreated on every call() and extra passed to called tools lacks augmented listTools/callTool methods.
packages/xmcp/src/runtime/utils/tools.ts Injects listTools/callTool via wrappedHandler but passes original extra to registry.call(), breaking nested callTool usage.
examples/code-mode-http/src/tools/execute.ts execute meta-tool; JSON.parse(args) unguarded, will throw SyntaxError on invalid JSON instead of returning a clean isError response.
apps/website/content/docs/guides/code-mode.mdx New Code Mode guide; quickstart execute snippet has the same unguarded JSON.parse issue.
packages/xmcp/src/types/tool.ts Adds internal flag, ToolInfo, CallToolResultCompat, and listTools/callTool signatures — clean, well-typed additions.
examples/code-mode-http/src/utils/search-utils.ts Standalone TF-IDF + Levenshtein fuzzy search utility; logic is sound.
examples/code-mode-http/src/tools/search.ts search meta-tool with tag pre-filtering and lazy index caching; correct.
packages/xmcp/src/index.ts Exports ToolInfo and CallToolResultCompat — minimal, correct change.

Comments Outside Diff (3)

  1. examples/code-mode-http/src/tools/execute.ts, line 377-378 (link)

    P1 JSON.parse can throw an uncaught SyntaxError

    JSON.parse(args) will throw a SyntaxError if the agent passes a malformed JSON string (e.g. "{name: World}"). Because it's not wrapped in a try/catch, this exception propagates up rather than returning a clean isError: true response — contradicting the "never throws" contract of callTool.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: examples/code-mode-http/src/tools/execute.ts
    Line: 377-378
    
    Comment:
    **`JSON.parse` can throw an uncaught `SyntaxError`**
    
    `JSON.parse(args)` will throw a `SyntaxError` if the agent passes a malformed JSON string (e.g. `"{name: World}"`). Because it's not wrapped in a try/catch, this exception propagates up rather than returning a clean `isError: true` response — contradicting the "never throws" contract of `callTool`.
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.
  2. packages/xmcp/src/runtime/utils/tools.ts, line 914-916 (link)

    P1 Nested callTool calls inside a called tool will fail

    registry.call(name, toolArgs, extra) passes the original MCP extra object (without listTools or callTool on it). The inner tool's handler consequently receives raw extra, so if that tool also tries to use extra.callTool(...) or extra.listTools(), those methods won't exist — causing a silent runtime TypeError.

    The PR description explicitly shows tool chaining as a feature (e.g. onboardUser calling create-user then send-welcome-email). If any of those called tools themselves need to invoke other tools, it will break.

    The fix is to pass augmentedExtra instead of extra so the augmented methods are propagated through the call chain:

    const augmentedExtra: any = {
      ...extra,
      listTools: () => registry.list(),
      callTool: (name: string, toolArgs: Record<string, unknown>) =>
        registry.call(name, toolArgs, augmentedExtra),
    };
    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: packages/xmcp/src/runtime/utils/tools.ts
    Line: 914-916
    
    Comment:
    **Nested `callTool` calls inside a called tool will fail**
    
    `registry.call(name, toolArgs, extra)` passes the original MCP `extra` object (without `listTools` or `callTool` on it). The inner tool's handler consequently receives raw `extra`, so if that tool also tries to use `extra.callTool(...)` or `extra.listTools()`, those methods won't exist — causing a silent runtime TypeError.
    
    The PR description explicitly shows tool chaining as a feature (e.g. `onboardUser` calling `create-user` then `send-welcome-email`). If any of those called tools themselves need to invoke other tools, it will break.
    
    The fix is to pass `augmentedExtra` instead of `extra` so the augmented methods are propagated through the call chain:
    
    ```typescript
    const augmentedExtra: any = {
      ...extra,
      listTools: () => registry.list(),
      callTool: (name: string, toolArgs: Record<string, unknown>) =>
        registry.call(name, toolArgs, augmentedExtra),
    };
    ```
    
    How can I resolve this? If you propose a fix, please make it concise.
  3. packages/xmcp/src/runtime/utils/tool-registry.ts, line 843-848 (link)

    P2 Transformed handler is recreated on every call() invocation

    transformToolHandler(entry.handler, ...) returns a new closure on every call. For high-throughput servers (many callTool invocations), this allocates an unnecessary new function object each time. Consider caching the transformed handler in ToolRegistryEntry at registration time instead.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: packages/xmcp/src/runtime/utils/tool-registry.ts
    Line: 843-848
    
    Comment:
    **Transformed handler is recreated on every `call()` invocation**
    
    `transformToolHandler(entry.handler, ...)` returns a new closure on every call. For high-throughput servers (many `callTool` invocations), this allocates an unnecessary new function object each time. Consider caching the transformed handler in `ToolRegistryEntry` at registration time instead.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: examples/code-mode-http/src/tools/execute.ts
Line: 377-378

Comment:
**`JSON.parse` can throw an uncaught `SyntaxError`**

`JSON.parse(args)` will throw a `SyntaxError` if the agent passes a malformed JSON string (e.g. `"{name: World}"`). Because it's not wrapped in a try/catch, this exception propagates up rather than returning a clean `isError: true` response — contradicting the "never throws" contract of `callTool`.

```suggestion
  let parsedArgs: Record<string, unknown>;
  try {
    parsedArgs = JSON.parse(args);
  } catch (e) {
    return {
      content: [{ type: "text", text: `Invalid JSON in args: ${e instanceof Error ? e.message : String(e)}` }],
      isError: true,
    };
  }
  return await extra.callTool(toolName, parsedArgs);
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: packages/xmcp/src/runtime/utils/tools.ts
Line: 914-916

Comment:
**Nested `callTool` calls inside a called tool will fail**

`registry.call(name, toolArgs, extra)` passes the original MCP `extra` object (without `listTools` or `callTool` on it). The inner tool's handler consequently receives raw `extra`, so if that tool also tries to use `extra.callTool(...)` or `extra.listTools()`, those methods won't exist — causing a silent runtime TypeError.

The PR description explicitly shows tool chaining as a feature (e.g. `onboardUser` calling `create-user` then `send-welcome-email`). If any of those called tools themselves need to invoke other tools, it will break.

The fix is to pass `augmentedExtra` instead of `extra` so the augmented methods are propagated through the call chain:

```typescript
const augmentedExtra: any = {
  ...extra,
  listTools: () => registry.list(),
  callTool: (name: string, toolArgs: Record<string, unknown>) =>
    registry.call(name, toolArgs, augmentedExtra),
};
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: packages/xmcp/src/runtime/utils/tool-registry.ts
Line: 843-848

Comment:
**Transformed handler is recreated on every `call()` invocation**

`transformToolHandler(entry.handler, ...)` returns a new closure on every call. For high-throughput servers (many `callTool` invocations), this allocates an unnecessary new function object each time. Consider caching the transformed handler in `ToolRegistryEntry` at registration time instead.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: apps/website/content/docs/guides/code-mode.mdx
Line: 98-99

Comment:
**Docs quickstart example also has unguarded `JSON.parse`**

The same `JSON.parse(args)` issue present in the example `execute.ts` appears in the documentation code snippet. Users copying this quickstart will inherit the same silent-failure behavior for invalid JSON input. The snippet should wrap the parse in a try/catch so the documented pattern is safe by default.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "Update pnpm-lock.yaml" | Re-trigger Greptile

Comment thread apps/website/content/docs/guides/code-mode.mdx
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant