diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index ded7d0d2..b89ef78b 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -98,13 +98,45 @@ export namespace MCP { return state().then((state) => state.clients) } - export async function tools() { + export async function tools(providerID?: string) { const result: Record = {} - for (const [clientName, client] of Object.entries(await clients())) { - for (const [toolName, tool] of Object.entries(await client.tools())) { - result[clientName + "_" + toolName] = tool + const clientEntries = Object.entries(await clients()) + + for (const [clientName, client] of clientEntries) { + try { + const clientTools = await client.tools() + + for (const [toolName, tool] of Object.entries(clientTools)) { + const toolKey = clientName + "_" + toolName + + if (providerID) { + const transformedTool = await transformToolForProvider(tool, providerID) + result[toolKey] = transformedTool + } else { + result[toolKey] = tool + } + } + } catch (error: any) { + log.error('Failed to get tools from MCP client', { clientName, error: error.message }) } } + return result } + + async function transformToolForProvider(tool: any, providerID: string): Promise { + if (!['openai', 'azure'].includes(providerID)) return tool + + try { + const { Provider } = await import("../provider/provider") + return Provider.transformMCPToolForProvider(tool, providerID) + } catch (error: any) { + log.warn('Could not transform MCP tool schema, using as-is', { + toolId: tool.id || 'unknown', + providerID, + error: error.message + }) + return tool + } + } } diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index f05d15ce..11933ecf 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -545,6 +545,66 @@ export namespace Provider { return schema } + function transformJsonSchema(schema: any): any { + // Only handle simple object schemas + if (!schema || schema.type !== 'object' || !schema.properties) { + return schema // Return as-is for anything we don't understand + } + + // If it has oneOf, anyOf, allOf, or other complex stuff - bail out + if (schema.oneOf || schema.anyOf || schema.allOf) { + log.warn('Complex JSON schema detected, skipping transformation', { schema }) + return schema + } + + // Simple transformation: all properties become required + nullable + const required = Object.keys(schema.properties) + const properties: Record = {} + + for (const [key, prop] of Object.entries(schema.properties)) { + if (!schema.required?.includes(key)) { + // Make it nullable + properties[key] = { + anyOf: [prop, { type: 'null' }] + } + } else { + properties[key] = prop + } + } + + return { + ...schema, + required, + properties + } + } + + export function transformMCPToolForProvider(tool: any, providerID: string): any { + if (!['openai', 'azure'].includes(providerID)) return tool + + try { + // AI SDK converts MCP tools to: { parameters: { jsonSchema: } } + if (tool.parameters?.jsonSchema) { + const transformedSchema = transformJsonSchema(tool.parameters.jsonSchema) + return { + ...tool, + parameters: { + ...tool.parameters, + jsonSchema: transformedSchema + } + } + } + + return tool + } catch (error: any) { + log.warn('Could not transform MCP tool schema, using as-is', { + toolId: tool.id || 'unknown', + error: error.message + }) + return tool + } + } + export const ModelNotFoundError = NamedError.create( "ProviderModelNotFoundError", z.object({ diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index f4e80ce9..84a41e03 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -549,7 +549,7 @@ export namespace Session { }) } - for (const [key, item] of Object.entries(await MCP.tools())) { + for (const [key, item] of Object.entries(await MCP.tools(input.providerID))) { const execute = item.execute if (!execute) continue item.execute = async (args, opts) => {