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
This commit is contained in:
parent
3690509fa0
commit
720a8102e9
3 changed files with 80 additions and 5 deletions
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
53
Tests/MCPTests/VersioningTests.swift
Normal file
53
Tests/MCPTests/VersioningTests.swift
Normal file
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue