{"record":{"id":"ef0a2688452d9e1e","repo":"different-ai/openwork","slug":"mcp-catalog-cursor-size-limit-ef0a26","errorCode":"MCP_CATALOG_CURSOR_SIZE_LIMIT","errorMessage":"MCP_CATALOG_CURSOR_SIZE_LIMIT","messagePattern":"MCP_CATALOG_CURSOR_SIZE_LIMIT","errorType":"error_code","errorClass":"ExternalMcpDiagnosticError","httpStatus":null,"severity":"error","filePath":"ee/apps/den-api/src/capability-sources/external-mcp-client.ts","lineNumber":715,"sourceCode":"      })\n      if (seenToolNames.has(tool.name)) {\n        throw catalogDiagnosticError({\n          tracker: input.diagnostic,\n          code: \"MCP_CATALOG_DUPLICATE_TOOL\",\n          operatorAction: \"Ensure every tools/list page uses a unique, stable tool name.\",\n        })\n      }\n      seenToolNames.add(tool.name)\n      tools.push(tool)\n    }\n    if (!result.nextCursor) {\n      input.diagnostic.passed(\"MCP_TOOL_DISCOVERY\", \"catalog_ready\")\n      return tools\n    }\n    if (serializedStringBytes(result.nextCursor) > EXTERNAL_MCP_CURSOR_LIMIT_BYTES) {\n      throw catalogDiagnosticError({\n        tracker: input.diagnostic,\n        code: \"MCP_CATALOG_CURSOR_SIZE_LIMIT\",\n        operatorAction: `Reduce each serialized tools/list cursor below ${EXTERNAL_MCP_CURSOR_LIMIT_BYTES} UTF-8 bytes.`,\n      })\n    }\n    const cursorMeasurement = measureSerializedJson(\n      result.nextCursor,\n      EXTERNAL_MCP_CATALOG_LIMIT_BYTES - catalogBytes,\n      1,\n    )\n    if (!cursorMeasurement.ok) {\n      throw catalogDiagnosticError({\n        tracker: input.diagnostic,\n        code: \"MCP_CATALOG_BYTE_LIMIT\",\n        operatorAction: `Reduce the complete serialized tool catalog below ${EXTERNAL_MCP_CATALOG_LIMIT_BYTES} bytes.`,\n      })\n    }\n    catalogBytes += cursorMeasurement.bytes\n    if (seenCursors.has(result.nextCursor)) {\n      throw catalogDiagnosticError({","sourceCodeStart":697,"sourceCodeEnd":733,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/ee/apps/den-api/src/capability-sources/external-mcp-client.ts#L697-L733","documentation":"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.","triggerScenarios":"After a successful page fetch (when result.nextCursor exists), serializedStringBytes(result.nextCursor) > 16384 bytes.","commonSituations":"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.","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"],"exampleFix":"// before\ncursor = base64(JSON.stringify({ allRemainingToolIds }))\n// after\ncursor = \"p:3\" // server-side page pointer","handlingStrategy":"validation","validationCode":"if (nextCursor !== undefined && new TextEncoder().encode(nextCursor).byteLength > 16384) {\n  throw new Error(\"nextCursor exceeds 16KB UTF-8 limit\")\n}","typeGuard":"function hasCompactCursor(page: { nextCursor?: string }): boolean {\n  return page.nextCursor === undefined || new TextEncoder().encode(page.nextCursor).byteLength <= 16384\n}","tryCatchPattern":"try {\n  await connectExternalMcp(...)\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"MCP_CATALOG_CURSOR_SIZE_LIMIT\")) {\n    console.error(\"Provider cursors too large; return short opaque tokens instead\")\n  } else throw err\n}","preventionTips":["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"],"tags":["mcp","pagination","cursor","byte-limit"],"backgroundTag":"payload-size-limit-exceeded","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}