forked from modelcontextprotocol/csharp-sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathHttpClientTransport.cs
More file actions
112 lines (98 loc) · 4.83 KB
/
HttpClientTransport.cs
File metadata and controls
112 lines (98 loc) · 4.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Authentication;
using ModelContextProtocol.Protocol;
namespace ModelContextProtocol.Client;
/// <summary>
/// Provides an <see cref="IClientTransport"/> over HTTP using the Server-Sent Events (SSE) or Streamable HTTP protocol.
/// </summary>
/// <remarks>
/// 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 <see cref="StdioClientTransport"/>, this transport connects to an existing server
/// rather than launching a new process.
/// </remarks>
public sealed class HttpClientTransport : IClientTransport, IAsyncDisposable
{
private readonly HttpClientTransportOptions _options;
private readonly McpHttpClient _mcpHttpClient;
private readonly ILoggerFactory? _loggerFactory;
private readonly HttpClient? _ownedHttpClient;
/// <summary>
/// Initializes a new instance of the <see cref="HttpClientTransport"/> class.
/// </summary>
/// <param name="transportOptions">The configuration options for the transport.</param>
/// <param name="loggerFactory">The logger factory for creating loggers used for diagnostic output during transport operations.</param>
public HttpClientTransport(HttpClientTransportOptions transportOptions, ILoggerFactory? loggerFactory = null)
: this(transportOptions, new HttpClient(), loggerFactory, ownsHttpClient: true)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="HttpClientTransport"/> class with a provided HTTP client.
/// </summary>
/// <param name="transportOptions">The configuration options for the transport.</param>
/// <param name="httpClient">The HTTP client instance used for requests.</param>
/// <param name="loggerFactory">The logger factory for creating loggers used for diagnostic output during transport operations.</param>
/// <param name="ownsHttpClient">
/// <see langword="true"/> to dispose of <paramref name="httpClient"/> when the transport is disposed;
/// <see langword="false"/> if the caller is retaining ownership of the <paramref name="httpClient"/>'s lifetime.
/// </param>
/// <exception cref="ArgumentNullException"><paramref name="transportOptions"/> or <paramref name="httpClient"/> is <see langword="null"/>.</exception>
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;
}
}
/// <inheritdoc />
public string Name { get; }
/// <inheritdoc />
public async Task<ITransport> 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<ITransport> 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;
}
}
/// <inheritdoc />
public ValueTask DisposeAsync()
{
_ownedHttpClient?.Dispose();
return default;
}
}