Skip to content

Strict schema validation rejects tool calls from mcp-remote/Claude Desktop with an unrecognized extra argument (HTTP transport) #21

Description

@WLammert

Summary

Calling bookstack_pages_create (and likely other write tools) through Claude Desktop's custom connector, bridged via mcp-remote over the HTTP transport, fails validation even with a minimal, schema-valid payload. The same exact payload succeeds when sent directly to POST /message via curl. This points to mcp-remote (or Claude Desktop) injecting an extra top-level key into arguments that the server's strictObject Zod schema rejects.

Environment

  • Server: self-hosted, Docker (oven/bun:1.3.14-alpine), MCP_TRANSPORT=http
  • Client: Claude Desktop custom connector → mcp-remote@latest (stdio↔HTTP bridge) → server over HTTPS (nginx reverse proxy in front)
  • Tool: bookstack_pages_create

Reproduction

  1. Configure Claude Desktop with a mcpServers entry using mcp-remote@latest pointed at the server's /message endpoint with --header "Authorization: Bearer <token>".
  2. Ask Claude to create a BookStack page with minimal args: chapter_id, name, markdown.
  3. Call fails. Claude Desktop surfaces only a generic <error>Tool execution failed</error> — no detail.
  4. Server logs (LOG_LEVEL=info) show:
    [info] Tool called {"tool":"bookstack_pages_create","argument_names":["chapter_id","markdown","name"],"unknown_argument_count":1}
    [error] Tool failed {"tool":"bookstack_pages_create","err":{"error_name":"ZodError","error_message":"[redacted: 152 chars]"}}
    
    unknown_argument_count: 1 confirms an extra, unrecognized key was present in arguments beyond the three intentionally sent.

Isolating the cause

The identical logical payload sent directly via curl to /message (bypassing Claude Desktop and mcp-remote entirely) succeeds:

curl -s -X POST https://<host>/message \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"bookstack_pages_create","arguments":{"chapter_id":3,"name":"Test","markdown":"test"}}}'

→ Returns a created page object (200/success). This rules out a server-side schema bug and a BookStack API permissions issue (both were initially suspected) — the server and its validation are correct for this exact argument set. The extra key must be added somewhere in the Claude Desktop → mcp-remote → HTTP path.

