Skip to content

Bug: MCP tool schemas crash in _buildNativeTools — raw JSON Schema misidentified as Zod #472

@buger

Description

@buger

Summary

After the null-destructure fix for #469 (shipped in rc266), there is a second crash in _buildNativeTools() when MCP tools are present. MCP tool inputSchema objects (raw JSON Schema) are passed to the Vercel AI SDK without wrapping, causing asSchema() to misidentify them as Zod schemas and crash.

Error

TypeError: Cannot read properties of undefined (reading 'typeName')
    at parseDef (dist/index.js:58179)
    at zodToJsonSchema (dist/index.js)
    at get jsonSchema (dist/index.js)
    at Array.map (<anonymous>)
    at prepareToolsAndToolChoice (dist/index.js:46751)
    at streamStep (dist/index.js)

Wrapped by:

NoOutputGeneratedError [AI_NoOutputGeneratedError]: No output generated. Check the stream for errors.

Steps to Reproduce

  1. Configure ProbeAgent with any MCP tools (e.g., via SSE or stdio MCP server)
  2. Update to rc266 (which fixes the null-destructure from Bug: _buildNativeTools crashes with TypeError when MCP tools are present in toolImplementations #469)
  3. Call ProbeAgent.answer() with any prompt
  4. Crash occurs before the first AI call, during prepareToolsAndToolChoice()

Debug output confirms tools load successfully — the crash happens when streamText() processes them:

[DEBUG] All Tools Initialized
[DEBUG] Native tools: 2, MCP tools: 4
[DEBUG] Available tools:
[DEBUG]   - analyze_all
[DEBUG]   - readImage
[DEBUG]   - __tools___slack-send-dm (MCP)
[DEBUG]   - __tools___slack-search (MCP)
[DEBUG]   - __tools___slack-read-thread (MCP)
[DEBUG]   - __tools___slack-download-file (MCP)

Root Cause

In _buildNativeTools(), MCP tools are added to nativeTools as raw objects, not wrapped with tool():

// _buildNativeTools(), line ~354328:
if (this.mcpBridge && !options._disableTools) {
    const mcpTools = this.mcpBridge.getVercelTools(
        this._filterMcpTools(this.mcpBridge.getToolNames())
    );
    for (const [name, mcpTool] of Object.entries(mcpTools)) {
        nativeTools[name] = mcpTool;  // ⚠️ Raw object, not a tool() instance
    }
}

MCPManager.getVercelTools() returns plain objects with raw JSON Schema:

// MCPManager.getVercelTools():
{
    description: tool.description,
    inputSchema: tool.inputSchema,  // Raw JSON Schema: { type: "object", properties: {...} }
    execute: async (args) => { ... }
}

When Vercel AI SDK's prepareToolsAndToolChoice() processes these, it calls asSchema(tool.inputSchema):

function asSchema(schema) {
    return schema == null
        ? jsonSchema({ properties: {}, additionalProperties: false })
        : isSchema(schema) ? schema              // ❌ No — raw JSON Schema lacks schemaSymbol
        : typeof schema === "function" ? schema() // ❌ No — it's an object
        : zodSchema(schema);                      // ⬅️ Falls through here!
}

isSchema() checks for a schemaSymbol property and validate method — raw JSON Schema objects don't have these. So asSchema() falls through to zodSchema(), which calls zodToJsonSchema()parseDef() → reads def.typeNameundefined → crash.

Compare with native toolswrapTool() correctly handles this:

const wrapTool = (toolName, schema, description, executeFn) => {
    // ✅ Checks for Zod (_def), otherwise wraps with jsonSchema()
    const resolvedSchema = schema && schema._def ? schema : jsonSchema(schema);
    return tool({
        description,
        inputSchema: resolvedSchema,
        execute: ...
    });
};

MCP tools bypass wrapTool() entirely and are added as raw objects without schema wrapping.

Suggested Fix

Wrap MCP tools with tool() and jsonSchema() before adding to nativeTools:

if (this.mcpBridge && !options._disableTools) {
    const mcpTools = this.mcpBridge.getVercelTools(
        this._filterMcpTools(this.mcpBridge.getToolNames())
    );
    for (const [name, mcpTool] of Object.entries(mcpTools)) {
        nativeTools[name] = tool({
            description: mcpTool.description,
            inputSchema: jsonSchema(mcpTool.inputSchema),
            execute: mcpTool.execute,
        });
    }
}

Or fix at the source in MCPManager.getVercelTools():

getVercelTools() {
    const tools = {};
    for (const [name, tool] of this.tools.entries()) {
        tools[name] = {
            description: tool.description,
            inputSchema: jsonSchema(tool.inputSchema),  // Wrap at source
            execute: async (args) => { ... }
        };
    }
    return tools;
}

Relationship to #469

This is the second of two bugs introduced by the _buildNativeTools refactor:

# Issue Status Version
1 #469_getToolSchemaAndDescription() returns null for MCP tool names → null destructure Fixed in rc266 rc265
2 This issue — MCP tools added as raw objects, JSON Schema misidentified as Zod Open rc266

Both stem from the same root cause: _buildNativeTools doesn't properly handle MCP tools that were injected into toolImplementations during initializeMCP().

Environment

  • Probe version: 0.6.0-rc266
  • Runtime: Node.js, bundled via ncc
  • Trigger: Any ProbeAgent.answer() call when MCP tools are configured

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions