using Microsoft.Extensions.Logging; using ModelContextProtocol.Authentication; using ModelContextProtocol.Protocol; namespace ModelContextProtocol.Client; /// /// Provides an over HTTP using the Server-Sent Events (SSE) or Streamable HTTP protocol. /// /// /// This transport connects to an MCP server over HTTP using SSE or Streamable HTTP, /// allowing for real-time server-to-client communication with standard HTTP requests. /// Unlike the , this transport connects to an existing server /// rather than launching a new process. /// public sealed class HttpClientTransport : IClientTransport, IAsyncDisposable { private readonly HttpClientTransportOptions _options; private readonly McpHttpClient _mcpHttpClient; private readonly ILoggerFactory? _loggerFactory; private readonly HttpClient? _ownedHttpClient; /// /// Initializes a new instance of the class. /// /// The configuration options for the transport. /// The logger factory for creating loggers used for diagnostic output during transport operations. public HttpClientTransport(HttpClientTransportOptions transportOptions, ILoggerFactory? loggerFactory = null) : this(transportOptions, new HttpClient(), loggerFactory, ownsHttpClient: true) { } /// /// Initializes a new instance of the class with a provided HTTP client. /// /// The configuration options for the transport. /// The HTTP client instance used for requests. /// The logger factory for creating loggers used for diagnostic output during transport operations. /// /// to dispose of when the transport is disposed; /// if the caller is retaining ownership of the 's lifetime. /// /// or is . public HttpClientTransport(HttpClientTransportOptions transportOptions, HttpClient httpClient, ILoggerFactory? loggerFactory = null, bool ownsHttpClient = false) { Throw.IfNull(transportOptions); Throw.IfNull(httpClient); _options = transportOptions; _loggerFactory = loggerFactory; Name = transportOptions.Name ?? transportOptions.Endpoint.ToString(); if (transportOptions.OAuth is { } clientOAuthOptions) { var oAuthProvider = new ClientOAuthProvider(_options.Endpoint, clientOAuthOptions, httpClient, loggerFactory); _mcpHttpClient = new AuthenticatingMcpHttpClient(httpClient, oAuthProvider); } else { _mcpHttpClient = new(httpClient); } if (ownsHttpClient) { _ownedHttpClient = httpClient; } } /// public string Name { get; } /// public async Task ConnectAsync(CancellationToken cancellationToken = default) { if (_options.KnownSessionId is not null && _options.TransportMode == HttpTransportMode.Sse) { throw new InvalidOperationException("SSE transport does not support resuming an existing session."); } return _options.TransportMode switch { HttpTransportMode.AutoDetect => new AutoDetectingClientSessionTransport(Name, _options, _mcpHttpClient, _loggerFactory), HttpTransportMode.StreamableHttp => new StreamableHttpClientSessionTransport(Name, _options, _mcpHttpClient, messageChannel: null, _loggerFactory), HttpTransportMode.Sse => await ConnectSseTransportAsync(cancellationToken).ConfigureAwait(false), _ => throw new InvalidOperationException($"Unsupported transport mode: {_options.TransportMode}"), }; } private async Task ConnectSseTransportAsync(CancellationToken cancellationToken) { var sessionTransport = new SseClientSessionTransport(Name, _options, _mcpHttpClient, messageChannel: null, _loggerFactory); try { await sessionTransport.ConnectAsync(cancellationToken).ConfigureAwait(false); return sessionTransport; } catch { await sessionTransport.DisposeAsync().ConfigureAwait(false); throw; } } /// public ValueTask DisposeAsync() { _ownedHttpClient?.Dispose(); return default; } }