66using ModelContextProtocol . Shared ;
77using ModelContextProtocol . Utils . Json ;
88using Microsoft . Extensions . Logging ;
9- using Microsoft . Extensions . Logging . Abstractions ;
109using System . Text . Json ;
1110
1211namespace ModelContextProtocol . Client ;
@@ -17,7 +16,7 @@ internal sealed class McpClient : McpJsonRpcEndpoint, IMcpClient
1716 private readonly McpClientOptions _options ;
1817 private readonly IClientTransport _clientTransport ;
1918
20- private volatile bool _isInitializing ;
19+ private int _connecting ;
2120
2221 /// <summary>
2322 /// Initializes a new instance of the <see cref="McpClient"/> class.
@@ -74,92 +73,75 @@ public McpClient(IClientTransport transport, McpClientOptions options, McpServer
7473 /// <inheritdoc/>
7574 public async Task ConnectAsync ( CancellationToken cancellationToken = default )
7675 {
77- if ( IsInitialized )
78- {
79- _logger . ClientAlreadyInitialized ( EndpointName ) ;
80- return ;
81- }
82-
83- if ( _isInitializing )
76+ if ( Interlocked . Exchange ( ref _connecting , 1 ) != 0 )
8477 {
8578 _logger . ClientAlreadyInitializing ( EndpointName ) ;
86- throw new InvalidOperationException ( "Client is already initializing " ) ;
79+ throw new InvalidOperationException ( "Client is already in use. " ) ;
8780 }
8881
89- _isInitializing = true ;
82+ CancellationTokenSource = CancellationTokenSource . CreateLinkedTokenSource ( cancellationToken ) ;
83+ cancellationToken = CancellationTokenSource . Token ;
84+
9085 try
9186 {
92- CancellationTokenSource = CancellationTokenSource . CreateLinkedTokenSource ( cancellationToken ) ;
93-
9487 // Connect transport
95- await _clientTransport . ConnectAsync ( CancellationTokenSource . Token ) . ConfigureAwait ( false ) ;
88+ await _clientTransport . ConnectAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
9689
9790 // Start processing messages
98- MessageProcessingTask = ProcessMessagesAsync ( CancellationTokenSource . Token ) ;
91+ MessageProcessingTask = ProcessMessagesAsync ( cancellationToken ) ;
9992
10093 // Perform initialization sequence
101- await InitializeAsync ( CancellationTokenSource . Token ) . ConfigureAwait ( false ) ;
94+ using var initializationCts = CancellationTokenSource . CreateLinkedTokenSource ( cancellationToken ) ;
95+ initializationCts . CancelAfter ( _options . InitializationTimeout ) ;
10296
103- IsInitialized = true ;
97+ try
98+ {
99+ // Send initialize request
100+ var initializeResponse = await SendRequestAsync < InitializeResult > (
101+ new JsonRpcRequest
102+ {
103+ Method = "initialize" ,
104+ Params = new
105+ {
106+ protocolVersion = _options . ProtocolVersion ,
107+ capabilities = _options . Capabilities ?? new ClientCapabilities ( ) ,
108+ clientInfo = _options . ClientInfo
109+ }
110+ } ,
111+ initializationCts . Token ) . ConfigureAwait ( false ) ;
112+
113+ // Store server information
114+ _logger . ServerCapabilitiesReceived ( EndpointName ,
115+ capabilities : JsonSerializer . Serialize ( initializeResponse . Capabilities , McpJsonUtilities . JsonContext . Default . ServerCapabilities ) ,
116+ serverInfo : JsonSerializer . Serialize ( initializeResponse . ServerInfo , McpJsonUtilities . JsonContext . Default . Implementation ) ) ;
117+
118+ ServerCapabilities = initializeResponse . Capabilities ;
119+ ServerInfo = initializeResponse . ServerInfo ;
120+ ServerInstructions = initializeResponse . Instructions ;
121+
122+ // Validate protocol version
123+ if ( initializeResponse . ProtocolVersion != _options . ProtocolVersion )
124+ {
125+ _logger . ServerProtocolVersionMismatch ( EndpointName , _options . ProtocolVersion , initializeResponse . ProtocolVersion ) ;
126+ throw new McpClientException ( $ "Server protocol version mismatch. Expected { _options . ProtocolVersion } , got { initializeResponse . ProtocolVersion } ") ;
127+ }
128+
129+ // Send initialized notification
130+ await SendMessageAsync (
131+ new JsonRpcNotification { Method = "notifications/initialized" } ,
132+ initializationCts . Token ) . ConfigureAwait ( false ) ;
133+ }
134+ catch ( OperationCanceledException ) when ( initializationCts . IsCancellationRequested )
135+ {
136+ _logger . ClientInitializationTimeout ( EndpointName ) ;
137+ throw new McpClientException ( "Initialization timed out" ) ;
138+ }
104139 }
105140 catch ( Exception e )
106141 {
107142 _logger . ClientInitializationError ( EndpointName , e ) ;
108143 await CleanupAsync ( ) . ConfigureAwait ( false ) ;
109144 throw ;
110145 }
111- finally
112- {
113- _isInitializing = false ;
114- }
115- }
116-
117- private async Task InitializeAsync ( CancellationToken cancellationToken )
118- {
119- using var initializationCts = CancellationTokenSource . CreateLinkedTokenSource ( cancellationToken ) ;
120- initializationCts . CancelAfter ( _options . InitializationTimeout ) ;
121-
122- try
123- {
124- // Send initialize request
125- var initializeResponse = await SendRequestAsync < InitializeResult > (
126- new JsonRpcRequest
127- {
128- Method = "initialize" ,
129- Params = new
130- {
131- protocolVersion = _options . ProtocolVersion ,
132- capabilities = _options . Capabilities ?? new ClientCapabilities ( ) ,
133- clientInfo = _options . ClientInfo
134- }
135- } ,
136- initializationCts . Token ) . ConfigureAwait ( false ) ;
137-
138- // Store server information
139- _logger . ServerCapabilitiesReceived ( EndpointName ,
140- capabilities : JsonSerializer . Serialize ( initializeResponse . Capabilities , McpJsonUtilities . JsonContext . Default . ServerCapabilities ) ,
141- serverInfo : JsonSerializer . Serialize ( initializeResponse . ServerInfo , McpJsonUtilities . JsonContext . Default . Implementation ) ) ;
142-
143- ServerCapabilities = initializeResponse . Capabilities ;
144- ServerInfo = initializeResponse . ServerInfo ;
145- ServerInstructions = initializeResponse . Instructions ;
146-
147- // Validate protocol version
148- if ( initializeResponse . ProtocolVersion != _options . ProtocolVersion )
149- {
150- _logger . ServerProtocolVersionMismatch ( EndpointName , _options . ProtocolVersion , initializeResponse . ProtocolVersion ) ;
151- throw new McpClientException ( $ "Server protocol version mismatch. Expected { _options . ProtocolVersion } , got { initializeResponse . ProtocolVersion } ") ;
152- }
153-
154- // Send initialized notification
155- await SendMessageAsync (
156- new JsonRpcNotification { Method = "notifications/initialized" } ,
157- initializationCts . Token ) . ConfigureAwait ( false ) ;
158- }
159- catch ( OperationCanceledException ) when ( initializationCts . IsCancellationRequested )
160- {
161- _logger . ClientInitializationTimeout ( EndpointName ) ;
162- throw new McpClientException ( "Initialization timed out" ) ;
163- }
164146 }
165147}
0 commit comments