From 7f0fde2d9877faa70dd2c6566f72d1f9aca2a3fc Mon Sep 17 00:00:00 2001 From: Serhii Zghama <20826225+serhiizghama@users.noreply.github.com> Date: Sat, 23 May 2026 20:43:40 +0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=AA=A8=20fix:=20Normalize=20Empty=20MCP?= =?UTF-8?q?=20Tool=20Descriptions=20to=20`undefined`=20for=20Bedrock=20Com?= =?UTF-8?q?pat.=20(#13217)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(agents): normalize empty MCP tool descriptions to undefined MCP servers (e.g. Asana MCP) can return tools with an empty string description. AWS Bedrock's converse API rejects toolSpec.description with length < 1, so any empty-description MCP tool caused the entire request to fail with a validation error. Convert empty strings to undefined at the two sites in loadToolDefinitions where MCP tool definitions are built. An undefined description is omitted from JSON serialization, so Bedrock never sees the empty value. OpenAI and Anthropic direct APIs are unaffected. Fixes #13209 * test(agents): add coverage for empty MCP tool description normalization Verify that loadToolDefinitions converts '' descriptions to undefined for both the sys__all__sys pattern and directly named MCP tools. --- packages/api/src/tools/definitions.spec.ts | 72 ++++++++++++++++++++++ packages/api/src/tools/definitions.ts | 4 +- 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/packages/api/src/tools/definitions.spec.ts b/packages/api/src/tools/definitions.spec.ts index 4dc1acdf83..139fdfac3a 100644 --- a/packages/api/src/tools/definitions.spec.ts +++ b/packages/api/src/tools/definitions.spec.ts @@ -553,6 +553,78 @@ describe('definitions.ts', () => { expect(toolDef.name).toBe('list_items_mcp_my-server'); expect((toolDef as { serverName?: string }).serverName).toBe('my-server'); }); + + it('should convert empty MCP tool descriptions to undefined', async () => { + const mockServerTools = { + no_desc_tool_mcp_asana: { + function: { + name: 'no_desc_tool_mcp_asana', + description: '', + parameters: { type: 'object', properties: {} }, + }, + }, + has_desc_tool_mcp_asana: { + function: { + name: 'has_desc_tool_mcp_asana', + description: 'List tasks', + parameters: { type: 'object', properties: {} }, + }, + }, + }; + + mockGetOrFetchMCPServerTools.mockResolvedValue(mockServerTools); + + const params: LoadToolDefinitionsParams = { + userId: 'user-123', + agentId: 'agent-123', + tools: ['sys__all__sys_mcp_asana'], + }; + + const deps: LoadToolDefinitionsDeps = { + getOrFetchMCPServerTools: mockGetOrFetchMCPServerTools, + isBuiltInTool: mockIsBuiltInTool, + }; + + const result = await loadToolDefinitions(params, deps); + + const noDef = result.toolDefinitions.find((d) => d.name === 'no_desc_tool_mcp_asana'); + expect(noDef).toBeDefined(); + expect(noDef?.description).toBeUndefined(); + + const hasDef = result.toolDefinitions.find((d) => d.name === 'has_desc_tool_mcp_asana'); + expect(hasDef).toBeDefined(); + expect(hasDef?.description).toBe('List tasks'); + }); + + it('should convert empty description to undefined for directly named MCP tool', async () => { + const toolName = 'no_desc_tool_mcp_asana'; + mockGetOrFetchMCPServerTools.mockResolvedValue({ + [toolName]: { + function: { + name: toolName, + description: '', + parameters: { type: 'object', properties: {} }, + }, + }, + }); + + const params: LoadToolDefinitionsParams = { + userId: 'user-123', + agentId: 'agent-123', + tools: [toolName], + }; + + const deps: LoadToolDefinitionsDeps = { + getOrFetchMCPServerTools: mockGetOrFetchMCPServerTools, + isBuiltInTool: mockIsBuiltInTool, + }; + + const result = await loadToolDefinitions(params, deps); + + const def = result.toolDefinitions.find((d) => d.name === toolName); + expect(def).toBeDefined(); + expect(def?.description).toBeUndefined(); + }); }); describe('toolkit expansion', () => { diff --git a/packages/api/src/tools/definitions.ts b/packages/api/src/tools/definitions.ts index 7f82bc06ec..bdb501e9d3 100644 --- a/packages/api/src/tools/definitions.ts +++ b/packages/api/src/tools/definitions.ts @@ -158,7 +158,7 @@ export async function loadToolDefinitions( if (toolDef?.function) { mcpToolDefs.push({ name: actualToolName, - description: toolDef.function.description, + description: toolDef.function.description || undefined, parameters: toolDef.function.parameters ? normalizeJsonSchema(resolveJsonSchemaRefs(toolDef.function.parameters)) : undefined, @@ -173,7 +173,7 @@ export async function loadToolDefinitions( if (toolDef?.function) { mcpToolDefs.push({ name: toolName, - description: toolDef.function.description, + description: toolDef.function.description || undefined, parameters: toolDef.function.parameters ? normalizeJsonSchema(resolveJsonSchemaRefs(toolDef.function.parameters)) : undefined,