diff --git a/Sources/MCP/Server/Server.swift b/Sources/MCP/Server/Server.swift index fa75b5bf..e81b35e3 100644 --- a/Sources/MCP/Server/Server.swift +++ b/Sources/MCP/Server/Server.swift @@ -761,7 +761,17 @@ public actor Server { case Initialize.name, Ping.name: break default: - try checkInitialized() + guard isInitialized else { + let response = AnyMethod.response( + id: request.id, + error: MCPError.invalidRequest("Server is not initialized") + ) + if sendResponse { + try await send(response) + return nil + } + return response + } } } @@ -853,8 +863,12 @@ public actor Server { if configuration.strict { // Check initialization state unless this is an initialized notification - if message.method != InitializedNotification.name { - try checkInitialized() + // Notifications never get a response, so drop them instead of throwing. + if message.method != InitializedNotification.name, !isInitialized { + await logger?.warning( + "Ignoring notification before initialization", + metadata: ["method": "\(message.method)"]) + return } } @@ -892,12 +906,6 @@ public actor Server { } } - private func checkInitialized() throws { - guard isInitialized else { - throw MCPError.invalidRequest("Server is not initialized") - } - } - /// Validate the client capabilities. /// Throws an error if the server is configured to be strict and the capability is not supported. private func validateClientCapability( diff --git a/Tests/MCPTests/ServerTests.swift b/Tests/MCPTests/ServerTests.swift index 4713b278..38129611 100644 --- a/Tests/MCPTests/ServerTests.swift +++ b/Tests/MCPTests/ServerTests.swift @@ -268,4 +268,48 @@ struct ServerTests { await clientTransport.disconnect() await serverTransport.disconnect() } + + @Test("Strict mode rejects requests before initialization with an error response") + func testStrictModeRejectsRequestBeforeInitialize() async throws { + let transport = MockTransport() + let server = Server( + name: "TestServer", + version: "1.0", + capabilities: .init(tools: .init()), + configuration: .strict + ) + await server.withMethodHandler(ListTools.self) { _ in + ListTools.Result(tools: []) + } + try await server.start(transport: transport) + + try await transport.queue(request: ListTools.request(id: "early", .init())) + try await Task.sleep(for: .milliseconds(200)) + + #expect(await transport.sentMessages.count == 1) + let response = try #require( + await transport.decodeLastSentMessage() as Response?) + #expect(response.id == "early") + guard case .failure(let error) = response.result else { + Issue.record("Expected an error response, got \(response.result)") + return + } + #expect(error.code == -32600) + + await server.stop() + } + + @Test("Strict mode does not respond to notifications before initialization") + func testStrictModeIgnoresNotificationBeforeInitialize() async throws { + let transport = MockTransport() + let server = Server(name: "TestServer", version: "1.0", configuration: .strict) + try await server.start(transport: transport) + + try await transport.queue(notification: RootsListChangedNotification.message()) + try await Task.sleep(for: .milliseconds(200)) + + #expect(await transport.sentMessages.isEmpty) + + await server.stop() + } }