{"record":{"id":"8b5945d1fa3c7918","repo":"CherryHQ/cherry-studio","slug":"mcp-tool-is-disabled-name","errorCode":null,"errorMessage":"MCP tool is disabled: ${name}","messagePattern":"MCP tool is disabled: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"src/main/ai/mcp/McpRuntimeService.ts","lineNumber":1232,"sourceCode":"        getServerLogger(server, { tool: name, callId: toolCallId }).debug(`Calling tool`, {\n          args: redactSensitive(args)\n        })\n        if (typeof args === 'string') {\n          if (args.trim() === '') {\n            args = {}\n          } else {\n            try {\n              args = JSON.parse(args)\n            } catch (e) {\n              // Fail fast instead of forwarding malformed JSON as a raw string — the MCP\n              // server expects an object/record, so a bare string yields opaque downstream errors.\n              throw new Error(`Invalid JSON tool arguments for ${name}: ${(e as Error).message}`)\n            }\n          }\n        }\n        const sourcePolicy = this.getLatestSourcePolicy(server)\n        if (isMcpToolDisabledBySource(sourcePolicy, { name })) {\n          throw new Error(`MCP tool is disabled: ${name}`)\n        }\n        // Client init (ping probe, transport connect, OAuth) has no unified timeout at this\n        // layer — release this call's wait on abort instead of blocking until it settles.\n        // The shared `pendingClients` init keeps running (only this caller's wait is released),\n        // and both racers are consumed, so the loser's late rejection is never unhandled.\n        // The listener is removed once the race settles: `once` only cleans up after an\n        // abort fires, and the composed signal is retained by the long-lived stream signal —\n        // leaving it installed would accumulate a closure per tool call.\n        let handleAbort: (() => void) | undefined\n        const client = await Promise.race([\n          this.getOrCreateClient(server),\n          new Promise<never>((_, reject) => {\n            handleAbort = (): void => reject(getAbortReason(effectiveSignal))\n            if (effectiveSignal.aborted) return handleAbort()\n            effectiveSignal.addEventListener('abort', handleAbort, { once: true })\n          })\n        ]).finally(() => {\n          if (handleAbort) effectiveSignal.removeEventListener('abort', handleAbort)","sourceCodeStart":1214,"sourceCodeEnd":1250,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/mcp/McpRuntimeService.ts#L1214-L1250","documentation":"Thrown before invoking an MCP tool when the latest source policy marks the tool as disabled. The check `isMcpToolDisabledBySource(sourcePolicy, { name })` evaluates the server's current disabled-tools configuration. This is a policy enforcement guard, not a transport or connection error.","triggerScenarios":"Calling `callToolByServer` or `callToolById` for a tool whose name appears in the server's `disabledTools` array or is disabled by the current source policy snapshot. The policy is fetched via `getLatestSourcePolicy(server)` at call time.","commonSituations":"User disabled specific tools in the MCP settings UI (per-tool toggle); an admin policy disabled certain tools; the tool was disabled by a security/compliance rule; the source policy was updated after the tool list was cached.","solutions":["Re-enable the tool in MCP settings → server details → tools list","Check the server's `disabledTools` array in the database to confirm which tools are disabled","If using source policies, review the policy configuration that disabled the tool","In the calling layer, check `isMcpToolDisabledBySource` before attempting the call to provide a cleaner error message"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"import { isMcpToolDisabledBySource } from '...'\n\nconst sourcePolicy = runtime.getLatestSourcePolicy(server)\nif (isMcpToolDisabledBySource(sourcePolicy, { name: toolName })) {\n  throw new Error(`Tool '${toolName}' is disabled for server '${server.name}'`)\n}\nawait runtime.callToolByServer({ server, name: toolName, args })","typeGuard":"function isToolEnabled(server: McpServer, toolName: string, policy: SourcePolicy): boolean {\n  return !isMcpToolDisabledBySource(policy, { name: toolName })\n}","tryCatchPattern":"try {\n  await runtime.callToolByServer({ server, name, args })\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('MCP tool is disabled')) {\n    // Skip this tool or inform the user it needs to be enabled\n    logger.info(`Tool ${name} is disabled, skipping`)\n    return null\n  }\n  throw e\n}","preventionTips":["Filter tool lists by the source policy before presenting them to the LLM","Keep the disabledTools array and source policy in sync with the UI state","Log which tools are disabled to aid debugging when calls fail unexpectedly"],"tags":["mcp","tool-calling","policy","configuration","access-control"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}