diff --git a/Sources/MCP/Base/Messages.swift b/Sources/MCP/Base/Messages.swift index 29d8725f..53e462a2 100644 --- a/Sources/MCP/Base/Messages.swift +++ b/Sources/MCP/Base/Messages.swift @@ -278,7 +278,7 @@ public protocol Notification: Hashable, Codable, Sendable { /// A type-erased notification for message handling struct AnyNotification: Notification, Sendable { static var name: String { "" } - typealias Parameters = Empty + typealias Parameters = Value } extension AnyNotification { @@ -325,10 +325,11 @@ public struct Message: Hashable, Codable, Sendable { } method = try container.decode(String.self, forKey: .method) - // Handle params field being optional - if N.Parameters.self == Empty.self { - // For Empty parameters, use Empty() if params is missing or null - params = Empty() as! N.Parameters + if N.Parameters.self is NotRequired.Type { + // For NotRequired parameters, use decodeIfPresent or init() + params = + (try container.decodeIfPresent(N.Parameters.self, forKey: .params) + ?? (N.Parameters.self as! NotRequired.Type).init() as! N.Parameters) } else if let value = try? container.decode(N.Parameters.self, forKey: .params) { // If params exists and can be decoded, use it params = value diff --git a/Tests/MCPTests/NotificationTests.swift b/Tests/MCPTests/NotificationTests.swift index 39886e3e..4bdd1873 100644 --- a/Tests/MCPTests/NotificationTests.swift +++ b/Tests/MCPTests/NotificationTests.swift @@ -86,4 +86,87 @@ struct NotificationTests { #expect(decoded.method == InitializedNotification.name) } + + @Test("Resource updated notification with parameters") + func testResourceUpdatedNotification() throws { + let params = ResourceUpdatedNotification.Parameters(uri: "test://resource") + let notification = ResourceUpdatedNotification.message(params) + + #expect(notification.method == ResourceUpdatedNotification.name) + #expect(notification.params.uri == "test://resource") + + let encoder = JSONEncoder() + let decoder = JSONDecoder() + + let data = try encoder.encode(notification) + + // Verify the exact JSON structure + let json = try JSONDecoder().decode([String: Value].self, from: data) + #expect(json["jsonrpc"] == "2.0") + #expect(json["method"] == "notifications/resources/updated") + #expect(json["params"] != nil) + #expect(json.count == 3, "Should contain jsonrpc, method, and params fields") + + // Verify we can decode it back + let decoded = try decoder.decode(Message.self, from: data) + #expect(decoded.method == ResourceUpdatedNotification.name) + #expect(decoded.params.uri == "test://resource") + } + + @Test("AnyNotification decoding - without params") + func testAnyNotificationDecodingWithoutParams() throws { + // Test decoding when params field is missing + let jsonString = """ + {"jsonrpc":"2.0","method":"notifications/initialized"} + """ + let data = jsonString.data(using: .utf8)! + + let decoder = JSONDecoder() + let decoded = try decoder.decode(AnyMessage.self, from: data) + + #expect(decoded.method == InitializedNotification.name) + } + + @Test("AnyNotification decoding - with null params") + func testAnyNotificationDecodingWithNullParams() throws { + // Test decoding when params field is null + let jsonString = """ + {"jsonrpc":"2.0","method":"notifications/initialized","params":null} + """ + let data = jsonString.data(using: .utf8)! + + let decoder = JSONDecoder() + let decoded = try decoder.decode(AnyMessage.self, from: data) + + #expect(decoded.method == InitializedNotification.name) + } + + @Test("AnyNotification decoding - with empty params") + func testAnyNotificationDecodingWithEmptyParams() throws { + // Test decoding when params field is empty + let jsonString = """ + {"jsonrpc":"2.0","method":"notifications/initialized","params":{}} + """ + let data = jsonString.data(using: .utf8)! + + let decoder = JSONDecoder() + let decoded = try decoder.decode(AnyMessage.self, from: data) + + #expect(decoded.method == InitializedNotification.name) + } + + @Test("AnyNotification decoding - with non-empty params") + func testAnyNotificationDecodingWithNonEmptyParams() throws { + // Test decoding when params field has values + let jsonString = """ + {"jsonrpc":"2.0","method":"notifications/resources/updated","params":{"uri":"test://resource"}} + """ + let data = jsonString.data(using: .utf8)! + + let decoder = JSONDecoder() + let decoded = try decoder.decode(AnyMessage.self, from: data) + + #expect(decoded.method == ResourceUpdatedNotification.name) + #expect(decoded.params.objectValue?["uri"]?.stringValue == "test://resource") + } }