Skip to content

instance_messages returns nextCursor and hasMore: true and has no cursor input — every message older than the first page is unreachable over MCP #566

Description

@serge-ivo

instance_messages returns nextCursor and hasMore: true and has no cursor input — every message older than the first page is unreachable over MCP

What happens

instance_messages(instance_id, limit) against production returns a body carrying nextCursor and hasMore: true. There is no way to use either: the tool's whole input schema is

{
	token: z.string().optional(),
	instance_id: z.string(),
	limit: z.number().int().min(1).max(100).optional(),
}

workers/mcp/src/instance-tools/observability.ts:21-24

so a caller reads the newest page, is told there is more, and has no argument to ask for it. Raising limit does not help past 100.

Mechanism — the cursor is dropped one layer above the API that already supports it

Verified. The API takes a cursor. workers/api/src/routes/instances-chat.ts:212-219:

const params = new URLSearchParams({ limit: String(limit) });
const before = c.req.query("before");
if (before) params.set("before", before);

with the comment naming the prior incident:

#428: this used to rebuild the DO query string from scratch with only limit, so before never reached the object and "Load older messages" re-served the newest page.

and the DO honours it (agent-do.ts:861-863, resolveCursor(url.searchParams.get("before")), rejecting a bad cursor with a 400 rather than an empty 200).

Verified. The MCP tool builds its URL with limit only — observability.ts:29:

`/v1/instances/${instance_id}/messages?limit=${limit || 50}`

So #428 fixed exactly this shape for the console and the MCP surface was never brought along. The tool is the fixed bug, one layer up.

Verified. The two limit ceilings also disagree: MCP clamps at 100 (:23), the route clamps at 2000 (instances-chat.ts:211). So even the single-page workaround is 20× smaller over MCP than over HTTP.

Why it matters

list_feedback's description (observability.ts:135) points debuggers here explicitly — "the natural next calls are agent_trace(trace_id=…) … and instance_messages for the surrounding conversation" — and a feedback row can be older than 100 messages. So the documented triage sequence for #514 dead-ends on a long conversation. The trace itself prunes at 14 days (lib/events.ts:69-76); the message history does not, which makes the transcript the only record of an older turn, and it is the one MCP cannot page.

What to do

One-line fix, in the tool that already proxies the right route:

  1. Add before: z.string().optional().describe("Cursor from a previous call's nextCursor — returns the page OLDER than it.").
  2. Append &before=${encodeURIComponent(before)} when present.
  3. Raise the limit max to match the route (2000), or state in the description why MCP is stricter.
  4. Mention in the description that the response carries nextCursor/hasMore and how to use them — an undocumented output field is how this stayed invisible.

Alternatives considered and rejected

Acceptance criteria

  • instance_messages accepts a cursor and, given a nextCursor from call N, call N+1 returns strictly older messages.
  • An invalid cursor surfaces the route's 400 rather than an empty page (the property instances-chat.ts:222 explicitly preserves).
  • A test drives two consecutive pages over the MCP tool and asserts no overlap and no gap.

Regression risk

Low — additive optional input. The one thing to check is that the cursor is passed through encodeURIComponent: the DO's cursor format is msg:<iso>:<id> (see instances.integration.test.ts:601) and the : characters are safe unencoded but the id is not guaranteed to be.

Related: #428 (the same defect, fixed for the console), #514 (list_feedbackinstance_messages is the documented triage path this blocks).

Activity

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

    backendBackend / Worker / API workbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions