Description
mcpx(daemon): /v1/complete never applies the 100-value spec cap — local fallback honors an unclamped completion.maxValues (0 = unlimited), upstream replies pass through verbatim
Summary
The MCP specification caps a completion reply at 100 values (CompleteResult.values, @maxItems 100 in the 2026 schema; prose in every earlier revision). The MCP-server surface enforces it; the daemon's HTTP /v1/complete surface does not — on either of its two reply paths. The same setting therefore produces a conforming reply over MCP and a non-conforming one over /v1.
Evidence (main c6564aa2)
Local (cache) fallback — the setting is applied raw:
pkgs/mcpx/internal/daemon/ops.go:390-391—handleCompletepassescs.Int("completion.maxValues")straight intolocalCompletion.pkgs/mcpx/internal/daemon/ops.go:427—localCompletiontruncates onlyif max > 0 && len(values) > max, tomaxitself. Nothing clampsmaxto 100, andmax <= 0means no truncation at all.pkgs/mcpx/internal/settings/value.go:312—Set.Intis a barestrconv.Atoi; there is no range validation for this setting anywhere.pkgs/mcpx/internal/settings/wire.go:185-191— the setting's own help text says: "The MCP specification caps a completion reply at one hundred. Lower is legal; higher is not, and a server that sends more is sending something a conforming client will reject." It is user-settable (flag--completion-max-values, envMCPX_COMPLETION_MAX_VALUES, config) with default 100.
So with completion.maxValues=250 and ≥101 cached prompt names / resource template URIs matching, /v1/complete returns up to 250 values. With completion.maxValues=0 it returns every match, unbounded.
Upstream passthrough — no cap at all:
pkgs/mcpx/internal/daemon/ops.go:381-388— when the upstream server answers, itscompletionpayload is forwarded verbatim (payload = res.Completion); values/total/hasMore are never inspected, let alone truncated. An upstream that returns 250 values is relayed as 250 values.
The MCP surface does it right (the contrast that proves the daemon path is the outlier):
pkgs/mcpx/internal/mcpserver/server.go:2657-2669— the same hot setting is read per request and then clamped:if max > specMaxCompletions { max = specMaxCompletions }, withspecMaxCompletions = 100(server.go:2695-2697), and a non-positive setting keeps the 100 default instead of disabling the cap.
Corroboration in-repo: pkgs/mcpx/docs/compare/register/completion.md row CMP-06 already records that "/v1/complete passes an upstream's completion through without applying the cap" and that a value above 100 "fails 2026 schema validation" — but no issue tracks fixing the daemon surface (searched open + closed for CMP-06 / completion cap / localCompletion: only the #2386 design family, whose Phase 3 #2389 is a register truth-up for the rate-limit/security rows, out of scope for this cap).
Verification this run
Code-read verification (the truncation arithmetic is deterministic in the source above), cross-checked against the MCP-server clamp and the CMP-06 register row. A laptop probe of localCompletion with max=250 / 150 candidates could not be run this run: ~/tools/laptop-ssh.sh failed on every attempt ("Connection closed by UNKNOWN port 65535") and sandbox builds are prohibited by fleet rule, so no probe result is claimed. The fix's regression test (below) is the executable proof.
Why it matters
- A conforming (especially 2026-schema-validating) client can reject the whole completion reply, so completion silently breaks exactly for users who raised the setting or use a sloppy upstream — the two cases the cap exists for.
- The two surfaces disagree about the same setting, which is the class of divergence the hot-setting plumbing (
Server.MaxCompletions, wp11 test) was built to eliminate on the MCP side. max=0disabling truncation entirely is a footgun with no documented meaning; on the MCP side 0 means "use the default".
Acceptance criteria
-
/v1/completelocal fallback never returns more than 100 values, whatevercompletion.maxValuesis set to; a setting of 0 or negative behaves like the MCP surface (default 100 applies), a setting between 1 and 100 still lowers the cap, andtotal/hasMorekeep reporting the full ranked match count. -
/v1/completeupstream passthrough truncates an over-cap upstream reply to the effective cap and recomputestotal/hasMoreconsistently (or documents and tests a deliberate alternative). - Regression tests in
internal/daemoncover: setting >100 with >100 matches, setting 0, setting below 100 (unchanged behavior), and an upstream reply with >100 values. Existing completion tests (completion_rank_test.go, handler tests) still pass. - The fix routes both daemon paths through one shared cap computation with the MCP surface (or a shared constant), so the surfaces cannot drift again.
Related: #2386 (completion design epic), #2388 (ranked local completion — its cap-after-ranking behavior is preserved; this issue is only about the cap's ceiling), CMP-06 in docs/compare/register/completion.md.
Activity
dezren39 commentedon Oct 31, 2026
🤖 Agent note · filed
/v1/completenever applies the 100-value spec cap on either reply path (local fallback uses unclampedcompletion.maxValues, 0 = unlimited; upstream payload forwarded verbatim), unlike the MCP surface which clamps the same setting at server.go:2667.3 remaining items
dezren39 commentedon Nov 1, 2026
🤖 Agent note · picked up
mcpx: preserve completion total through the MCP backend (#2878, Codex…
Merge pull request #3505 from developing-today/queue/5456-2878-comple…
dezren39 commentedon Nov 1, 2026
🤖 Agent note · done