CherryHQ/cherry-studio · error · Error
Invalid tool ID format: ${toolId}
Error message
Invalid tool ID format: ${toolId} What it means
Thrown by `callToolById` when the toolId string does not contain the `__` (double underscore) delimiter. The method splits toolId on `__` to extract the serverId (first segment) and toolName (remaining segments joined back). A toolId without `__` yields a single-element array, failing the `parts.length < 2` guard.
Source
Thrown at src/main/ai/mcp/McpRuntimeService.ts:334
return
}
const status: McpRuntimeStatus = {
state,
lastCheckedAt: Date.now(),
...(lastError !== undefined ? { lastError } : {})
}
cacheService.setShared(key, status)
}
/**
* Call a tool by its full ID (serverId__toolName format).
* Used by Hub server's runtime.
*/
public async callToolById(toolId: string, params: unknown, callId?: string): Promise<McpCallToolResponse> {
const parts = toolId.split('__')
if (parts.length < 2) {
throw new Error(`Invalid tool ID format: ${toolId}`)
}
const serverId = parts[0]
const toolName = parts.slice(1).join('__')
const server = mcpServerService.getById(serverId)
logger.debug(`[callToolById] Calling tool ${toolName} on server ${server.name}`)
return this.callToolByServer({
server,
name: toolName,
args: params,
callId
})
}
public getServerKey(server: McpServer): string {View on GitHub (pinned to 726446b54c)
Solutions
- Ensure toolId is formatted as `${serverId}__${toolName}` before calling callToolById
- Check the caller (e.g. Hub server's tool routing) to confirm it prepends the serverId with double underscore
- If building toolIds dynamically, use a helper: `${server.id}__${toolDef.name}`
- Add a debug log before the call to inspect the actual toolId value being passed
Example fix
// before
const result = await runtime.callToolById('search_web', params)
// after
const result = await runtime.callToolById(`${serverId}__search_web`, params) Defensive patterns
Strategy: validation
Validate before calling
function buildToolId(serverId: string, toolName: string): string {
if (!serverId || !toolName) {
throw new Error('Both serverId and toolName are required')
}
return `${serverId}__${toolName}`
}
// Validate before calling
function isValidToolId(toolId: string): boolean {
return toolId.split('__').length >= 2
} Type guard
function isFullToolId(toolId: string): boolean {
const parts = toolId.split('__')
return parts.length >= 2 && parts[0].length > 0 && parts[1].length > 0
} Try / catch
try {
await runtime.callToolById(toolId, params)
} catch (e) {
if (e instanceof Error && e.message.startsWith('Invalid tool ID format')) {
// Log the malformed toolId and skip or construct a valid one
logger.warn(`Skipping tool call with invalid ID: ${toolId}`)
return null
}
throw e
} Prevention
- Always construct tool IDs with a helper function, never by string concatenation in multiple places
- Validate tool IDs at the boundary where they enter your system (e.g. from LLM output parsing)
- Document the serverId__toolName convention in your tool routing layer
When it happens
Trigger: Calling `callToolById(toolId, params, callId)` with a plain tool name like 'search' instead of the full 'server-id__search' format. Also triggered by a toolId that is empty, null-coerced, or uses a different separator (single underscore, colon, dot).
Common situations: Hub server runtime or LLM tool-call layer passes a bare tool name without the server prefix; a serialization/deserialization step strips or mangles the delimiter; the tool registration uses a different naming convention than expected.
Related errors
- Invalid JSON tool arguments for ${name}: ${(e as Error).mess
- Failed to resolve image model: ${modelId} for provider: ${pr
- Unsupported agent runtime type: ${entry.agentType}
- Invalid command: command must be a non-empty string
- Invalid command: command cannot be empty
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/d18cdced60e5e789.
Report an issue: GitHub.