toeverything/AFFiNE · error · BadRequestException
MCP write tools are not available
Error message
MCP write tools are not available
What it means
BadRequestException('MCP write tools are not available') from the createMcpCredential GraphQL mutation: requesting accessMode READ_WRITE is only allowed when env.dev is true or the deployment's namespaces.canary flag is set. On production namespaces, write-capable MCP credentials are feature-flagged off, so only READ may be issued.
Source
Thrown at packages/backend/server/src/plugins/copilot/mcp/resolver.ts:125
return await this.credentials.list(user.id, workspaceId);
}
@Query(() => Boolean)
mcpCredentialReadWriteAvailable() {
return env.dev || env.namespaces.canary;
}
@Mutation(() => RevealedMcpCredentialType)
async createMcpCredential(
@CurrentUser() user: CurrentUser,
@Args('input') input: CreateMcpCredentialInput
) {
if (
input.accessMode === McpAccessMode.READ_WRITE &&
!env.dev &&
!env.namespaces.canary
) {
throw new BadRequestException('MCP write tools are not available');
}
await this.ac
.user(user.id)
.workspace(input.workspaceId)
.assert('Workspace.Read');
return await this.credentials.create({ ...input, userId: user.id });
}
@Mutation(() => RevealedMcpCredentialType)
async rotateMcpCredential(
@CurrentUser() user: CurrentUser,
@Args('id', { type: () => ID }) id: string,
@Args('workspaceId') workspaceId: string,
@Args('expirationDays', { type: () => Int, defaultValue: 90 })
expirationDays: number
) {
await this.ac.user(user.id).workspace(workspaceId).assert('Workspace.Read');
return await this.credentials.rotate(View on GitHub (pinned to b4c8548c09)
Solutions
- Use accessMode: READ on non-canary production deployments
- To test write tools locally, set the dev environment flag (env.dev = true)
- To enable writes for a deployment, run it in the canary namespace (env.namespaces.canary)
- Client-side: hide/disable the READ_WRITE option based on a server capability flag instead of letting users hit the 400
Example fix
// before
await graphql(`mutation { createMcpCredential(input: { accessMode: READ_WRITE, ... }) }`);
// after
const accessMode = canaryOrDev ? 'READ_WRITE' : 'READ';
await graphql(`mutation { createMcpCredential(input: { accessMode: ${accessMode}, ... }) }`); Defensive patterns
Strategy: validation
Validate before calling
const canWrite = env.dev || env.namespaces.canary; // mirror server flags const accessMode = canWrite ? 'READ_WRITE' : 'READ'; if (input.accessMode === 'READ_WRITE' && !canWrite) input.accessMode = 'READ'; await createMcpCredential(input);
Type guard
const isAllowedAccessMode = (mode: string, canWrite: boolean): mode is 'READ' | 'READ_WRITE' => mode === 'READ' || (mode === 'READ_WRITE' && canWrite);
Try / catch
try {
await createMcpCredential(input);
} catch (e) {
if (e instanceof BadRequestException && /write tools/.test(e.message)) {
input.accessMode = 'READ';
await createMcpCredential(input);
} else throw e;
} Prevention
- Expose a server capability flag for MCP write support and gate the UI option on it
- Default forms to READ on production deployments
- Read the deployment's namespace config before requesting write credentials
When it happens
Trigger: Calling createMcpCredential with accessMode: READ_WRITE on a production deployment; canary flag not set in the release environment; dev flag accidentally false in local docker-compose.
Common situations: Feature ships to canary but the client is pointed at prod; environment misconfiguration where canary namespace label is missing; users requesting write credentials before GA of the feature.
Related errors
- MCP credential not found
- Unauthorized
- -32000
- Unsupported MCP credential expiration
- MCP credential name is required
AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18).
Data as JSON: /api/errors/5880b73624b8c3ab.
Report an issue: GitHub.