Skip to content

Implement client protocol version negotiation in server initialization #107

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
May 7, 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
21 changes: 19 additions & 2 deletions Sources/MCP/Base/Versioning.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> = [
"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
}
}
11 changes: 8 additions & 3 deletions Sources/MCP/Server/Server.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
53 changes: 53 additions & 0 deletions Tests/MCPTests/VersioningTests.swift
Original file line number Diff line number Diff line change
@@ -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")
}
}