Compare commits

...

3 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
e8fc3fb0a2 test: add tests for HTTP MCP compatibility fix
Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com>
2025-11-01 18:25:18 +00:00
copilot-swe-agent[bot]
4b254637b0 🐛 fix: remove strict mode from MCP tools to fix HTTP MCP errors
Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com>
2025-11-01 18:23:45 +00:00
copilot-swe-agent[bot]
456b709c29 Initial plan 2025-11-01 18:12:43 +00:00
2 changed files with 135 additions and 7 deletions

View File

@@ -0,0 +1,128 @@
import { describe, expect, it } from 'vitest'
import { mcpToolsToOpenAIChatTools, mcpToolsToOpenAIResponseTools } from '../mcp-tools'
import type { MCPTool } from '@renderer/types'
describe('MCP Tools - HTTP MCP Compatibility Fix', () => {
const mockMCPTool: MCPTool = {
id: 'test_server-search_tool',
name: 'search_tool',
description: 'Search for information',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Search query'
},
limit: {
type: 'number',
description: 'Optional limit',
minimum: 1
}
},
required: ['query'] // Only query is required, limit is optional
},
serverId: 'test-server-id',
serverName: 'test-server',
type: 'mcp'
}
describe('mcpToolsToOpenAIResponseTools', () => {
it('should preserve original schema without forcing all properties to be required', () => {
const tools = mcpToolsToOpenAIResponseTools([mockMCPTool])
expect(tools).toHaveLength(1)
const tool = tools[0]
// Should use the tool id
expect(tool.name).toBe('test_server-search_tool')
// Should preserve the original required array (only 'query')
expect(tool.parameters.required).toEqual(['query'])
// Should have both properties
expect(tool.parameters.properties).toHaveProperty('query')
expect(tool.parameters.properties).toHaveProperty('limit')
})
it('should not include strict: true', () => {
const tools = mcpToolsToOpenAIResponseTools([mockMCPTool])
expect(tools).toHaveLength(1)
const tool = tools[0] as any
// strict property should not be present
expect(tool.strict).toBeUndefined()
})
})
describe('mcpToolsToOpenAIChatTools', () => {
it('should preserve original schema without forcing all properties to be required', () => {
const tools = mcpToolsToOpenAIChatTools([mockMCPTool])
expect(tools).toHaveLength(1)
const tool = tools[0]
// Should use the tool id
expect(tool.function.name).toBe('test_server-search_tool')
// Should include description
expect(tool.function.description).toBe('Search for information')
// Should preserve the original required array (only 'query')
expect(tool.function.parameters.required).toEqual(['query'])
// Should have both properties
expect(tool.function.parameters.properties).toHaveProperty('query')
expect(tool.function.parameters.properties).toHaveProperty('limit')
})
it('should not include strict: true in function parameters', () => {
const tools = mcpToolsToOpenAIChatTools([mockMCPTool])
expect(tools).toHaveLength(1)
const tool = tools[0] as any
// strict property should not be present
expect(tool.function.strict).toBeUndefined()
})
})
describe('HTTP MCP with complex nested schemas', () => {
it('should handle nested objects with optional fields', () => {
const complexTool: MCPTool = {
id: 'http_server-complex_tool',
name: 'complex_tool',
description: 'Complex tool with nested schemas',
inputSchema: {
type: 'object',
properties: {
required_field: { type: 'string' },
optional_object: {
type: 'object',
properties: {
nested_required: { type: 'string' },
nested_optional: { type: 'number' }
},
required: ['nested_required']
}
},
required: ['required_field']
},
serverId: 'http-server-id',
serverName: 'http-server',
type: 'mcp'
}
const tools = mcpToolsToOpenAIChatTools([complexTool])
const parameters = tools[0].function.parameters
// Top level should only require 'required_field'
expect(parameters.required).toEqual(['required_field'])
// Nested object should only require 'nested_required'
expect(parameters.properties.optional_object.required).toEqual(['nested_required'])
})
})
})

View File

@@ -32,13 +32,14 @@ import { nanoid } from 'nanoid'
import { isToolUseModeFunction } from './assistant'
import { convertBase64ImageToAwsBedrockFormat } from './aws-bedrock-utils'
import { filterProperties, processSchemaForO3 } from './mcp-schema'
import { filterProperties } from './mcp-schema'
const logger = loggerService.withContext('Utils:MCPTools')
export function mcpToolsToOpenAIResponseTools(mcpTools: MCPTool[]): OpenAI.Responses.Tool[] {
return mcpTools.map((tool) => {
const parameters = processSchemaForO3(tool.inputSchema)
// Use the original schema without strict mode processing to maintain compatibility with all MCP types
const parameters = tool.inputSchema
return {
type: 'function',
@@ -46,15 +47,15 @@ export function mcpToolsToOpenAIResponseTools(mcpTools: MCPTool[]): OpenAI.Respo
parameters: {
type: 'object' as const,
...parameters
},
strict: true
}
} satisfies OpenAI.Responses.Tool
})
}
export function mcpToolsToOpenAIChatTools(mcpTools: MCPTool[]): Array<ChatCompletionTool> {
return mcpTools.map((tool) => {
const parameters = processSchemaForO3(tool.inputSchema)
// Use the original schema without strict mode processing to maintain compatibility with all MCP types
const parameters = tool.inputSchema
return {
type: 'function',
@@ -64,8 +65,7 @@ export function mcpToolsToOpenAIChatTools(mcpTools: MCPTool[]): Array<ChatComple
parameters: {
type: 'object' as const,
...parameters
},
strict: true
}
}
} as ChatCompletionTool
})