diff --git a/src/ModelContextProtocol.Core/Client/McpClientImpl.cs b/src/ModelContextProtocol.Core/Client/McpClientImpl.cs
index 639718885..729185a3e 100644
--- a/src/ModelContextProtocol.Core/Client/McpClientImpl.cs
+++ b/src/ModelContextProtocol.Core/Client/McpClientImpl.cs
@@ -26,6 +26,7 @@ internal sealed partial class McpClientImpl : McpClient
private ServerCapabilities? _serverCapabilities;
private Implementation? _serverInfo;
private string? _serverInstructions;
+ private string? _negotiatedProtocolVersion;
private bool _disposed;
@@ -112,6 +113,9 @@ private void RegisterHandlers(ClientCapabilities capabilities, NotificationHandl
///
public override string? SessionId => _transport.SessionId;
+ ///
+ public override string? NegotiatedProtocolVersion => _negotiatedProtocolVersion;
+
///
public override ServerCapabilities ServerCapabilities => _serverCapabilities ?? throw new InvalidOperationException("The client is not connected.");
@@ -177,6 +181,8 @@ public async Task ConnectAsync(CancellationToken cancellationToken = default)
throw new McpException($"Server protocol version mismatch. Expected {requestProtocol}, got {initializeResponse.ProtocolVersion}");
}
+ _negotiatedProtocolVersion = initializeResponse.ProtocolVersion;
+
// Send initialized notification
await this.SendNotificationAsync(
NotificationMethods.InitializedNotification,
diff --git a/src/ModelContextProtocol.Core/McpSession.cs b/src/ModelContextProtocol.Core/McpSession.cs
index 241c36d4c..429fdbfd4 100644
--- a/src/ModelContextProtocol.Core/McpSession.cs
+++ b/src/ModelContextProtocol.Core/McpSession.cs
@@ -38,6 +38,15 @@ public abstract partial class McpSession : IMcpEndpoint, IAsyncDisposable
///
public abstract string? SessionId { get; }
+ ///
+ /// Gets the negotiated protocol version for the current MCP session.
+ ///
+ ///
+ /// Returns the protocol version negotiated during session initialization,
+ /// or if initialization hasn't yet occurred.
+ ///
+ public abstract string? NegotiatedProtocolVersion { get; }
+
///
/// Sends a JSON-RPC request to the connected session and waits for a response.
///
@@ -45,7 +54,7 @@ public abstract partial class McpSession : IMcpEndpoint, IAsyncDisposable
/// The to monitor for cancellation requests. The default is .
/// A task containing the session's response.
/// The transport is not connected, or another error occurs during request processing.
- /// An error occured during request processing.
+ /// An error occurred during request processing.
///
/// This method provides low-level access to send raw JSON-RPC requests. For most use cases,
/// consider using the strongly-typed methods that provide a more convenient API.
diff --git a/src/ModelContextProtocol.Core/Server/DestinationBoundMcpServer.cs b/src/ModelContextProtocol.Core/Server/DestinationBoundMcpServer.cs
index f74dc29b0..bbbc45dcc 100644
--- a/src/ModelContextProtocol.Core/Server/DestinationBoundMcpServer.cs
+++ b/src/ModelContextProtocol.Core/Server/DestinationBoundMcpServer.cs
@@ -6,6 +6,7 @@ namespace ModelContextProtocol.Server;
internal sealed class DestinationBoundMcpServer(McpServerImpl server, ITransport? transport) : McpServer
{
public override string? SessionId => transport?.SessionId ?? server.SessionId;
+ public override string? NegotiatedProtocolVersion => server.NegotiatedProtocolVersion;
public override ClientCapabilities? ClientCapabilities => server.ClientCapabilities;
public override Implementation? ClientInfo => server.ClientInfo;
public override McpServerOptions ServerOptions => server.ServerOptions;
diff --git a/src/ModelContextProtocol.Core/Server/McpServerImpl.cs b/src/ModelContextProtocol.Core/Server/McpServerImpl.cs
index 1ece8af23..f4f9e8b32 100644
--- a/src/ModelContextProtocol.Core/Server/McpServerImpl.cs
+++ b/src/ModelContextProtocol.Core/Server/McpServerImpl.cs
@@ -7,8 +7,6 @@
namespace ModelContextProtocol.Server;
-// TODO: Fix merge conflicts in this file.
-
///
internal sealed partial class McpServerImpl : McpServer
{
@@ -31,6 +29,7 @@ internal sealed partial class McpServerImpl : McpServer
private Implementation? _clientInfo;
private readonly string _serverOnlyEndpointName;
+ private string? _negotiatedProtocolVersion;
private string _endpointName;
private int _started;
@@ -116,6 +115,9 @@ void Register(McpServerPrimitiveCollection? collection,
///
public override string? SessionId => _sessionTransport.SessionId;
+ ///
+ public override string? NegotiatedProtocolVersion => _negotiatedProtocolVersion;
+
///
public ServerCapabilities ServerCapabilities { get; } = new();
@@ -212,6 +214,8 @@ private void ConfigureInitialize(McpServerOptions options)
McpSessionHandler.LatestProtocolVersion;
}
+ _negotiatedProtocolVersion = protocolVersion;
+
return new InitializeResult
{
ProtocolVersion = protocolVersion,
diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/HttpServerIntegrationTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/HttpServerIntegrationTests.cs
index f9aa5a5e9..5da37146a 100644
--- a/tests/ModelContextProtocol.AspNetCore.Tests/HttpServerIntegrationTests.cs
+++ b/tests/ModelContextProtocol.AspNetCore.Tests/HttpServerIntegrationTests.cs
@@ -52,6 +52,7 @@ public async Task Connect_TestServer_ShouldProvideServerFields()
// Assert
Assert.NotNull(client.ServerCapabilities);
Assert.NotNull(client.ServerInfo);
+ Assert.NotNull(client.NegotiatedProtocolVersion);
if (ClientTransportOptions.Endpoint.AbsolutePath.EndsWith("/sse"))
{
diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpStreamableHttpTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpStreamableHttpTests.cs
index cb1f86db9..f8b61aa21 100644
--- a/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpStreamableHttpTests.cs
+++ b/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpStreamableHttpTests.cs
@@ -171,13 +171,13 @@ public async Task StreamableHttpClient_SendsMcpProtocolVersionHeader_AfterInitia
await app.StartAsync(TestContext.Current.CancellationToken);
- await using (var mcpClient = await ConnectAsync(clientOptions: new()
+ await using var mcpClient = await ConnectAsync(clientOptions: new()
{
ProtocolVersion = "2025-03-26",
- }))
- {
- await mcpClient.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
- }
+ });
+
+ Assert.Equal("2025-03-26", mcpClient.NegotiatedProtocolVersion);
+ await mcpClient.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
// The header should be included in the GET request, the initialized notification, the tools/list call, and the delete request.
Assert.NotEmpty(protocolVersionHeaderValues);
diff --git a/tests/ModelContextProtocol.Tests/Client/McpClientTests.cs b/tests/ModelContextProtocol.Tests/Client/McpClientTests.cs
index 779e31e62..d7034660b 100644
--- a/tests/ModelContextProtocol.Tests/Client/McpClientTests.cs
+++ b/tests/ModelContextProtocol.Tests/Client/McpClientTests.cs
@@ -468,4 +468,13 @@ public async Task AsClientLoggerProvider_MessagesSentToClient()
],
data.OrderBy(s => s));
}
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("2025-03-26")]
+ public async Task ReturnsNegotiatedProtocolVersion(string? protocolVersion)
+ {
+ await using McpClient client = await CreateMcpClientForServer(new() { ProtocolVersion = protocolVersion });
+ Assert.Equal(protocolVersion ?? "2025-06-18", client.NegotiatedProtocolVersion);
+ }
}
\ No newline at end of file
diff --git a/tests/ModelContextProtocol.Tests/ClientIntegrationTests.cs b/tests/ModelContextProtocol.Tests/ClientIntegrationTests.cs
index 211688419..e2f03805f 100644
--- a/tests/ModelContextProtocol.Tests/ClientIntegrationTests.cs
+++ b/tests/ModelContextProtocol.Tests/ClientIntegrationTests.cs
@@ -52,8 +52,12 @@ public async Task Connect_ShouldProvideServerFields(string clientId)
// Assert
Assert.NotNull(client.ServerCapabilities);
Assert.NotNull(client.ServerInfo);
+ Assert.NotNull(client.NegotiatedProtocolVersion);
+
if (clientId != "everything") // Note: Comment the below assertion back when the everything server is updated to provide instructions
+ {
Assert.NotNull(client.ServerInstructions);
+ }
Assert.Null(client.SessionId);
}
diff --git a/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs b/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs
index 61cda7015..736d63ec4 100644
--- a/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs
+++ b/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs
@@ -41,6 +41,7 @@ public async Task Create_Should_Initialize_With_Valid_Parameters()
// Assert
Assert.NotNull(server);
+ Assert.Null(server.NegotiatedProtocolVersion);
}
[Fact]
@@ -232,7 +233,7 @@ await Can_Handle_Requests(
serverCapabilities: null,
method: RequestMethods.Ping,
configureOptions: null,
- assertResult: response =>
+ assertResult: (_, response) =>
{
JsonObject jObj = Assert.IsType(response);
Assert.Empty(jObj);
@@ -247,13 +248,14 @@ await Can_Handle_Requests(
serverCapabilities: null,
method: RequestMethods.Initialize,
configureOptions: null,
- assertResult: response =>
+ assertResult: (server, response) =>
{
var result = JsonSerializer.Deserialize(response, McpJsonUtilities.DefaultOptions);
Assert.NotNull(result);
Assert.Equal(expectedAssemblyName.Name, result.ServerInfo.Name);
Assert.Equal(expectedAssemblyName.Version?.ToString() ?? "1.0.0", result.ServerInfo.Version);
Assert.Equal("2024", result.ProtocolVersion);
+ Assert.Equal("2024", server.NegotiatedProtocolVersion);
});
}
@@ -279,7 +281,7 @@ await Can_Handle_Requests(
},
method: RequestMethods.CompletionComplete,
configureOptions: null,
- assertResult: response =>
+ assertResult: (_, response) =>
{
var result = JsonSerializer.Deserialize(response, McpJsonUtilities.DefaultOptions);
Assert.NotNull(result?.Completion);
@@ -316,7 +318,7 @@ await Can_Handle_Requests(
},
RequestMethods.ResourcesTemplatesList,
configureOptions: null,
- assertResult: response =>
+ assertResult: (_, response) =>
{
var result = JsonSerializer.Deserialize(response, McpJsonUtilities.DefaultOptions);
Assert.NotNull(result?.ResourceTemplates);
@@ -345,7 +347,7 @@ await Can_Handle_Requests(
},
RequestMethods.ResourcesList,
configureOptions: null,
- assertResult: response =>
+ assertResult: (_, response) =>
{
var result = JsonSerializer.Deserialize(response, McpJsonUtilities.DefaultOptions);
Assert.NotNull(result?.Resources);
@@ -380,7 +382,7 @@ await Can_Handle_Requests(
},
method: RequestMethods.ResourcesRead,
configureOptions: null,
- assertResult: response =>
+ assertResult: (_, response) =>
{
var result = JsonSerializer.Deserialize(response, McpJsonUtilities.DefaultOptions);
Assert.NotNull(result?.Contents);
@@ -417,7 +419,7 @@ await Can_Handle_Requests(
},
method: RequestMethods.PromptsList,
configureOptions: null,
- assertResult: response =>
+ assertResult: (_, response) =>
{
var result = JsonSerializer.Deserialize(response, McpJsonUtilities.DefaultOptions);
Assert.NotNull(result?.Prompts);
@@ -446,7 +448,7 @@ await Can_Handle_Requests(
},
method: RequestMethods.PromptsGet,
configureOptions: null,
- assertResult: response =>
+ assertResult: (_, response) =>
{
var result = JsonSerializer.Deserialize(response, McpJsonUtilities.DefaultOptions);
Assert.NotNull(result);
@@ -480,7 +482,7 @@ await Can_Handle_Requests(
},
method: RequestMethods.ToolsList,
configureOptions: null,
- assertResult: response =>
+ assertResult: (_, response) =>
{
var result = JsonSerializer.Deserialize(response, McpJsonUtilities.DefaultOptions);
Assert.NotNull(result);
@@ -515,7 +517,7 @@ await Can_Handle_Requests(
},
method: RequestMethods.ToolsCall,
configureOptions: null,
- assertResult: response =>
+ assertResult: (_, response) =>
{
var result = JsonSerializer.Deserialize(response, McpJsonUtilities.DefaultOptions);
Assert.NotNull(result);
@@ -530,7 +532,7 @@ public async Task Can_Handle_Call_Tool_Requests_Throws_Exception_If_No_Handler_A
await Succeeds_Even_If_No_Handler_Assigned(new ServerCapabilities { Tools = new() }, RequestMethods.ToolsCall, "CallTool handler not configured");
}
- private async Task Can_Handle_Requests(ServerCapabilities? serverCapabilities, string method, Action? configureOptions, Action assertResult)
+ private async Task Can_Handle_Requests(ServerCapabilities? serverCapabilities, string method, Action? configureOptions, Action assertResult)
{
await using var transport = new TestServerTransport();
var options = CreateOptions(serverCapabilities);
@@ -559,7 +561,7 @@ await transport.SendMessageAsync(
var response = await receivedMessage.Task.WaitAsync(TimeSpan.FromSeconds(5));
Assert.NotNull(response);
- assertResult(response.Result);
+ assertResult(server, response.Result);
await transport.DisposeAsync();
await runTask;
@@ -682,6 +684,7 @@ public override Task SendRequestAsync(JsonRpcRequest request, C
public override ValueTask DisposeAsync() => default;
public override string? SessionId => throw new NotImplementedException();
+ public override string? NegotiatedProtocolVersion => throw new NotImplementedException();
public override Implementation? ClientInfo => throw new NotImplementedException();
public override IServiceProvider? Services => throw new NotImplementedException();
public override LoggingLevel? LoggingLevel => throw new NotImplementedException();