Skip to content

Update AnyNotification to include notification params #86

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions Sources/MCP/Base/Messages.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -325,10 +325,11 @@ public struct Message<N: Notification>: 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
Expand Down
83 changes: 83 additions & 0 deletions Tests/MCPTests/NotificationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<ResourceUpdatedNotification>.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")
}
}