different-ai/openwork · error · ExternalMcpDiagnosticError
MCP_CATALOG_CURSOR_SIZE_LIMIT
MCP_CATALOG_CURSOR_SIZE_LIMIT
Error message
MCP_CATALOG_CURSOR_SIZE_LIMIT
What it means
Thrown when a tools/list page returns a nextCursor whose serialized UTF-8 size exceeds EXTERNAL_MCP_CURSOR_LIMIT_BYTES (16 KiB). Cursors are re-sent by the gateway on subsequent calls and counted against the catalog byte budget, so oversized cursors are rejected.
Source
Thrown at ee/apps/den-api/src/capability-sources/external-mcp-client.ts:715
})
if (seenToolNames.has(tool.name)) {
throw catalogDiagnosticError({
tracker: input.diagnostic,
code: "MCP_CATALOG_DUPLICATE_TOOL",
operatorAction: "Ensure every tools/list page uses a unique, stable tool name.",
})
}
seenToolNames.add(tool.name)
tools.push(tool)
}
if (!result.nextCursor) {
input.diagnostic.passed("MCP_TOOL_DISCOVERY", "catalog_ready")
return tools
}
if (serializedStringBytes(result.nextCursor) > EXTERNAL_MCP_CURSOR_LIMIT_BYTES) {
throw catalogDiagnosticError({
tracker: input.diagnostic,
code: "MCP_CATALOG_CURSOR_SIZE_LIMIT",
operatorAction: `Reduce each serialized tools/list cursor below ${EXTERNAL_MCP_CURSOR_LIMIT_BYTES} UTF-8 bytes.`,
})
}
const cursorMeasurement = measureSerializedJson(
result.nextCursor,
EXTERNAL_MCP_CATALOG_LIMIT_BYTES - catalogBytes,
1,
)
if (!cursorMeasurement.ok) {
throw catalogDiagnosticError({
tracker: input.diagnostic,
code: "MCP_CATALOG_BYTE_LIMIT",
operatorAction: `Reduce the complete serialized tool catalog below ${EXTERNAL_MCP_CATALOG_LIMIT_BYTES} bytes.`,
})
}
catalogBytes += cursorMeasurement.bytes
if (seenCursors.has(result.nextCursor)) {
throw catalogDiagnosticError({View on GitHub (pinned to 2b7df46e8a)
Solutions
- Fix the provider to issue compact opaque cursors (e.g. an offset or short token)
- Move pagination state server-side and return a short handle as the cursor
- Reduce per-page page size so cursor state stays small
- If you cannot change the provider, use a scoped server with a small, simple result set
Example fix
// before
cursor = base64(JSON.stringify({ allRemainingToolIds }))
// after
cursor = "p:3" // server-side page pointer Defensive patterns
Strategy: validation
Validate before calling
if (nextCursor !== undefined && new TextEncoder().encode(nextCursor).byteLength > 16384) {
throw new Error("nextCursor exceeds 16KB UTF-8 limit")
} Type guard
function hasCompactCursor(page: { nextCursor?: string }): boolean {
return page.nextCursor === undefined || new TextEncoder().encode(page.nextCursor).byteLength <= 16384
} Try / catch
try {
await connectExternalMcp(...)
} catch (err) {
if (err instanceof Error && err.message.includes("MCP_CATALOG_CURSOR_SIZE_LIMIT")) {
console.error("Provider cursors too large; return short opaque tokens instead")
} else throw err
} Prevention
- Return short opaque cursor tokens (offset/id), never serialized state blobs
- Keep pagination state server-side
- Cap cursor size in your server's tools/list implementation
- Test cursor size with maximum page sizes
When it happens
Trigger: After a successful page fetch (when result.nextCursor exists), serializedStringBytes(result.nextCursor) > 16384 bytes.
Common situations: Providers embedding the entire remaining result set or a giant opaque state blob in the cursor; base64 of full response snapshots; ORM cursors serializing huge filter state.
Related errors
- MCP_CATALOG_CURSOR_LOOP
- pagination_unsupported
- MCP_TOOL_DISCOVERY
- MCP_CATALOG_TOOL_TITLE_LIMIT
- MCP_CATALOG_TOOL_DESCRIPTION_LIMIT
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/ef0a2688452d9e1e.
Report an issue: GitHub.