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
- Use PUT/PATCH on the existing id instead of POST if you intend an update.
- Choose a different name or explicit unique id for the new client.
- Fetch the existing client first and decide whether to reuse or rename.
- 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
- Make create calls idempotent by treating 409 as success-when-identical.
- Check existence before POST in seeding scripts.
- Use unique names or explicit uuid ids to avoid slug collisions.
- Guard concurrent creates with a lock or upsert-style flow if supported.
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
- Agent with id ${id} already exists
- Scorer definition with id ${id} already exists
- Skill with id ${id} already exists
- Skipped: Scorer ${filename} already exists at ${scorersPath}
- Skill with id "${id}" already exists.
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/f7afd6a15c4b5ed0.
Report an issue: GitHub.