{"record":{"id":"21b45813d91b6437","repo":"toeverything/AFFiNE","slug":"unauthorized","errorCode":null,"errorMessage":"Unauthorized","messagePattern":"Unauthorized","errorType":"exception","errorClass":"UnauthorizedException","httpStatus":401,"severity":"critical","filePath":"packages/backend/server/src/plugins/copilot/mcp/credential.ts","lineNumber":166,"sourceCode":"    }\n    const result = await this.models.mcpCredential.revokeFamily(\n      credential.familyId,\n      userId,\n      workspaceId\n    );\n    if (result.count) {\n      this.event.emit('mcp.credential.revoked', {\n        credentialId: id,\n        userId,\n        workspaceId,\n      });\n    }\n    return result.count > 0;\n  }\n\n  async authenticate(token: string, workspaceId: string) {\n    const parsed = this.parse(token);\n    if (!parsed) throw new UnauthorizedException();\n\n    const credential = await this.models.mcpCredential.authenticate(\n      parsed.id,\n      workspaceId\n    );\n    if (!credential) throw new UnauthorizedException();\n\n    const actualHash = this.crypto.sha256(parsed.secret).toString('hex');\n    if (!this.crypto.compare(actualHash, credential.secretHash)) {\n      throw new UnauthorizedException();\n    }\n\n    const now = new Date();\n    await this.models.mcpCredential.touch(\n      credential.id,\n      new Date(now.getTime() - LAST_USED_WRITE_INTERVAL_MS),\n      now\n    );","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/packages/backend/server/src/plugins/copilot/mcp/credential.ts#L148-L184","documentation":"UnauthorizedException thrown in MCP credential authenticate() when this.parse(token) returns null — the bearer token is not a syntactically valid 'id.secret' MCP credential token. Parsing fails before any database or hash work; the credential was never even looked up. The MCP HTTP controller catches it and returns JSON-RPC error code -32000 'Authentication failed' (HTTP 401).","triggerScenarios":"Sending an Authorization header that is not a well-formed MCP credential token (wrong scheme, base64 garbage, missing secret segment, empty string); using a JWT or API key where the MCP credential token belongs; token truncated by copy-paste.","commonSituations":"Client config pastes the wrong secret type into the MCP server URL/header; secrets manager mangles the token; UI shows the token with whitespace/newline that gets included.","solutions":["Regenerate and copy the full revealed token (id + secret) exactly as issued by createMcpCredential","Strip whitespace/newlines from the token before putting it in the Authorization header","Confirm you are using the MCP credential token format, not a JWT or other API key","If you control the client, validate the token shape (two non-empty segments) before connecting"],"exampleFix":"// before\nheaders: { Authorization: `Bearer ${jwt}` } // wrong token type\n\n// after\nheaders: { Authorization: `Bearer ${mcpCredentialToken.trim()}` } // 'id.secret' from reveal","handlingStrategy":"validation","validationCode":"const isValidMcpToken = (t: string) => {\n  const parts = t.trim().split('.');\n  return parts.length === 2 && parts[0].length > 0 && parts[1].length > 0;\n};\nif (!isValidMcpToken(token)) throw new Error('Token is not a valid MCP credential token');","typeGuard":"const isMcpCredentialToken = (t: unknown): t is string =>\n  typeof t === 'string' && /^[^.]+\\.[^.]+$/.test(t.trim());","tryCatchPattern":"try {\n  await mcpClient.connect();\n} catch (e) {\n  if (e.code === -32000 && e.message === 'Authentication failed') {\n    const revealed = await reissueMcpCredential();\n    mcpClient.setToken(revealed.token);\n    await mcpClient.connect();\n  } else throw e;\n}","preventionTips":["Validate the id.secret shape client-side before the first request","Copy tokens from the credential reveal output verbatim, stripping whitespace","Never substitute a JWT or other API key for the MCP credential token"],"tags":["mcp","credential","authentication","token","unauthorized"],"backgroundTag":"invalid-credentials","analyzedSha":"b4c8548c09da21b2898443559a5b846f0ccf5dd8","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}