From 71cf08fea12c4ec1b3ffd5f89eedc9978f974da1 Mon Sep 17 00:00:00 2001 From: Jon Galloway Date: Tue, 31 Mar 2026 22:47:52 -0700 Subject: [PATCH] Upgrade MCP C# SDK to v1.2.0 and re-enable InMemoryMcpTaskStore - Update ModelContextProtocol from 1.1.0 to 1.2.0 in both projects - Uncomment InMemoryMcpTaskStore registration in Program.cs (upstream DI scope lifetime bug fixed in csharp-sdk#1433) - Update ServerCapabilitiesTests to assert AsyncTasks == true - Replace degradation tests with task lifecycle conformance tests - Update MCP C# SDK docs URL to new vanity domain Fixes #410 --- DotNetMcp.Tests/DotNetMcp.Tests.csproj | 2 +- DotNetMcp.Tests/Server/McpConformanceTests.cs | 26 ++++++++----------- .../Server/ServerCapabilitiesTests.cs | 11 ++++---- DotNetMcp/DotNetMcp.csproj | 2 +- DotNetMcp/Program.cs | 8 ++---- DotNetMcp/Tools/Cli/DotNetCliTools.Misc.cs | 2 +- README.md | 2 +- 7 files changed, 23 insertions(+), 30 deletions(-) diff --git a/DotNetMcp.Tests/DotNetMcp.Tests.csproj b/DotNetMcp.Tests/DotNetMcp.Tests.csproj index 852ca5a..a1e295b 100644 --- a/DotNetMcp.Tests/DotNetMcp.Tests.csproj +++ b/DotNetMcp.Tests/DotNetMcp.Tests.csproj @@ -14,7 +14,7 @@ - + diff --git a/DotNetMcp.Tests/Server/McpConformanceTests.cs b/DotNetMcp.Tests/Server/McpConformanceTests.cs index ac73d3b..c44abf5 100644 --- a/DotNetMcp.Tests/Server/McpConformanceTests.cs +++ b/DotNetMcp.Tests/Server/McpConformanceTests.cs @@ -713,22 +713,21 @@ public async Task Server_CompleteHandler_ShouldReturnConfigurationSuggestions() #endregion - #region MCP Task Support Tests (Graceful Degradation) + #region MCP Task Support Tests - // Task support is intentionally disabled: MCP SDK v1.1.0's ExecuteToolAsTaskAsync - // disposes the DI scope before the background task resolves services, causing - // ObjectDisposedException on every tool call. Without InMemoryMcpTaskStore, - // the server runs tools synchronously and does not advertise task capabilities. + // Task support is enabled: MCP SDK v1.2.0 fixed the DI scope lifetime bug (#1430). + // InMemoryMcpTaskStore is registered, so tool calls with TaskSupport = Optional + // can run as background tasks. [Fact] - public void Server_ShouldNotAdvertiseTasksCapability_WhenTaskStoreNotRegistered() + public void Server_ShouldAdvertiseTasksCapability_WhenTaskStoreRegistered() { // Arrange Assert.NotNull(_client); - // Assert - no task store means no tasks capability advertised + // Assert - task store is registered so tasks capability is advertised Assert.NotNull(_client.ServerCapabilities); - Assert.Null(_client.ServerCapabilities.Tasks); + Assert.NotNull(_client.ServerCapabilities.Tasks); } [Fact] @@ -741,10 +740,7 @@ public async Task Server_DotnetProject_ShouldDeclareTaskSupportOptional() var tools = await _client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken); var projectTool = tools.FirstOrDefault(t => t.Name == "dotnet_project"); - // Assert - the tool attribute declares TaskSupport = Optional so it's ready when - // task support is re-enabled by un-commenting the IMcpTaskStore registration in Program.cs - // (pending fix of https://github.com/modelcontextprotocol/csharp-sdk/issues/1430). - // Without a task store registered, the SDK runs it synchronously inline (graceful degradation). + // Assert - the tool attribute declares TaskSupport = Optional Assert.NotNull(projectTool); var execution = projectTool.ProtocolTool.Execution; Assert.NotNull(execution); @@ -752,12 +748,12 @@ public async Task Server_DotnetProject_ShouldDeclareTaskSupportOptional() } [Fact] - public async Task Server_DotnetProject_ShouldWorkSynchronously() + public async Task Server_DotnetProject_ShouldWorkWithTaskStore() { - // Arrange - verify tools work reliably via normal tools/call (no tasks) + // Arrange - verify tools work reliably with task store registered Assert.NotNull(_client); - // Act - standard synchronous tool call + // Act - tool call (task store is present; SDK may run as task or inline) var result = await _client.CallToolAsync( "dotnet_project", new Dictionary diff --git a/DotNetMcp.Tests/Server/ServerCapabilitiesTests.cs b/DotNetMcp.Tests/Server/ServerCapabilitiesTests.cs index 65b2b5c..6a7396b 100644 --- a/DotNetMcp.Tests/Server/ServerCapabilitiesTests.cs +++ b/DotNetMcp.Tests/Server/ServerCapabilitiesTests.cs @@ -2,6 +2,7 @@ using DotNetMcp; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using ModelContextProtocol; using Xunit; namespace DotNetMcp.Tests; @@ -16,7 +17,7 @@ public ServerCapabilitiesTests() { _logger = NullLogger.Instance; _concurrencyManager = new ConcurrencyManager(); - _tools = new DotNetCliTools(_logger, _concurrencyManager, new ProcessSessionManager()); + _tools = new DotNetCliTools(_logger, _concurrencyManager, new ProcessSessionManager(), taskStore: new InMemoryMcpTaskStore()); } [Fact] @@ -210,7 +211,7 @@ public async Task DotnetServerCapabilities_SdkVersions_Lts_IsNet100() } [Fact] - public async Task DotnetServerCapabilities_Supports_AsyncTasks_IsFalse() + public async Task DotnetServerCapabilities_Supports_AsyncTasks_IsTrue() { // Act var result = (await _tools.DotnetServerCapabilities()).GetText(); @@ -220,8 +221,8 @@ public async Task DotnetServerCapabilities_Supports_AsyncTasks_IsFalse() .GetProperty("asyncTasks") .GetBoolean(); - // Assert - MCP Task support disabled due to SDK v1.1.0 DI scope lifetime bug - Assert.False(asyncTasks); + // Assert - MCP Task support enabled with MCP SDK v1.2.0 fix for DI scope lifetime bug + Assert.True(asyncTasks); } [Fact] @@ -262,7 +263,7 @@ public async Task DotnetServerCapabilities_JsonSchema_MatchesExpectedStructure() Assert.True(capabilities.Supports.MachineReadable); Assert.True(capabilities.Supports.Cancellation); Assert.True(capabilities.Supports.Telemetry); - Assert.False(capabilities.Supports.AsyncTasks); + Assert.True(capabilities.Supports.AsyncTasks); Assert.True(capabilities.Supports.Prompts); Assert.True(capabilities.Supports.Elicitation); Assert.True(capabilities.Supports.Completions); diff --git a/DotNetMcp/DotNetMcp.csproj b/DotNetMcp/DotNetMcp.csproj index 2a2d5a4..d2896a6 100644 --- a/DotNetMcp/DotNetMcp.csproj +++ b/DotNetMcp/DotNetMcp.csproj @@ -51,7 +51,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/DotNetMcp/Program.cs b/DotNetMcp/Program.cs index 226cd72..8cbbb3d 100644 --- a/DotNetMcp/Program.cs +++ b/DotNetMcp/Program.cs @@ -21,12 +21,8 @@ // Register ProcessSessionManager as a singleton builder.Services.AddSingleton(); -// TODO: Re-enable InMemoryMcpTaskStore once the MCP SDK fixes the DI scope lifetime bug. -// Upstream issue: https://github.com/modelcontextprotocol/csharp-sdk/issues/1430 -// MCP SDK v1.1.0's ExecuteToolAsTaskAsync disposes the request-scoped IServiceProvider -// before the background task resolves services, causing ObjectDisposedException on every -// tool call. Without a task store, the SDK runs tools synchronously inline. -// builder.Services.AddSingleton(); +// Register InMemoryMcpTaskStore for async task support (MCP SDK v1.2.0 fixed #1430). +builder.Services.AddSingleton(); // Register ToolMetricsAccumulator for in-memory telemetry collection. // The accumulator is also captured by the telemetry filter added below. diff --git a/DotNetMcp/Tools/Cli/DotNetCliTools.Misc.cs b/DotNetMcp/Tools/Cli/DotNetCliTools.Misc.cs index fcb8fc5..67d6c58 100644 --- a/DotNetMcp/Tools/Cli/DotNetCliTools.Misc.cs +++ b/DotNetMcp/Tools/Cli/DotNetCliTools.Misc.cs @@ -71,7 +71,7 @@ public async partial Task DotnetServerCapabilities() Cancellation = true, Telemetry = true, // SDK v0.6+ supports request duration logging and OpenTelemetry semantic conventions Metrics = true, // In-memory per-tool metrics via MCP message filter (dotnet_server_metrics tool) - AsyncTasks = _taskStore != null, // Derived from DI: true when IMcpTaskStore is registered; currently false (MCP SDK DI scope bug https://github.com/modelcontextprotocol/csharp-sdk/issues/1430) + AsyncTasks = _taskStore != null, // Derived from DI: true when IMcpTaskStore is registered Prompts = true, // Predefined prompt catalog: create_new_webapi, add_package_and_restore, run_tests_with_coverage Elicitation = true, // Elicitation for confirmation before destructive ops (Clean, solution Remove) McpLogging = true, // MCP log notifications sent to client during key operations (build, test, publish, restore, package add/update) diff --git a/README.md b/README.md index d1e301e..e8bf8fc 100644 --- a/README.md +++ b/README.md @@ -1096,7 +1096,7 @@ Key files to start with: - 📖 [Concurrency Safety](doc/concurrency.md) - Parallel execution guidance for AI orchestrators - 📖 [Testing](doc/testing.md) - How to run tests (including opt-in interactive tests) - 📖 [Model Context Protocol](https://modelcontextprotocol.io/) - Official MCP specification -- 📖 [MCP C# SDK Docs](https://modelcontextprotocol.github.io/csharp-sdk/) - SDK documentation +- 📖 [MCP C# SDK Docs](https://csharp.sdk.modelcontextprotocol.io/) - SDK documentation ## Interoperability