{"record":{"id":"3a8ca4d2f8555714","repo":"different-ai/openwork","slug":"mcp-catalog-cursor-loop-3a8ca4","errorCode":"MCP_CATALOG_CURSOR_LOOP","errorMessage":"MCP_CATALOG_CURSOR_LOOP","messagePattern":"MCP_CATALOG_CURSOR_LOOP","errorType":"error_code","errorClass":"ExternalMcpDiagnosticError","httpStatus":null,"severity":"error","filePath":"ee/apps/den-api/src/capability-sources/external-mcp-client.ts","lineNumber":735,"sourceCode":"      })\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({\n        tracker: input.diagnostic,\n        code: \"MCP_CATALOG_CURSOR_LOOP\",\n        operatorAction: \"Fix the provider's tools/list pagination so each nextCursor advances.\",\n      })\n    }\n    seenCursors.add(result.nextCursor)\n    cursor = result.nextCursor\n  }\n  throw catalogDiagnosticError({\n    tracker: input.diagnostic,\n    code: \"MCP_CATALOG_PAGE_LIMIT\",\n    operatorAction: `Reduce the provider catalog to at most ${pageLimit} pages or use a scoped MCP server.`,\n  })\n}\n\nexport type ExternalMcpConnectResult =\n  | { status: \"connected\" }\n  | { status: \"needs_auth\"; authorizeUrl: string }\n\n/**","sourceCodeStart":717,"sourceCodeEnd":753,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/ee/apps/den-api/src/capability-sources/external-mcp-client.ts#L717-L753","documentation":"Thrown when tools/list pagination returns a nextCursor that was already seen in this collection pass, indicating the provider's pagination is looping and will never terminate. The gateway detects this via the seenCursors set and aborts instead of looping forever.","triggerScenarios":"seenCursors.has(result.nextCursor) is true when the next page's cursor is generated — i.e. the provider handed back a cursor identical to one already consumed.","commonSituations":"Broken server pagination that ignores the cursor and returns page 1's next cursor forever; cursors that encode a filter the server does not actually apply; stale/cached pages; static nextCursor pointing at a page that returns itself.","solutions":["Fix the provider's tools/list so each nextCursor advances to new results","Verify the server actually applies the cursor argument when filtering results","Check for caching layers serving a stale first page for every cursor","If a middle page cursor repeats due to a server bug, report it to the provider operator"],"exampleFix":"// before: handler ignores cursor, always returns nextCursor \"p:2\"\n// after: derive nextCursor from the served page, e.g. offset += pageSize","handlingStrategy":"retry","validationCode":"const seen = new Set<string>()\nlet cursor: string | undefined\nwhile (cursor !== undefined) {\n  if (seen.has(cursor)) throw new Error(\"pagination loop detected: repeated nextCursor\")\n  seen.add(cursor)\n  cursor = (await listPage(cursor)).nextCursor\n}","typeGuard":null,"tryCatchPattern":"try {\n  await connectExternalMcp(...)\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"MCP_CATALOG_CURSOR_LOOP\")) {\n    console.error(\"Provider pagination loops; fix tools/list nextCursor advancement before retrying\")\n    // do not blind-retry: the loop is deterministic until the server is fixed\n  } else throw err\n}","preventionTips":["Ensure your server derives nextCursor from the served page, not a constant","Integration-test full pagination of your tools/list","Check caching layers for stale first-page responses","Log cursor values server-side to spot non-advancing sequences"],"tags":["mcp","pagination","cursor","infinite-loop"],"backgroundTag":"pagination-cursor-loop","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}