{"record":{"id":"a58d6a707414632f","repo":"n8n-io/n8n","slug":"mcp-api-key-rotate-endpoint-returned-no-apikey","errorCode":null,"errorMessage":"MCP api-key rotate endpoint returned no apiKey","messagePattern":"MCP api-key rotate endpoint returned no apiKey","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/@n8n/instance-ai/evaluations/clients/n8n-client.ts","lineNumber":658,"sourceCode":"\t\t}\n\t}\n\n\t/**\n\t * Mint a fresh MCP API key for the authenticated user.\n\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}","sourceCodeStart":640,"sourceCodeEnd":676,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/instance-ai/evaluations/clients/n8n-client.ts#L640-L676","documentation":"rotateMcpApiKey POSTs to /rest/mcp/api-key/rotate, which should delete and recreate the key, returning an unredacted apiKey. If the response data has no apiKey field at all, the contract is broken and the caller cannot proceed — staging MCP auth would produce a broken config.","triggerScenarios":"Backend that does not implement the rotate endpoint; a future API change removing/renaming the field; a proxy returning an empty body.","commonSituations":"Pointing the eval client at an older n8n that lacks the rotate endpoint; version skew between client and server.","solutions":["Upgrade the n8n instance to a version that implements POST /rest/mcp/api-key/rotate.","Check the raw response (network tab / logs) to confirm the endpoint exists and returns the expected shape.","If rotation is unavailable, fall back to manually creating an MCP key and staging it."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Probe endpoint capability before relying on rotate:\nconst probe = await fetch(`${baseUrl}/rest/mcp/api-key/rotate`, { method: 'OPTIONS' });\nif (!probe.ok) throw new Error('rotate endpoint unavailable on this backend');","typeGuard":"const hasApiKey = (v: unknown): v is { apiKey: string } =>\n  typeof v === 'object' && v !== null && typeof (v as any).apiKey === 'string';","tryCatchPattern":"try { return await client.rotateMcpApiKey(); }\ncatch (e) {\n  if (e instanceof Error && e.message.includes('no apiKey')) { /* upgrade backend or stage manual key */ }\n  else throw e;\n}","preventionTips":["Pin a backend version that implements rotate.","Have a manual key-creation fallback documented.","Log the raw rotate response when debugging."],"tags":["mcp","auth","n8n-api","version-mismatch"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}