Skip to content

Commit 20be4c1

Browse files
committed
Clean up (use of) McpException
1 parent b0cd654 commit 20be4c1

File tree

18 files changed

+181
-128
lines changed

18 files changed

+181
-128
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/Server/AIFunctionMcpServerPrompt.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ private static AIFunctionFactoryOptions CreateAIFunctionFactoryOptions(
101101
BindParameter = (pi, args) =>
102102
GetRequestContext(args)?.Services?.GetService(pi.ParameterType) ??
103103
(pi.HasDefaultValue ? null :
104-
throw new ArgumentException("No service of the requested type was found.")),
104+
throw new InvalidOperationException("No service of the requested type was found.")),
105105
};
106106
}
107107

@@ -113,7 +113,7 @@ private static AIFunctionFactoryOptions CreateAIFunctionFactoryOptions(
113113
BindParameter = (pi, args) =>
114114
(GetRequestContext(args)?.Services as IKeyedServiceProvider)?.GetKeyedService(pi.ParameterType, keyedAttr.Key) ??
115115
(pi.HasDefaultValue ? null :
116-
throw new ArgumentException("No service of the requested type was found.")),
116+
throw new InvalidOperationException("No service of the requested type was found.")),
117117
};
118118
}
119119

src/ModelContextProtocol/Server/AIFunctionMcpServerTool.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ private static AIFunctionFactoryOptions CreateAIFunctionFactoryOptions(
122122
BindParameter = (pi, args) =>
123123
GetRequestContext(args)?.Services?.GetService(pi.ParameterType) ??
124124
(pi.HasDefaultValue ? null :
125-
throw new ArgumentException("No service of the requested type was found.")),
125+
throw new InvalidOperationException("No service of the requested type was found.")),
126126
};
127127
}
128128

@@ -134,7 +134,7 @@ private static AIFunctionFactoryOptions CreateAIFunctionFactoryOptions(
134134
BindParameter = (pi, args) =>
135135
(GetRequestContext(args)?.Services as IKeyedServiceProvider)?.GetKeyedService(pi.ParameterType, keyedAttr.Key) ??
136136
(pi.HasDefaultValue ? null :
137-
throw new ArgumentException("No service of the requested type was found.")),
137+
throw new InvalidOperationException("No service of the requested type was found.")),
138138
};
139139
}
140140

@@ -265,10 +265,14 @@ public override async ValueTask<CallToolResponse> InvokeAsync(
265265
}
266266
catch (Exception e) when (e is not OperationCanceledException)
267267
{
268-
return new CallToolResponse()
268+
string errorMessage = e is McpException ?
269+
$"An error occurred invoking '{request.Params?.Name}': {e.Message}" :
270+
$"An error occurred invoking '{request.Params?.Name}'.";
271+
272+
return new()
269273
{
270274
IsError = true,
271-
Content = [new() { Text = $"An error occurred invoking '{request.Params?.Name}'.", Type = "text" }],
275+
Content = [new() { Text = errorMessage, Type = "text" }],
272276
};
273277
}
274278

src/ModelContextProtocol/Server/McpServer.cs

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,9 @@ private void SetCompletionHandler(McpServerOptions options)
196196
}
197197

198198
var completeHandler = completionsCapability.CompleteHandler ??
199-
throw new McpException("Completions capability was enabled, but Complete handler was not specified.");
199+
throw new InvalidOperationException(
200+
$"{nameof(ServerCapabilities)}.{nameof(ServerCapabilities.Completions)} was enabled, " +
201+
$"but {nameof(CompletionsCapability.CompleteHandler)} was not specified.");
200202

