diff --git a/src/api/providers/base-provider.ts b/src/api/providers/base-provider.ts index 84c8cf6fe9..64d99b3f0c 100644 --- a/src/api/providers/base-provider.ts +++ b/src/api/providers/base-provider.ts @@ -5,6 +5,7 @@ import type { ModelInfo } from "@roo-code/types" import type { ApiHandler, ApiHandlerCreateMessageMetadata } from "../index" import { ApiStream } from "../transform/stream" import { countTokens } from "../../utils/countTokens" +import { isMcpTool } from "../../utils/mcp-name" /** * Base class for API providers that implements common functionality. @@ -28,18 +29,26 @@ export abstract class BaseProvider implements ApiHandler { return undefined } - return tools.map((tool) => - tool.type === "function" - ? { - ...tool, - function: { - ...tool.function, - strict: true, - parameters: this.convertToolSchemaForOpenAI(tool.function.parameters), - }, - } - : tool, - ) + return tools.map((tool) => { + if (tool.type !== "function") { + return tool + } + + // MCP tools use the 'mcp--' prefix - disable strict mode for them + // to preserve optional parameters from the MCP server schema + const isMcp = isMcpTool(tool.function.name) + + return { + ...tool, + function: { + ...tool.function, + strict: !isMcp, + parameters: isMcp + ? tool.function.parameters + : this.convertToolSchemaForOpenAI(tool.function.parameters), + }, + } + }) } /** diff --git a/src/api/providers/openai-native.ts b/src/api/providers/openai-native.ts index 762b81fc83..8f9cc2297f 100644 --- a/src/api/providers/openai-native.ts +++ b/src/api/providers/openai-native.ts @@ -24,6 +24,7 @@ import { getModelParams } from "../transform/model-params" import { BaseProvider } from "./base-provider" import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index" +import { isMcpTool } from "../../utils/mcp-name" export type OpenAiNativeModel = ReturnType @@ -291,13 +292,18 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio ...(metadata?.tools && { tools: metadata.tools .filter((tool) => tool.type === "function") - .map((tool) => ({ - type: "function", - name: tool.function.name, - description: tool.function.description, - parameters: ensureAllRequired(tool.function.parameters), - strict: true, - })), + .map((tool) => { + // MCP tools use the 'mcp--' prefix - disable strict mode for them + // to preserve optional parameters from the MCP server schema + const isMcp = isMcpTool(tool.function.name) + return { + type: "function", + name: tool.function.name, + description: tool.function.description, + parameters: isMcp ? tool.function.parameters : ensureAllRequired(tool.function.parameters), + strict: !isMcp, + } + }), }), ...(metadata?.tool_choice && { tool_choice: metadata.tool_choice }), } diff --git a/src/utils/__tests__/mcp-name.spec.ts b/src/utils/__tests__/mcp-name.spec.ts index b28c2e504c..5511893f79 100644 --- a/src/utils/__tests__/mcp-name.spec.ts +++ b/src/utils/__tests__/mcp-name.spec.ts @@ -1,4 +1,11 @@ -import { sanitizeMcpName, buildMcpToolName, parseMcpToolName, MCP_TOOL_SEPARATOR, MCP_TOOL_PREFIX } from "../mcp-name" +import { + sanitizeMcpName, + buildMcpToolName, + parseMcpToolName, + isMcpTool, + MCP_TOOL_SEPARATOR, + MCP_TOOL_PREFIX, +} from "../mcp-name" describe("mcp-name utilities", () => { describe("constants", () => { @@ -8,6 +15,29 @@ describe("mcp-name utilities", () => { }) }) + describe("isMcpTool", () => { + it("should return true for valid MCP tool names", () => { + expect(isMcpTool("mcp--server--tool")).toBe(true) + expect(isMcpTool("mcp--my_server--get_forecast")).toBe(true) + }) + + it("should return false for non-MCP tool names", () => { + expect(isMcpTool("server--tool")).toBe(false) + expect(isMcpTool("tool")).toBe(false) + expect(isMcpTool("read_file")).toBe(false) + expect(isMcpTool("")).toBe(false) + }) + + it("should return false for old underscore format", () => { + expect(isMcpTool("mcp_server_tool")).toBe(false) + }) + + it("should return false for partial prefix", () => { + expect(isMcpTool("mcp-server")).toBe(false) + expect(isMcpTool("mcp")).toBe(false) + }) + }) + describe("sanitizeMcpName", () => { it("should return underscore placeholder for empty input", () => { expect(sanitizeMcpName("")).toBe("_") diff --git a/src/utils/mcp-name.ts b/src/utils/mcp-name.ts index 55845d67ed..c81d5e770f 100644 --- a/src/utils/mcp-name.ts +++ b/src/utils/mcp-name.ts @@ -17,6 +17,16 @@ export const MCP_TOOL_SEPARATOR = "--" */ export const MCP_TOOL_PREFIX = "mcp" +/** + * Check if a tool name is an MCP tool (starts with the MCP prefix and separator). + * + * @param toolName - The tool name to check + * @returns true if the tool name starts with "mcp--", false otherwise + */ +export function isMcpTool(toolName: string): boolean { + return toolName.startsWith(`${MCP_TOOL_PREFIX}${MCP_TOOL_SEPARATOR}`) +} + /** * Sanitize a name to be safe for use in API function names. * This removes special characters and ensures the name starts correctly.