Fix: create per-task DI scope in ExecuteToolAsTaskAsync to prevent ObjectDisposedException (#1433)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Co-authored-by: Stephen Toub <stoub@microsoft.com>
This commit is contained in:
Copilot 2026-03-27 15:41:53 -04:00 • committed by GitHub
commit cf63f0e42b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1138,6 +1138,19 @@ internal sealed partial class McpServerImpl : McpServer
// Execute the tool asynchronously in the background
_ = Task.Run(async () =>
{
// When per-request service scoping is enabled, InvokeHandlerAsync creates a new
// IServiceScope and disposes it once the handler returns. Since ExecuteToolAsTaskAsync
// returns immediately (before the tool runs), the scope is disposed before the tool
// gets a chance to resolve any DI services. Create a fresh scope here, tied to this
// background task's lifetime, so the tool's DI resolution uses a live provider.
var taskScope = _servicesScopePerRequest
? Services?.GetService<IServiceScopeFactory>()?.CreateAsyncScope()
: null;
if (taskScope is not null)
{
request.Services = taskScope.Value.ServiceProvider;
}
// Set up the task execution context for automatic input_required status tracking
TaskExecutionContext.Current = new TaskExecutionContext
{
@ -1233,6 +1246,12 @@ internal sealed partial class McpServerImpl : McpServer
// Clean up task cancellation tracking
_taskCancellationTokenProvider!.Complete(mcpTask.TaskId);
// Dispose the per-task service scope (if one was created)
if (taskScope is not null)
{
await taskScope.Value.DisposeAsync().ConfigureAwait(false);
}
}
}, CancellationToken.None);