From 56305331441d95496bf1db681ed62964d2d323d5 Mon Sep 17 00:00:00 2001 From: VictorPuga <39507381+VictorPuga@users.noreply.github.com> Date: Sun, 12 Jul 2026 12:28:18 -0600 Subject: [PATCH] fix: make initialize idempotent instead of erroring on repeat calls Repeated `initialize` requests previously failed with "Server is already initialized", breaking stateless HTTP servers and clients that retry initialization. Per the MCP spec, initialize should be safe to call again, so the server now re-negotiates and returns a fresh result instead of throwing. Fixes modelcontextprotocol/swift-sdk#219 Fixes modelcontextprotocol/swift-sdk#144 Co-Authored-By: Claude Sonnet 5 --- Sources/MCP/Server/Server.swift | 4 -- Tests/MCPTests/ServerTests.swift | 85 ++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 4 deletions(-) diff --git a/Sources/MCP/Server/Server.swift b/Sources/MCP/Server/Server.swift index 060b51f8..a7630cfd 100644 --- a/Sources/MCP/Server/Server.swift +++ b/Sources/MCP/Server/Server.swift @@ -925,10 +925,6 @@ public actor Server { throw MCPError.internalError("Server was deallocated") } - guard await !self.isInitialized else { - throw MCPError.invalidRequest("Server is already initialized") - } - // Call initialization hook if registered if let hook = initializeHook { try await hook(params.clientInfo, params.capabilities) diff --git a/Tests/MCPTests/ServerTests.swift b/Tests/MCPTests/ServerTests.swift index a7b61667..9d8f71b1 100644 --- a/Tests/MCPTests/ServerTests.swift +++ b/Tests/MCPTests/ServerTests.swift @@ -53,6 +53,53 @@ struct ServerTests { await transport.disconnect() } + @Test("Repeated initialize requests are idempotent") + func testServerHandleRepeatedInitialize() async throws { + let transport = MockTransport() + + let server = Server(name: "TestServer", version: "1.0") + try await server.start(transport: transport) + + // Wait for server to start receiving + try await Task.sleep(for: .milliseconds(10)) + + // First initialize request + try await transport.queue( + request: Initialize.request( + .init( + protocolVersion: Version.latest, + capabilities: .init(), + clientInfo: .init(name: "TestClient", version: "1.0") + ) + )) + + try await Task.sleep(for: .milliseconds(200)) + + // Second initialize request on the same server connection should + // succeed rather than returning a "Server is already initialized" error + try await transport.queue( + request: Initialize.request( + .init( + protocolVersion: Version.latest, + capabilities: .init(), + clientInfo: .init(name: "TestClient", version: "1.0") + ) + )) + + try await Task.sleep(for: .milliseconds(200)) + + let messages = await transport.sentMessages + #expect(messages.count == 2) + for response in messages { + #expect(response.contains("serverInfo")) + #expect(!response.contains("\"error\"")) + #expect(!response.contains("already initialized")) + } + + await server.stop() + await transport.disconnect() + } + @Test("Initialize hook - successful") func testInitializeHookSuccess() async throws { let transport = MockTransport() @@ -101,6 +148,44 @@ struct ServerTests { await transport.disconnect() } + @Test("Initialize hook runs again on repeated initialize") + func testInitializeHookCalledOnRepeatedInitialize() async throws { + let transport = MockTransport() + + actor TestState { + var callCount = 0 + func increment() { callCount += 1 } + func count() -> Int { callCount } + } + + let state = TestState() + let server = Server(name: "TestServer", version: "1.0") + + try await server.start(transport: transport) { _, _ in + await state.increment() + } + + try await Task.sleep(for: .milliseconds(10)) + + for _ in 0..<2 { + try await transport.queue( + request: Initialize.request( + .init( + protocolVersion: Version.latest, + capabilities: .init(), + clientInfo: .init(name: "TestClient", version: "1.0") + ) + )) + try await Task.sleep(for: .milliseconds(200)) + } + + #expect(await state.count() == 2) + #expect(await transport.sentMessages.count == 2) + + await server.stop() + await transport.disconnect() + } + @Test("Initialize hook - rejection") func testInitializeHookRejection() async throws { let transport = MockTransport()