Key takeaways

  • Client 2.0.0’s cursorless listTools path automatically aggregates pages, but its seen-cursor guard treats the second appearance of an opaque cursor as completion.
  • With nextCursor:"" on pages one and two, the client made two requests, returned alpha and beta, omitted page-three gamma, deleted nextCursor and threw no error.
  • The unique-cursor control made all three requests and returned all three tools. A separate endless distinct-cursor control threw LIST_PAGINATION_EXCEEDED at the configured three-page cap.
  • This is a bounded compatibility result against a synthetic server, not evidence that production MCP servers commonly reuse cursors or that a model invoked the wrong tool.
  • Fail closed on any incomplete or ambiguous discovery walk: preserve pagination evidence, reconcile expected inventory and do not let an agent plan until completeness is independently established.
01

The sharp question: can an AI agent tell that tool discovery stopped early?

An MCP client uses tools/list to learn what an AI agent may call. Version 2.0.0 of the TypeScript client makes the common cursorless call convenient: listTools() walks pages and returns one aggregate. That convenience creates a reliability promise at the application boundary. If the aggregate is partial, the caller needs a visible failure or a surviving continuation signal.

My three-page fixture produced neither. Page one returned alpha and nextCursor:"". Page two returned beta and the same empty-string cursor. Page three was ready to return gamma and terminate. The client sent only the first two requests, returned alpha and beta, removed nextCursor, and threw no error. From ordinary application code, the short inventory looked complete.

02

Chronology: discovery moved from a protocol primitive to an SDK aggregate

Anthropic announced MCP on November 25, 2024 as an open standard connecting AI assistants to data sources and tools. Tool discovery was a protocol operation: a client requested tools/list and servers could paginate the result.

The July 28, 2026 protocol says cursor values are opaque and explicitly calls an empty string a valid cursor. The split @modelcontextprotocol/client 2.0.0 package was published through npm on July 27. Its cursorless list methods add automatic aggregation and a default page cap. On August 28, independent contributor issue 2735 reported that a repeated cursor could stop this aggregate early. This investigation reproduced that boundary on September 1 with controls rather than treating the report as proof.

03

The protocol gives presence meaning, not cursor text

The pagination specification tells clients not to make a determination from a cursor value beyond whether a non-null value was provided. It uses the empty string to make the point concrete: falsy does not mean finished. Completion is represented by omitting nextCursor from the response.

The specification does not require each successive cursor string to be unique. A server may encode position in the token, keep position in server-side state, or use another opaque strategy. Equality can still be useful as a defensive signal, but turning equality into successful completion adds a client policy not expressed by the wire contract.

04

The client promises one complete aggregate

The distributed client documents that listTools without an explicit cursor walks every page, returns the complete aggregated list with no nextCursor and writes that aggregate to its response cache. Supplying an explicit cursor takes a different per-page path that returns the raw page.

That distinction matters for agent hosts. A caller choosing auto-aggregation does not receive each intermediate continuation decision. It delegates traversal and expects either a complete collection or a visible failure. The returned absence of nextCursor is therefore read as SDK finalization, not direct evidence that the server sent a terminal page.

05

The loop has two nonconvergence controls with different outcomes

Inside _listAllPages, the client stores cursor strings in a seen set. Its loop continues only while cursor is defined and has not appeared before. A configured listMaxPages check sits inside that loop and throws LIST_PAGINATION_EXCEEDED when a distinct sequence reaches the cap.

When a cursor repeats, the while condition becomes false before the cap branch. Execution then deletes acc.nextCursor, finalizes the aggregate and can write it to cache. The repeated-cursor path is therefore a successful return; the page-cap path is a loud failure. Both detect possible nonconvergence, but only one preserves uncertainty for the caller.

06

The fixture offered three tools behind one opaque handle

The checked-in audit uses a minimal transport implementing initialize and tools/list. Its repeated-cursor scenario serves alpha with nextCursor:"", beta with nextCursor:"", and gamma with no nextCursor. Every page contains one inert synthetic tool definition with an object input schema. No callTool request exists, so the experiment cannot cause an external action.

The transport records the cursor on every tools/list request. This lets the test distinguish a short aggregate from a fully traversed server: receiving [undefined, ""] means page three was never requested. The server fixture is synthetic and intentionally exercises a legal-value edge case; it is not offered as a prevalence claim.

07

Repeated empty string stopped after request two

The first call carried no cursor and returned alpha plus the empty-string continuation. The second request correctly carried cursor:"" and received beta plus the same continuation. At that point the seen set already contained the empty string, so the loop did not issue a third request.

Five assertions bound the observed result: the request sequence was [undefined, ""]; the tool names were ["alpha", "beta"]; nextCursor was undefined; exactly two requests were made; and gamma was absent. No exception was raised. The result matched the mechanism visible in the pinned bundle.

08

Unique cursors reached the terminal page

The first control changed only the continuation sequence: page one returned after-alpha, page two returned after-beta, and page three omitted nextCursor. The client sent [undefined, "after-alpha", "after-beta"] and returned alpha, beta and gamma.

