Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 21 additions & 12 deletions src/api/providers/base-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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),
},
}
})
}

/**
Expand Down
20 changes: 13 additions & 7 deletions src/api/providers/openai-native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<OpenAiNativeHandler["getModel"]>

Expand Down Expand Up @@ -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 }),
}
Expand Down
32 changes: 31 additions & 1 deletion src/utils/__tests__/mcp-name.spec.ts
Original file line number Diff line number Diff line change
@@ -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", () => {
Expand All @@ -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("_")
Expand Down
10 changes: 10 additions & 0 deletions src/utils/mcp-name.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading