{"record":{"id":"fc0143fc6fb92d33","repo":"n8n-io/n8n","slug":"mcp-api-key-rotate-endpoint-returned-a-redacted-ke","errorCode":null,"errorMessage":"MCP api-key rotate endpoint returned a redacted key — cannot stage it for `claude` MCP auth","messagePattern":"MCP api-key rotate endpoint returned a redacted key — cannot stage it for `claude` MCP auth","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/@n8n/instance-ai/evaluations/clients/n8n-client.ts","lineNumber":663,"sourceCode":"\t * POST /rest/mcp/api-key/rotate\n\t *\n\t * Uses rotate rather than GET /rest/mcp/api-key because the GET only returns\n\t * the raw JWT when it creates the key; a pre-existing key comes back redacted\n\t * (`******abcd`), which would silently break MCP auth if staged into a\n\t * `claude` config. Rotate deletes + recreates, so the response is always\n\t * unredacted — at the cost of invalidating any prior MCP key for this user.\n\t */\n\tasync rotateMcpApiKey(): Promise<string> {\n\t\tconst data = this.unwrapRestData<{ apiKey?: string }>(\n\t\t\tawait this.fetch('/rest/mcp/api-key/rotate', { method: 'POST' }),\n\t\t);\n\t\tif (!data.apiKey) {\n\t\t\tthrow new Error('MCP api-key rotate endpoint returned no apiKey');\n\t\t}\n\t\t// JWTs are base64url segments and never contain \"*\" — its presence means\n\t\t// the server redacted the key, which would fail MCP auth downstream.\n\t\tif (data.apiKey.includes('*')) {\n\t\t\tthrow new Error(\n\t\t\t\t'MCP api-key rotate endpoint returned a redacted key — cannot stage it for `claude` MCP auth',\n\t\t\t);\n\t\t}\n\t\treturn data.apiKey;\n\t}\n\n\t/**\n\t * Delete a credential by ID.\n\t * DELETE /rest/credentials/:id\n\t */\n\tasync deleteCredential(id: string): Promise<void> {\n\t\tawait this.fetch(`/rest/credentials/${id}`, { method: 'DELETE' });\n\t}\n\n\t/**\n\t * Invite member users in one batched request. Requires an owner session.\n\t * Returns one row per invitee, reporting rather than throwing on failure:\n\t * n8n creates the user shells before it reports per-invite errors, so the","sourceCodeStart":645,"sourceCodeEnd":681,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/instance-ai/evaluations/clients/n8n-client.ts#L645-L681","documentation":"After rotating the MCP key, the client checks whether the returned value contains '*' — a marker that the server redacted it (e.g. `******abcd`). JWTs are base64url and never contain '*', so its presence means the key is unusable for staging claude MCP auth. This guard exists because rotate is supposed to always return unredacted keys.","triggerScenarios":"Server bug returning a redacted key despite the rotate contract; a backend that returns the GET-style redacted representation from the rotate endpoint.","commonSituations":"Pre-release n8n with a rotate bug; a fork that altered the rotate handler; race where rotate returns the cached redacted form.","solutions":["Upgrade n8n to a version where rotate reliably returns the raw key.","File a backend bug — the rotate contract was violated; do not attempt to unredact.","As a workaround, create a brand-new MCP key via the UI/API and stage that manually."],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":"const key = data.apiKey;\nif (typeof key !== 'string' || key.includes('*'))\n  throw new Error('redacted key returned; cannot stage for claude MCP');","typeGuard":"const isUnredactedKey = (v: unknown): v is string =>\n  typeof v === 'string' && v.length > 0 && !v.includes('*');","tryCatchPattern":"try { return await client.rotateMcpApiKey(); }\ncatch (e) {\n  if (e instanceof Error && e.message.includes('redacted')) { /* file backend bug, do not use */ }\n  else throw e;\n}","preventionTips":["Never attempt to unredact a returned key; treat '*' as poison.","Track the backend version that fixes the rotate redaction bug.","Add a regression test asserting rotate returns a JWT-shaped string."],"tags":["mcp","auth","n8n-api","contract-violation"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}