Skip to content

Commit 14b184e

Browse files
Remove McpServerConfig and have McpClientFactory accept IClientTransport instances directly.
1 parent 8bd9ee8 commit 14b184e

26 files changed

+172
-463
lines changed

samples/ChatWithTools/Program.cs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,13 @@
66
// Connect to an MCP server
77
Console.WriteLine("Connecting client to MCP 'everything' server");
88
var mcpClient = await McpClientFactory.CreateAsync(
9-
new()
9+
new StdioClientTransport(new()
1010
{
1111
Id = "everything",
1212
Name = "Everything",
13-
TransportType = TransportTypes.StdIo,
14-
TransportOptions = new()
15-
{
16-
["command"] = "npx", ["arguments"] = "-y @modelcontextprotocol/server-everything",
17-
}
18-
});
13+
Command = "npx",
14+
Arguments = "-y --verbose @modelcontextprotocol/server-everything",
15+
}));
1916

2017
// Get all available tools
2118
Console.WriteLine("Tools available:");

samples/QuickstartClient/Program.cs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,13 @@
1313

1414
var (command, arguments) = GetCommandAndArguments(args);
1515

16-
await using var mcpClient = await McpClientFactory.CreateAsync(new()
16+
await using var mcpClient = await McpClientFactory.CreateAsync(new StdioClientTransport(new()
1717
{
1818
Id = "demo-server",
1919
Name = "Demo Server",
20-
TransportType = TransportTypes.StdIo,
21-
TransportOptions = new()
22-
{
23-
["command"] = command,
24-
["arguments"] = arguments,
25-
}
26-
});
20+
Command = command,
21+
Arguments = arguments,
22+
}));
2723

2824
var tools = await mcpClient.ListToolsAsync();
2925
foreach (var tool in tools)

src/ModelContextProtocol/Client/McpClient.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,16 @@ internal sealed class McpClient : McpEndpoint, IMcpClient
3333
/// </summary>
3434
/// <param name="clientTransport">The transport to use for communication with the server.</param>
3535
/// <param name="options">Options for the client, defining protocol version and capabilities.</param>
36-
/// <param name="serverConfig">The server configuration.</param>
3736
/// <param name="loggerFactory">The logger factory.</param>
38-
public McpClient(IClientTransport clientTransport, McpClientOptions? options, McpServerConfig serverConfig, ILoggerFactory? loggerFactory)
37+
public McpClient(IClientTransport clientTransport, McpClientOptions? options, ILoggerFactory? loggerFactory)
3938
: base(loggerFactory)
4039
{
4140
options ??= new();
4241

4342
_clientTransport = clientTransport;
4443
_options = options;
4544

46-
EndpointName = $"Client ({serverConfig.Id}: {serverConfig.Name})";
45+
EndpointName = clientTransport.EndpointName;
4746

4847
if (options.Capabilities is { } capabilities)
4948
{

src/ModelContextProtocol/Client/McpClientFactory.cs

Lines changed: 8 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -12,42 +12,31 @@ namespace ModelContextProtocol.Client;
1212
public static class McpClientFactory
1313
{
1414
/// <summary>Creates an <see cref="IMcpClient"/>, connecting it to the specified server.</summary>
15-
/// <param name="serverConfig">Configuration for the target server to which the client should connect.</param>
15+
/// <param name="clientTransport">The transport instance used to communicate with the server.</param>
1616
/// <param name="clientOptions">
1717
/// A client configuration object which specifies client capabilities and protocol version.
1818
/// If <see langword="null"/>, details based on the current process will be employed.
1919
/// </param>
20-
/// <param name="createTransportFunc">An optional factory method which returns transport implementations based on a server configuration.</param>
2120
/// <param name="loggerFactory">A logger factory for creating loggers for clients.</param>
2221
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
2322
/// <returns>An <see cref="IMcpClient"/> that's connected to the specified server.</returns>
24-
/// <exception cref="ArgumentNullException"><paramref name="serverConfig"/> is <see langword="null"/>.</exception>
23+
/// <exception cref="ArgumentNullException"><paramref name="clientTransport"/> is <see langword="null"/>.</exception>
2524
/// <exception cref="ArgumentNullException"><paramref name="clientOptions"/> is <see langword="null"/>.</exception>
26-
/// <exception cref="ArgumentException"><paramref name="serverConfig"/> contains invalid information.</exception>
27-
/// <exception cref="InvalidOperationException"><paramref name="createTransportFunc"/> returns an invalid transport.</exception>
2825
public static async Task<IMcpClient> CreateAsync(
29-
McpServerConfig serverConfig,
26+
IClientTransport clientTransport,
3027
McpClientOptions? clientOptions = null,
31-
Func<McpServerConfig, ILoggerFactory?, IClientTransport>? createTransportFunc = null,
3228
ILoggerFactory? loggerFactory = null,
3329
CancellationToken cancellationToken = default)
3430
{
35-
Throw.IfNull(serverConfig);
36-
37-
createTransportFunc ??= CreateTransport;
38-
39-
string endpointName = $"Client ({serverConfig.Id}: {serverConfig.Name})";
31+
Throw.IfNull(clientTransport);
4032

33+
string endpointName = clientTransport.EndpointName;
4134
var logger = loggerFactory?.CreateLogger(typeof(McpClientFactory)) ?? NullLogger.Instance;
4235
logger.CreatingClient(endpointName);
4336

44-
var transport =
45-
createTransportFunc(serverConfig, loggerFactory) ??
46-
throw new InvalidOperationException($"{nameof(createTransportFunc)} returned a null transport.");
47-
4837
try
4938
{
50-
McpClient client = new(transport, clientOptions, serverConfig, loggerFactory);
39+
McpClient client = new(clientTransport, clientOptions, loggerFactory);
5140
try
5241
{
5342
await client.ConnectAsync(cancellationToken).ConfigureAwait(false);
@@ -62,78 +51,15 @@ public static async Task<IMcpClient> CreateAsync(
6251
}
6352
catch
6453
{
65-
if (transport is IAsyncDisposable asyncDisposableTransport)
54+
if (clientTransport is IAsyncDisposable asyncDisposableTransport)
6655
{
6756
await asyncDisposableTransport.DisposeAsync().ConfigureAwait(false);
6857
}
69-
else if (transport is IDisposable disposableTransport)
58+
else if (clientTransport is IDisposable disposableTransport)
7059
{
7160
disposableTransport.Dispose();
7261
}
7362
throw;
7463
}
7564
}
76-
77-
private static IClientTransport CreateTransport(McpServerConfig serverConfig, ILoggerFactory? loggerFactory)
78-
{
79-
if (string.Equals(serverConfig.TransportType, TransportTypes.StdIo, StringComparison.OrdinalIgnoreCase))
80-
{
81-
string? command = serverConfig.TransportOptions?.GetValueOrDefault("command");
82-
if (string.IsNullOrWhiteSpace(command))
83-
{
84-
command = serverConfig.Location;
85-
if (string.IsNullOrWhiteSpace(command))
86-
{
87-
throw new ArgumentException("Command is required for stdio transport.", nameof(serverConfig));
88-
}
89-
}
90-
91-
string? arguments = serverConfig.TransportOptions?.GetValueOrDefault("arguments");
92-
93-
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
94-
serverConfig.TransportType.Equals(TransportTypes.StdIo, StringComparison.OrdinalIgnoreCase) &&
95-
!string.IsNullOrEmpty(command) &&
96-
!string.Equals(Path.GetFileName(command), "cmd.exe", StringComparison.OrdinalIgnoreCase))
97-
{
98-
// On Windows, for stdio, we need to wrap non-shell commands with cmd.exe /c {command} (usually npx or uvicorn).
99-
// The stdio transport will not work correctly if the command is not run in a shell.
100-
arguments = string.IsNullOrWhiteSpace(arguments) ?
101-
$"/c {command}" :
102-
$"/c {command} {arguments}";
103-
command = "cmd.exe";
104-
}
105-
106-
return new StdioClientTransport(new StdioClientTransportOptions
107-
{
108-
Command = command!,
109-
Arguments = arguments,
110-
WorkingDirectory = serverConfig.TransportOptions?.GetValueOrDefault("workingDirectory"),
111-
EnvironmentVariables = serverConfig.TransportOptions?
112-
.Where(kv => kv.Key.StartsWith("env:", StringComparison.Ordinal))
113-
.ToDictionary(kv => kv.Key.Substring("env:".Length), kv => kv.Value),
114-
ShutdownTimeout = TimeSpan.TryParse(serverConfig.TransportOptions?.GetValueOrDefault("shutdownTimeout"), CultureInfo.InvariantCulture, out var timespan) ? timespan : StdioClientTransportOptions.DefaultShutdownTimeout
115-
}, serverConfig, loggerFactory);
116-
}
117-
118-
if (string.Equals(serverConfig.TransportType, TransportTypes.Sse, StringComparison.OrdinalIgnoreCase) ||
119-
string.Equals(serverConfig.TransportType, "http", StringComparison.OrdinalIgnoreCase))
120-
{
121-
return new SseClientTransport(new SseClientTransportOptions
122-
{
123-
ConnectionTimeout = TimeSpan.FromSeconds(ParseInt32OrDefault(serverConfig.TransportOptions, "connectionTimeout", 30)),
124-
MaxReconnectAttempts = ParseInt32OrDefault(serverConfig.TransportOptions, "maxReconnectAttempts", 3),
125-
ReconnectDelay = TimeSpan.FromSeconds(ParseInt32OrDefault(serverConfig.TransportOptions, "reconnectDelay", 5)),
126-
AdditionalHeaders = serverConfig.TransportOptions?
127-
.Where(kv => kv.Key.StartsWith("header.", StringComparison.Ordinal))
128-
.ToDictionary(kv => kv.Key.Substring("header.".Length), kv => kv.Value)
129-
}, serverConfig, loggerFactory);
130-
131-
static int ParseInt32OrDefault(Dictionary<string, string>? options, string key, int defaultValue) =>
132-
options?.TryGetValue(key, out var value) is not true ? defaultValue :
133-
int.TryParse(value, out var result) ? result :
134-
throw new ArgumentException($"Invalid value '{value}' for option '{key}' in transport options.", nameof(serverConfig));
135-
}
136-
137-
throw new ArgumentException($"Unsupported transport type '{serverConfig.TransportType}'.", nameof(serverConfig));
138-
}
13965
}

src/ModelContextProtocol/Configuration/McpServerConfig.cs

Lines changed: 0 additions & 34 deletions
This file was deleted.

src/ModelContextProtocol/ModelContextProtocol.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<PackageId>ModelContextProtocol</PackageId>
88
<Description>.NET SDK for the Model Context Protocol (MCP)</Description>
99
<PackageReadmeFile>README.md</PackageReadmeFile>
10+
<LangVersion>preview</LangVersion>
1011
</PropertyGroup>
1112

1213
<PropertyGroup Condition="'$(TargetFramework)' != 'netstandard2.0'">

src/ModelContextProtocol/Protocol/Transport/IClientTransport.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55
/// </summary>
66
public interface IClientTransport
77
{
8+
/// <summary>
9+
/// Gets a description of the endpoint the client is connecting to.
10+
/// </summary>
11+
string EndpointName { get; }
12+
813
/// <summary>
914
/// Asynchronously establishes a transport session with an MCP server and returns an interface for the duplex JSON-RPC message stream.
1015
/// </summary>

src/ModelContextProtocol/Protocol/Transport/SseClientSessionTransport.cs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,29 +23,25 @@ internal sealed class SseClientSessionTransport : TransportBase
2323
private readonly CancellationTokenSource _connectionCts;
2424
private Task? _receiveTask;
2525
private readonly ILogger _logger;
26-
private readonly McpServerConfig _serverConfig;
2726
private readonly TaskCompletionSource<bool> _connectionEstablished;
2827

29-
private string EndpointName => $"Client (SSE) for ({_serverConfig.Id}: {_serverConfig.Name})";
28+
private string EndpointName => $"Client (SSE) for ({_options.Id}: {_options.Name})";
3029

3130
/// <summary>
3231
/// SSE transport for client endpoints. Unlike stdio it does not launch a process, but connects to an existing server.
3332
/// The HTTP server can be local or remote, and must support the SSE protocol.
3433
/// </summary>
3534
/// <param name="transportOptions">Configuration options for the transport.</param>
36-
/// <param name="serverConfig">The configuration object indicating which server to connect to.</param>
3735
/// <param name="httpClient">The HTTP client instance used for requests.</param>
3836
/// <param name="loggerFactory">Logger factory for creating loggers.</param>
39-
public SseClientSessionTransport(SseClientTransportOptions transportOptions, McpServerConfig serverConfig, HttpClient httpClient, ILoggerFactory? loggerFactory)
37+
public SseClientSessionTransport(SseClientTransportOptions transportOptions, HttpClient httpClient, ILoggerFactory? loggerFactory)
4038
: base(loggerFactory)
4139
{
4240
Throw.IfNull(transportOptions);
43-
Throw.IfNull(serverConfig);
4441
Throw.IfNull(httpClient);
4542

4643
_options = transportOptions;
47-
_serverConfig = serverConfig;
48-
_sseEndpoint = new Uri(serverConfig.Location!);
44+
_sseEndpoint = transportOptions.Endpoint;
4945
_httpClient = httpClient;
5046
_connectionCts = new CancellationTokenSource();
5147
_logger = (ILogger?)loggerFactory?.CreateLogger<SseClientTransport>() ?? NullLogger.Instance;

src/ModelContextProtocol/Protocol/Transport/SseClientTransport.cs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ namespace ModelContextProtocol.Protocol.Transport;
99
public sealed class SseClientTransport : IClientTransport, IAsyncDisposable
1010
{
1111
private readonly SseClientTransportOptions _options;
12-
private readonly McpServerConfig _serverConfig;
1312
private readonly HttpClient _httpClient;
1413
private readonly ILoggerFactory? _loggerFactory;
1514
private readonly bool _ownsHttpClient;
@@ -19,10 +18,9 @@ public sealed class SseClientTransport : IClientTransport, IAsyncDisposable
1918
/// The HTTP server can be local or remote, and must support the SSE protocol.
2019
/// </summary>
2120
/// <param name="transportOptions">Configuration options for the transport.</param>
22-
/// <param name="serverConfig">The configuration object indicating which server to connect to.</param>
2321
/// <param name="loggerFactory">Logger factory for creating loggers.</param>
24-
public SseClientTransport(SseClientTransportOptions transportOptions, McpServerConfig serverConfig, ILoggerFactory? loggerFactory)
25-
: this(transportOptions, serverConfig, new HttpClient(), loggerFactory, true)
22+
public SseClientTransport(SseClientTransportOptions transportOptions, ILoggerFactory? loggerFactory = null)
23+
: this(transportOptions, new HttpClient(), loggerFactory, true)
2624
{
2725
}
2826

@@ -31,27 +29,27 @@ public SseClientTransport(SseClientTransportOptions transportOptions, McpServerC
3129
/// The HTTP server can be local or remote, and must support the SSE protocol.
3230
/// </summary>
3331
/// <param name="transportOptions">Configuration options for the transport.</param>
34-
/// <param name="serverConfig">The configuration object indicating which server to connect to.</param>
3532
/// <param name="httpClient">The HTTP client instance used for requests.</param>
3633
/// <param name="loggerFactory">Logger factory for creating loggers.</param>
3734
/// <param name="ownsHttpClient">True to dispose HTTP client on close connection.</param>
38-
public SseClientTransport(SseClientTransportOptions transportOptions, McpServerConfig serverConfig, HttpClient httpClient, ILoggerFactory? loggerFactory, bool ownsHttpClient = false)
35+
public SseClientTransport(SseClientTransportOptions transportOptions, HttpClient httpClient, ILoggerFactory? loggerFactory = null, bool ownsHttpClient = false)
3936
{
4037
Throw.IfNull(transportOptions);
41-
Throw.IfNull(serverConfig);
4238
Throw.IfNull(httpClient);
4339

4440
_options = transportOptions;
45-
_serverConfig = serverConfig;
4641
_httpClient = httpClient;
4742
_loggerFactory = loggerFactory;
4843
_ownsHttpClient = ownsHttpClient;
4944
}
5045

46+
/// <inheritdoc />
47+
public string EndpointName => $"Client (SSE) for ({_options.Id}: {_options.Name})";
48+
5149
/// <inheritdoc />
5250
public async Task<ITransport> ConnectAsync(CancellationToken cancellationToken = default)
5351
{
54-
var sessionTransport = new SseClientSessionTransport(_options, _serverConfig, _httpClient, _loggerFactory);
52+
var sessionTransport = new SseClientSessionTransport(_options, _httpClient, _loggerFactory);
5553

5654
try
5755
{

src/ModelContextProtocol/Protocol/Transport/SseClientTransportOptions.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,41 @@
55
/// </summary>
66
public record SseClientTransportOptions
77
{
8+
/// <summary>
9+
/// Unique identifier for this server configuration.
10+
/// </summary>
11+
public required string Id { get; init; }
12+
13+
/// <summary>
14+
/// Display name for the server.
15+
/// </summary>
16+
public required string Name { get; init; }
17+
18+
/// <summary>
19+
/// The base address of the server for SSE connections.
20+
/// </summary>
21+
public required Uri Endpoint
22+
{
23+
get;
24+
init
25+
{
26+
if (value is null)
27+
{
28+
throw new ArgumentNullException(nameof(value), "Endpoint cannot be null.");
29+
}
30+
if (!value.IsAbsoluteUri)
31+
{
32+
throw new ArgumentException("Endpoint must be an absolute URI.", nameof(value));
33+
}
34+
if (value.Scheme != Uri.UriSchemeHttp && value.Scheme != Uri.UriSchemeHttps)
35+
{
36+
throw new ArgumentException("Endpoint must use HTTP or HTTPS scheme.", nameof(value));
37+
}
38+
39+
field = value;
40+
}
41+
}
42+
843
/// <summary>
944
/// Timeout for initial connection and endpoint event.
1045
/// </summary>

0 commit comments

Comments
 (0)