@@ -32,8 +32,8 @@ internal sealed partial class McpServerImpl : McpServer, IMcpServerLifetimeFeatu
3232 private readonly string [ ] _perRequestMetadataProtocolVersions ;
3333 private readonly SemaphoreSlim _disposeLock = new ( 1 , 1 ) ;
3434 private readonly CancellationTokenSource _serverLifetimeCts = new ( ) ;
35- private readonly object _backgroundTasksLock = new ( ) ;
36- private readonly ConcurrentDictionary < Task , byte > _backgroundTasks = new ( ) ;
35+ private readonly object _serverLifetimeRegistrationsLock = new ( ) ;
36+ private readonly HashSet < ServerLifetimeRegistration > _serverLifetimeRegistrations = [ ] ;
3737 private readonly ConcurrentDictionary < string , MrtrContinuation > _mrtrContinuations = new ( ) ;
3838 private readonly ConcurrentDictionary < RequestId , MrtrContext > _mrtrContextsByRequestId = new ( ) ;
3939 private static readonly string [ ] s_perRequestMetadataKeys =
@@ -58,7 +58,7 @@ internal sealed partial class McpServerImpl : McpServer, IMcpServerLifetimeFeatu
5858 private int _started ;
5959
6060 private bool _disposed ;
61- private bool _backgroundTaskRegistrationClosed ;
61+ private bool _serverLifetimeRegistrationClosed ;
6262
6363 /// <summary>Holds a boxed <see cref="LoggingLevel"/> value for the server.</summary>
6464 /// <remarks>
@@ -508,36 +508,40 @@ public override Task SendMessageAsync(JsonRpcMessage message, CancellationToken
508508 public override IAsyncDisposable RegisterNotificationHandler ( string method , Func < JsonRpcNotification , CancellationToken , ValueTask > handler )
509509 => _sessionHandler . RegisterNotificationHandler ( method , handler ) ;
510510
511- CancellationToken IMcpServerLifetimeFeature . BackgroundTaskCancellationToken =>
511+ CancellationToken IMcpServerLifetimeFeature . ServerCancellationToken =>
512512 HasStatefulTransport ( ) ? _serverLifetimeCts . Token : CancellationToken . None ;
513513
514- void IMcpServerLifetimeFeature . RegisterBackgroundTask ( Task backgroundTask )
514+ IDisposable IMcpServerLifetimeFeature . RegisterForDisposeAsync ( IAsyncDisposable disposable )
515515 {
516- Throw . IfNull ( backgroundTask ) ;
516+ Throw . IfNull ( disposable ) ;
517517
518518 // Stateless HTTP servers are request-scoped, while Tasks runners intentionally outlive
519519 // the originating request and are governed by tasks/cancel and task-store retention.
520520 if ( ! HasStatefulTransport ( ) )
521521 {
522- return ;
522+ return NoopRegistration . Instance ;
523523 }
524524
525- lock ( _backgroundTasksLock )
525+ var registration = new ServerLifetimeRegistration ( this , disposable ) ;
526+ lock ( _serverLifetimeRegistrationsLock )
526527 {
527- if ( _backgroundTaskRegistrationClosed )
528+ if ( _serverLifetimeRegistrationClosed )
528529 {
529530 throw new ObjectDisposedException ( nameof ( McpServer ) ) ;
530531 }
531532
532- _backgroundTasks . TryAdd ( backgroundTask , 0 ) ;
533+ _serverLifetimeRegistrations . Add ( registration ) ;
533534 }
534535
535- _ = backgroundTask . ContinueWith (
536- static ( task , state ) => ( ( ConcurrentDictionary < Task , byte > ) state ! ) . TryRemove ( task , out _ ) ,
537- _backgroundTasks ,
538- CancellationToken . None ,
539- TaskContinuationOptions . ExecuteSynchronously ,
540- TaskScheduler . Default ) ;
536+ return registration ;
537+ }
538+
539+ private void UnregisterServerLifetime ( ServerLifetimeRegistration registration )
540+ {
541+ lock ( _serverLifetimeRegistrationsLock )
542+ {
543+ _serverLifetimeRegistrations . Remove ( registration ) ;
544+ }
541545 }
542546
543547 /// <inheritdoc/>
@@ -560,11 +564,11 @@ public override async ValueTask DisposeAsync()
560564 _disposables . ForEach ( d => d ( ) ) ;
561565 await _sessionHandler . DisposeAsync ( ) . ConfigureAwait ( false ) ;
562566
563- Task [ ] backgroundTasks ;
564- lock ( _backgroundTasksLock )
567+ ServerLifetimeRegistration [ ] serverLifetimeRegistrations ;
568+ lock ( _serverLifetimeRegistrationsLock )
565569 {
566- _backgroundTaskRegistrationClosed = true ;
567- backgroundTasks = [ .. _backgroundTasks . Keys ] ;
570+ _serverLifetimeRegistrationClosed = true ;
571+ serverLifetimeRegistrations = [ .. _serverLifetimeRegistrations ] ;
568572 }
569573
570574 // Cancel all orphaned MRTR handlers still suspended in continuations (waiting for
@@ -589,9 +593,34 @@ public override async ValueTask DisposeAsync()
589593 await _allMrtrHandlersCompleted . Task . ConfigureAwait ( false ) ;
590594 }
591595
592- if ( backgroundTasks . Length > 0 )
596+ if ( serverLifetimeRegistrations . Length > 0 )
597+ {
598+ await Task . WhenAll (
599+ serverLifetimeRegistrations . Select ( static registration => registration . DisposeResourceAsync ( ) . AsTask ( ) )
600+ ) . ConfigureAwait ( false ) ;
601+ }
602+ }
603+
604+ private sealed class ServerLifetimeRegistration (
605+ McpServerImpl server ,
606+ IAsyncDisposable resource ) : IDisposable
607+ {
608+ private McpServerImpl ? _server = server ;
609+
610+ public ValueTask DisposeResourceAsync ( ) => resource . DisposeAsync ( ) ;
611+
612+ public void Dispose ( )
613+ {
614+ Interlocked . Exchange ( ref _server , null ) ? . UnregisterServerLifetime ( this ) ;
615+ }
616+ }
617+
618+ private sealed class NoopRegistration : IDisposable
619+ {
620+ public static NoopRegistration Instance { get ; } = new ( ) ;
621+
622+ public void Dispose ( )
593623 {
594- await Task . WhenAll ( backgroundTasks ) . ConfigureAwait ( false ) ;
595624 }
596625 }
597626
0 commit comments