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.

Solutions

  1. Fix the provider to issue compact opaque cursors (e.g. an offset or short token)
  2. Move pagination state server-side and return a short handle as the cursor
  3. Reduce per-page page size so cursor state stays small
  4. 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

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


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/ef0a2688452d9e1e. Report an issue: GitHub.

Appendix: 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)