Skip to content

Commit 720a810

Browse files
authored
Implement client protocol version negotiation in server initialization (#107)
* Implement client version negotiation in server initialization * Add test coverage for version negotiation * Make Version.supported and Version.negotiate() APIs internal to not create new surface area
1 parent 3690509 commit 720a810

File tree

3 files changed

+80
-5
lines changed

3 files changed

+80
-5
lines changed

Sources/MCP/Base/Versioning.swift

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,23 @@ import Foundation
66
///
77
/// - SeeAlso: https://spec.modelcontextprotocol.io/specification/2025-03-26/
88
public enum Version {
9-
/// The current protocol version.
10-
public static let latest = "2025-03-26"
9+
/// All protocol versions supported by this implementation, ordered from newest to oldest.
10+
static let supported: Set<String> = [
11+
"2025-03-26",
12+
"2024-11-05",
13+
]
14+
15+
/// The latest protocol version supported by this implementation.
16+
public static let latest = supported.max()!
17+
18+
/// Negotiates the protocol version based on the client's request and server's capabilities.
19+
/// - Parameter clientRequestedVersion: The protocol version requested by the client.
20+
/// - Returns: The negotiated protocol version. If the client's requested version is supported,
21+
/// that version is returned. Otherwise, the server's latest supported version is returned.
22+
static func negotiate(clientRequestedVersion: String) -> String {
23+
if supported.contains(clientRequestedVersion) {
24+
return clientRequestedVersion
25+
}
26+
return latest
27+
}
1128
}

Sources/MCP/Server/Server.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -486,15 +486,20 @@ public actor Server {
486486
try await hook(params.clientInfo, params.capabilities)
487487
}
488488

489-
// Set initial state
489+
// Perform version negotiation
490+
let clientRequestedVersion = params.protocolVersion
491+
let negotiatedProtocolVersion = Version.negotiate(
492+
clientRequestedVersion: clientRequestedVersion)
493+
494+
// Set initial state with the negotiated protocol version
490495
await self.setInitialState(
491496
clientInfo: params.clientInfo,
492497
clientCapabilities: params.capabilities,
493-
protocolVersion: params.protocolVersion
498+
protocolVersion: negotiatedProtocolVersion
494499
)
495500

496501
return Initialize.Result(
497-
protocolVersion: Version.latest,
502+
protocolVersion: negotiatedProtocolVersion,
498503
capabilities: await self.capabilities,
499504
serverInfo: self.serverInfo,
500505
instructions: nil
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import Testing
2+
3+
@testable import MCP
4+
5+
@Suite("Version Negotiation Tests")
6+
struct VersioningTests {
7+
@Test("Client requests latest supported version")
8+
func testClientRequestsLatestSupportedVersion() {
9+
let clientVersion = Version.latest
10+
let negotiatedVersion = Version.negotiate(clientRequestedVersion: clientVersion)
11+
#expect(negotiatedVersion == Version.latest)
12+
}
13+
14+
@Test("Client requests older supported version")
15+
func testClientRequestsOlderSupportedVersion() {
16+
let clientVersion = "2024-11-05"
17+
let negotiatedVersion = Version.negotiate(clientRequestedVersion: clientVersion)
18+
#expect(negotiatedVersion == "2024-11-05")
19+
}
20+
21+
@Test("Client requests unsupported version")
22+
func testClientRequestsUnsupportedVersion() {
23+
let clientVersion = "2023-01-01" // An unsupported version
24+
let negotiatedVersion = Version.negotiate(clientRequestedVersion: clientVersion)
25+
#expect(negotiatedVersion == Version.latest)
26+
}
27+
28+
@Test("Client requests empty version string")
29+
func testClientRequestsEmptyVersionString() {
30+
let clientVersion = ""
31+
let negotiatedVersion = Version.negotiate(clientRequestedVersion: clientVersion)
32+
#expect(negotiatedVersion == Version.latest)
33+
}
34+
35+
@Test("Client requests garbage version string")
36+
func testClientRequestsGarbageVersionString() {
37+
let clientVersion = "not-a-version"
38+
let negotiatedVersion = Version.negotiate(clientRequestedVersion: clientVersion)
39+
#expect(negotiatedVersion == Version.latest)
40+
}
41+
42+
@Test("Server's supported versions correctly defined")
43+
func testServerSupportedVersions() {
44+
#expect(Version.supported.contains("2025-03-26"))
45+
#expect(Version.supported.contains("2024-11-05"))
46+
#expect(Version.supported.count == 2)
47+
}
48+
49+
@Test("Server's latest version is correct")
50+
func testServerLatestVersion() {
51+
#expect(Version.latest == "2025-03-26")
52+
}
53+
}

0 commit comments

Comments
 (0)