Refactor transports to help enable graceful shutdown (#142)

- This also paves the way for better multi-session support
- We should definitely rethink names for the transport API
- For now, I kept the names similar as possible, so we can
  focus on the API shape

Co-authored-by: Stephen Toub <stoub@microsoft.com>
This commit is contained in:
Stephen Halter 2025-03-30 17:10:14 -07:00 • committed by GitHub
commit 2ab9db0878
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 2015 additions and 1847 deletions

View file

@ -1,31 +0,0 @@
using ModelContextProtocol.Server;
using ModelContextProtocol.Utils;
using Microsoft.Extensions.Hosting;
namespace ModelContextProtocol.Hosting;
/// <summary>
/// Hosted service for the MCP server.
/// </summary>
public class McpServerHostedService : BackgroundService
{
private readonly IMcpServer _server;
/// <summary>
/// Creates a new instance of the McpServerHostedService.
/// </summary>
/// <param name="server">The MCP server instance</param>
/// <exception cref="ArgumentNullException"></exception>
public McpServerHostedService(IMcpServer server)
{
Throw.IfNull(server);
_server = server;
}
/// <inheritdoc />
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await _server.StartAsync(stoppingToken).ConfigureAwait(false);
}
}

View file

@ -0,0 +1,43 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using ModelContextProtocol.Protocol.Transport;
using ModelContextProtocol.Server;
namespace ModelContextProtocol.Hosting;
/// <summary>
/// Hosted service for a multi-session (i.e. HTTP) MCP server.
/// </summary>
internal sealed class McpServerMultiSessionHostedService : BackgroundService
{
private readonly IServerTransport _serverTransport;
private readonly McpServerOptions _serverOptions;
private readonly ILoggerFactory _loggerFactory;
private readonly IServiceProvider _serviceProvider;
public McpServerMultiSessionHostedService(
IServerTransport serverTransport,
IOptions<McpServerOptions> serverOptions,
ILoggerFactory loggerFactory,
IServiceProvider serviceProvider)
{
_serverTransport = serverTransport;
_serverOptions = serverOptions.Value;
_loggerFactory = loggerFactory;
_serviceProvider = serviceProvider;
}
/// <inheritdoc />
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (await AcceptSessionAsync(stoppingToken).ConfigureAwait(false) is { } server)
{
// TODO: Track all running sessions and wait for all sessions to complete for graceful shutdown.
_ = server.RunAsync(stoppingToken);
}
}
private Task<IMcpServer> AcceptSessionAsync(CancellationToken cancellationToken)
=> McpServerFactory.AcceptAsync(_serverTransport, _serverOptions, _loggerFactory, _serviceProvider, cancellationToken);
}

View file

@ -0,0 +1,13 @@
using Microsoft.Extensions.Hosting;
using ModelContextProtocol.Server;
namespace ModelContextProtocol.Hosting;
/// <summary>
/// Hosted service for a single-session (i.e stdio) MCP server.
/// </summary>
internal sealed class McpServerSingleSessionHostedService(IMcpServer session) : BackgroundService
{
/// <inheritdoc />
protected override Task ExecuteAsync(CancellationToken stoppingToken) => session.RunAsync(stoppingToken);
}