Hmbown/CodeWhale · error
MCP repeated a pagination cursor
Error message
MCP {} repeated a pagination cursor What it means
observe_page records every nextCursor in a seen_cursors set. If the server returns a cursor that was already followed, the listing would loop forever, so it fails immediately with this error instead of spinning or returning partial results.
Solutions
- Fix/upgrade the MCP server so each nextCursor advances the pagination state
- Avoid a proxy that strips pagination parameters and replays the same request
- Report the server's loop; hardcode a smaller catalog or skip listing for that server
Defensive patterns
Strategy: try-catch
Validate before calling
// client-side: detect cursor repetition before the library does
let mut seen = std::collections::HashSet::new();
// on each page: if !seen.insert(cursor.clone()) { /* server is looping */ } Try / catch
match client.list_resources_with_metadata().await {
Ok(entries) => use(entries),
Err(e) if e.to_string().contains("repeated a pagination cursor") => {
eprintln!("MCP server pagination loop detected; filing bug against server");
}
Err(e) => return Err(e),
} Prevention
- Ensure server cursors strictly advance pagination state
- Never reuse a cursor value within one listing session
- Add server integration tests that walk the full cursor chain
When it happens
Trigger: list_paginated receives a nextCursor identical to one previously returned by the same listing operation (set insertion returns false).
Common situations: Buggy server pagination that re-emits the same cursor; cursor derived from a stable offset that never advances; server restart mid-listing resetting cursor state.
Related errors
- repeated pagination cursor; aborting
- exceeded the -page catalogue limit
- MCP exceeded its overall deadline
- MCP exceeded the -item catalog limit
- MCP exceeded the -page catalog limit
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/d99cf5e8fd257f78.
Report an issue: GitHub.
Appendix: source
Thrown at crates/mcp/src/stdio_client.rs:398
);
}
if self.bytes > self.max_bytes {
bail!(
"MCP {} exceeded the {}-byte aggregate catalog limit",
self.method,
self.max_bytes
);
}
let next_cursor = match page.get("nextCursor") {
None => None,
Some(Value::String(cursor)) => Some(cursor.clone()),
Some(_) => bail!("MCP {} returned a non-string nextCursor", self.method),
};
if let Some(cursor) = next_cursor.as_ref()
&& !self.seen_cursors.insert(cursor.clone())
{
bail!("MCP {} repeated a pagination cursor", self.method);
}
if next_cursor.is_some() && self.pages >= self.max_pages {
bail!(
"MCP {} exceeded the {}-page catalog limit",
self.method,
self.max_pages
);
}
Ok(next_cursor)
}
}
/// What the server said it supports in its `initialize` response.
///
/// `None` means the server sent no `capabilities` object at all. Those are
/// treated as legacy servers and probed optimistically; an explicit
/// capabilities object is honoured, because a tools-only server answers
/// `resources/list` with a "method not found" error that would otherwise failView on GitHub (pinned to 73e0f67d83)