201203
// This capability is not optional, so return an empty result if there is no handler.
202204
RequestHandlers.Set(
@@ -219,7 +221,9 @@ private void SetResourcesHandler(McpServerOptions options)
219221
if ((listResourcesHandler is not { } && listResourceTemplatesHandler is not { }) ||
220222
resourcesCapability.ReadResourceHandler is not { } readResourceHandler)
221223
{
222-
throw new McpException("Resources capability was enabled, but ListResources and/or ReadResource handlers were not specified.");
224+
throw new InvalidOperationException(
225+
$"{nameof(ServerCapabilities)}.{nameof(ServerCapabilities.Resources)} was enabled, " +
226+
$"but {nameof(ResourcesCapability.ListResourcesHandler)} or {nameof(ResourcesCapability.ReadResourceHandler)} was not specified.");
223227
}
224228

225229
listResourcesHandler ??= static async (_, _) => new ListResourcesResult();
@@ -252,7 +256,9 @@ private void SetResourcesHandler(McpServerOptions options)
252256
var unsubscribeHandler = resourcesCapability.UnsubscribeFromResourcesHandler;
253257
if (subscribeHandler is null || unsubscribeHandler is null)
254258
{
255-
throw new McpException("Resources capability was enabled with subscribe support, but SubscribeToResources and/or UnsubscribeFromResources handlers were not specified.");
259+
throw new InvalidOperationException(
260+
$"{nameof(ServerCapabilities)}.{nameof(ServerCapabilities.Resources)}.{nameof(ResourcesCapability.Subscribe)} is set, " +
261+
$"but {nameof(ResourcesCapability.SubscribeToResourcesHandler)} or {nameof(ResourcesCapability.UnsubscribeFromResourcesHandler)} was not specified.");
256262
}
257263

258264
RequestHandlers.Set(
@@ -277,7 +283,10 @@ private void SetPromptsHandler(McpServerOptions options)
277283

278284
if (listPromptsHandler is null != getPromptHandler is null)
279285
{
280-
throw new McpException("ListPrompts and GetPrompt handlers should be specified together.");
286+
throw new InvalidOperationException(
287+
$"{nameof(PromptsCapability)}.{nameof(promptsCapability.ListPromptsHandler)} or " +
288+
$"{nameof(PromptsCapability)}.{nameof(promptsCapability.GetPromptHandler)} was specified without the other. " +
289+
$"Both or neither must be provided.");
281290
}
282291

283292
// Handle prompts provided via DI.
@@ -310,7 +319,7 @@ await originalListPromptsHandler(request, cancellationToken).ConfigureAwait(fals
310319
return originalGetPromptHandler(request, cancellationToken);
311320
}
312321

313-
throw new McpException($"Unknown prompt '{request.Params?.Name}'");
322+
throw new McpException($"Unknown prompt: '{request.Params?.Name}'", McpErrorCode.InvalidParams);
314323
}
315324

316325
return prompt.GetAsync(request, cancellationToken);
@@ -344,7 +353,9 @@ await originalListPromptsHandler(request, cancellationToken).ConfigureAwait(fals
344353
// Make sure the handlers are provided if the capability is enabled.
345354
if (listPromptsHandler is null || getPromptHandler is null)
346355
{
347-
throw new McpException("ListPrompts and/or GetPrompt handlers were not specified but the Prompts capability was enabled.");
356+
throw new InvalidOperationException(
357+
$"{nameof(ServerCapabilities)}.{nameof(ServerCapabilities.Prompts)} was enabled, " +
358+
$"but {nameof(PromptsCapability.ListPromptsHandler)} or {nameof(PromptsCapability.GetPromptHandler)} was not specified.");
348359
}
349360
}
350361

@@ -370,7 +381,10 @@ private void SetToolsHandler(McpServerOptions options)
370381

371382
if (listToolsHandler is null != callToolHandler is null)
372383
{
373-
throw new McpException("ListTools and CallTool handlers should be specified together.");
384+
throw new InvalidOperationException(
385+
$"{nameof(ToolsCapability)}.{nameof(ToolsCapability.ListToolsHandler)} or " +
386+
$"{nameof(ToolsCapability)}.{nameof(ToolsCapability.CallToolHandler)} was specified without the other. " +
387+
$"Both or neither must be provided.");
374388
}
375389

376390
// Handle tools provided via DI.
@@ -403,7 +417,7 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false)
403417
return originalCallToolHandler(request, cancellationToken);
404418
}
405419

406-
throw new McpException($"Unknown tool '{request.Params?.Name}'");
420+
throw new McpException($"Unknown tool: '{request.Params?.Name}'", McpErrorCode.InvalidParams);
407421
}
408422

409423
return tool.InvokeAsync(request, cancellationToken);
@@ -437,7 +451,9 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false)
437451
// Make sure the handlers are provided if the capability is enabled.
438452
if (listToolsHandler is null || callToolHandler is null)
439453
{
440-
throw new McpException("ListTools and/or CallTool handlers were not specified but the Tools capability was enabled.");
454+
throw new InvalidOperationException(
455+
$"{nameof(ServerCapabilities)}.{nameof(ServerCapabilities.Tools)} was enabled, " +
456+
$"but {nameof(ToolsCapability.ListToolsHandler)} or {nameof(ToolsCapability.CallToolHandler)} was not specified.");
441457
}
442458
}
443459

src/ModelContextProtocol/Server/McpServerExtensions.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public static Task<CreateMessageResult> RequestSamplingAsync(
3636

3737
if (server.ClientCapabilities?.Sampling is null)
3838
{
39-
throw new ArgumentException("Client connected to the server does not support sampling.", nameof(server));
39+
throw new InvalidOperationException("Client does not support sampling.");
4040
}
4141

4242
return server.SendRequestAsync(
@@ -166,7 +166,7 @@ public static IChatClient AsSamplingChatClient(this IMcpServer server)
166166

167167
if (server.ClientCapabilities?.Sampling is null)
168168
{
169-
throw new ArgumentException("Client connected to the server does not support sampling.", nameof(server));
169+
throw new InvalidOperationException("Client does not support sampling.");
170170
}
171171

172172
return new SamplingChatClient(server);
@@ -204,7 +204,7 @@ public static Task<ListRootsResult> RequestRootsAsync(
204204

205205
if (server.ClientCapabilities?.Roots is null)
206206
{
207-
throw new ArgumentException("Client connected to the server does not support roots.", nameof(server));
207+
throw new InvalidOperationException("Client does not support roots.");
208208
}
209209

210210
return server.SendRequestAsync(

0 commit comments

Comments
 (0)