Skip to content

mcpx: Phase 2 of #1481 — validate tool arguments at CallTool (enforce, -32602) + structuredContent against outputSchema (warn-only) #1483

Closed

Description

@dezren39

Phase 2 of design #1481 (JSON Schema validation of tool input and output). Design doc: files/designs/code-mcpx-json-schema-validation.md in the keep-the-four-repos-healthy goal workspace. Origin gap: dezren39/darwin-nix#254.

Dependency gate: do not start until Phase 1 (the internal/schemaval core) is merged — this phase consumes its compile/validate API. If claimed earlier, park naming the dependency.

Scope

Enforce at the one choke point every call surface funnels through — internal/mcpclient:

  • List time: in Client.ListTools (internal/mcpclient/client.go:1051), compile each tool's InputSchema and OutputSchema with schemaval, as a sibling pass to checkToolHeaders (internal/mcpclient/modern.go:495). A tool whose schema does not compile (unsupported dialect, unresolvable/external $ref, invalid or oversized schema) is excluded from the list with a Warning carrying the cause — the x-mcp-header precedent; the pool already surfaces warnings as server.warning lifecycle events. Compiled validators are cached per client, per tool, keyed by schema hash so a re-list recompiles only changed schemas.
  • Inputs, enforced: Client.CallTool (client.go:1164) validates the marshalled arguments against the tool's compiled input validator before the transport is touched. Failure returns an Invalid Params (-32602) error naming the tool and the path-qualified reason. Surfaces that translate generic call errors into an isError tool result (the gateway invoke path in internal/mcpserver) must pass this error through as the protocol error it is — a caller bug is not a tool failure. Scripts, mcpx_call, /v1/call and pass-through inherit the check via Pool.Call → CallTool; no per-surface duplication.
  • Outputs, warn-only: when a CallTool result carries structuredContent and the tool declared an outputSchema, validate it. Failure produces a Warning (+ the lifecycle event that follows from it) and the result is delivered unchanged. Do not enforce outputs in this phase — the flip decision is an open question on design(mcpx): JSON Schema validation of tool input and output — one validator at the client choke point #1481, answered with this phase's warning data.
  • Tools with no inputSchema/outputSchema behave exactly as today.

Out of scope: elicitation answer validation (Phase 3); format assertion; any per-server validation-mode config; codegen changes; closing the conformance rows for elicitation (Phase 3) — this phase updates the matrix/gap registry for the tool input/output + dialect rows it closes.

Acceptance

  • Defining test: a call whose arguments violate the tool's inputSchema (wrong type; missing required; forbidden additional property — at least the first two) fails with -32602 naming the path, and the fake upstream's call count stays 0.
  • Defining test: valid arguments reach the upstream byte-identical to today's behaviour (existing call tests unmodified and green).
  • Defining test: a tool whose schema declares an unsupported dialect (or carries an unresolvable $ref) is absent from ListTools, absent from a gateway tools/list, and a Warning with the cause was emitted.
  • Defining test: a result whose structuredContent violates outputSchema is returned to the caller unchanged, with a Warning recorded; a conforming one records none.
  • Benchmark sanity: argument validation on a representative nested schema (triage picks it from a real catalog tool) stays within a triage-set per-call budget; record the measured number on this issue.
  • Conformance matrix rows for json-schema-support-2020-12, json-schema-validate-per-dialect-and-error-on-unsupported, json-schema-document-dialects, json-schema-reject-unresolved-ref and the matching gap-registry entries are updated to reflect enforcement (server + client sides as each applies).
  • Full suite passes: cd pkgs/mcpx && go test ./... (via CI on the PR per fleet policy).

agent-note:v1 — filed by the repo design architect (dedicated code job, pass 3, 2026-10-27) as a sub-issue of design #1481. Filed as task/triage: triage confirms scope against current main once Phase 1 has landed (gate above) and enqueues the implementation packet; do not implement in the triage task.

Activity

  1. added
    kind/featureNew feature or capability request
    task/triageQueue packet kind: verify, reproduce, and route this item
    priority/normalOrdinary priority (the default)
    queue/queuedHas a queued task in the Supabase agent queue
    on Oct 27, 2026
  2. developing-today commented on Oct 27, 2026

    @developing-today
    Contributor
  3. added
    app/mcpxPackage/app: mcpx (pkgs/ or apps/ in this repo)
    effort/lLarge: multi-day or open-ended; expect staged check-ins
    on Oct 27, 2026
  4. dezren39 commented on Oct 27, 2026

    @dezren39
    MemberAuthor

    Intake review

    🤖 Agent note · filed

    Related: #1481 (parent design, decision recorded there), #1482 / #1483 / #1484 (the three phases), dezren39/darwin-nix#254 (origin).

    Liveness: searched merged PRs for schemaval / schema-validation work — none merged. Not solved; the Phase 1 dependency gate in the body still governs start order.

    Notes / risks: the body's ## Acceptance section is a full acceptance-criteria set in substance (defining tests enumerated), so no duplicate criteria section was appended; status/needs-triage added to complete the filed-state label set. This item already carries queue/queued — triage via the queue, do not implement in the triage task (per the filer's note).

    Suggested approach: exactly as scoped in the body; park on claim if the Phase 1 gate has not cleared, naming the dependency.

  5. added
    meta/enrichedIntake review done: acceptance criteria, links, and normalized labels added
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Oct 27, 2026
  6. 184 remaining items

  7. added
    queue/claimedQueue worker lifecycle state
    and removed
    queue/claimedQueue worker lifecycle state
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    on Oct 28, 2026
  8. dezren39 commented on Oct 28, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  9. added a commit that references this issue on Oct 28, 2026
  10. dezren39 commented on Oct 28, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

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-inskind/featureNew feature or capability requestmeta/enrichedIntake review done: acceptance criteria, links, and normalized labels addedpriority/normalOrdinary priority (the default)source/design-architectFiled by the repo design architect jobstatus:donetask/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