Skip to content

MCP tools crash: TypeError on value.output.output in processor.ts (AI SDK 6.0.74 breaking change) #13042

@kil-penguin

Description

@kil-penguin

Bug Description

All MCP tool calls crash with:

TypeError: undefined is not an object (evaluating 'output.output.toLowerCase')

This affects every MCP tool (Slack, fetch, grep_app, etc.) — 100% reproducible.

Root Cause

AI SDK v6.0.74 (released Feb 6, 2026) introduced createToolModelOutput() in stream-text.ts (line 1270) which changed the tool-result output structure:

Before (what OpenCode expects):

value.output = {
  output: string,
  metadata: Record<string, any>,
  title: string,
  attachments?: FilePart[]
}

After (what AI SDK now returns):

value.output = {
  type: 'text' | 'json' | 'error-text' | 'error-json' | 'execution-denied' | 'content',
  value: string | JSONValue,
  providerOptions?: ProviderOptions
}

Crash Location

packages/opencode/src/session/processor.tstool-result case:

case "tool-result": {
  const match = toolcalls[value.toolCallId]
  if (match && match.state.status === "running") {
    await Session.updatePart({
      ...match,
      state: {
        status: "completed",
        input: value.input ?? match.state.input,
        output: value.output.output,       // ← undefined.toLowerCase() crashes here
        metadata: value.output.metadata,   // ← undefined
        title: value.output.title,         // ← undefined
        attachments: value.output.attachments,
      },
    })
  }
}

Full Data Flow

  1. MCP tool execute (mcp/index.tsconvertMcpTool()) returns MCP SDK CallToolResult: { content: [{type: "text", text: "..."}], isError? }
  2. AI SDK execute-tool-call.ts wraps it as { type: 'tool-result', output: <raw result> }
  3. AI SDK stream-text.ts (line 1270) now passes output through createToolModelOutput() → transforms to { type: 'text', value: '...' }
  4. OpenCode processor.ts tries to access .output, .metadata, .title on the new structure → crash

Why It Wasn't Caught

  • OpenCode's built-in tools return { output, title, metadata } from their execute functions
  • createToolModelOutput() wraps ALL tool outputs (including MCP) into the new ToolResultOutput shape
  • The processor was never updated to handle the new shape

Suggested Fix

Option A: Convert MCP result in mcp/index.ts (recommended)

// In convertMcpTool(), wrap the execute return:
execute: async (args: unknown) => {
  const result = await client.callTool(...)
  const textContent = result.content
    ?.filter((c: any) => c.type === "text")
    .map((c: any) => c.text)
    .join("\n") ?? ""
  return {
    output: textContent,
    title: mcpTool.name,
    metadata: {},
  }
},

Option B: Handle new output shape in processor.ts

case "tool-result": {
  const match = toolcalls[value.toolCallId]
  if (match && match.state.status === "running") {
    const raw = value.output ?? {}
    // Handle both old format { output, metadata, title } and new AI SDK format { type, value }
    const output = raw.output ?? raw.value ?? (typeof raw === "string" ? raw : JSON.stringify(raw))
    const metadata = raw.metadata ?? {}
    const title = raw.title ?? match.tool
    await Session.updatePart({
      ...match,
      state: {
        status: "completed",
        input: value.input ?? match.state.input,
        output,
        metadata,
        title,
        time: { start: match.state.time.start, end: Date.now() },
        attachments: raw.attachments,
      },
    })
  }
}

Environment

  • OpenCode: v1.1.56
  • AI SDK: 6.0.74 (from pnpm catalog)
  • @modelcontextprotocol/sdk: 1.25.2
  • OS: macOS (darwin)

Reproduction

  1. Configure any MCP server in opencode.json
  2. Call any MCP tool
  3. Crash occurs immediately on tool result processing

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions