Hmbown/CodeWhale · error · anyhow::Error
{} repeated pagination cursor; aborting
Error message
{} repeated pagination cursor; aborting What it means
After each catalogue page, observe_page records the returned nextCursor in a HashSet; if a cursor that was already seen appears again, pagination would loop forever and the method aborts (crates/tui/src/mcp.rs:1332-1340). This is an infinite-loop guard against servers whose cursor generation regresses or echoes the cursor that was just sent.
Source
Thrown at crates/tui/src/mcp.rs:1339
self.method,
MAX_MCP_CATALOG_ITEMS
);
}
if self.bytes > MAX_MCP_CATALOG_BYTES {
anyhow::bail!(
"{} exceeded the {}-byte aggregate catalogue limit",
self.method,
MAX_MCP_CATALOG_BYTES
);
}
let cursor = result
.get("nextCursor")
.and_then(|value| value.as_str())
.map(str::to_owned);
if let Some(cursor) = cursor.as_ref()
&& !self.seen_cursors.insert(cursor.clone())
{
anyhow::bail!("{} repeated pagination cursor; aborting", self.method);
}
Ok(cursor)
}
}
fn is_legacy_sse_transport(config: &McpServerConfig) -> bool {
config
.transport
.as_deref()
.map(|transport| transport.trim().eq_ignore_ascii_case("sse"))
.unwrap_or(false)
}
pub fn validate_mcp_transport(transport: Option<&str>) -> Result<()> {
let Some(transport) = transport else {
return Ok(());
};
if transport.trim().eq_ignore_ascii_case("sse") {View on GitHub (pinned to 8880682c63)
Solutions
- Fix the server: cursors must advance and never repeat; opaque stateless tokens (e.g. encoding the offset) are the robust pattern.
- If the catalogue is small, disable server-side pagination and return everything on one page with no nextCursor.
- Ensure load-balanced instances share cursor state or use sticky sessions.
- Retry after a server restart has settled - transient resets clear themselves.
Example fix
# before (server bug): every page returns nextCursor="page-1"
# tools/list repeated pagination cursor; aborting
# after (server): cursor derived from a monotonic offset, never repeated
# cursor = base64(f"{offset}:{catalogue_version}") Defensive patterns
Strategy: validation
Validate before calling
#!/usr/bin/env bash
# Walk the catalogue once and fail if any cursor repeats.
url="https://mcp.example.com/mcp"; cursor=""; seen=""
while :; do
body=$(curl -s "$url" -H 'content-type: application/json' \
-d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\"${cursor:+,\"params\":{\"cursor\":\"$cursor\"}}}")
cursor=$(printf '%s' "$body" | jq -r '.result.nextCursor // empty')
[ -z "$cursor" ] && break
case " $seen " in *" $cursor "*) echo "cursor repeated: $cursor"; exit 1;; esac
seen="$seen $cursor"
done; echo "cursors unique" Prevention
- Implement cursors as opaque stateless tokens derived from an offset.
- Never echo the incoming cursor back as nextCursor on the same page.
- Share cursor state across load-balanced instances or use sticky sessions.
When it happens
Trigger: A server that ignores the cursor parameter and always returns the same first-page cursor; server state resetting mid-pagination (restart, cache eviction); a load balancer alternating between instances with independent cursor state.
Common situations: Stateless server implementations that do not really implement cursors; rolling restarts during discovery; sticky-session-less LBs in front of stateful MCP servers.
Related errors
- {} exceeded the {}-page catalogue limit
- {} exceeded the {}-item catalogue limit
- {} exceeded the {}-byte aggregate catalogue limit
- overlap must be smaller than max_chars
- invalid JSON payload at key {MCP_SERVER_DEFINITIONS_KEY}; co
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/42aaa582abeb2f1a.
Report an issue: GitHub.