Skip to content

ObjectDisposedException - IServiceProvider disposed while tool handler execution. #1269

@r-Larch

Description

@r-Larch

Describe the bug

Sometimes I get ObjectDisposedException from DI services like the IServiceProvider itself or DbContext
inside the tool call handler.

It looks like OpenAI closes the connection for too-long-running tools,
and instead of canceling the CancellationToken, the service scope gets disposed.
Or the scope gets disposed of without waiting for the handler to cancel cleanly.

To Reproduce
Steps to reproduce the behavior:

  1. Setup: Stateless, Http Transport
  2. Call a long-running tool
  3. Close the connection to simulate a read-timeout.

Note

I use it with OpenAI
OpenAI requires statelessness and closes the connection after some timeout.

services.AddMcpServer(options => options.ScopeRequests = true)
    .WithHttpTransport(options => {
        options.Stateless = true;
    })
    .AddAuthorizationFilters()
    .WithToolsFromAssemblyFixed(toolAssembly)
    .AddCallToolFilter(next => (context, token) => ToolCallErrorHandler(next, context, token));

// ...

app.MapMcp("/mcp").RequireAuthorization("mcp");

static async ValueTask<CallToolResult> ToolCallErrorHandler(McpRequestHandler<CallToolRequestParams, CallToolResult> next, RequestContext<CallToolRequestParams> context, CancellationToken cancellationToken)
{
    try {
        return await next(context, cancellationToken);
    }
    catch (Exception ex) {
        if (x is not OperationCanceledException) {
            // Error here inside `GetRequiredService` - the `ex` error is also an ObjectDisposedException with origin in DbContext.SaveChanges()
            context.Services!.GetRequiredService<ILogger<McpFeatureAttribute>>()
                  .LogCritical(ex, "MCP Error {tool}: {message}", context.Params?.Name, ex.Message);
        }

        return new CallToolResult {
            Content = [new TextContentBlock { Text = $"Error: {ex.Message}" }],
            IsError = true
        };
    }
}

Expected behavior

I expect the handler to either complete or cancel through CancellationToken before the service scope is disposed.

Logs

ERROR  ModelContextProtocol.Server.McpServer  "{ToolName}" threw an unhandled exception.
System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'IServiceProvider'.
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ThrowHelper.ThrowObjectDisposedException()
   at ModelContextProtocol.Server.RequestServiceProvider`1.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at Nomos.Web.Features.Mcp.Abstructions.McpFeatureInstaller.ToolCallErrorHandler(McpRequestHandler`2 next, RequestContext`1 context, CancellationToken cancellationToken) in /src/Nomos.Web/Features/Mcp/Abstructions/McpFeatureInstaller.cs:line 42
   at ModelContextProtocol.AspNetCore.AuthorizationFilterSetup.<>c__DisplayClass7_0.<<ConfigureCallToolFilter>b__1>d.MoveNext()
--- End of stack trace from previous location ---
   at ModelContextProtocol.Server.McpServerImpl.<>c__DisplayClass51_2.<<ConfigureTools>b__5>d.MoveNext()

Additional context
Add any other context about the problem here.

Metadata

Metadata

Labels

bugSomething isn't workingready for workHas enough information to start

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions