Skip to content

Commit 99517b0

Browse files
stallentStephen Tallentmattt
authored
Update AnyNotification to include notification params (#86)
* Update AnyNotification to not wipe out params that come with a notification * Add test coverage for notification with params * Allow params to be omitted when empty --------- Co-authored-by: Stephen Tallent <stallent@mercury.io> Co-authored-by: Mattt Zmuda <mattt@loopwork.com>
1 parent 25dee3b commit 99517b0

2 files changed

Lines changed: 89 additions & 5 deletions

File tree

Sources/MCP/Base/Messages.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ public protocol Notification: Hashable, Codable, Sendable {
278278
/// A type-erased notification for message handling
279279
struct AnyNotification: Notification, Sendable {
280280
static var name: String { "" }
281-
typealias Parameters = Empty
281+
typealias Parameters = Value
282282
}
283283

284284
extension AnyNotification {
@@ -325,10 +325,11 @@ public struct Message<N: Notification>: Hashable, Codable, Sendable {
325325
}
326326
method = try container.decode(String.self, forKey: .method)
327327

328-
// Handle params field being optional
329-
if N.Parameters.self == Empty.self {
330-
// For Empty parameters, use Empty() if params is missing or null
331-
params = Empty() as! N.Parameters
328+
if N.Parameters.self is NotRequired.Type {
329+
// For NotRequired parameters, use decodeIfPresent or init()
330+
params =
331+
(try container.decodeIfPresent(N.Parameters.self, forKey: .params)
332+
?? (N.Parameters.self as! NotRequired.Type).init() as! N.Parameters)
332333
} else if let value = try? container.decode(N.Parameters.self, forKey: .params) {
333334
// If params exists and can be decoded, use it
334335
params = value

Tests/MCPTests/NotificationTests.swift

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,87 @@ struct NotificationTests {
8686

8787
#expect(decoded.method == InitializedNotification.name)
8888
}
89+
90+
@Test("Resource updated notification with parameters")
91+
func testResourceUpdatedNotification() throws {
92+
let params = ResourceUpdatedNotification.Parameters(uri: "test://resource")
93+
let notification = ResourceUpdatedNotification.message(params)
94+
95+
#expect(notification.method == ResourceUpdatedNotification.name)
96+
#expect(notification.params.uri == "test://resource")
97+
98+
let encoder = JSONEncoder()
99+
let decoder = JSONDecoder()
100+
101+
let data = try encoder.encode(notification)
102+
103+
// Verify the exact JSON structure
104+
let json = try JSONDecoder().decode([String: Value].self, from: data)
105+
#expect(json["jsonrpc"] == "2.0")
106+
#expect(json["method"] == "notifications/resources/updated")
107+
#expect(json["params"] != nil)
108+
#expect(json.count == 3, "Should contain jsonrpc, method, and params fields")
109+
110+
// Verify we can decode it back
111+
let decoded = try decoder.decode(Message<ResourceUpdatedNotification>.self, from: data)
112+
#expect(decoded.method == ResourceUpdatedNotification.name)
113+
#expect(decoded.params.uri == "test://resource")
114+
}
115+
116+
@Test("AnyNotification decoding - without params")
117+
func testAnyNotificationDecodingWithoutParams() throws {
118+
// Test decoding when params field is missing
119+
let jsonString = """
120+
{"jsonrpc":"2.0","method":"notifications/initialized"}
121+
"""
122+
let data = jsonString.data(using: .utf8)!
123+
124+
let decoder = JSONDecoder()
125+
let decoded = try decoder.decode(AnyMessage.self, from: data)
126+
127+
#expect(decoded.method == InitializedNotification.name)
128+
}
129+
130+
@Test("AnyNotification decoding - with null params")
131+
func testAnyNotificationDecodingWithNullParams() throws {
132+
// Test decoding when params field is null
133+
let jsonString = """
134+
{"jsonrpc":"2.0","method":"notifications/initialized","params":null}
135+
"""
136+
let data = jsonString.data(using: .utf8)!
137+
138+
let decoder = JSONDecoder()
139+
let decoded = try decoder.decode(AnyMessage.self, from: data)
140+
141+
#expect(decoded.method == InitializedNotification.name)
142+
}
143+
144+
@Test("AnyNotification decoding - with empty params")
145+
func testAnyNotificationDecodingWithEmptyParams() throws {
146+
// Test decoding when params field is empty
147+
let jsonString = """
148+
{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}
149+
"""
150+
let data = jsonString.data(using: .utf8)!
151+
152+
let decoder = JSONDecoder()
153+
let decoded = try decoder.decode(AnyMessage.self, from: data)
154+
155+
#expect(decoded.method == InitializedNotification.name)
156+
}
157+
158+
@Test("AnyNotification decoding - with non-empty params")
159+
func testAnyNotificationDecodingWithNonEmptyParams() throws {
160+
// Test decoding when params field has values
161+
let jsonString = """
162+
{"jsonrpc":"2.0","method":"notifications/resources/updated","params":{"uri":"test://resource"}}
163+
"""
164+
let data = jsonString.data(using: .utf8)!
165+
166+
let decoder = JSONDecoder()
167+
let decoded = try decoder.decode(AnyMessage.self, from: data)
168+
169+
#expect(decoded.method == ResourceUpdatedNotification.name)
170+
#expect(decoded.params.objectValue?["uri"]?.stringValue == "test://resource")
171+
}
89172
}

0 commit comments

Comments
 (0)