Skip to content

Design: mcpx upstream question rate limits — one limiter at the client choke point, keyed by the configured server #2333

Closed
2 of 2 issues completed

Description

@dezren39

Design — filed by repo-design-architect-code pass 14, 2026-10-29. Origin: #2192 (split of #1957, ex-nix#257). Design doc: files/designs/code-mcpx-upstream-question-rate-limits.md in the keep-the-four-repos-healthy goal workspace. No user/review: #2192's acceptance is explicit and the judgment calls below are the architect's recorded decisions.

Problem

An upstream MCP server can ask mcpx's client questions — sampling (sampling/createMessage: a model call or a person's review) and elicitation (elicitation/create: a person's attention) — at any rate. A buggy upstream loops; a hostile one floods the broker with questions, each holding a TTL and a slot in every watching UI. The spec's security considerations say clients SHOULD implement rate limiting for both features; mcpx's conformance register records both as gaps (internal/conformance/coverage_clientfeatures.go:118-119): sampling has "no per-server or per-session limit", elicitation "no per-server limit" — the only bounds today are per-request (proto.askRounds = 8) and per-question TTL. #2192 names the work and its acceptance but leaves the mechanism open; this design records it and adopts #2192 as Phase 1 instead of filing a parallel.

Recorded decisions

  1. One limiter, at Client.answer (internal/mcpclient/modern.go:288) — the single dispatch both eras reach (legacy via handleServerRequest, client.go:607; 2026 via resolveInput, modern.go:377). Enforced at entry, for sampling/createMessage and elicitation/create only, before the handler: an over-limit question never reaches the handler, the broker, or a person.
  2. Keyed by the configured server, never the claimed one. Per-server bucket keyed by the pool's cfg.Name, pool-owned and passed via a new mcpclient.Options field; ServerInfo.Name is self-reported and is never a key. Per-session bucket is a field on Client (one Client = one session). Non-pool clients degenerate to per-session only — recorded, acceptable.
  3. A question counts when admitted, once — whether later answered, declined or expired (the bounded resource is spent on the asking). Refusals count against neither bucket. Each 2026 inputRequests entry counts separately; askRounds still bounds rounds underneath.
  4. Fixed per-minute windows, in memory, no new dependency (no limiter exists in-tree and x/time/rate is not in go.mod). Windows reset with the daemon; nothing persists.
  5. Settings in the proto.* family (following proto.askRounds/proto.askTTL): proto.samplingPerMinute default 60 (per server), proto.samplingSessionPerMinute default 20, proto.elicitationPerMinute default 30; ScopeDaemon ints; 0 disables that bound.
  6. Refusal is a named -32603 error, never a fabricated answer: rpcError -32603 with a message naming the limit, plus the refusal emitted via c.warn — following design mcpx: design — sampling trust boundary (reviewable, validated, honestly error-coded) #1834's "expiry stays -32603 named" precedent (the spec assigns no rate-limit code). For elicitation the refusal is the JSON-RPC error, NOT {action: "cancel"} — nobody declined; mcpx conformance: rate limits for sampling and elicitation requests (split of #1957) #2192 requires clear failure, not a silent drop.

Phases

Alternatives rejected (detail in the design doc)

Broker-side enforcement (misses non-broker handlers and the modern in-client path); pool-side enforcement (server-initiated requests are only visible inside Client); token bucket (flood bound, not burst shaping; no new dependency); delay/queue instead of refuse (stale questions, invisible stalls, teaches a looping upstream nothing).

Explicitly out of scope

The server-side tasks-rate-limit gap (coverage_stateful.go:120, citing #209) — the other side of the protocol; it belongs to design #1554's operational-limits open question. Daemon-originated /v1/ask questions from local scripts — bounding those waits on the principals work (#1968/#1972).

Acceptance criteria

  • A per-server rate limiter for upstream sampling/elicitation questions exists at the mcpx client choke point, keyed by the configured server, per the recorded decisions.
  • The conformance register entries (internal/conformance/coverage_clientfeatures.go:118-119) are updated from gap to implemented with the limits named.
  • mcpx conformance: rate limits for sampling and elicitation requests (split of #1957) #2192 (adopted as Phase 1 by this design) is updated with the implementation link, and its acceptance is checked off or carried into a follow-up issue.

Activity

  1. added
    priority/normalOrdinary priority (the default)
    app/mcpxPackage/app: mcpx (pkgs/ or apps/ in this repo)
    epicMeta-issue grouping multiple items (org Project Type=Epic)
    kind/designA design proposal (design-architect): multi-step, phased, decision-bearing
    on Oct 29, 2026
  2. developing-today commented on Oct 29, 2026

    @developing-today
    Contributor
  3. dezren39 commented on Oct 29, 2026

    @dezren39
    MemberAuthor

    Design filed today by repo-design-architect-code pass 14 (origin #2192, split of #1957). Phases: Phase 1 is ADOPTED as #2192 itself (no parallel filed; labelled source/design-architect and linked as a sub-issue here); Phase 2 is #2334 (register truth-up, gated on #2192, triage packet → pilot task 3489 queued). Both sub-issue links verified in this issue's sub_issues list (2192, 2334). Design doc: files/designs/code-mcpx-upstream-question-rate-limits.md in the keep-the-four-repos-healthy goal workspace; central register entry added (status: proposed). Decisions are recorded in the body above.

  4. added
    effort/lLarge: multi-day or open-ended; expect staged check-ins
    meta/enrichedIntake review done: acceptance criteria, links, and normalized labels added
    on Oct 29, 2026
  5. dezren39 commented on Oct 29, 2026

    @dezren39
    MemberAuthor

    Intake review — fleet monitor (2026-10-29)

    Related: origin #2192 (split of #1957, ex-nix#257), adopted as Phase 1 by this design; design doc: files/designs/code-mcpx-upstream-question-rate-limits.md in the keep-the-four-repos-healthy goal workspace. Companion elicitation work: #1675 series (Phases 1-3, #1676-#1678).

    Notes: body left intact apart from the appended acceptance criteria. Labels already carried kind/design, source/design-architect, priority/normal, app/mcpx, status/needs-triage; effort/l + meta/enriched added. Per the standing rule that a design is not finished work, the implementation link is #2192 itself (named in the body) — no parallel issue filed.

    Suggested approach: implement under #2192 as Phase 1; this design issue closes when the limiter lands and the conformance register is updated.

  6. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    Agent note (program tending, 2026-11-01): completion check — all sub-issues of this epic are now CLOSED (REST-verified this run): #2192 and #2334, 2 of 2. The thread's last note is the 2026-10-29 intake review, which predates the final closures, so this landing is recorded here. Proposing closure of this epic: if the rate-limiter design's acceptance is fully met by #2192/#2334, this epic can close; if a phase was deliberately left unfiled, name it here and the epic stays open with that as its named next step.

  7. added
    queue/queuedHas a queued task in the Supabase agent queue
    task/triageQueue packet kind: verify, reproduce, and route this item
    on Nov 3, 2026
  8. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · closed as completed (epic acceptance met)

    Verified at main e438032cd, 2026-11-03T16:25Z.

  9. removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 3, 2026
  10. developing-today commented on Nov 3, 2026

    @developing-today
    Contributor

    agent-note:v1
    state: triaged (coverage pass)
    Coverage pass 2026-11-03: reviewed in the fleet-wide per-issue pass (drift / overlap / already-handled / stale / blockers / ordering). Verdict: still live; queued in repo-health-code-pilot as triage at priority 40 (priority/normal). If this is already handled at current main, the triage closes it with evidence; if it needs splitting, the triage files the split.
    Found via: coverage pass 2026-11-03

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/lLarge: multi-day or open-ended; expect staged check-insepicMeta-issue grouping multiple items (org Project Type=Epic)kind/designA design proposal (design-architect): multi-step, phased, decision-bearingmeta/enrichedIntake review done: acceptance criteria, links, and normalized labels addedpriority/normalOrdinary priority (the default)queue/queuedHas a queued task in the Supabase agent queuesource/design-architectFiled by the repo design architect 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