{"record":{"id":"df5d918ab89061d5","repo":"toeverything/AFFiNE","slug":"mcp-credential-name-is-required","errorCode":null,"errorMessage":"MCP credential name is required","messagePattern":"MCP credential name is required","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"packages/backend/server/src/plugins/copilot/mcp/credential.ts","lineNumber":200,"sourceCode":"      new Date(now.getTime() - LAST_USED_WRITE_INTERVAL_MS),\n      now\n    );\n    return credential;\n  }\n\n  private async issue(\n    input: IssueMcpCredential & {\n      familyId?: string;\n      generation?: number;\n      graceEndsAt?: Date;\n    }\n  ) {\n    if (!ALLOWED_EXPIRATION_DAYS.has(input.expirationDays)) {\n      throw new BadRequestException('Unsupported MCP credential expiration');\n    }\n    const name = input.name.trim();\n    if (!name || name.length > 64) {\n      throw new BadRequestException('MCP credential name is required');\n    }\n\n    const id = randomUUID();\n    const secret = this.crypto.randomBytes(32).toString('base64url');\n    const secretHash = this.crypto.sha256(secret).toString('hex');\n    const credential = await this.models.mcpCredential.create({\n      id,\n      familyId: input.familyId ?? id,\n      generation: input.generation ?? 0,\n      name,\n      secretHash,\n      fingerprint: secretHash.slice(0, 12),\n      userId: input.userId,\n      workspaceId: input.workspaceId,\n      accessMode: input.accessMode,\n      expiresAt: new Date(\n        Date.now() + input.expirationDays * 24 * 60 * 60 * 1000\n      ),","sourceCodeStart":182,"sourceCodeEnd":218,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/packages/backend/server/src/plugins/copilot/mcp/credential.ts#L182-L218","documentation":"BadRequestException('MCP credential name is required') thrown in issue() when the trimmed name is empty or longer than 64 characters. Rejected before creation, so no partial credential rows appear. Note the message only says 'required' even though >64 chars also triggers it — a client showing only the message will mislead users about over-length names.","triggerScenarios":"createMcpCredential with name of spaces/empty string; a 65+ character name (e.g. auto-generated from a long server URL); name that is only whitespace after trim.","commonSituations":"UI auto-names credentials from MCP server URLs which can exceed 64 chars; form validation missing on the client; user pastes a sentence as the name.","solutions":["Trim the name client-side and enforce 1-64 characters before submitting","Shorten auto-generated names (truncate the URL-derived suffix)","As a maintainer: split the message into 'required' vs 'too long' for accurate client feedback"],"exampleFix":"// before\nawait credentials.create({ name: rawUrl, expirationDays: 90, ... });\n\n// after\nconst name = rawUrl.trim().slice(0, 64) || 'mcp-credential';\nif (!rawUrl.trim()) throw new UserError('Name is required');\nawait credentials.create({ name, expirationDays: 90, ... });","handlingStrategy":"validation","validationCode":"const name = rawName.trim();\nif (!name) throw new UserError('Credential name is required');\nif (name.length > 64) throw new UserError('Credential name must be at most 64 characters');\nawait credentials.create({ ...input, name });","typeGuard":"const isValidCredentialName = (n: unknown): n is string =>\n  typeof n === 'string' && n.trim().length >= 1 && n.trim().length <= 64;","tryCatchPattern":"try {\n  await credentials.create(input);\n} catch (e) {\n  if (e instanceof BadRequestException && /name is required/.test(e.message)) {\n    promptUserForName(); // covers both empty and >64 cases\n  } else throw e;\n}","preventionTips":["Trim and length-check names client-side before submit","Truncate auto-generated (URL-derived) names to 64 characters","Remember the server reuses this message for over-length names — validate both conditions locally"],"tags":["mcp","credential","validation","name-length"],"backgroundTag":"field-validation-failed","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"}