This control rules out a generic transport, schema or fixture failure. Version 2.0.0 can aggregate three pages through the same code path. The material difference was cursor equality, not item count, page count, tool shape or the final page.

09

The page cap failed loudly when cursors stayed distinct

The second control generated a different cursor forever and configured listMaxPages:3. The client requested the initial page and two continuations, then raised LIST_PAGINATION_EXCEEDED with the message “tools/list: exceeded listMaxPages (3); server pagination did not terminate.”

That is the safer failure shape: application code can quarantine discovery, retain its previous inventory or alert an operator. The test does not prove that removing cursor deduplication is the only correct fix. It demonstrates that the existing page cap already detects one nonconverging sequence without returning a partial aggregate as complete.

10

A partial inventory changes agent behavior without adding a malicious tool

Most MCP security discussion focuses on an untrusted tool entering the inventory. Truncation creates the inverse failure: an expected control, lookup or recovery tool can disappear. A planner may choose a more privileged fallback, tell a user that a capability is unavailable, or route work to a different system because the local inventory is incomplete.

This probe did not run a model and therefore does not establish any of those downstream outcomes. It establishes the prerequisite state: application code received two tools as a finalized aggregate even though the synthetic server still offered a third page. Agent impact depends on how a host reconciles and uses that inventory.

11

Caching can make one ambiguous walk last longer

The _listAllPages implementation deletes nextCursor before its cache write. Whether a later listTools call reuses the result depends on cache freshness configuration and invalidation. A positive TTL can therefore turn one truncated walk into the inventory served to subsequent callers until refresh or list_changed handling replaces it.

The audit used the default freshness behavior and did not test cache reuse, list_changed races or reconnects. Teams should nevertheless treat cache admission as part of completeness: never write a discovery result to a trusted cache merely because traversal returned normally. Admission needs an independent reason to believe the terminal condition was real.

12

Preserve the difference between server termination and client termination

Record why a walk ended. “Server omitted nextCursor” is materially different from “client saw the same token,” “page budget expired,” “request timed out,” “schema parsing failed” or “inventory changed mid-walk.” Collapsing those states into nextCursor:undefined destroys evidence the host needs for policy.

A wrapper can expose a traversal receipt containing server identity, protocol and SDK versions, page count, cursor digests, terminal reason, item count, duplicate-name decisions and schema digests. Hash cursor values before durable logs when they may be sensitive. The receipt—not an absent property alone—should control whether an agent can plan against the result.

13

Inventory reconciliation provides a second completeness signal

For production servers, compare discovery against an approved manifest or a minimum required capability set. The goal is not to freeze every dynamic tool forever. It is to catch implausible removal of critical tools, duplicate identity, unauthorized additions and large count changes before a model adapts to them.

When inventory is expected to vary by user or tenant, scope the baseline to the authenticated principal and policy context. A mismatch should suspend new autonomous calls, preserve the last known-good registry where safe, and trigger a bounded refresh. Do not silently route around a missing approval, validation or rollback tool.

14

Test all four list families and both pagination modes

The same _listAllPages helper backs automatic prompts, resources, resource-template and tools lists. The local audit exercised tools/list only. A production compatibility suite should cover each capability the host uses with empty, repeated and cycling cursors; terminal and nonterminal empty pages; maximum page counts; duplicate items; timeouts; and list changes during traversal.

Also compare cursorless auto-aggregation with explicit per-page calls. The explicit path gives the host raw continuation state, but it also makes the host responsible for bounds, retries, identity and atomic installation. A passing happy-path array comparison is insufficient; negative tests must prove that ambiguous termination never becomes an approved registry.

15

What failed, and what the evidence does not establish

A lookup for a GitHub release tagged 2.0.0 returned 404, so I did not invent a release entry or date from that endpoint. I used the official npm package publication record and installed artifact instead. The checked-in audit then passed all fifteen assertions and removed its temporary dependency tree.

The probe did not test a language model, production MCP host, v1 SDK, another language SDK, remote HTTP or stdio, authentication, retries, concurrent walks, cancellation, response-cache reuse, list_changed, reconnect, prompts, resources, resource templates, hostile pages, large inventories, real side effects or user data. It does not establish prevalence, exploitability or maintainer intent. Issue 2735 is independent reporting, not independent reproduction of this run.

16

Decision: approve only inventories with an explicit terminal receipt

Deploy automatic MCP discovery for an autonomous AI agent only when the exact client version fails closed on ambiguous pagination, bounds pages and items, distinguishes server termination from client guards, validates every page, reconciles critical capabilities and atomically installs one complete registry. Until then, constrain discovery behind a wrapper that walks pages explicitly and produces a terminal receipt.

Reject or roll back when a repeated or cycling cursor returns success, when nextCursor is removed without a terminal server response, when a page cap produces a partial cache entry, when critical tools disappear without an approved change, or when the host lets a model plan during traversal. The safe fallback is a pinned last known-good inventory or no autonomous call—not an apparently complete short list.

17

Copy-ready MCP discovery-completeness gate

Complete this record for one authenticated server, client artifact and full discovery transaction before an AI planner can use the resulting inventory.

Entries stay in this browser tab and are not submitted to AccessAllGPT. Blank responses are copied as [Unresolved].

