Skip to content

Commit 7d77be6

Browse files
authored
Clean up (use of) McpException and McpTransportException (#321)
* Clean up (use of) McpException * Replace McpTransportException with InvalidOperationException * Add cancellation test
1 parent d65ddce commit 7d77be6

27 files changed

+231
-219
lines changed

samples/EverythingServer/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ await ctx.Server.RequestSamplingAsync([
176176
{
177177
if (ctx.Params?.Level is null)
178178
{
179-
throw new McpException("Missing required argument 'level'");
179+
throw new McpException("Missing required argument 'level'", McpErrorCode.InvalidParams);
180180
}
181181

182182
_minimumLoggingLevel = ctx.Params.Level;

src/ModelContextProtocol/Client/McpClient.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,10 +155,10 @@ await SendMessageAsync(
155155
new JsonRpcNotification { Method = NotificationMethods.InitializedNotification },
156156
initializationCts.Token).ConfigureAwait(false);
157157
}
158-
catch (OperationCanceledException oce) when (initializationCts.IsCancellationRequested)
158+
catch (OperationCanceledException oce) when (initializationCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested)
159159
{
160160
LogClientInitializationTimeout(EndpointName);
161-
throw new McpException("Initialization timed out", oce);
161+
throw new TimeoutException("Initialization timed out", oce);
162162
}
163163
}
164164
catch (Exception e)
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
namespace ModelContextProtocol;
2+
3+
/// <summary>
4+
/// Represents standard JSON-RPC error codes as defined in the MCP specification.
5+
/// </summary>
6+
public enum McpErrorCode
7+
{
8+
/// <summary>
9+
/// Indicates that the JSON received could not be parsed.
10+
/// </summary>
11+
/// <remarks>
12+
/// This error occurs when the input contains malformed JSON or incorrect syntax.
13+
/// </remarks>
14+
ParseError = -32700,
15+
16+
/// <summary>
17+
/// Indicates that the JSON payload does not conform to the expected Request object structure.
18+
/// </summary>
19+
/// <remarks>
20+
/// The request is considered invalid if it lacks required fields or fails to follow the JSON-RPC protocol.
21+
/// </remarks>
22+
InvalidRequest = -32600,
23+
24+
/// <summary>
25+
/// Indicates that the requested method does not exist or is not available on the server.
26+
/// </summary>
27+
/// <remarks>
28+
/// This error is returned when the method name specified in the request cannot be found.
29+
/// </remarks>
30+
MethodNotFound = -32601,
31+
32+
/// <summary>
33+
/// Indicates that one or more parameters provided in the request are invalid.
34+
/// </summary>
35+
/// <remarks>
36+
/// This error is returned when the parameters do not match the expected method signature or constraints.
37+
/// This includes cases where required parameters are missing or not understood, such as when a name for
38+
/// a tool or prompt is not recognized.
39+
/// </remarks>
40+
InvalidParams = -32602,
41+
42+
/// <summary>
43+
/// Indicates that an internal error occurred while processing the request.
44+
/// </summary>
45+
/// <remarks>
46+
/// This error is used when the endpoint encounters an unexpected condition that prevents it from fulfilling the request.
47+
/// </remarks>
48+
InternalError = -32603,
49+
}

src/ModelContextProtocol/McpException.cs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
namespace ModelContextProtocol;
22

33
/// <summary>
4-
/// Represents an exception that is thrown when a Model Context Protocol (MCP) error occurs.
4+
/// Represents an exception that is thrown when an Model Context Protocol (MCP) error occurs.
55
/// </summary>
6+
/// <remarks>
7+
/// This exception is used to represent failures to do with protocol-level concerns, such as invalid JSON-RPC requests,
8+
/// invalid parameters, or internal errors. It is not intended to be used for application-level errors.
9+
/// <see cref="Exception.Message"/> or <see cref="ErrorCode"/> from a <see cref="McpException"/> may be
10+
/// propagated to the remote endpoint; sensitive information should not be included. If sensitive details need
11+
/// to be included, a different exception type should be used.
12+
/// </remarks>
613
public class McpException : Exception
714
{
815
/// <summary>
@@ -33,8 +40,8 @@ public McpException(string message, Exception? innerException) : base(message, i
3340
/// Initializes a new instance of the <see cref="McpException"/> class with a specified error message and JSON-RPC error code.
3441
/// </summary>
3542
/// <param name="message">The message that describes the error.</param>
36-
/// <param name="errorCode">A JSON-RPC error code from <see cref="Protocol.Messages.ErrorCodes"/> class.</param>
37-
public McpException(string message, int? errorCode) : this(message, null, errorCode)
43+
/// <param name="errorCode">A <see cref="McpErrorCode"/>.</param>
44+
public McpException(string message, McpErrorCode errorCode) : this(message, null, errorCode)
3845
{
3946
}
4047

@@ -43,18 +50,15 @@ public McpException(string message, int? errorCode) : this(message, null, errorC
4350
/// </summary>
4451
/// <param name="message">The message that describes the error.</param>
4552
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
46-
/// <param name="errorCode">A JSON-RPC error code from <see cref="Protocol.Messages.ErrorCodes"/> class.</param>
47-
public McpException(string message, Exception? innerException, int? errorCode) : base(message, innerException)
53+
/// <param name="errorCode">A <see cref="McpErrorCode"/>.</param>
54+
public McpException(string message, Exception? innerException, McpErrorCode errorCode) : base(message, innerException)
4855
{
4956
ErrorCode = errorCode;
5057
}
5158

5259
/// <summary>
53-
/// Gets the JSON-RPC error code associated with this exception.
60+
/// Gets the error code associated with this exception.
5461
/// </summary>
55-
/// <value>
56-
/// A standard JSON-RPC error code, or <see langword="null"/> if the exception wasn't caused by a JSON-RPC error.
57-
/// </value>
5862
/// <remarks>
5963
/// This property contains a standard JSON-RPC error code as defined in the MCP specification. Common error codes include:
6064
/// <list type="bullet">
@@ -65,5 +69,5 @@ public McpException(string message, Exception? innerException, int? errorCode) :
6569
/// <item><description>-32603: Internal error - Internal JSON-RPC error</description></item>
6670
/// </list>
6771
/// </remarks>
68-
public int? ErrorCode { get; }
72+
public McpErrorCode ErrorCode { get; } = McpErrorCode.InternalError;
6973
}

src/ModelContextProtocol/Protocol/Messages/ErrorCodes.cs

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

src/ModelContextProtocol/Protocol/Transport/IClientTransport.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,6 @@ public interface IClientTransport
4242
/// This method is used by <see cref="McpClientFactory"/> to initialize the connection.
4343
/// </para>
4444
/// </remarks>
45-
/// <exception cref="McpTransportException">The transport connection could not be established.</exception>
45+
/// <exception cref="InvalidOperationException">The transport connection could not be established.</exception>
4646
Task<ITransport> ConnectAsync(CancellationToken cancellationToken = default);
4747
}

src/ModelContextProtocol/Protocol/Transport/McpTransportException.cs

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

src/ModelContextProtocol/Protocol/Transport/SseClientSessionTransport.cs

Lines changed: 13 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,11 @@ public async Task ConnectAsync(CancellationToken cancellationToken = default)
5858

5959
await _connectionEstablished.Task.WaitAsync(_options.ConnectionTimeout, cancellationToken).ConfigureAwait(false);
6060
}
61-
catch (Exception ex) when (ex is not McpTransportException) // propagate transport exceptions
61+
catch (Exception ex)
6262
{
6363
LogTransportConnectFailed(Name, ex);
6464
await CloseAsync().ConfigureAwait(false);
65-
throw new McpTransportException("Failed to connect transport", ex);
65+
throw new InvalidOperationException("Failed to connect transport", ex);
6666
}
6767
}
6868

@@ -110,7 +110,7 @@ public override async Task SendMessageAsync(
110110
else
111111
{
112112
JsonRpcResponse initializeResponse = JsonSerializer.Deserialize(responseContent, McpJsonUtilities.JsonContext.Default.JsonRpcResponse) ??
113-
throw new McpTransportException("Failed to initialize client");
113+
throw new InvalidOperationException("Failed to initialize client");
114114

115115
LogTransportReceivedMessage(Name, messageId);
116116
await WriteMessageAsync(initializeResponse, cancellationToken).ConfigureAwait(false);
@@ -136,7 +136,7 @@ public override async Task SendMessageAsync(
136136
LogRejectedPost(Name, messageId);
137137
}
138138

139-
throw new McpTransportException("Failed to send message");
139+
throw new InvalidOperationException("Failed to send message");
140140
}
141141
}
142142

@@ -273,34 +273,18 @@ private async Task ProcessSseMessage(string data, CancellationToken cancellation
273273

274274
private void HandleEndpointEvent(string data)
275275
{
276-
try
276+
if (string.IsNullOrEmpty(data))
277277
{
278-
if (string.IsNullOrEmpty(data))
279-
{
280-
LogTransportEndpointEventInvalid(Name);
281-
return;
282-
}
283-
284-
// If data is an absolute URL, the Uri will be constructed entirely from it and not the _sseEndpoint.
285-
_messageEndpoint = new Uri(_sseEndpoint, data);
286-
287-
// Set connected state
288-
SetConnected(true);
289-
_connectionEstablished.TrySetResult(true);
278+
LogTransportEndpointEventInvalid(Name);
279+
return;
290280
}
291-
catch (JsonException ex)
292-
{
293-
if (_logger.IsEnabled(LogLevel.Trace))
294-
{
295-
LogTransportEndpointEventParseFailedSensitive(Name, data, ex);
296-
}
297-
else
298-
{
299-
LogTransportEndpointEventParseFailed(Name, ex);
300-
}
301281

302-
throw new McpTransportException("Failed to parse endpoint event", ex);
303-
}
282+
// If data is an absolute URL, the Uri will be constructed entirely from it and not the _sseEndpoint.
283+
_messageEndpoint = new Uri(_sseEndpoint, data);
284+
285+
// Set connected state
286+
SetConnected(true);
287+
_connectionEstablished.TrySetResult(true);
304288
}
305289

306290
private void CopyAdditionalHeaders(HttpRequestHeaders headers)

src/ModelContextProtocol/Protocol/Transport/StdioClientSessionTransport.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@ public StdioClientSessionTransport(StdioClientTransportOptions options, Process
2323
/// <para>
2424
/// For stdio-based transports, this implementation first verifies that the underlying process
2525
/// is still running before attempting to send the message. If the process has exited or cannot
26-
/// be accessed, a <see cref="McpTransportException"/> is thrown with details about the failure.
26+
/// be accessed, a <see cref="InvalidOperationException"/> is thrown with details about the failure.
2727
/// </para>
2828
/// <para>
2929
/// After verifying the process state, this method delegates to the base class implementation
3030
/// to handle the actual message serialization and transmission to the process's standard input stream.
3131
/// </para>
3232
/// </remarks>
33-
/// <exception cref="McpTransportException">
33+
/// <exception cref="InvalidOperationException">
3434
/// Thrown when the underlying process has exited or cannot be accessed.
3535
/// </exception>
3636
public override async Task SendMessageAsync(IJsonRpcMessage message, CancellationToken cancellationToken = default)
@@ -49,7 +49,7 @@ public override async Task SendMessageAsync(IJsonRpcMessage message, Cancellatio
4949

5050
if (hasExited)
5151
{
52-
throw new McpTransportException("Transport is not connected", processException);
52+
throw new InvalidOperationException("Transport is not connected", processException);
5353
}
5454

5555
await base.SendMessageAsync(message, cancellationToken).ConfigureAwait(false);

src/ModelContextProtocol/Protocol/Transport/StdioClientTransport.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ public async Task<ITransport> ConnectAsync(CancellationToken cancellationToken =
154154
if (!processStarted)
155155
{
156156
LogTransportProcessStartFailed(logger, endpointName);
157-
throw new McpTransportException("Failed to start MCP server process");
157+
throw new InvalidOperationException("Failed to start MCP server process");
158158
}
159159

160160
LogTransportProcessStarted(logger, endpointName, process.Id);
@@ -176,7 +176,7 @@ public async Task<ITransport> ConnectAsync(CancellationToken cancellationToken =
176176
LogTransportShutdownFailed(logger, endpointName, ex2);
177177
}
178178

179-
throw new McpTransportException("Failed to connect transport", ex);
179+
throw new InvalidOperationException("Failed to connect transport", ex);
180180
}
181181
}
182182

0 commit comments

Comments
 (0)