test(e2e): cover tools/call without arguments - #2933
Conversation
Add two requirement ids and bodies: a tool whose inputSchema fields are
all optional, called with no `arguments` key, runs with {} and returns its
result; a tool with a required argument, called with no `arguments` key,
is refused as a tool execution error (isError true) naming the argument.
Existing ids are unchanged. Exercises the `args ?? {}` fallback in the
McpServer tools/call path on every transport and both entry arms.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012VRbFCp41otcScXE1YY3es
|
@modelcontextprotocol/client
@modelcontextprotocol/codemod
@modelcontextprotocol/core
@modelcontextprotocol/server
@modelcontextprotocol/server-legacy
@modelcontextprotocol/express
@modelcontextprotocol/fastify
@modelcontextprotocol/hono
@modelcontextprotocol/node
commit: |
There was a problem hiding this comment.
Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.
Beyond the inline nits, I also checked that the outbound tx.send tap and the not.toHaveProperty('arguments') assertion hold on the entryModern arm — the client's per-request _meta envelope lands on params, not arguments, so the wire shape under test is unchanged there. The stringContaining('text') clause in the required-arg case is loose but is satisfied by the argument name in the validation message, and the send-restore skipping on assertion failure only affects a client that is torn down immediately afterward.
Extended reasoning...
Test-only change: two manifest entries in test/e2e/requirements.ts and two matching verifies() bodies in test/e2e/scenarios/tools.test.ts covering tools/call with the arguments field omitted; no SDK source, helpers, or coverage gates touched and no security-sensitive surface. The only reported findings are style nits, and the repository's catch-all CODEOWNERS entry means a human owner still signs off.
…error Also narrow the captured request with isJSONRPCRequest instead of a cast. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012VRbFCp41otcScXE1YY3es
There was a problem hiding this comment.
I re-reviewed after the follow-up commit and found no bugs; the typed isJSONRPCRequest guard replacing the as cast reads cleanly. Since this adds two spec-conformance requirements to the e2e manifest, a maintainer look is still worthwhile.
What was reviewed:
- Both assertions against the SDK path:
validateToolInputrunsargs ?? {}through the schema and thetools/callhandler wraps the resultingProtocolErrorintoisError: truecontent viacreateToolError, so the all-optional and required cases match the message shape asserted (packages/server/src/server/mcp.ts:306-320,:370-375). - Cross-arm state:
received/handlerCallsare hoisted outsidemakeServer, and only the singletools/callis counted, so stateless re-creation and the initialize handshake do not perturb the[{}]/0expectations. - Minor, non-blocking: the inline
tx.sendtap duplicates the existingtapWire(client)helper intest/e2e/helpers/index.ts:220that the other scenario files use for the same purpose; switching to it would drop the manual restore line.
Extended reasoning...
The PR touches only test/e2e/requirements.ts (two new manifest entries) and test/e2e/scenarios/tools.test.ts (two verifies() bodies plus one added import); no SDK source changes and no security-sensitive surface. The follow-up commit replaced an unsafe as cast with the isJSONRPCRequest type guard and reworded one behavior string, which is the substantive change since the prior review. I verified the asserted behavior against the tools/call handler in packages/server/src/server/mcp.ts and that the hoisted state pattern follows test/e2e/CLAUDE.md. Deferred rather than approved because CODEOWNERS assigns every path to the typescript-sdk team and the manifest entries assert spec-conformance wording that maintainers own; the only observation is a small helper-reuse nit.
|
The red Generated by Claude Code |
Requested by Felix Weinberger · Slack thread
Motivation and Context
Before:
mainhas accepted atools/callrequest that omits theargumentsfield since theargs ?? {}change in theMcpServertools/call path, but no e2e requirement covered it:mcpserver:tool:input-validationsendsarguments: {}, which is a different wire shape.After: the suite adds
tools:call:omitted-args:all-optionalandtools:call:omitted-args:required. The first registers a tool whose input schema fields are all optional, calls it with noargumentskey (an outbound wire tap proves the key is absent), and sees the handler run with{}and return its result. The second registers a tool with a required argument, calls it with noargumentskey, and sees a tool execution error (isError: true, content naming the argument:Input validation error: Invalid arguments for tool summarize: text: Invalid input: expected string, received undefined) without the handler running — the spec's Error Handling section lists input validation errors under tool execution errors. Both run on every transport and both entry arms (12 cells each); no knownFailures or exclusions.With
args ?? {}reverted the all-optional case fails on all 12 cells withAssertionError: expected true to be falsy(the SDK returnsisError: truewithInput validation error: Invalid arguments for tool greet: Invalid input: expected object, received undefined); with it, it passes.How Has This Been Tested
Breaking Changes
None — tests only.
Types of changes
Checklist
🤖 Generated with Claude Code
https://claude.ai/code/session_012VRbFCp41otcScXE1YY3es
Generated by Claude Code