-
Notifications
You must be signed in to change notification settings - Fork 47
Description
Summary
ProbeAgent._buildNativeTools() throws a TypeError: Cannot destructure property 'schema' of 'this._getToolSchemaAndDescription(...)' as it is null when MCP tools have been injected into this.toolImplementations. This regression was introduced in v0.6.0-rc265.
Error
TypeError: Cannot destructure property 'schema' of 'this._getToolSchemaAndDescription(...)' as it is null.
at _ProbeAgent._buildNativeTools (dist/index.js:354370:19)
at _ProbeAgent.answer (dist/index.js:355596:31)
Steps to Reproduce
- Configure ProbeAgent with MCP tools (e.g., via an SSE or stdio MCP server)
- Call
ProbeAgent.answer()with any prompt - The agent initializes MCP, injects MCP tools into
toolImplementations, then crashes when_buildNativeToolsiterates them
Example configuration that triggers the crash — any MCP tools configuration works:
tools:
- id: slack-send-dm
type: mcp
# ...
- id: slack-search
type: mcp
# ...Debug output confirms the MCP tools are successfully loaded into toolImplementations:
[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)
The crash happens immediately after, when answer() calls _buildNativeTools().
Root Cause Analysis
There are two code paths that interact incorrectly:
1. MCP tool injection into toolImplementations (during initializeMCP)
When MCP is enabled, MCP tools are added to this.toolImplementations alongside native tools:
// During initializeMCP():
const mcpTools = this.mcpBridge.mcpTools || {};
for (const [toolName, toolImpl] of Object.entries(mcpTools)) {
if (this._isMcpToolAllowed(toolName)) {
this.toolImplementations[toolName] = toolImpl; // MCP tools added here
}
}After this, toolImplementations contains both native tools (analyze_all, readImage, etc.) and MCP tools (__tools___slack-send-dm, etc.).
2. _buildNativeTools() iterates ALL toolImplementations
// In _buildNativeTools():
for (const [toolName, toolImpl] of Object.entries(this.toolImplementations)) {
const { schema, description } = this._getToolSchemaAndDescription(toolName); // 💥 CRASH
if (schema && description) {
nativeTools[toolName] = wrapTool(toolName, schema, description, toolImpl.execute);
}
}3. _getToolSchemaAndDescription() only knows about native tools
_getToolSchemaAndDescription(toolName) {
const toolMap = {
search: { schema: searchSchema, description: "..." },
query: { schema: querySchema, description: "..." },
extract: { schema: extractSchema, description: "..." },
// ... other native tools only
readImage: { schema: readImageSchema, description: "..." },
task: { schema: taskSchema, description: "..." },
};
return toolMap[toolName] || null; // Returns null for MCP tools
}When an MCP tool name like __tools___slack-send-dm is looked up, the method returns null. Destructuring { schema, description } from null throws the TypeError.
Note: Line 354371 has if (schema && description) which was intended to guard against unknown tools, but the destructuring on the previous line crashes before reaching that guard.
Suggested Fix
Replace the unsafe destructuring with a null check:
// In _buildNativeTools(), change:
for (const [toolName, toolImpl] of Object.entries(this.toolImplementations)) {
const { schema, description } = this._getToolSchemaAndDescription(toolName);
if (schema && description) {
nativeTools[toolName] = wrapTool(toolName, schema, description, toolImpl.execute);
}
}
// To:
for (const [toolName, toolImpl] of Object.entries(this.toolImplementations)) {
const toolInfo = this._getToolSchemaAndDescription(toolName);
if (!toolInfo) continue;
const { schema, description } = toolInfo;
nativeTools[toolName] = wrapTool(toolName, schema, description, toolImpl.execute);
}This is safe because MCP tools are already handled separately further down in the same method:
// MCP tools are wired up here, after the native tool loop:
if (this.mcpBridge && !options._disableTools) {
const mcpTools = this.mcpBridge.getVercelTools(this._filterMcpTools(this.mcpBridge.getToolNames()));
for (const [name, mcpTool] of Object.entries(mcpTools)) {
// ...
}
}An alternative (or complementary) fix would be to not add MCP tools to toolImplementations in the first place, since they're consumed through mcpBridge.getVercelTools() in _buildNativeTools. The dual registration (in toolImplementations AND via mcpBridge) seems like the root design issue.
Environment
- Probe version: 0.6.0-rc265 (works fine on 0.6.0-rc263)
- Runtime: Node.js, bundled via ncc
- Trigger: Any ProbeAgent usage with MCP tools configured