From 94f3804b6155cee1b98371b241f3a3bf6ad4dddc Mon Sep 17 00:00:00 2001 From: Jonah Grant Date: Sat, 14 Nov 2026 09:17:02 -0500 Subject: [PATCH] Return invalid params when typed request decoding fails JSON-RPC 2.0 section 5.1 assigns -32602 to invalid method parameters. Map typed request decoding failures to that code before invoking the registered handler. --- Sources/MCP/Base/Messages.swift | 9 +++++++-- Tests/MCPTests/ServerTests.swift | 26 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/Sources/MCP/Base/Messages.swift b/Sources/MCP/Base/Messages.swift index e53bb729..86ad45a7 100644 --- a/Sources/MCP/Base/Messages.swift +++ b/Sources/MCP/Base/Messages.swift @@ -186,10 +186,15 @@ final class TypedRequestHandler: RequestHandlerBox, @unchecked Sendab // Create a concrete request from the type-erased one let data = try encoder.encode(request) - let request = try decoder.decode(Request.self, from: data) + let typedRequest: Request + do { + typedRequest = try decoder.decode(Request.self, from: data) + } catch is DecodingError { + throw MCPError.invalidParams("Invalid parameters for \(request.method)") + } // Handle with concrete type - let response = try await _handle(request) + let response = try await _handle(typedRequest) // Convert result to AnyMethod response switch response.result { diff --git a/Tests/MCPTests/ServerTests.swift b/Tests/MCPTests/ServerTests.swift index 4713b278..d31e9519 100644 --- a/Tests/MCPTests/ServerTests.swift +++ b/Tests/MCPTests/ServerTests.swift @@ -5,6 +5,32 @@ import Testing @Suite("Server Tests") struct ServerTests { + @Test("Invalid tool call parameters return a JSON-RPC invalid params error", arguments: [ + #"{}"#, #"{"name":"echo","arguments":[]}"#, + ]) + func testInvalidToolCallParameters(_ params: String) async throws { + let (clientTransport, serverTransport) = await InMemoryTransport.createConnectedPair() + let server = Server(name: "TestServer", version: "1.0", capabilities: .init(tools: .init())) + await server.withMethodHandler(CallTool.self) { _ in + Issue.record("Malformed parameters reached the tool handler") + return .init(content: []) + } + + try await server.start(transport: serverTransport) + try await clientTransport.connect() + var replies = await clientTransport.receive().makeAsyncIterator() + let request = #"{"jsonrpc":"2.0","id":7,"method":"tools/call","params":\#(params)}"# + try await clientTransport.send(Data(request.utf8)) + + let data = try #require(try await replies.next()) + let response = try #require(JSONSerialization.jsonObject(with: data) as? [String: Any]) + #expect(response["id"] as? Int == 7) + #expect((response["error"] as? [String: Any])?["code"] as? Int == -32602) + + await clientTransport.disconnect() + await server.stop() + } + @Test("Start and stop server") func testServerStartAndStop() async throws { let transport = MockTransport()