Skip to content
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
2 changes: 1 addition & 1 deletion DotNetMcp.Tests/DotNetMcp.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.5" />
<PackageReference Include="Microsoft.Extensions.TimeProvider.Testing" Version="10.4.0" />
<PackageReference Include="Microsoft.Testing.Extensions.CodeCoverage" Version="18.5.2" />
<PackageReference Include="ModelContextProtocol" Version="1.1.0" />
<PackageReference Include="ModelContextProtocol" Version="1.2.0" />
<PackageReference Include="xunit.v3.mtp-v2" Version="3.2.2" />
</ItemGroup>

Expand Down
26 changes: 11 additions & 15 deletions DotNetMcp.Tests/Server/McpConformanceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -741,23 +740,20 @@ 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);
Assert.Equal(ModelContextProtocol.Protocol.ToolTaskSupport.Optional, execution.TaskSupport);
}

[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<string, object?>
Expand Down
11 changes: 6 additions & 5 deletions DotNetMcp.Tests/Server/ServerCapabilitiesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using DotNetMcp;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using ModelContextProtocol;
using Xunit;

namespace DotNetMcp.Tests;
Expand All @@ -16,7 +17,7 @@ public ServerCapabilitiesTests()
{
_logger = NullLogger<DotNetCliTools>.Instance;
_concurrencyManager = new ConcurrencyManager();
_tools = new DotNetCliTools(_logger, _concurrencyManager, new ProcessSessionManager());
_tools = new DotNetCliTools(_logger, _concurrencyManager, new ProcessSessionManager(), taskStore: new InMemoryMcpTaskStore());
}

[Fact]
Expand Down Expand Up @@ -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();
Expand All @@ -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]
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion DotNetMcp/DotNetMcp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.5" />
<PackageReference Include="Microsoft.TemplateEngine.Abstractions" Version="10.0.201" />
<PackageReference Include="Microsoft.TemplateEngine.Edge" Version="10.0.201" />
<PackageReference Include="ModelContextProtocol" Version="1.1.0" />
<PackageReference Include="ModelContextProtocol" Version="1.2.0" />
<PackageReference Include="MinVer" Version="7.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
8 changes: 2 additions & 6 deletions DotNetMcp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,8 @@
// Register ProcessSessionManager as a singleton
builder.Services.AddSingleton<ProcessSessionManager>();

// 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<IMcpTaskStore, InMemoryMcpTaskStore>();
// Register InMemoryMcpTaskStore for async task support (MCP SDK v1.2.0 fixed #1430).
builder.Services.AddSingleton<IMcpTaskStore, InMemoryMcpTaskStore>();

// Register ToolMetricsAccumulator for in-memory telemetry collection.
// The accumulator is also captured by the telemetry filter added below.
Expand Down
2 changes: 1 addition & 1 deletion DotNetMcp/Tools/Cli/DotNetCliTools.Misc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public async partial Task<CallToolResult> 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)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading