@@ -12,42 +12,31 @@ namespace ModelContextProtocol.Client;
12
12
public static class McpClientFactory
13
13
{
14
14
/// <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>
16
16
/// <param name="clientOptions">
17
17
/// A client configuration object which specifies client capabilities and protocol version.
18
18
/// If <see langword="null"/>, details based on the current process will be employed.
19
19
/// </param>
20
- /// <param name="createTransportFunc">An optional factory method which returns transport implementations based on a server configuration.</param>
21
20
/// <param name="loggerFactory">A logger factory for creating loggers for clients.</param>
22
21
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
23
22
/// <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>
25
24
/// <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>
28
25
public static async Task < IMcpClient > CreateAsync (
29
- McpServerConfig serverConfig ,
26
+ IClientTransport clientTransport ,
30
27
McpClientOptions ? clientOptions = null ,
31
- Func < McpServerConfig , ILoggerFactory ? , IClientTransport > ? createTransportFunc = null ,
32
28
ILoggerFactory ? loggerFactory = null ,
33
29
CancellationToken cancellationToken = default )
34
30
{
35
- Throw . IfNull ( serverConfig ) ;
36
-
37
- createTransportFunc ??= CreateTransport ;
38
-
39
- string endpointName = $ "Client ({ serverConfig . Id } : { serverConfig . Name } )";
31
+ Throw . IfNull ( clientTransport ) ;
40
32
33
+ string endpointName = clientTransport . EndpointName ;
41
34
var logger = loggerFactory ? . CreateLogger ( typeof ( McpClientFactory ) ) ?? NullLogger . Instance ;
42
35
logger . CreatingClient ( endpointName ) ;
43
36
44
- var transport =
45
- createTransportFunc ( serverConfig , loggerFactory ) ??
46
- throw new InvalidOperationException ( $ "{ nameof ( createTransportFunc ) } returned a null transport.") ;
47
-
48
37
try
49
38
{
50
- McpClient client = new ( transport , clientOptions , serverConfig , loggerFactory ) ;
39
+ McpClient client = new ( clientTransport , clientOptions , loggerFactory ) ;
51
40
try
52
41
{
53
42
await client . ConnectAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
@@ -62,78 +51,15 @@ public static async Task<IMcpClient> CreateAsync(
62
51
}
63
52
catch
64
53
{
65
- if ( transport is IAsyncDisposable asyncDisposableTransport )
54
+ if ( clientTransport is IAsyncDisposable asyncDisposableTransport )
66
55
{
67
56
await asyncDisposableTransport . DisposeAsync ( ) . ConfigureAwait ( false ) ;
68
57
}
69
- else if ( transport is IDisposable disposableTransport )
58
+ else if ( clientTransport is IDisposable disposableTransport )
70
59
{
71
60
disposableTransport . Dispose ( ) ;
72
61
}
73
62
throw ;
74
63
}
75
64
}
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
- }
139
65
}
0 commit comments