Skip to content

tools/list returns a non-advancing nextCursor on the last/only page → spec-compliant clients paginate infinitely (tool list inflates to ~160 in Claude Code) #1247

Closed as not planned

Description

@sqpsqp0208

Title

tools/list returns a non-advancing nextCursor on the last/only page → spec-compliant clients paginate infinitely (tool list inflates to ~160 in Claude Code)


Summary

tools/list always includes a nextCursor even when the whole tool set fits in a
single page, and the cursor does not advance: re-requesting with that cursor
returns the exact same tools plus the same nextCursor. Any MCP client that follows
nextCursor pagination (per the MCP spec) loops indefinitely and accumulates the
tool list. In Claude Code this shows up as ~160 tools (8 unique × ~20).

Environment

  • codebase-memory-mcp 0.9.0 (linux-amd64)
  • Server exposes 8 tools: get_architecture, get_code_snippet, get_graph_schema, index_repository, query_graph, search_code, search_graph, trace_path
  • Exposed over MCP via mcp-proxy 0.12.0 (reproduces on both SSE /sse and streamable-http /mcp). The offending tools/list payload originates from codebase-memory-mcp (mcp-proxy forwards it verbatim).
  • Clients: Claude Code 2.1.218 (affected), Codex (not affected — see Impact).

Steps to reproduce

  1. Initialize an MCP session against the server.
  2. Call tools/list (no cursor). Response returns 8 tools and "nextCursor":"8".
  3. Call tools/list again with params.cursor = "8".
  4. Response returns the same 8 tools and the same "nextCursor":"8" — the cursor never advances and pagination never terminates.

Observed responses

First page (tools/list, no cursor):

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "tools": [ /* 8 tools: get_architecture, ..., trace_path */ ],
    "nextCursor": "8"
  }
}

Follow-up page (tools/list with cursor:"8"):

{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "tools": [ /* the SAME 8 tools again */ ],
    "nextCursor": "8"
  }
}

→ identical tools + identical cursor every time = infinite pagination.

Expected behavior

Per the MCP spec, nextCursor should be present only when there are more results.
With 8 tools total returned in one page, the result should either:

  • omit nextCursor entirely (preferred, since it is the last/only page), or
  • return an advancing cursor whose next fetch yields the remaining items and then terminates.

Actual behavior

nextCursor is always "8" (appears to be = total tool count / offset), the supplied
cursor is ignored, and the same page is returned forever. Pagination never ends.

Impact

  • Claude Code 2.1.218 follows nextCursor pagination and appends results without
    dedup, so it loops (~20 iterations to an internal cap) and displays 8 × ~20 = 160
    tools — bloating the tool picker and the model context.
  • Codex does not paginate tools/list (ignores nextCursor), so it correctly shows 8.
  • Any spec-compliant paginating client is affected.

Likely root cause

The tools/list handler appears to always set nextCursor to the number of returned
tools ("8") and does not treat the incoming cursor as a real offset nor detect
end-of-list, so it never signals completion.

Suggested fix

Only emit nextCursor when offset + returned < total; otherwise omit it. Honor the
incoming cursor as an offset so a follow-up request advances and eventually returns an
empty final page (or no cursor).

Workaround (for others hitting this before a fix)

Strip nextCursor from tools/list responses at a reverse proxy. Example nginx (the
tool count is fixed at 8, so the cursor value is the literal "8"):

proxy_set_header Accept-Encoding "";                 # ensure uncompressed so sub_filter matches
sub_filter ',"nextCursor":"8"' '';
sub_filter '"nextCursor":"8",' '';
sub_filter_once off;
sub_filter_types text/event-stream application/json;

After stripping nextCursor, Claude Code stops paginating and shows the correct 8 tools.

