Skip to content

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 #2878

Closed

Description

@dezren39

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 — handleComplete passes cs.Int("completion.maxValues") straight into localCompletion.
  • pkgs/mcpx/internal/daemon/ops.go:427 — localCompletion truncates only if max > 0 && len(values) > max, to max itself. Nothing clamps max to 100, and max <= 0 means no truncation at all.
  • pkgs/mcpx/internal/settings/value.go:312 — Set.Int is a bare strconv.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, env MCPX_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, its completion payload 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 }, with specMaxCompletions = 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=0 disabling truncation entirely is a footgun with no documented meaning; on the MCP side 0 means "use the default".

Acceptance criteria

  • /v1/complete local fallback never returns more than 100 values, whatever completion.maxValues is 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, and total/hasMore keep reporting the full ranked match count.
  • /v1/complete upstream passthrough truncates an over-cap upstream reply to the effective cap and recomputes total/hasMore consistently (or documents and tests a deliberate alternative).
  • Regression tests in internal/daemon cover: 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

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    priority/normalOrdinary priority (the default)
    queue/queuedHas a queued task in the Supabase agent queue
    source/creatorFiled by the new-issue-creator job
    task/triageQueue packet kind: verify, reproduce, and route this item
    app/mcpxPackage/app: mcpx (pkgs/ or apps/ in this repo)
    effort/xsTrivial: expected under 30 minutes of work
    on Oct 31, 2026
  2. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

  3. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 1, 2026
  4. 3 remaining items

  5. added
    queue/queuedHas a queued task in the Supabase agent queue
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 1, 2026
  6. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  7. added 2 commits that reference this issue on Nov 1, 2026
  8. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

    • Agent: queue worker (repo-worker-code), Lane D shift
    • Session / ID: queue task 5456
    • Found via: queue claim
    • Task: Fixed — shared completion cap: settings.SpecCompletionCap + EffectiveCompletionCap now drive the daemon local path, the daemon upstream passthrough (new capUpstreamCompletion), and the mcpserver clamp; the Backend.Complete contract also carries the full match total so the MCP surface no longer recomputes total/hasMore from a daemon-capped page (Codex P2 on the PR, fixed and replied on its thread)
    • Expectation: met — PR mcpx: shared 100-value completion cap on both daemon /v1/complete paths (#2878) #3505 merged (17b3874), Codex re-review of the final commit completed with no findings, go test daemon + mcpserver + cli packages all pass (settings failures byte-identical to clean main, pre-existing)
    • Links: PR mcpx: shared 100-value completion cap on both daemon /v1/complete paths (#2878) #3505 · branch queue/5456-2878-completion-cap
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/mcpxPackage/app: mcpx (pkgs/ or apps/ in this repo)effort/xsTrivial: expected under 30 minutes of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/normalOrdinary priority (the default)source/creatorFiled by the new-issue-creator jobtask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions