{"record":{"id":"d18cdced60e5e789","repo":"CherryHQ/cherry-studio","slug":"invalid-tool-id-format-toolid","errorCode":null,"errorMessage":"Invalid tool ID format: ${toolId}","messagePattern":"Invalid tool ID format: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ai/mcp/McpRuntimeService.ts","lineNumber":334,"sourceCode":"      return\n    }\n\n    const status: McpRuntimeStatus = {\n      state,\n      lastCheckedAt: Date.now(),\n      ...(lastError !== undefined ? { lastError } : {})\n    }\n    cacheService.setShared(key, status)\n  }\n\n  /**\n   * Call a tool by its full ID (serverId__toolName format).\n   * Used by Hub server's runtime.\n   */\n  public async callToolById(toolId: string, params: unknown, callId?: string): Promise<McpCallToolResponse> {\n    const parts = toolId.split('__')\n    if (parts.length < 2) {\n      throw new Error(`Invalid tool ID format: ${toolId}`)\n    }\n\n    const serverId = parts[0]\n    const toolName = parts.slice(1).join('__')\n\n    const server = mcpServerService.getById(serverId)\n\n    logger.debug(`[callToolById] Calling tool ${toolName} on server ${server.name}`)\n\n    return this.callToolByServer({\n      server,\n      name: toolName,\n      args: params,\n      callId\n    })\n  }\n\n  public getServerKey(server: McpServer): string {","sourceCodeStart":316,"sourceCodeEnd":352,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/mcp/McpRuntimeService.ts#L316-L352","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before\nconst result = await runtime.callToolById('search_web', params)\n\n// after\nconst result = await runtime.callToolById(`${serverId}__search_web`, params)","handlingStrategy":"validation","validationCode":"function buildToolId(serverId: string, toolName: string): string {\n  if (!serverId || !toolName) {\n    throw new Error('Both serverId and toolName are required')\n  }\n  return `${serverId}__${toolName}`\n}\n\n// Validate before calling\nfunction isValidToolId(toolId: string): boolean {\n  return toolId.split('__').length >= 2\n}","typeGuard":"function isFullToolId(toolId: string): boolean {\n  const parts = toolId.split('__')\n  return parts.length >= 2 && parts[0].length > 0 && parts[1].length > 0\n}","tryCatchPattern":"try {\n  await runtime.callToolById(toolId, params)\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Invalid tool ID format')) {\n    // Log the malformed toolId and skip or construct a valid one\n    logger.warn(`Skipping tool call with invalid ID: ${toolId}`)\n    return null\n  }\n  throw e\n}","preventionTips":["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"],"tags":["mcp","tool-calling","validation","runtime"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}