SDK package/version/digest, protocol version, server deployment, transport, principal, tenant and policy context.

Automatic or explicit mode, page/item/time limits, retry rules, cursor confidentiality and supported list operations.

Server omitted nextCursor, page count, item count, cursor digests and proof no client guard was presented as server completion.

Repeated/cycling cursor, timeout, parse error, empty page and page-cap fixtures must fail closed without caching a partial aggregate.

Expected critical tools, allowed dynamic variance, unique-name rule, additions/removals and schema-digest comparison.

Temporary aggregate, validation stages, single commit point, previous-registry preservation and call suspension during refresh.

Admission condition, TTL, principal scope, invalidation, list_changed handling and evidence that partial results cannot become fresh hits.

Planner behavior when tools vanish, forbidden fallbacks, approval/rollback dependencies and no-call default on uncertainty.

Empty, repeated, cycling and unique cursor tests across every list family and exact production client/server versions.

Ambiguous completion, inventory drift, partial cache, duplicate identity or unapproved fallback; kill switch, owner and residue cleanup.

Primary sources

  1. Pagination (version 2026-07-28)Model Context Protocol Specification · Reviewed: Cursor model, request and response formats, opaque-value rule, supported list operations, implementation guidance and error handling · Retrieved · Supports: The current protocol makes cursors opaque, says clients must not determine anything from a cursor value beyond whether a non-null value exists, treats an empty string as valid and defines absence of nextCursor as completion.
  2. Tools (version 2026-07-28)Model Context Protocol Specification · Reviewed: Tool discovery, list request and result shapes, pagination link, tool definition and security considerations · Retrieved · Supports: The current tools contract makes tools/list a paginated discovery operation through which an AI client learns the callable tool inventory exposed by a server.
  3. @modelcontextprotocol/client 2.0.0 distributed implementationModel Context Protocol TypeScript SDK · Reviewed: Client.listTools, _listAllPages, repeated-cursor seen set, listMaxPages branch, aggregate finalization and response-cache write · Retrieved · Supports: The pinned v2 client auto-aggregates a cursorless listTools call, stops its loop when a cursor has been seen, deletes nextCursor and returns or caches the resulting aggregate; the separate page cap throws when reached.
  4. @modelcontextprotocol/client 2.0.0 registry recordnpm Registry · Reviewed: Package identity, version, publication artifact, repository, runtime requirement, integrity and distribution metadata · Retrieved · Supports: The official npm registry record binds the tested split client package to version 2.0.0 on the TypeScript SDK repository and identifies the installable artifact plus its integrity metadata.
  5. Client.listTools() silently drops pages when a server repeats a cursor (issue 2735)Model Context Protocol TypeScript SDK issue tracker · Reviewed: Independent issue report, empty-string reproduction, actual and expected output, source analysis, proposed fix, comments and open status · Retrieved · Supports: An independent contributor reported that client 2.0.0 stops automatic aggregation on a repeated cursor and removes the remaining-cursor signal; this prompted but did not independently validate the AccessAllGPT reproduction.
  6. Introducing the Model Context ProtocolAnthropic · Reviewed: November 25, 2024 announcement, protocol purpose, architecture, SDKs and ecosystem description · Retrieved · Supports: Anthropic introduced MCP as an open standard for connecting AI assistants to data sources and tools, establishing why tool discovery affects an agent host’s available action surface.

Limitations

This is a bounded local reproduction on Node v24.10.0, Darwin arm64 and @modelcontextprotocol/client 2.0.0 using one synthetic in-process transport, three one-tool pages, an empty-string repeated-cursor sequence, a unique-cursor control and a distinct nonconverging control capped at three pages. It did not test a model, production host or server, v1, another SDK, remote transport, authentication, retries, concurrency, cancellation, caching across calls, notifications, reconnect, other list families, hostile content, external effects or real data. It demonstrates that the tested cursorless tools/list aggregate returned two tools without nextCursor or error while the synthetic server still had a third page; it does not measure prevalence, prove exploitability, settle every interpretation of opaque-cursor semantics or replace a deployment review.

Disclosures

AccessAllGPT created and ran only the synthetic local fixture in the checked-in harness. Public issue 2735 prompted the question but did not supply private information or validate this run. No model, production client, third-party MCP server, credential, external endpoint or user data was used. The MCP project, Anthropic, npm, the issue author and SDK maintainers did not review this article or provide access, private data, payment or endorsement. AccessAllGPT Research is operated by NeuralArc, is independent, and is not affiliated with OpenAI, Anthropic, the MCP project or npm. Publication-wide relationships are listed on the disclosures page.

Further AccessAllGPT guidance

  1. MCP Pagination Can Evict Earlier Tool Validators
  2. MCP Output Validation Depends on Tool Discovery
  3. MCP’s readOnlyHint Is Not a Permission Boundary
  4. MCP Token Passthrough Needs an Audience Boundary
  5. Before You Connect an MCP Server to Production
  6. Design an Agent Benchmark That Predicts Production
  7. AccessAllGPT Research methodology
  8. Publication disclosures

Continue the research

Get evidence-led updates for teams making production AI decisions.