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

  1. Use accessMode: READ on non-canary production deployments
  2. To test write tools locally, set the dev environment flag (env.dev = true)
  3. To enable writes for a deployment, run it in the canary namespace (env.namespaces.canary)
  4. 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

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


AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18). Data as JSON: /api/errors/5880b73624b8c3ab. Report an issue: GitHub.