diff --git a/Sources/MCP/Base/Versioning.swift b/Sources/MCP/Base/Versioning.swift index 44142fe3..05c77a00 100644 --- a/Sources/MCP/Base/Versioning.swift +++ b/Sources/MCP/Base/Versioning.swift @@ -6,6 +6,23 @@ import Foundation /// /// - SeeAlso: https://spec.modelcontextprotocol.io/specification/2025-03-26/ public enum Version { - /// The current protocol version. - public static let latest = "2025-03-26" + /// All protocol versions supported by this implementation, ordered from newest to oldest. + static let supported: Set = [ + "2025-03-26", + "2024-11-05", + ] + + /// The latest protocol version supported by this implementation. + public static let latest = supported.max()! + + /// Negotiates the protocol version based on the client's request and server's capabilities. + /// - Parameter clientRequestedVersion: The protocol version requested by the client. + /// - Returns: The negotiated protocol version. If the client's requested version is supported, + /// that version is returned. Otherwise, the server's latest supported version is returned. + static func negotiate(clientRequestedVersion: String) -> String { + if supported.contains(clientRequestedVersion) { + return clientRequestedVersion + } + return latest + } } diff --git a/Sources/MCP/Server/Server.swift b/Sources/MCP/Server/Server.swift index 945698d9..15ad3db6 100644 --- a/Sources/MCP/Server/Server.swift +++ b/Sources/MCP/Server/Server.swift @@ -486,15 +486,20 @@ public actor Server { try await hook(params.clientInfo, params.capabilities) } - // Set initial state + // Perform version negotiation + let clientRequestedVersion = params.protocolVersion + let negotiatedProtocolVersion = Version.negotiate( + clientRequestedVersion: clientRequestedVersion) + + // Set initial state with the negotiated protocol version await self.setInitialState( clientInfo: params.clientInfo, clientCapabilities: params.capabilities, - protocolVersion: params.protocolVersion + protocolVersion: negotiatedProtocolVersion ) return Initialize.Result( - protocolVersion: Version.latest, + protocolVersion: negotiatedProtocolVersion, capabilities: await self.capabilities, serverInfo: self.serverInfo, instructions: nil diff --git a/Tests/MCPTests/VersioningTests.swift b/Tests/MCPTests/VersioningTests.swift new file mode 100644 index 00000000..d1896b53 --- /dev/null +++ b/Tests/MCPTests/VersioningTests.swift @@ -0,0 +1,53 @@ +import Testing + +@testable import MCP + +@Suite("Version Negotiation Tests") +struct VersioningTests { + @Test("Client requests latest supported version") + func testClientRequestsLatestSupportedVersion() { + let clientVersion = Version.latest + let negotiatedVersion = Version.negotiate(clientRequestedVersion: clientVersion) + #expect(negotiatedVersion == Version.latest) + } + + @Test("Client requests older supported version") + func testClientRequestsOlderSupportedVersion() { + let clientVersion = "2024-11-05" + let negotiatedVersion = Version.negotiate(clientRequestedVersion: clientVersion) + #expect(negotiatedVersion == "2024-11-05") + } + + @Test("Client requests unsupported version") + func testClientRequestsUnsupportedVersion() { + let clientVersion = "2023-01-01" // An unsupported version + let negotiatedVersion = Version.negotiate(clientRequestedVersion: clientVersion) + #expect(negotiatedVersion == Version.latest) + } + + @Test("Client requests empty version string") + func testClientRequestsEmptyVersionString() { + let clientVersion = "" + let negotiatedVersion = Version.negotiate(clientRequestedVersion: clientVersion) + #expect(negotiatedVersion == Version.latest) + } + + @Test("Client requests garbage version string") + func testClientRequestsGarbageVersionString() { + let clientVersion = "not-a-version" + let negotiatedVersion = Version.negotiate(clientRequestedVersion: clientVersion) + #expect(negotiatedVersion == Version.latest) + } + + @Test("Server's supported versions correctly defined") + func testServerSupportedVersions() { + #expect(Version.supported.contains("2025-03-26")) + #expect(Version.supported.contains("2024-11-05")) + #expect(Version.supported.count == 2) + } + + @Test("Server's latest version is correct") + func testServerLatestVersion() { + #expect(Version.latest == "2025-03-26") + } +}