@@ -12,42 +12,31 @@ namespace ModelContextProtocol.Client;
1212public 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}
0 commit comments