Skip to content

Commit 44d430a

Browse files
committed
fix: MCP tool execution preserve message parts through Zod validation
Root cause: Zod's default .strip() mode on messageSchema silently removed the 'parts' field from messages during request validation. The AI SDK stores tool invocations in this field, so processToolInvocations() never received any tool calls, causing an infinite loop where the LLM kept proposing the same tool. Fix: Added .passthrough() to messageSchema to preserve all AI SDK fields including 'parts' for MCP tool invocation processing. Also improved mcpService.ts: - Added parts.length === 0 to early return guard - Added debug logging for successful tool calls - Added warning when tool has no execute function
1 parent f918f53 commit 44d430a

2 files changed

Lines changed: 10 additions & 6 deletions

File tree

app/lib/services/mcpService.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ export class MCPService {
378378
const lastMessage = messages[messages.length - 1];
379379
const parts = lastMessage.parts;
380380

381-
if (!parts) {
381+
if (!parts || parts.length === 0) {
382382
return messages;
383383
}
384384

@@ -410,11 +410,13 @@ export class MCPService {
410410
messages: convertToCoreMessages(messages),
411411
toolCallId,
412412
});
413+
logger.debug(`tool "${toolName}" returned successfully`);
413414
} catch (error) {
414415
logger.error(`error while calling tool "${toolName}":`, error);
415416
result = TOOL_EXECUTION_ERROR;
416417
}
417418
} else {
419+
logger.warn(`tool "${toolName}" has no execute function`);
418420
result = TOOL_NO_EXECUTE_FUNCTION;
419421
}
420422
} else if (toolInvocation.result === TOOL_EXECUTION_APPROVAL.REJECT) {

app/routes/api.chat.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,13 @@ export const action = withSecurity(chatAction, {
3636
const logger = createScopedLogger('api.chat');
3737

3838
// Zod schema for chat request validation
39-
const messageSchema = z.object({
40-
id: z.string().optional(),
41-
role: z.enum(['user', 'assistant', 'system']),
42-
content: z.string(),
43-
});
39+
const messageSchema = z
40+
.object({
41+
id: z.string().optional(),
42+
role: z.enum(['user', 'assistant', 'system']),
43+
content: z.string(),
44+
})
45+
.passthrough(); // Preserve 'parts' and other AI SDK fields for MCP tool invocations
4446

4547
const designSchemeSchema = z
4648
.object({

0 commit comments

Comments
 (0)