Skip to content

Commit 25dee3b

Browse files
authored
Send initialized notification from client instead of server (#85)
1 parent 29aa74d commit 25dee3b

File tree

4 files changed

+130
-12
lines changed

4 files changed

+130
-12
lines changed

Sources/MCP/Client/Client.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,16 @@ public actor Client {
249249
return self
250250
}
251251

252+
/// Send a notification to the server
253+
public func notify<N: Notification>(_ notification: Message<N>) async throws {
254+
guard let connection = connection else {
255+
throw MCPError.internalError("Client connection not initialized")
256+
}
257+
258+
let notificationData = try encoder.encode(notification)
259+
try await connection.send(notificationData)
260+
}
261+
252262
// MARK: - Requests
253263

254264
/// Send a request and receive its response
@@ -454,6 +464,8 @@ public actor Client {
454464
self.serverVersion = result.protocolVersion
455465
self.instructions = result.instructions
456466

467+
try await notify(InitializedNotification.message())
468+
457469
return result
458470
}
459471

Sources/MCP/Server/Server.swift

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -496,12 +496,6 @@ public actor Server {
496496
protocolVersion: params.protocolVersion
497497
)
498498

499-
// Send initialized notification after a short delay
500-
Task {
501-
try? await Task.sleep(for: .milliseconds(10))
502-
try? await self.notify(InitializedNotification.message())
503-
}
504-
505499
return Initialize.Result(
506500
protocolVersion: Version.latest,
507501
capabilities: await self.capabilities,
@@ -571,4 +565,4 @@ extension Server.Batch.Item: Codable {
571565
try notification.encode(to: encoder)
572566
}
573567
}
574-
}
568+
}

Tests/MCPTests/ClientTests.swift

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,4 +363,120 @@ struct ClientTests {
363363

364364
await client.disconnect()
365365
}
366+
367+
@Test("Notify method sends notifications")
368+
func testClientNotify() async throws {
369+
let transport = MockTransport()
370+
let client = Client(name: "TestClient", version: "1.0")
371+
372+
try await client.connect(transport: transport)
373+
try await Task.sleep(for: .milliseconds(10))
374+
375+
// Create a test notification
376+
let notification = InitializedNotification.message()
377+
try await client.notify(notification)
378+
379+
// Verify notification was sent
380+
#expect(await transport.sentMessages.count == 1)
381+
382+
if let sentMessage = await transport.sentMessages.first,
383+
let data = sentMessage.data(using: .utf8)
384+
{
385+
386+
// Decode as Message<InitializedNotification>
387+
let decoder = JSONDecoder()
388+
do {
389+
let decodedNotification = try decoder.decode(
390+
Message<InitializedNotification>.self, from: data)
391+
#expect(decodedNotification.method == InitializedNotification.name)
392+
} catch {
393+
#expect(Bool(false), "Failed to decode notification: \(error)")
394+
}
395+
} else {
396+
#expect(Bool(false), "No message was sent")
397+
}
398+
399+
await client.disconnect()
400+
}
401+
402+
@Test("Initialize sends initialized notification")
403+
func testClientInitializeNotification() async throws {
404+
let transport = MockTransport()
405+
let client = Client(name: "TestClient", version: "1.0")
406+
407+
try await client.connect(transport: transport)
408+
try await Task.sleep(for: .milliseconds(10))
409+
410+
// Create a task for initialize
411+
let initTask = Task {
412+
// Queue a response for the initialize request
413+
try await Task.sleep(for: .milliseconds(10)) // Wait for request to be sent
414+
415+
if let lastMessage = await transport.sentMessages.last,
416+
let data = lastMessage.data(using: .utf8),
417+
let request = try? JSONDecoder().decode(Request<Initialize>.self, from: data)
418+
{
419+
420+
// Create a valid initialize response
421+
let response = Initialize.response(
422+
id: request.id,
423+
result: .init(
424+
protocolVersion: Version.latest,
425+
capabilities: .init(),
426+
serverInfo: .init(name: "TestServer", version: "1.0"),
427+
instructions: nil
428+
)
429+
)
430+
431+
try await transport.queue(response: response)
432+
433+
// Now complete the initialize call
434+
_ = try await client.initialize()
435+
436+
// Verify that two messages were sent: initialize request and initialized notification
437+
#expect(await transport.sentMessages.count == 2)
438+
439+
// Check that the second message is the initialized notification
440+
let notifications = await transport.sentMessages
441+
if notifications.count >= 2 {
442+
let notificationJson = notifications[1]
443+
if let notificationData = notificationJson.data(using: .utf8) {
444+
do {
445+
let decoder = JSONDecoder()
446+
let decodedNotification = try decoder.decode(
447+
Message<InitializedNotification>.self, from: notificationData)
448+
#expect(decodedNotification.method == InitializedNotification.name)
449+
} catch {
450+
#expect(Bool(false), "Failed to decode notification: \(error)")
451+
}
452+
} else {
453+
#expect(Bool(false), "Could not convert notification to data")
454+
}
455+
} else {
456+
#expect(
457+
Bool(false), "Expected both initialize request and initialized notification"
458+
)
459+
}
460+
}
461+
}
462+
463+
// Wait with timeout
464+
let timeoutTask = Task {
465+
try await Task.sleep(for: .seconds(1))
466+
initTask.cancel()
467+
}
468+
469+
// Wait for the task to complete
470+
do {
471+
_ = try await initTask.value
472+
} catch is CancellationError {
473+
#expect(Bool(false), "Test timed out")
474+
} catch {
475+
#expect(Bool(false), "Unexpected error: \(error)")
476+
}
477+
478+
timeoutTask.cancel()
479+
480+
await client.disconnect()
481+
}
366482
}

Tests/MCPTests/ServerTests.swift

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,13 @@ struct ServerTests {
4141
// Wait for message processing and response
4242
try await Task.sleep(nanoseconds: 100_000_000) // 100ms
4343

44-
#expect(await transport.sentMessages.count == 2)
44+
#expect(await transport.sentMessages.count == 1)
4545

4646
let messages = await transport.sentMessages
4747
if let response = messages.first {
4848
#expect(response.contains("serverInfo"))
4949
}
5050

51-
if let noticiation = messages.last {
52-
#expect(noticiation.contains("initialized"))
53-
}
54-
5551
// Clean up
5652
await server.stop()
5753
await transport.disconnect()

0 commit comments

Comments
 (0)