Skip to content

Commit 9924fff

Browse files
committed
🤖 fix: transform MCP image content to AI SDK media format
MCP returns images with type 'image' and 'mimeType', but AI SDK expects type 'media' with 'mediaType'. This wraps MCP tool execute functions to transform the result format, enabling proper image handling for tools like Chrome DevTools MCP's take_screenshot.
1 parent d96bed9 commit 9924fff

File tree

1 file changed

+101
-1
lines changed

1 file changed

+101
-1
lines changed

src/node/services/mcpServerManager.ts

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,105 @@ import { createRuntime } from "@/node/runtime/runtimeFactory";
99

1010
const TEST_TIMEOUT_MS = 10_000;
1111

12+
/**
13+
* MCP CallToolResult content types (from @ai-sdk/mcp)
14+
*/
15+
interface MCPTextContent {
16+
type: "text";
17+
text: string;
18+
}
19+
20+
interface MCPImageContent {
21+
type: "image";
22+
data: string; // base64
23+
mimeType: string;
24+
}
25+
26+
interface MCPResourceContent {
27+
type: "resource";
28+
resource: { uri: string; text?: string; blob?: string; mimeType?: string };
29+
}
30+
31+
type MCPContent = MCPTextContent | MCPImageContent | MCPResourceContent;
32+
33+
interface MCPCallToolResult {
34+
content?: MCPContent[];
35+
isError?: boolean;
36+
toolResult?: unknown;
37+
}
38+
39+
/**
40+
* AI SDK LanguageModelV2ToolResultOutput content types
41+
*/
42+
type AISDKContentPart =
43+
| { type: "text"; text: string }
44+
| { type: "media"; data: string; mediaType: string };
45+
46+
/**
47+
* Transform MCP tool result to AI SDK format.
48+
* Converts MCP's "image" content type to AI SDK's "media" type.
49+
*/
50+
function transformMCPResult(result: MCPCallToolResult): unknown {
51+
// If it's an error or has toolResult, pass through as-is
52+
if (result.isError || result.toolResult !== undefined) {
53+
return result;
54+
}
55+
56+
// If no content array, pass through
57+
if (!result.content || !Array.isArray(result.content)) {
58+
return result;
59+
}
60+
61+
// Check if any content is an image
62+
const hasImage = result.content.some((c) => c.type === "image");
63+
if (!hasImage) {
64+
return result;
65+
}
66+
67+
// Transform to AI SDK content format
68+
const transformedContent: AISDKContentPart[] = result.content.map((item) => {
69+
if (item.type === "text") {
70+
return { type: "text" as const, text: item.text };
71+
}
72+
if (item.type === "image") {
73+
return { type: "media" as const, data: item.data, mediaType: item.mimeType };
74+
}
75+
// For resource type, convert to text representation
76+
if (item.type === "resource") {
77+
const text = item.resource.text ?? item.resource.uri;
78+
return { type: "text" as const, text };
79+
}
80+
// Fallback: stringify unknown content
81+
return { type: "text" as const, text: JSON.stringify(item) };
82+
});
83+
84+
return { type: "content", value: transformedContent };
85+
}
86+
87+
/**
88+
* Wrap MCP tools to transform their results to AI SDK format.
89+
* This ensures image content is properly converted to media type.
90+
*/
91+
function wrapMCPTools(tools: Record<string, Tool>): Record<string, Tool> {
92+
const wrapped: Record<string, Tool> = {};
93+
for (const [name, tool] of Object.entries(tools)) {
94+
// Only wrap tools that have an execute function
95+
if (!tool.execute) {
96+
wrapped[name] = tool;
97+
continue;
98+
}
99+
const originalExecute = tool.execute;
100+
wrapped[name] = {
101+
...tool,
102+
execute: async (args: Parameters<typeof originalExecute>[0], options) => {
103+
const result: unknown = await originalExecute(args, options);
104+
return transformMCPResult(result as MCPCallToolResult);
105+
},
106+
};
107+
}
108+
return wrapped;
109+
}
110+
12111
export type MCPTestResult = { success: true; tools: string[] } | { success: false; error: string };
13112

14113
interface MCPServerInstance {
@@ -231,7 +330,8 @@ export class MCPServerManager {
231330

232331
await transport.start();
233332
const client = await experimental_createMCPClient({ transport });
234-
const tools = await client.tools();
333+
const rawTools = await client.tools();
334+
const tools = wrapMCPTools(rawTools);
235335
const toolNames = Object.keys(tools);
236336
log.info("[MCP] Server ready", { name, tools: toolNames });
237337

0 commit comments

Comments
 (0)