Repository navigation
Strict schema validation rejects tool calls from mcp-remote/Claude Desktop with an unrecognized extra argument (HTTP transport) #21
Description
Activity
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 adescriptionargument tobookstack_pages_create, which the tool's schema doesn't define (book_id, chapter_id, name, html, markdown, tags, priorityonly).strictObjectcorrectly rejected it. My earlier "extra key injected in the transport path" theory was wrong — withdrawing the_metaallowlist 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'sErrorHandleralready 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.validationarray reaches the caller, a model can self-correct on retry ("oh, dropdescription, 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 directcurlto/message).Not sure yet whether that's a
mcp-remotebehavior, 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.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-supplieddescriptionargument. The issue comment establishes thatdescriptionwas supplied by the calling model, not injected bymcp-remoteor Claude Desktop. It is not part ofbookstack_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 inmcp-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
descriptionand explicitly withdraws the_metatheory. Thebookstack_pages_createcontract only advertisesbook_id,chapter_id,name,html,markdown,tags, andpriority; the follow-up's minimal call is otherwise valid.2. Strict rejection is intentional and correctly advertised
PageTools.createCreatePageTooldefines exactly those page-create properties (src/tools/pages.ts:168-273). All tool schemas are passed throughwithClosedSchemas, which recursively setsadditionalProperties: falseon 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
descriptionis invalid twice over:- A conforming client can see it is forbidden from
tools/list. - 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=falseis 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).ErrorHandlermaps it toMcpError(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.tsalready 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 asserterror.data(itsJsonRpcReply.errorcurrently has onlycodeandmessage). 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.tscompleted successfully.Recommended follow-up
-
Add one HTTP-transport regression test in
tests/transport/tools.test.tsfor 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
descriptionas an unrecognized key; - the BookStack stub received no request (validation remains a boundary check).
Update the test-only
JsonRpcReplytype to include the expecteddatashape. This guards the server-side guarantee without tying the project to a particular desktop UI. - HTTP status is
-
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 examplefields: ["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. -
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-remoteand show thaterror.datais 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
_metain toolarguments: the issue follow-up rules out_meta, and MCP metadata is not a blanket exemption for arbitrary tool parameters. - Do not add
descriptionto 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-remoteor 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.- A conforming client can see it is forbidden from
Resolved at this server boundary by #23 (merged as 5141811).
The new direct-HTTP regression test proves an unknown
descriptionargument returns JSON-RPC-32602with actionableerror.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
descriptionwas model-supplied rather than transport-injected. Since the server preserves the diagnostic payload onPOST /message, the generic Claude Desktop banner is downstream of this repository. Please open or continue a bridge/client issue if a capture shows whereerror.datais hidden or removed.
Summary
Calling
bookstack_pages_create(and likely other write tools) through Claude Desktop's custom connector, bridged viamcp-remoteover the HTTP transport, fails validation even with a minimal, schema-valid payload. The same exact payload succeeds when sent directly toPOST /messageviacurl. This points tomcp-remote(or Claude Desktop) injecting an extra top-level key intoargumentsthat the server'sstrictObjectZod schema rejects.Environment
oven/bun:1.3.14-alpine),MCP_TRANSPORT=httpmcp-remote@latest(stdio↔HTTP bridge) → server over HTTPS (nginx reverse proxy in front)bookstack_pages_createReproduction
mcpServersentry usingmcp-remote@latestpointed at the server's/messageendpoint with--header "Authorization: Bearer <token>".chapter_id,name,markdown.<error>Tool execution failed</error>— no detail.LOG_LEVEL=info) show:unknown_argument_count: 1confirms an extra, unrecognized key was present inargumentsbeyond the three intentionally sent.Isolating the cause
The identical logical payload sent directly via
curlto/message(bypassing Claude Desktop andmcp-remoteentirely) succeeds:→ 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
mcp-remote's own tracker instead) — whatever is injecting the extra key intoarguments.strictObjectZod validation failures returnMcpError(InvalidParams, 'Validation failed', {type: 'validation_error', validation: [...]})with a structuredvalidationarray naming the offending field(s) — but Claude Desktop shows only a generic banner, and the server's own structured logs redact the validationmessagetext (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
_metaif that turns out to be the culprit, rather than blanketstrictObject) be acceptable, once the actual injected key is confirmed?mcp-remotebridging 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.