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

  1. Fix/upgrade the MCP server so each nextCursor advances the pagination state
  2. Avoid a proxy that strips pagination parameters and replays the same request
  3. 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

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


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 fail

View on GitHub (pinned to 73e0f67d83)