payloadcms/payload · error · APIError

Error initializing MCP handler: ${String(error)}

Error message

Error initializing MCP handler: ${String(error)}

What it means

Thrown by the MCP plugin's server builder when anything inside the tool/resource registration loop throws — a malformed Zod input schema that fails `toStandardSchema`, a duplicate `mcpName`, or a rejection from the underlying `server.registerTool`/`server.registerResource` call. The whole build is wrapped in one try/catch that re-throws the original cause as a 500 APIError, so the root error is preserved in the message suffix.

Source

Thrown at packages/plugin-mcp/src/mcp/buildMcpServer.ts:243

                input: toolInput,
                req,
                serverContext: ctx,
              })
              return finalizeToolResponse({
                input: toolInput,
                overrideResponse: tool.overrideResponse,
                response,
                toolName: item.mcpName,
              })
            },
          )
          logger.info(`✅ Tool: ${item.mcpName} Registered.`)
          break
        }
      }
    }
  } catch (error) {
    throw new APIError(`Error initializing MCP handler: ${String(error)}`, 500)
  }

  return server
}

const withSlugInput = ({
  name,
  input,
}: {
  input?: ToolInputSchema
  name: 'collectionSlug' | 'globalSlug'
}): ToolInputSchema => {
  const description = name === 'collectionSlug' ? 'The collection slug' : 'The global slug'
  const slugSchema = z.string().describe(description)

  if (!input) {
    return z.object({ [name]: slugSchema })
  }

View on GitHub (pinned to 00c58b35c0)

Solutions

  1. Read the `String(error)` suffix — it carries the original cause verbatim and pinpoints the failing item
  2. Validate each tool's `input` Zod schema in isolation before registering it with the plugin
  3. Ensure every tool/resource `mcpName` is unique across builtin + custom items
  4. Pin `@modelcontextprotocol/sdk` to the exact version the installed plugin-mcp release expects

Example fix

// before: schema with an unsupported construct
const input = z.object({ id: z.any() })
// after: use a concrete primitive
const input = z.object({ id: z.string() })
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate each tool's Zod schema parses a sample input before registering
import type { ZodTypeAny } from 'zod'
function assertSchemaOk(name: string, schema: ZodTypeAny | undefined, sample: unknown) {
  if (!schema) return
  const r = schema.safeParse(sample)
  if (!r.success) throw new Error(`tool ${name} schema rejects sample: ${r.error.message}`)
}

Type guard

import { APIError } from 'payload'
function isMcpInitError(e: unknown): e is APIError {
  return e instanceof APIError && typeof (e as any).statusCode === 'number'
    && /^Error initializing MCP handler:/.test((e as APIError).message)
}

Try / catch

try {
  const server = await buildMcpServer(config)
} catch (e) {
  if (e instanceof APIError && /^Error initializing MCP handler:/.test(e.message)) {
    // e.message suffix is the original cause — log it and surface to operator
    const cause = e.message.replace('Error initializing MCP handler: ', '')
    logger.error('MCP build failed: ' + cause)
  }
  throw e
}

Prevention

When it happens

Trigger: Registering an MCP tool whose `input` Zod schema uses a construct `toStandardSchema` cannot convert; registering two tools/resources with the same `mcpName`; an incompatible `@modelcontextprotocol/sdk` version where `registerTool`'s signature changed; a builtin resource whose URI template is invalid.

Common situations: Custom MCP tool authored with an unsupported Zod type (e.g. `z.any()`, `z.transform`); name collision between builtin collections tool and a user-supplied tool; major SDK upgrade of `@modelcontextprotocol/sdk` without bumping the plugin; passing a non-Zod schema object as `input`.

Related errors


AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12). Data as JSON: /api/errors/6c7054131cd3fcb1. Report an issue: GitHub.