From fd213e6df6ba050b83a31d4ae9427c7099de3205 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Mon, 29 Jun 2026 15:51:52 -0500 Subject: [PATCH] fix(mcp): prefer content over structured output (#34505) --- packages/opencode/src/mcp/catalog.ts | 2 +- packages/opencode/test/mcp/catalog.test.ts | 48 ++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 packages/opencode/test/mcp/catalog.test.ts diff --git a/packages/opencode/src/mcp/catalog.ts b/packages/opencode/src/mcp/catalog.ts index 15eb5af40d6..cc80a117e16 100644 --- a/packages/opencode/src/mcp/catalog.ts +++ b/packages/opencode/src/mcp/catalog.ts @@ -72,7 +72,7 @@ export function convertTool(mcpTool: MCPToolDef, client: Client, timeout?: numbe .filter((text) => text.trim()) .join("\n\n") || "MCP tool returned an error", ) - if (result.structuredContent === undefined || result.structuredContent === null) return result + if (result.content.length > 0 || result.structuredContent === undefined || result.structuredContent === null) return result return { ...result, content: [{ type: "text" as const, text: JSON.stringify(result.structuredContent) }], diff --git a/packages/opencode/test/mcp/catalog.test.ts b/packages/opencode/test/mcp/catalog.test.ts new file mode 100644 index 00000000000..7dbfa40f540 --- /dev/null +++ b/packages/opencode/test/mcp/catalog.test.ts @@ -0,0 +1,48 @@ +import { describe, expect, test } from "bun:test" +import type { Client } from "@modelcontextprotocol/sdk/client/index.js" +import { McpCatalog } from "@/mcp/catalog" + +const options = { toolCallId: "call_mcp", abortSignal: new AbortController().signal } as any + +function clientReturning(result: unknown) { + return { + callTool: async () => result, + } as unknown as Client +} + +function mcpTool() { + return { + name: "screenshot", + description: "Take a screenshot", + inputSchema: { + type: "object", + properties: {}, + additionalProperties: false, + }, + } as any +} + +describe("McpCatalog.convertTool", () => { + test("preserves content when structuredContent is also present", async () => { + const content = [{ type: "image" as const, mimeType: "image/png", data: "AAAA" }] + const structuredContent = { image: { mimeType: "image/png", data: "AAAA" } } + const converted = McpCatalog.convertTool(mcpTool(), clientReturning({ content, structuredContent })) + + const output = await converted.execute?.({}, options) + + expect(output).toMatchObject({ content, structuredContent }) + }) + + test("falls back to structuredContent only when content is absent", async () => { + const structuredContent = { results: [{ title: "one" }] } + const converted = McpCatalog.convertTool(mcpTool(), clientReturning({ content: [], structuredContent })) + + const output = await converted.execute?.({}, options) + + expect(output).toMatchObject({ + structuredContent, + content: [{ type: "text", text: JSON.stringify(structuredContent) }], + }) + }) + +})