Two things this issue is really about

  1. (Client-side, may belong in mcp-remote's own tracker instead) — whatever is injecting the extra key into arguments.
  2. (Server-side, actionable here) — the server's error surfacing makes this very hard to diagnose from the client. strictObject Zod validation failures return McpError(InvalidParams, 'Validation failed', {type: 'validation_error', validation: [...]}) with a structured validation array naming the offending field(s) — but Claude Desktop shows only a generic banner, and the server's own structured logs redact the validation message text (by design, per the logger's redaction policy) and never log the actual value it saw, only a count. Confirming the real culprit key required a local patch to log unknown key names at debug level.

Ask

  • Would a slightly more permissive top-level parse (e.g., explicitly allow-and-ignore a documented MCP reserved key like _meta if that turns out to be the culprit, rather than blanket strictObject) be acceptable, once the actual injected key is confirmed?
  • Separately: any interest in a startup/README note flagging that mcp-remote bridging is untested against this server's strict validation, so others hit this faster?

I can supply the exact extra key name once I re-run with a local debug patch that logs unknown argument names (not values) — will update this issue.

Activity

  1. WLammert commented on Aug 12, 2026

    @WLammert
    Author

    Root cause confirmed — correcting my original hypothesis.

    Added a temporary debug-level log line (unrecognized argument names only, never values) to the tool-call boundary and reproduced the failure. The extra key was:

    "debug_unknown_argument_names": ["description"]
    

    So this is not mcp-remote/Claude Desktop injecting a protocol-level field like _meta — the calling model itself supplied a description argument to bookstack_pages_create, which the tool's schema doesn't define (book_id, chapter_id, name, html, markdown, tags, priority only). strictObject correctly rejected it. My earlier "extra key injected in the transport path" theory was wrong — withdrawing the _meta allowlist suggestion.

    What's still worth fixing here: the calling model never saw why the call failed. Claude Desktop only surfaced a generic <error>Tool execution failed</error> banner, even though the server's ErrorHandler already builds a precise, structured payload for exactly this case:

    return new McpError(ErrorCode.InvalidParams, 'Validation failed', {
      type: 'validation_error',
      validation: validationDetails, // [{field: 'description', message: '...'}]
    });

    If that data.validation array reaches the caller, a model can self-correct on retry ("oh, drop description, retry with the other fields"). Worth checking whether it's being dropped somewhere between the MCP SDK's error response and what actually reaches the client, since the tool call otherwise round-trips correctly (verified identical payloads succeed via direct curl to /message).

    Not sure yet whether that's a mcp-remote behavior, a Claude Desktop rendering choice, or something on this server's HTTP transport layer — will report back if I find it's the latter. Feel free to close this if you consider strict rejection + a swallowed-error-detail-elsewhere out of scope for this repo.

  2. pnocera commented on Aug 22, 2026

    @pnocera
    Owner

    Issue #21 analysis: validation details reach the HTTP client

    Issue: #21 — Strict schema validation rejects tool calls from mcp-remote/Claude Desktop with an unrecognized extra argument (HTTP transport)
    Analysed: 2026-08-22
    Repository state: 0c201b7 (main)

    Decision

    Do not loosen the tool schemas, special-case MCP _meta, or accept the model-supplied description argument. The issue comment establishes that description was supplied by the calling model, not injected by mcp-remote or Claude Desktop. It is not part of bookstack_pages_create's advertised or runtime input contract.

    The server already returns the actionable validation detail over POST /message. The generic error seen in Claude Desktop is therefore outside this server's HTTP boundary (either in mcp-remote, the desktop connector, or Claude Desktop's rendering). That exact downstream component remains unproven without a bridge/client capture.

    The appropriate disposition is close as not a server transport defect, ideally after adding one direct-HTTP regression test that makes the current response contract explicit. If maintainers prefer to keep it open, re-title it to track that test/documentation work rather than a strict-validation failure.

    Evidence

    1. The premise was corrected in the issue itself

    The original report suspected a transport-added key. Its follow-up identifies the key as description and explicitly withdraws the _meta theory. The bookstack_pages_create contract only advertises book_id, chapter_id, name, html, markdown, tags, and priority; the follow-up's minimal call is otherwise valid.

    2. Strict rejection is intentional and correctly advertised

    PageTools.createCreatePageTool defines exactly those page-create properties (src/tools/pages.ts:168-273). All tool schemas are passed through withClosedSchemas, which recursively sets additionalProperties: false on every object with properties (src/types.ts:1281-1309). Its stated purpose is parity with the strict Zod objects used at runtime.

    So an extra top-level description is invalid twice over:

    1. A conforming client can see it is forbidden from tools/list.
    2. The runtime validator rejects it before any BookStack request can be made.

    Permitting this one unknown key would make the published schema and handler disagree, encourage models to send invented parameters, and create an undocumented divergence from the BookStack page API. VALIDATION_STRICT_MODE=false is already the explicit compatibility escape hatch for operators who intentionally want forwarding rather than rejection; it should not become the default.

    3. The server preserves the validation payload through HTTP

    The tool dispatcher catches the Zod error and calls ErrorHandler.handleError (src/server.ts:301-368). ErrorHandler maps it to McpError(ErrorCode.InvalidParams, 'Validation failed', { type: 'validation_error', validation: [...] }) (src/utils/errors.ts:190-212). The HTTP route gives the connected SDK transport the original request and body without replacing MCP responses (src/server.ts:888-941).

    I exercised that exact boundary on the current checkout with a real local Express server and the repository's installed MCP SDK. Request:

    {
      "jsonrpc": "2.0",
      "id": 21,
      "method": "tools/call",
      "params": {
        "name": "bookstack_pages_create",
        "arguments": {
          "chapter_id": 3,
          "name": "Test",
          "markdown": "test",
          "description": "extra"
        }
      }
    }

    Observed HTTP response: status 200 (normal for a JSON-RPC application error) with:

    {
      "jsonrpc": "2.0",
      "id": 21,
      "error": {
        "code": -32602,
        "message": "MCP error -32602: Validation failed",
        "data": {
          "type": "validation_error",
          "validation": [
            { "field": "", "message": "Unrecognized key: \"description\"" }
          ]
        }
      }
    }

    This is the protocol payload the client/bridge receives from this server. The implementation does not swallow error.data; the SDK serializes it as shown. The issue's reported generic UI banner is consequently not evidence of a server-side loss.

    4. Existing coverage proves contract agreement, but not this error payload

    tests/transport/tools.test.ts already sends requests over the actual HTTP route and checks that published JSON Schema agrees with runtime validation, including the page-create parent/content requirements. It does not type or assert error.data (its JsonRpcReply.error currently has only code and message). That leaves the most relevant fact for this issue unprotected even though it works today.

    The focused transport suite was run on this checkout: bun test tests/transport/tools.test.ts completed successfully.

    Recommended follow-up

    1. Add one HTTP-transport regression test in tests/transport/tools.test.ts for the request above. Assert all of the following:

      • HTTP status is 200;
      • the JSON-RPC error code is -32602 (InvalidParams);
      • error.data.type === 'validation_error';
      • one validation message identifies description as an unrecognized key;
      • the BookStack stub received no request (validation remains a boundary check).

      Update the test-only JsonRpcReply type to include the expected data shape. This guards the server-side guarantee without tying the project to a particular desktop UI.

    2. Optionally improve the structured detail for unknown keys. Zod reports an unrecognized-key issue at the object path, so the current mapper yields field: "" while the human-readable message contains the actual name. A future, deliberately version-aware change could expose the Zod unknown-key list in a separate structured field (for example fields: ["description"]) while retaining the existing message. This is a usability enhancement, not a fix required to resolve Strict schema validation rejects tool calls from mcp-remote/Claude Desktop with an unrecognized extra argument (HTTP transport) #21, and it must preserve the current rule that caller-controlled keys/values are not written to logs.

    3. Post the direct-wire evidence to Strict schema validation rejects tool calls from mcp-remote/Claude Desktop with an unrecognized extra argument (HTTP transport) #21 and close it as downstream-client/bridge behavior. If the reporter can capture the response leaving mcp-remote and show that error.data is removed there, the follow-up belongs in that project's tracker; if it remains present there but is hidden by Claude Desktop, it belongs with the client UI.

    Non-recommendations

    • Do not allow or ignore _meta in tool arguments: the issue follow-up rules out _meta, and MCP metadata is not a blanket exemption for arbitrary tool parameters.
    • Do not add description to page creation solely to accommodate this call. It has no demonstrated BookStack API meaning for pages and would turn a model mistake into a public API promise.
    • Do not rely on server logs to diagnose this at the default level. The current redaction policy intentionally records the count of unknown argument names rather than user-controlled names or values; the JSON-RPC error response is the correct diagnostic channel for the caller.

    Residual uncertainty

    This analysis establishes the server-to-direct-HTTP-client boundary only. It does not reproduce the exact version/configuration of mcp-remote or Claude Desktop, so it cannot assign the downstream detail loss to one of them. The observed response is sufficient to rule out this repository's HTTP transport as the point where the validation payload is dropped.

  3. pnocera commented on Aug 22, 2026

    @pnocera
    Owner

    Resolved at this server boundary by #23 (merged as 5141811).

    The new direct-HTTP regression test proves an unknown description argument returns JSON-RPC -32602 with actionable error.data.validation, while making no BookStack request. Strict validation remains intentional and is now pinned against ambient environment overrides.

    The issue follow-up established that description was model-supplied rather than transport-injected. Since the server preserves the diagnostic payload on POST /message, the generic Claude Desktop banner is downstream of this repository. Please open or continue a bridge/client issue if a capture shows where error.data is hidden or removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions