Imagine asking Claude Code to remove unused service accounts. An MCP tool lists the accounts and their active bindings. The response arrives successfully, the agent finds no binding for one account, and the proposed cleanup looks reasonable.
Except the tool returned the first page. The binding was on the second.
This is a hypothetical integration failure, not a reported Claude Code incident. It matters because a successful read is easy to mistake for a complete read. The same mistake can affect dependency inventories, cloud resources and ticket searches. If a plan depends on something being absent, an incomplete result can turn a cautious cleanup into a bad change.
I would put a completeness check between the inventory adapter and the planner. A prompt asking the agent to “check everything” is useful guidance, but it cannot supply evidence the adapter discarded.
Decide what the read actually proves
“No binding appeared in this response” is a narrow observation. “This account has no bindings” needs a stronger contract.
The adapter must establish the enumeration scope: which tenant, environment, filters and identity were used. It must follow pagination to a documented terminal condition and preserve any truncation warning. Where the decision needs a coherent inventory, the backend must also support a stable snapshot or an equivalent consistency mechanism.
Pagination alone does not establish completeness. A search endpoint might cap results before it exposes them to the adapter. An identity might lack permission to see certain bindings. A tool might summarize results and drop the cursor. A perfectly implemented page loop cannot repair those gaps.
Define the scope narrowly enough that the backend can support the claim. If the endpoint only offers best-effort search, do not use its empty result as authority to delete an account.
Use an all-or-fail read contract
The downloadable Python fixture models an adapter that normalizes each response into four fields:
{
"ids": ["binding-17", "binding-18"],
"next": "page-2",
"complete": False,
"snapshot": "inventory-version-482"
}
These are fields in the example adapter contract. They are not universal MCP tool-response fields. An actual integration must map its backend’s pagination and consistency semantics into the contract rather than inventing a snapshot value.
The collector returns an Inventory only after it sees a terminal page with explicit completion evidence. Until then, accumulated IDs stay inside the collector. A timeout raises IncompleteRead; it never returns the first page as a successful inventory.
The fixture also rejects repeated cursors, duplicate IDs and a changed snapshot. Rejecting duplicate IDs is deliberately conservative: a backend that legitimately overlaps pages needs a separately specified reconciliation rule. Silently discarding duplicates would conceal an enumeration defect in this example.
A page budget limits the collector’s work. Hitting that budget means the read is incomplete. It does not mean the inventory contains everything collected so far.
Run the failure cases before wiring in a planner
Save the fixture, inspect it, then run it with Python 3.10 or later. It uses the standard library and makes no network calls:
python3 mcp-inventory-completeness.py
The tests cover a successful two-page read and a genuinely empty inventory. They also exercise a truncated terminal page, a cursor loop, an exhausted page budget, a second-page timeout, a changed snapshot, a duplicate ID, missing completion evidence and a contradictory completion flag.
The distinction between empty and incomplete is the point. An empty, complete inventory can be a valid input to planning. An incomplete inventory should stop any plan that relies on absence.
These tests exercise the local collector, not a deployed MCP server. Before using the pattern, run equivalent integration tests against the actual adapter. Confirm that a backend timeout cannot become an empty list and that the identity has the visibility the task requires. Keep a permitted, complete read as a positive control so a broken integration cannot pass merely by refusing everything.
Keep completeness evidence in the review packet
The review packet should identify the inventory scope, backend snapshot, page count and item count. Link it to the collector result and record the policy for truncation. If the read failed, record the failure and block the dependent action rather than attaching a partial list under a reassuring “inventory checked” heading.
Avoid recording raw continuation tokens in broad-access logs. Depending on the backend, they may expose internal state or grant access. A restricted diagnostic record can retain what operators need without copying tokens into the model transcript.
A complete inventory is still not approval to mutate anything. State can change after enumeration. A destructive operation needs its own authorization and an execution-time check that its preconditions still hold. The guide to state-bound MCP writes covers that separate boundary.
Start with one inventory-dependent workflow. Make its adapter distinguish a verified empty result from a read it could not finish. Then check whether the planner can still act after IncompleteRead. If it can, the exception is only a log entry; the control has not reached the action path.
For the wider operating model around tool boundaries and review evidence, read Claude Code: Building Production Agents.