mastra-ai/mastra · error · HTTPException

MCP client with id ${id} already exists

Error message

MCP client with id ${id} already exists

What it means

HTTP 409 Conflict thrown because a stored MCP client with the derived or provided id already exists in the mcpClients store. The handler checks getById(id) before create to enforce unique ids; a duplicate is rejected rather than overwritten.

Source

Thrown at packages/server/src/server/handlers/stored-mcp-clients.ts:148

      const mcpClientStore = await storage.getStore('mcpClients');
      if (!mcpClientStore) {
        throw new HTTPException(500, { message: 'MCP clients storage domain is not available' });
      }

      // Derive ID from name if not explicitly provided
      const id = providedId || toSlug(name);

      if (!id) {
        throw new HTTPException(400, {
          message: 'Could not derive MCP client ID from name. Please provide an explicit id.',
        });
      }

      // Check if MCP client with this ID already exists
      const existing = await mcpClientStore.getById(id);
      if (existing) {
        throw new HTTPException(409, { message: `MCP client with id ${id} already exists` });
      }

      await mcpClientStore.create({
        mcpClient: {
          id,
          authorId,
          metadata: scopeStoredResourceMetadata(metadata, await getStoredResourceScope(mastra, requestContext)),
          name,
          description,
          servers,
        },
      });

      // Return the resolved MCP client (thin record + version config)
      // Use draft status since newly created entities start as drafts
      const resolved = await mcpClientStore.getByIdResolved(id, { status: 'draft' });
      if (!resolved) {
        throw new HTTPException(500, { message: 'Failed to resolve created MCP client' });

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Use PUT/PATCH on the existing id instead of POST if you intend an update.
  2. Choose a different name or explicit unique id for the new client.
  3. Fetch the existing client first and decide whether to reuse or rename.
  4. Implement client-side idempotency: on 409, GET the existing record and treat it as success if it matches the desired state.

Example fix

// before
await createClient({ id: 'my-client' }); // 409 on retry
// after
const existing = await getClient('my-client');
if (!existing) await createClient({ id: 'my-client' });
Defensive patterns

Strategy: try-catch

Validate before calling

const existing = await fetch(`/api/mcp/clients/${id}`).then(r => r.status !== 404);
if (existing) throw new Error(`Client ${id} already exists; use update instead`);

Type guard

function isConflict(status: number): boolean { return status === 409; }

Try / catch

const res = await fetch('/api/mcp/clients', { method: 'POST', body: JSON.stringify(payload) });
if (res.status === 409) {
  return fetch(`/api/mcp/clients/${payload.id}`).then(r => r.json()); // idempotent reuse
}
if (!res.ok) throw new Error(`Create failed: ${res.status}`);
return res.json();

Prevention

When it happens

Trigger: POST create with an id (explicit or slugified from name) that collides with an existing record; retrying a create that already succeeded; two clients whose names slugify to the same id.

Common situations: Idempotent retry logic replaying a successful request; naming collisions like 'My Client' and 'my-client' both becoming 'my-client'; re-running seed scripts without cleanup.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/f7afd6a15c4b5ed0. Report an issue: GitHub.