{"record":{"id":"e8a7a360d1fbd59e","repo":"abhigyanpatwari/GitNexus","slug":"tool-toolname-is-not-available-in-gitnexus-mc","errorCode":null,"errorMessage":"Tool \"${toolName}\" is not available in GitNexus MCP read-only mode.","messagePattern":"Tool \"(.+?)\" is not available in GitNexus MCP read-only mode\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gitnexus/src/mcp/read-only-policy.ts","lineNumber":37,"sourceCode":"]);\n\nconst MCP_READ_ONLY_ALIASES = new Set(['search', 'explore', 'overview']);\n\nexport function resolveMcpReadOnlyMode(env: NodeJS.ProcessEnv = process.env): boolean {\n  const value = env.GITNEXUS_MCP_READ_ONLY?.trim();\n  if (value === undefined || value === '' || value === '0') return false;\n  if (value === '1') return true;\n  throw new Error('GITNEXUS_MCP_READ_ONLY must be 0 or 1.');\n}\n\nexport function assertMcpReadOnlyToolCall(\n  toolName: string,\n  args: Record<string, unknown> | undefined,\n  readOnly: boolean,\n): void {\n  if (!readOnly) return;\n  if (!MCP_READ_ONLY_TOOLS.has(toolName) && !MCP_READ_ONLY_ALIASES.has(toolName)) {\n    throw new Error(`Tool \"${toolName}\" is not available in GitNexus MCP read-only mode.`);\n  }\n  if (typeof args?.repo === 'string' && args.repo.trim().startsWith('@')) {\n    throw new Error('Group routing is not available in GitNexus MCP read-only mode.');\n  }\n  // crossDepth/subgroup only do anything on the @group path rejected above,\n  // but rejecting them here keeps the advertised schema and the dispatch\n  // contract in agreement.\n  for (const groupOnlyArg of ['crossDepth', 'subgroup']) {\n    if (args?.[groupOnlyArg] !== undefined) {\n      throw new Error(\n        `Parameter \"${groupOnlyArg}\" is not available in GitNexus MCP read-only mode.`,\n      );\n    }\n  }\n}\n\nexport function readOnlyResourceTemplateAllowed(uriTemplate: string, readOnly: boolean): boolean {\n  return !readOnly || !/^gitnexus:\\/\\/group\\//iu.test(uriTemplate);","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/aac7515d2a8c50a1f8f923c6fb77218b333560d6/gitnexus/src/mcp/read-only-policy.ts#L19-L55","documentation":"Thrown by assertMcpReadOnlyToolCall when the MCP server runs in read-only mode (GITNEXUS_MCP_READ_ONLY=1) and a client invokes a tool that is not on the read-only allowlist. In read-only mode only the safe query tools in MCP_READ_ONLY_TOOLS and the legacy aliases 'search', 'explore', 'overview' are dispatchable; every mutating or non-allowlisted tool is rejected before it reaches the backend.","triggerScenarios":"With GITNEXUS_MCP_READ_ONLY=1, calling any tool outside the allowlist — e.g. rename, detect_changes, analyze, or any group_* tool — through the MCP client (callTool / tools/call).","commonSituations":"An operator hardens a shared MCP endpoint with read-only mode, then an agent re-runs its usual workflow that includes rename or detect_changes. A prompt/agent template written for read-write mode is reused against a read-only deployment.","solutions":["Switch the workflow to read-only-safe tools (query, context, impact, explain, and the other allowlisted read tools).","If the mutation is genuinely required, restart the server with GITNEXUS_MCP_READ_ONLY unset or set to 0.","Catch this error client-side and report to the user that the endpoint is read-only instead of retrying — retry cannot succeed."],"exampleFix":"// before\nawait client.callTool({ name: 'rename', arguments: { target: 'parseArgs', new_name: 'parseCliArgs', repo: '.' } });\n\n// after (read-only mode)\nawait client.callTool({ name: 'query', arguments: { search_query: 'parseArgs', repo: '.' } });","handlingStrategy":"validation","validationCode":"const READ_ONLY_OK = new Set(['query', 'context', 'impact', 'explain', 'search', 'explore', 'overview' /* + the rest of the server's allowlist */]);\nfunction assertToolAllowed(tool: string, readOnly: boolean) {\n  if (readOnly && !READ_ONLY_OK.has(tool)) {\n    throw new Error(`Tool ${tool} is not available in read-only mode; pick a read-only tool.`);\n  }\n}","typeGuard":"const isReadOnlyTool = (tool: string): boolean =>\n  MCP_READ_ONLY_TOOLS.has(tool) || ['search', 'explore', 'overview'].includes(tool);","tryCatchPattern":"try {\n  return await client.callTool({ name, arguments });\n} catch (e) {\n  if (e instanceof Error && /not available in GitNexus MCP read-only mode/.test(e.message)) {\n    return { skipped: true, reason: 'endpoint is read-only' }; // degrade gracefully, never retry\n  }\n  throw e;\n}","preventionTips":["Fetch the server's advertised tool list at connect time and filter workflows to it, instead of hard-coding tool names.","Keep agent templates parameterized by deployment mode (read-only vs read-write).","Treat read-only rejections as permanent for the session: surface them to the operator, do not retry."],"tags":["mcp","read-only-mode","authorization","tool-dispatch"],"backgroundTag":"readonly-mode-tool-denied","analyzedSha":"aac7515d2a8c50a1f8f923c6fb77218b333560d6","analyzedAt":"2026-08-20T23:29:22.980Z","contentChangedAt":"2026-08-20T23:29:22.980Z","schemaVersion":2},"datasetVersion":"2026-09-08T20:17:18.057Z"}