Activity

  1. added
    windowsWindows-specific issues
    editor/integrationEditor compatibility and CLI integration
    parsing/qualityGraph extraction bugs, false positives, missing edges
    on Jul 24, 2026
  2. changed the title [-]1[/-] [+]`tools/list` returns a non-advancing `nextCursor` on the last/only page → spec-compliant clients paginate infinitely (tool list inflates to ~160 in Claude Code)[/+] on Jul 24, 2026
  3. added
    ux/behaviorDisplay bugs, docs, adoption UX
    priority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.
    and removed
    parsing/qualityGraph extraction bugs, false positives, missing edges
    windowsWindows-specific issues
    on Jul 24, 2026
  4. added this to the 0.9.1-rc milestone on Jul 24, 2026
  5. DeusData commented on Jul 24, 2026

    @DeusData
    Owner

    Thanks for the protocol-level report. The direct server code does advance and terminate tools/list pagination: v0.9.0 parses params.cursor, and current tests require page 1 to return nextCursor: 8 while the cursor: 8 page omits nextCursor. Your reproduction is through mcp-proxy over SSE/HTTP, so the remaining boundary is whether that path forwards the request parameters and server profile unchanged. Please attach one raw direct stdio request/response pair against the same binary for cursor: 8, plus codebase-memory-mcp --version and the binary SHA-256. That will tell us whether this is a direct server regression or a proxy/profile translation defect. It remains high priority in 0.9.1-rc under #395.

  6. sqpsqp0208 commented on Jul 27, 2026

    @sqpsqp0208
    Author

    ▎ Confirmed it's the mcp-proxy path, not your server — thanks for the pointer.
    ▎
    ▎ Direct stdio against the same binary paginates and terminates correctly:
    ▎ - codebase-memory-mcp --version: 0.9.0
    ▎ - binary SHA-256: 9f5a306d54a8248f736939a42cd19678273731d735f6a1c9a92659b5e2585e32 (statically-linked ELF, BuildID sha1 63ee2bae5f46ede4585deb3ede61156fec27c34b)
    ▎ - page 1 (tools/list, no cursor): 8 tools + "nextCursor":"8"
    ▎ - page 2 (tools/list, cursor:"8"): the other 6 tools (list_projects, delete_project, index_status, detect_changes, manage_adr, ingest_traces), no nextCursor → terminates.
    ▎
    ▎ {"jsonrpc":"2.0","id":3,"result":{"tools":[{"name":"list_projects",...},{"name":"delete_project",...},{"name":"index_status",...},{"name":"detect_changes",...},{"name":"manage_adr",...},{"name":"ingest_trac
    ▎ es",...}]}}
    ▎
    ▎ Through mcp-proxy 0.12.0 (SSE/streamable-http), the same cursor:"8" request returns page 1 again (same 8 tools + nextCursor:"8") — i.e. mcp-proxy does not forward params.cursor, so the server always returns
    ▎ page 1. That both (a) makes paginating clients loop → 160 tools, and (b) hides the 6 page-2 tools. Not a server regression — feel free to close #395 as external; I'll file against mcp-proxy.

  7. sqpsqp0208 commented on Jul 27, 2026

    @sqpsqp0208
    Author

    Follow-up — resolved on our side, thank you!

    Huge thanks for the quick and precise pointer — your instinct was exactly right, this was never a codebase-memory-mcp bug. For the record, direct stdio against 0.9.0 paginates perfectly: page 1 = 8 tools + nextCursor:"8", page 2 (cursor:"8") = the other 6 tools with no cursor → terminates cleanly (14 tools total). Your pagination implementation is spot on.

    The culprit was mcp-proxy 0.12.0 not forwarding params.cursor. Since that project hasn't been updated in a couple of months, we ended up writing a small streamable-http bridge using the official MCP Python SDK: it drives your server as a normal MCP client (so it follows nextCursor correctly), aggregates both pages, and re-exposes the full set to downstream clients. Verified end-to-end — Claude Code now sees all 14 tools (including list_projects) with no infinite tools/list loop and no more inflated tool count.

    Really appreciate you taking the time to look into it. Feel free to close #395. 🙏

  8. DeusData commented on Jul 28, 2026

    @DeusData
    Owner

    Thanks for closing the loop with a direct stdio comparison and for identifying the exact proxy behavior.

    Your final evidence settles the boundary: codebase-memory-mcp 0.9.0 returns page 1, then page 2, then no nextCursor; mcp-proxy 0.12.0 drops params.cursor and repeatedly asks for page 1. That matches the server's pagination tests and means there is no CBM regression to fix here.

    I am closing this as resolved externally. The raw request comparison will be useful to anyone who encounters the same inflated tool list through a proxy. Thank you for documenting the working bridge as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingeditor/integrationEditor compatibility and CLI integrationpriority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.ux/behaviorDisplay bugs, docs, adoption UX

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions