[GH-ISSUE #3866] [Bug] MCP completion guide promises all matching values, but replies stop at 100 #1487

Closed
opened 2026-10-16 21:06:05 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @coygeek on GitHub (Oct 15, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/3866

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Documentation

Observed behavior

docs/mcp-prompts.md says prompt-argument completion "returns all matching values". With 101 matching residual unknowns, the completion/complete reply contains only 100 values and reports total: 101, hasMore: true. The runtime signals truncation correctly; the guide's exhaustive-return promise is wrong and should describe the 100-value cap and how to narrow the prefix.

The empty-prefix reply contains 100 values with total: 101 and hasMore: true; one retained identifier is omitted. Requesting completion with that identifier as the prefix returns it, with hasMore: false.

Reproduction

Save this script as reproduce-completion.mjs outside the checkout. It starts rea mcp over stdio from a source checkout with an isolated HOME, records 101 harmless in-memory unknowns, then requests completion for the unknown_id argument of the audit_residual_unknowns prompt.

import { resolve } from 'node:path';
import { pathToFileURL } from 'node:url';
import { createRequire } from 'node:module';
import { mkdir } from 'node:fs/promises';
const checkout = resolve(process.argv[2]);
const fixture = resolve(process.argv[3]);
await mkdir(resolve(fixture, 'home'), { recursive: true });
const require = createRequire(resolve(checkout, 'package.json'));
const { Client } = await import(pathToFileURL(require.resolve('@modelcontextprotocol/client')));
const { StdioClientTransport } = await import(pathToFileURL(require.resolve('@modelcontextprotocol/client/stdio')));
const transport = new StdioClientTransport({
  command: process.execPath, args: [resolve(checkout, 'scripts/rea.mjs'), 'mcp'],
  cwd: checkout, env: { PATH: process.env.PATH, HOME: resolve(fixture, 'home'), REA_LOG_LEVEL: 'silent' }, stderr: 'pipe'
});
const client = new Client({ name: 'completion-cap-probe', version: '1' });
try {
  await client.connect(transport);
  const ids = [];
  for (let i = 0; i < 101; i++) {
    const reply = await client.callTool({ name: 'record_unknown', arguments: {
      question: `Fixture question ${String(i).padStart(3, '0')}`, severity: 'low',
      domain: 'completion-fixture', required_authority: null, required_confidence: 'observed',
      required_environment: null, recommended_probes: [], relationships: []
    } });
    if (reply.isError) throw new Error(JSON.stringify(reply));
    ids.push(reply.structuredContent.result.unknown_id);
  }
  const complete = (value) => client.complete({
    ref: { type: 'ref/prompt', name: 'audit_residual_unknowns' },
    argument: { name: 'unknown_id', value }
  });
  const reply = await complete('');
  const missing = ids.filter(id => !reply.completion.values.includes(id));
  console.log(JSON.stringify({ retained: ids.length, values: reply.completion.values.length,
    total: reply.completion.total, hasMore: reply.completion.hasMore, omitted: missing.length }));
  const control = await complete(missing[0]);
  console.log(JSON.stringify({ exact_prefix_values: control.completion.values.length,
    exact_prefix_returns_omitted_id: control.completion.values[0] === missing[0],
    total: control.completion.total, hasMore: control.completion.hasMore }));
} finally { await client.close(); await transport.close(); }
node reproduce-completion.mjs /path/to/rea-checkout "$(mktemp -d)"

Expected behavior

Per docs/mcp-prompts.md:111-115 ("Session-aware completion"), completion "returns all matching values; it does not expose paging controls". An empty prefix matches all 101 non-resolved unknown heads (table at line 109), so the guide promises 101 values. Since the runtime reply is bounded by the MCP SDK, the guide should instead state the 100-value cap, that total and hasMore report truncation, and that callers narrow the prefix to reach further matches.

Impact and scope

The guide misleads agents and client authors who treat a broad completion reply as a complete identifier inventory once more than 100 values match; the reply itself reports truncation and no data is lost.

Workaround: available. Narrowing the completion prefix returned the omitted identifier in the reproduction, and hasMore signals when narrowing is needed.

Reporter's impact estimate: P3 (supported).

REA version

6.5.0, built from source at main commit 034342ba28 (latest npm release is 6.5.0)

Runtime and platform

Node.js v26.10.0; macOS 27.0.1 arm64; @modelcontextprotocol/server 2.3.1 and @modelcontextprotocol/client as pinned by the checkout's lockfile

Target and artifact context

not applicable

Interface

MCP

Earliest failure stage

Transport or result projection

Relevant output or evidence

  • Expected source: docs/mcp-prompts.md:111-115 "Session-aware completion" ("Completion returns all matching values")
  • Failure source: observed stdio completion/complete replies below; cap applied in the pinned SDK's createCompletionResult (node_modules/@modelcontextprotocol/server/dist/mcp-DIH4cS6P.mjs:2298-2303)
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete
{"retained":101,"values":100,"total":101,"hasMore":true,"omitted":1}
{"exact_prefix_values":1,"exact_prefix_returns_omitted_id":true,"total":1,"hasMore":false}

A raw JSON-RPC stdio probe observed the same cap and the same exact-prefix recovery.

Source-derived analysis: src/server/promptCompletion.ts:39-50 returns every matching candidate, and src/server/registerPrompts.ts:91-96 passes them to the SDK's completable callback. The SDK applies suggestions.map(String).slice(0, 100) and reports the uncapped count separately. This is a documentation mismatch; the SDK limit itself is not in question.

Typed outcome or recovery expected

The guide should state that completion replies carry at most 100 values, report truncation through total and hasMore, and require a narrower prefix to reach further matches.

Restoration check:

docs/mcp-prompts.md currently says completion "returns all matching values"; after a fix it should describe the 100-value reply cap, total/hasMore, and prefix narrowing, consistent with the observed reply (100 values, total: 101, hasMore: true) and the exact-prefix control (hasMore: false).

Analysis or suspected cause

Source-derived: src/server/promptCompletion.ts:39-50 returns every match; src/server/registerPrompts.ts:91-96 passes them to the pinned SDK's completable callback, whose createCompletionResult applies slice(0, 100) and reports the uncapped total.

Additional context

Closed #3220 concerned omission of imported artifact inventories after the first substep and explicitly distinguished completion size limits; this report is about the wire cap with 101 independently retained values. No open issue reports this mismatch.

This report came from AI-assisted source review with local reproduction where stated above; the evidence labels distinguish observed runs from source-only reasoning.

Originally created by @coygeek on GitHub (Oct 15, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3866 ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Documentation ### Observed behavior `docs/mcp-prompts.md` says prompt-argument completion "returns all matching values". With 101 matching residual unknowns, the `completion/complete` reply contains only 100 values and reports `total: 101`, `hasMore: true`. The runtime signals truncation correctly; the guide's exhaustive-return promise is wrong and should describe the 100-value cap and how to narrow the prefix. The empty-prefix reply contains 100 values with `total: 101` and `hasMore: true`; one retained identifier is omitted. Requesting completion with that identifier as the prefix returns it, with `hasMore: false`. ### Reproduction Save this script as `reproduce-completion.mjs` outside the checkout. It starts `rea mcp` over stdio from a source checkout with an isolated `HOME`, records 101 harmless in-memory unknowns, then requests completion for the `unknown_id` argument of the `audit_residual_unknowns` prompt. ```js import { resolve } from 'node:path'; import { pathToFileURL } from 'node:url'; import { createRequire } from 'node:module'; import { mkdir } from 'node:fs/promises'; const checkout = resolve(process.argv[2]); const fixture = resolve(process.argv[3]); await mkdir(resolve(fixture, 'home'), { recursive: true }); const require = createRequire(resolve(checkout, 'package.json')); const { Client } = await import(pathToFileURL(require.resolve('@modelcontextprotocol/client'))); const { StdioClientTransport } = await import(pathToFileURL(require.resolve('@modelcontextprotocol/client/stdio'))); const transport = new StdioClientTransport({ command: process.execPath, args: [resolve(checkout, 'scripts/rea.mjs'), 'mcp'], cwd: checkout, env: { PATH: process.env.PATH, HOME: resolve(fixture, 'home'), REA_LOG_LEVEL: 'silent' }, stderr: 'pipe' }); const client = new Client({ name: 'completion-cap-probe', version: '1' }); try { await client.connect(transport); const ids = []; for (let i = 0; i < 101; i++) { const reply = await client.callTool({ name: 'record_unknown', arguments: { question: `Fixture question ${String(i).padStart(3, '0')}`, severity: 'low', domain: 'completion-fixture', required_authority: null, required_confidence: 'observed', required_environment: null, recommended_probes: [], relationships: [] } }); if (reply.isError) throw new Error(JSON.stringify(reply)); ids.push(reply.structuredContent.result.unknown_id); } const complete = (value) => client.complete({ ref: { type: 'ref/prompt', name: 'audit_residual_unknowns' }, argument: { name: 'unknown_id', value } }); const reply = await complete(''); const missing = ids.filter(id => !reply.completion.values.includes(id)); console.log(JSON.stringify({ retained: ids.length, values: reply.completion.values.length, total: reply.completion.total, hasMore: reply.completion.hasMore, omitted: missing.length })); const control = await complete(missing[0]); console.log(JSON.stringify({ exact_prefix_values: control.completion.values.length, exact_prefix_returns_omitted_id: control.completion.values[0] === missing[0], total: control.completion.total, hasMore: control.completion.hasMore })); } finally { await client.close(); await transport.close(); } ``` ```sh node reproduce-completion.mjs /path/to/rea-checkout "$(mktemp -d)" ``` ### Expected behavior Per `docs/mcp-prompts.md:111-115` ("Session-aware completion"), completion "returns all matching values; it does not expose paging controls". An empty prefix matches all 101 non-resolved unknown heads (table at line 109), so the guide promises 101 values. Since the runtime reply is bounded by the MCP SDK, the guide should instead state the 100-value cap, that `total` and `hasMore` report truncation, and that callers narrow the prefix to reach further matches. ### Impact and scope The guide misleads agents and client authors who treat a broad completion reply as a complete identifier inventory once more than 100 values match; the reply itself reports truncation and no data is lost. Workaround: available. Narrowing the completion prefix returned the omitted identifier in the reproduction, and `hasMore` signals when narrowing is needed. Reporter's impact estimate: P3 (supported). ### REA version 6.5.0, built from source at main commit 034342ba28c8e56c3746be92b78aa7299afa02bd (latest npm release is 6.5.0) ### Runtime and platform Node.js v26.10.0; macOS 27.0.1 arm64; `@modelcontextprotocol/server` 2.3.1 and `@modelcontextprotocol/client` as pinned by the checkout's lockfile ### Target and artifact context not applicable ### Interface MCP ### Earliest failure stage Transport or result projection ### Relevant output or evidence - Expected source: `docs/mcp-prompts.md:111-115` "Session-aware completion" ("Completion returns all matching values") - Failure source: observed stdio `completion/complete` replies below; cap applied in the pinned SDK's `createCompletionResult` (`node_modules/@modelcontextprotocol/server/dist/mcp-DIH4cS6P.mjs:2298-2303`) - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete ```text {"retained":101,"values":100,"total":101,"hasMore":true,"omitted":1} {"exact_prefix_values":1,"exact_prefix_returns_omitted_id":true,"total":1,"hasMore":false} ``` A raw JSON-RPC stdio probe observed the same cap and the same exact-prefix recovery. Source-derived analysis: `src/server/promptCompletion.ts:39-50` returns every matching candidate, and `src/server/registerPrompts.ts:91-96` passes them to the SDK's `completable` callback. The SDK applies `suggestions.map(String).slice(0, 100)` and reports the uncapped count separately. This is a documentation mismatch; the SDK limit itself is not in question. ### Typed outcome or recovery expected The guide should state that completion replies carry at most 100 values, report truncation through total and hasMore, and require a narrower prefix to reach further matches. Restoration check: `docs/mcp-prompts.md` currently says completion "returns all matching values"; after a fix it should describe the 100-value reply cap, `total`/`hasMore`, and prefix narrowing, consistent with the observed reply (100 values, `total: 101`, `hasMore: true`) and the exact-prefix control (`hasMore: false`). ### Analysis or suspected cause Source-derived: src/server/promptCompletion.ts:39-50 returns every match; src/server/registerPrompts.ts:91-96 passes them to the pinned SDK's completable callback, whose createCompletionResult applies slice(0, 100) and reports the uncapped total. ### Additional context Closed #3220 concerned omission of imported artifact inventories after the first substep and explicitly distinguished completion size limits; this report is about the wire cap with 101 independently retained values. No open issue reports this mismatch. This report came from AI-assisted source review with local reproduction where stated above; the evidence labels distinguish observed runs from source-only reasoning.
Author
Owner

@z2Ace0107 commented on GitHub (Oct 15, 2026):

I'd like to take this on. I reproduced the completion cap through stdio MCP on Windows: 101 retained matching unknowns produce 100 values with total: 101 and hasMore: true; narrowing the prefix returns the omitted ID. I'll correct the guide to explain the 100-value cap, truncation metadata and prefix narrowing, without changing the runtime limit.

<!-- gh-comment-id:6187046437 --> @z2Ace0107 commented on GitHub (Oct 15, 2026): I'd like to take this on. I reproduced the completion cap through stdio MCP on Windows: 101 retained matching unknowns produce 100 values with total: 101 and hasMore: true; narrowing the prefix returns the omitted ID. I'll correct the guide to explain the 100-value cap, truncation metadata and prefix narrowing, without changing the runtime limit.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#1487
No description provided.