Skip to content

Commit 0d600e9

Browse files
committed
Fix mcp tools: use __ instead of /
1 parent 7b15103 commit 0d600e9

File tree

3 files changed

+29
-11
lines changed

3 files changed

+29
-11
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* Separator used between MCP server name and tool name.
3+
*
4+
* LLM APIs (OpenRouter/Anthropic) only allow tool names matching the pattern
5+
* ^[a-zA-Z0-9_-]{1,128}$, which doesn't include forward slashes.
6+
*
7+
* We use double underscore as the separator since it's:
8+
* - Allowed by the LLM API pattern
9+
* - Unlikely to conflict with existing tool names
10+
* - Clearly identifiable as a separator
11+
*/
12+
export const MCP_TOOL_SEPARATOR = '__'

packages/agent-runtime/src/mcp.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { convertJsonSchemaToZod } from 'zod-from-json-schema'
22

3+
import { MCP_TOOL_SEPARATOR } from './mcp-constants'
4+
35
import type { AgentTemplate } from './templates/types'
46
import type { RequestMcpToolDataFn } from '@codebuff/common/types/contracts/client'
57
import type { OptionalFields } from '@codebuff/common/types/function-params'
@@ -22,13 +24,16 @@ export async function getMCPToolData(
2224
const withDefaults = { writeTo: {}, ...params }
2325
const { toolNames, mcpServers, writeTo, requestMcpToolData } = withDefaults
2426

27+
// User-facing toolNames use '/' as separator (e.g., 'supabase/list_tables')
28+
// but internally we use MCP_TOOL_SEPARATOR ('__') for LLM API compatibility
29+
const USER_INPUT_SEPARATOR = '/'
2530
const requestedToolsByMcp: Record<string, string[] | undefined> = {}
2631
for (const t of toolNames) {
27-
if (!t.includes('/')) {
32+
if (!t.includes(USER_INPUT_SEPARATOR)) {
2833
continue
2934
}
30-
const [mcpName, ...remaining] = t.split('/')
31-
const toolName = remaining.join('/')
35+
const [mcpName, ...remaining] = t.split(USER_INPUT_SEPARATOR)
36+
const toolName = remaining.join(USER_INPUT_SEPARATOR)
3237
if (!requestedToolsByMcp[mcpName]) {
3338
requestedToolsByMcp[mcpName] = []
3439
}
@@ -45,7 +50,7 @@ export async function getMCPToolData(
4550
})
4651

4752
for (const { name, description, inputSchema } of mcpData) {
48-
writeTo[mcpName + '/' + name] = {
53+
writeTo[mcpName + MCP_TOOL_SEPARATOR + name] = {
4954
inputSchema: convertJsonSchemaToZod(inputSchema as any) as any,
5055
endsAgentStep: true,
5156
description,

packages/agent-runtime/src/tools/tool-executor.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { toolParams } from '@codebuff/common/tools/list'
33
import { generateCompactId } from '@codebuff/common/util/string'
44
import { cloneDeep } from 'lodash'
55

6+
import { MCP_TOOL_SEPARATOR } from '../mcp-constants'
67
import { getMCPToolData } from '../mcp'
78
import { getAgentShortName } from '../templates/prompts'
89
import { codebuffToolHandlers } from './handlers/list'
@@ -274,7 +275,7 @@ export function parseRawCustomToolCall(params: {
274275

275276
if (
276277
!(customToolDefs && toolName in customToolDefs) &&
277-
!toolName.includes('/')
278+
!toolName.includes(MCP_TOOL_SEPARATOR)
278279
) {
279280
return {
280281
toolName,
@@ -370,8 +371,8 @@ export async function executeCustomToolCall(
370371
!(agentTemplate.toolNames as string[]).includes(toolCall.toolName) &&
371372
!fromHandleSteps &&
372373
!(
373-
toolCall.toolName.includes('/') &&
374-
toolCall.toolName.split('/')[0] in agentTemplate.mcpServers
374+
toolCall.toolName.includes(MCP_TOOL_SEPARATOR) &&
375+
toolCall.toolName.split(MCP_TOOL_SEPARATOR)[0] in agentTemplate.mcpServers
375376
)
376377
) {
377378
// Emit an error event instead of tool call/result pair
@@ -415,15 +416,15 @@ export async function executeCustomToolCall(
415416
return null
416417
}
417418

418-
const toolName = toolCall.toolName.includes('/')
419-
? toolCall.toolName.split('/').slice(1).join('/')
419+
const toolName = toolCall.toolName.includes(MCP_TOOL_SEPARATOR)
420+
? toolCall.toolName.split(MCP_TOOL_SEPARATOR).slice(1).join(MCP_TOOL_SEPARATOR)
420421
: toolCall.toolName
421422
const clientToolResult = await requestToolCall({
422423
userInputId,
423424
toolName,
424425
input: toolCall.input,
425-
mcpConfig: toolCall.toolName.includes('/')
426-
? agentTemplate.mcpServers[toolCall.toolName.split('/')[0]]
426+
mcpConfig: toolCall.toolName.includes(MCP_TOOL_SEPARATOR)
427+
? agentTemplate.mcpServers[toolCall.toolName.split(MCP_TOOL_SEPARATOR)[0]]
427428
: undefined,
428429
})
429430
return clientToolResult.output satisfies ToolResultOutput[]

0 commit comments

Comments
 (0)