Skip to content

Commit 823ae7e

Browse files
authored
Merge branch 'main' into copilot/fix-empty-collection-handlers
2 parents 3437496 + 2ec3ceb commit 823ae7e

File tree

23 files changed

+336
-166
lines changed

23 files changed

+336
-166
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ McpServerOptions options = new()
207207
{
208208
if (request.Params.Arguments?.TryGetValue("message", out var message) is not true)
209209
{
210-
throw new McpException("Missing required argument 'message'");
210+
throw new McpProtocolException("Missing required argument 'message'", McpErrorCode.InvalidParams);
211211
}
212212

213213
return ValueTask.FromResult(new CallToolResult
@@ -216,7 +216,7 @@ McpServerOptions options = new()
216216
});
217217
}
218218

219-
throw new McpException($"Unknown tool: '{request.Params?.Name}'");
219+
throw new McpProtocolException($"Unknown tool: '{request.Params?.Name}'", McpErrorCode.InvalidRequest);
220220
}
221221
}
222222
};

global.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"sdk": {
3-
"version": "10.0.100-rc.1",
4-
"rollForward": "minor",
3+
"version": "10.0.100-rc.1.25451.107",
4+
"rollForward": "disable",
55
"allowPrerelease": true
66
}
77
}

samples/EverythingServer/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ await ctx.Server.SampleAsync([
123123
{
124124
if (ctx.Params?.Level is null)
125125
{
126-
throw new McpException("Missing required argument 'level'", McpErrorCode.InvalidParams);
126+
throw new McpProtocolException("Missing required argument 'level'", McpErrorCode.InvalidParams);
127127
}
128128

129129
_minimumLoggingLevel = ctx.Params.Level;

src/ModelContextProtocol.AspNetCore/AuthorizationFilterSetup.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ private void ConfigureCallToolFilter(McpServerOptions options)
7878
var authResult = await GetAuthorizationResultAsync(context.User, context.MatchedPrimitive, context.Services, context);
7979
if (!authResult.Succeeded)
8080
{
81-
throw new McpException("Access forbidden: This tool requires authorization.", McpErrorCode.InvalidRequest);
81+
throw new McpProtocolException("Access forbidden: This tool requires authorization.", McpErrorCode.InvalidRequest);
8282
}
8383

8484
context.Items[AuthorizationFilterInvokedKey] = true;
@@ -170,7 +170,7 @@ private void ConfigureReadResourceFilter(McpServerOptions options)
170170
var authResult = await GetAuthorizationResultAsync(context.User, context.MatchedPrimitive, context.Services, context);
171171
if (!authResult.Succeeded)
172172
{
173-
throw new McpException("Access forbidden: This resource requires authorization.", McpErrorCode.InvalidRequest);
173+
throw new McpProtocolException("Access forbidden: This resource requires authorization.", McpErrorCode.InvalidRequest);
174174
}
175175

176176
return await next(context, cancellationToken);
@@ -230,7 +230,7 @@ private void ConfigureGetPromptFilter(McpServerOptions options)
230230
var authResult = await GetAuthorizationResultAsync(context.User, context.MatchedPrimitive, context.Services, context);
231231
if (!authResult.Succeeded)
232232
{
233-
throw new McpException("Access forbidden: This prompt requires authorization.", McpErrorCode.InvalidRequest);
233+
throw new McpProtocolException("Access forbidden: This prompt requires authorization.", McpErrorCode.InvalidRequest);
234234
}
235235

236236
return await next(context, cancellationToken);
Lines changed: 17 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
1+
using ModelContextProtocol.Protocol;
2+
13
namespace ModelContextProtocol;
24

35
/// <summary>
46
/// Represents an exception that is thrown when an Model Context Protocol (MCP) error occurs.
57
/// </summary>
68
/// <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.
9+
/// The <see cref="Exception.Message"/> from a <see cref="McpException"/> may be propagated to the remote
10+
/// endpoint; sensitive information should not be included. If sensitive details need to be included,
11+
/// a different exception type should be used.
12+
///
13+
/// This exception type can be thrown by MCP tools or tool call filters to propagate detailed error messages
14+
/// from <see cref="Exception.Message"/> when a tool execution fails via a <see cref="CallToolResult"/>.
15+
/// For non-tool calls, this exception controls the message propogated via a <see cref="JsonRpcError"/>.
16+
///
17+
/// <see cref="McpProtocolException"/> is a derived type that can be used to also specify the
18+
/// <see cref="McpErrorCode"/> that should be used for the resulting <see cref="JsonRpcError"/>.
1219
/// </remarks>
1320
public class McpException : Exception
1421
{
@@ -28,46 +35,13 @@ public McpException(string message) : base(message)
2835
}
2936

3037
/// <summary>
31-
/// Initializes a new instance of the <see cref="McpException"/> class with a specified error message and a reference to the inner exception that is the cause of this exception.
38+
/// Initializes a new instance of the <see cref="McpException"/> class with a specified error message and
39+
/// a reference to the inner exception that is the cause of this exception.
3240
/// </summary>
3341
/// <param name="message">The message that describes the error.</param>
34-
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
42+
/// <param name="innerException">The exception that is the cause of the current exception, or a null
43+
/// reference if no inner exception is specified.</param>
3544
public McpException(string message, Exception? innerException) : base(message, innerException)
3645
{
3746
}
38-
39-
/// <summary>
40-
/// Initializes a new instance of the <see cref="McpException"/> class with a specified error message and JSON-RPC error code.
41-
/// </summary>
42-
/// <param name="message">The message that describes the error.</param>
43-
/// <param name="errorCode">A <see cref="McpErrorCode"/>.</param>
44-
public McpException(string message, McpErrorCode errorCode) : this(message, null, errorCode)
45-
{
46-
}
47-
48-
/// <summary>
49-
/// Initializes a new instance of the <see cref="McpException"/> class with a specified error message, inner exception, and JSON-RPC error code.
50-
/// </summary>
51-
/// <param name="message">The message that describes the error.</param>
52-
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
53-
/// <param name="errorCode">A <see cref="McpErrorCode"/>.</param>
54-
public McpException(string message, Exception? innerException, McpErrorCode errorCode) : base(message, innerException)
55-
{
56-
ErrorCode = errorCode;
57-
}
58-
59-
/// <summary>
60-
/// Gets the error code associated with this exception.
61-
/// </summary>
62-
/// <remarks>
63-
/// This property contains a standard JSON-RPC error code as defined in the MCP specification. Common error codes include:
64-
/// <list type="bullet">
65-
/// <item><description>-32700: Parse error - Invalid JSON received</description></item>
66-
/// <item><description>-32600: Invalid request - The JSON is not a valid Request object</description></item>
67-
/// <item><description>-32601: Method not found - The method does not exist or is not available</description></item>
68-
/// <item><description>-32602: Invalid params - Invalid method parameters</description></item>
69-
/// <item><description>-32603: Internal error - Internal JSON-RPC error</description></item>
70-
/// </list>
71-
/// </remarks>
72-
public McpErrorCode ErrorCode { get; } = McpErrorCode.InternalError;
73-
}
47+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
namespace ModelContextProtocol;
2+
3+
/// <summary>
4+
/// Represents an exception that is thrown when an Model Context Protocol (MCP) error occurs.
5+
/// </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="McpProtocolException"/> 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>
13+
public sealed class McpProtocolException : McpException
14+
{
15+
/// <summary>
16+
/// Initializes a new instance of the <see cref="McpProtocolException"/> class.
17+
/// </summary>
18+
public McpProtocolException()
19+
{
20+
}
21+
22+
/// <summary>
23+
/// Initializes a new instance of the <see cref="McpProtocolException"/> class with a specified error message.
24+
/// </summary>
25+
/// <param name="message">The message that describes the error.</param>
26+
public McpProtocolException(string message) : base(message)
27+
{
28+
}
29+
30+
/// <summary>
31+
/// Initializes a new instance of the <see cref="McpProtocolException"/> class with a specified error message and a reference to the inner exception that is the cause of this exception.
32+
/// </summary>
33+
/// <param name="message">The message that describes the error.</param>
34+
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
35+
public McpProtocolException(string message, Exception? innerException) : base(message, innerException)
36+
{
37+
}
38+
39+
/// <summary>
40+
/// Initializes a new instance of the <see cref="McpProtocolException"/> class with a specified error message and JSON-RPC error code.
41+
/// </summary>
42+
/// <param name="message">The message that describes the error.</param>
43+
/// <param name="errorCode">A <see cref="McpErrorCode"/>.</param>
44+
public McpProtocolException(string message, McpErrorCode errorCode) : this(message, null, errorCode)
45+
{
46+
}
47+
48+
/// <summary>
49+
/// Initializes a new instance of the <see cref="McpProtocolException"/> class with a specified error message, inner exception, and JSON-RPC error code.
50+
/// </summary>
51+
/// <param name="message">The message that describes the error.</param>
52+
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
53+
/// <param name="errorCode">A <see cref="McpErrorCode"/>.</param>
54+
public McpProtocolException(string message, Exception? innerException, McpErrorCode errorCode) : base(message, innerException)
55+
{
56+
ErrorCode = errorCode;
57+
}
58+
59+
/// <summary>
60+
/// Gets the error code associated with this exception.
61+
/// </summary>
62+
/// <remarks>
63+
/// This property contains a standard JSON-RPC error code as defined in the MCP specification. Common error codes include:
64+
/// <list type="bullet">
65+
/// <item><description>-32700: Parse error - Invalid JSON received</description></item>
66+
/// <item><description>-32600: Invalid request - The JSON is not a valid Request object</description></item>
67+
/// <item><description>-32601: Method not found - The method does not exist or is not available</description></item>
68+
/// <item><description>-32602: Invalid params - Invalid method parameters</description></item>
69+
/// <item><description>-32603: Internal error - Internal JSON-RPC error</description></item>
70+
/// </list>
71+
/// </remarks>
72+
public McpErrorCode ErrorCode { get; } = McpErrorCode.InternalError;
73+
}

src/ModelContextProtocol.Core/McpSessionHandler.cs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -181,11 +181,17 @@ ex is OperationCanceledException &&
181181
{
182182
LogRequestHandlerException(EndpointName, request.Method, ex);
183183

184-
JsonRpcErrorDetail detail = ex is McpException mcpe ?
184+
JsonRpcErrorDetail detail = ex is McpProtocolException mcpProtocolException ?
185185
new()
186186
{
187-
Code = (int)mcpe.ErrorCode,
188-
Message = mcpe.Message,
187+
Code = (int)mcpProtocolException.ErrorCode,
188+
Message = mcpProtocolException.Message,
189+
} : ex is McpException mcpException ?
190+
new()
191+
{
192+
193+
Code = (int)McpErrorCode.InternalError,
194+
Message = mcpException.Message,
189195
} :
190196
new()
191197
{
@@ -336,7 +342,7 @@ private void HandleMessageWithId(JsonRpcMessage message, JsonRpcMessageWithId me
336342
if (!_requestHandlers.TryGetValue(request.Method, out var handler))
337343
{
338344
LogNoHandlerFoundForRequest(EndpointName, request.Method);
339-
throw new McpException($"Method '{request.Method}' is not available.", McpErrorCode.MethodNotFound);
345+
throw new McpProtocolException($"Method '{request.Method}' is not available.", McpErrorCode.MethodNotFound);
340346
}
341347

342348
LogRequestHandlerCalled(EndpointName, request.Method);
@@ -446,7 +452,7 @@ public async Task<JsonRpcResponse> SendRequestAsync(JsonRpcRequest request, Canc
446452
if (response is JsonRpcError error)
447453
{
448454
LogSendingRequestFailed(EndpointName, request.Method, error.Error.Message, error.Error.Code);
449-
throw new McpException($"Request failed (remote): {error.Error.Message}", (McpErrorCode)error.Error.Code);
455+
throw new McpProtocolException($"Request failed (remote): {error.Error.Message}", (McpErrorCode)error.Error.Code);
450456
}
451457

452458
if (response is JsonRpcResponse success)
@@ -640,7 +646,7 @@ private static void AddExceptionTags(ref TagList tags, Activity? activity, Excep
640646
}
641647

642648
int? intErrorCode =
643-
(int?)((e as McpException)?.ErrorCode) is int errorCode ? errorCode :
649+
(int?)((e as McpProtocolException)?.ErrorCode) is int errorCode ? errorCode :
644650
e is JsonException ? (int)McpErrorCode.ParseError :
645651
null;
646652

src/ModelContextProtocol.Core/Server/McpServer.Methods.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ public async ValueTask<ElicitResult<T>> ElicitAsync<T>(
291291
/// <param name="type">The type of the schema being built.</param>
292292
/// <param name="serializerOptions">The serializer options to use.</param>
293293
/// <returns>The built request schema.</returns>
294-
/// <exception cref="McpException"></exception>
294+
/// <exception cref="McpProtocolException"></exception>
295295
private static ElicitRequestParams.RequestSchema BuildRequestSchema(Type type, JsonSerializerOptions serializerOptions)
296296
{
297297
var schema = new ElicitRequestParams.RequestSchema();
@@ -301,7 +301,7 @@ private static ElicitRequestParams.RequestSchema BuildRequestSchema(Type type, J
301301

302302
if (typeInfo.Kind != JsonTypeInfoKind.Object)
303303
{
304-
throw new McpException($"Type '{type.FullName}' is not supported for elicitation requests.");
304+
throw new McpProtocolException($"Type '{type.FullName}' is not supported for elicitation requests.");
305305
}
306306

307307
foreach (JsonPropertyInfo pi in typeInfo.Properties)
@@ -319,33 +319,33 @@ private static ElicitRequestParams.RequestSchema BuildRequestSchema(Type type, J
319319
/// <param name="type">The type to create the schema for.</param>
320320
/// <param name="serializerOptions">The serializer options to use.</param>
321321
/// <returns>The created primitive schema definition.</returns>
322-
/// <exception cref="McpException">Thrown when the type is not supported.</exception>
322+
/// <exception cref="McpProtocolException">Thrown when the type is not supported.</exception>
323323
private static ElicitRequestParams.PrimitiveSchemaDefinition CreatePrimitiveSchema(Type type, JsonSerializerOptions serializerOptions)
324324
{
325325
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
326326
{
327-
throw new McpException($"Type '{type.FullName}' is not a supported property type for elicitation requests. Nullable types are not supported.");
327+
throw new McpProtocolException($"Type '{type.FullName}' is not a supported property type for elicitation requests. Nullable types are not supported.");
328328
}
329329

330330
var typeInfo = serializerOptions.GetTypeInfo(type);
331331

332332
if (typeInfo.Kind != JsonTypeInfoKind.None)
333333
{
334-
throw new McpException($"Type '{type.FullName}' is not a supported property type for elicitation requests.");
334+
throw new McpProtocolException($"Type '{type.FullName}' is not a supported property type for elicitation requests.");
335335
}
336336

337337
var jsonElement = AIJsonUtilities.CreateJsonSchema(type, serializerOptions: serializerOptions);
338338

339339
if (!TryValidateElicitationPrimitiveSchema(jsonElement, type, out var error))
340340
{
341-
throw new McpException(error);
341+
throw new McpProtocolException(error);
342342
}
343343

344344
var primitiveSchemaDefinition =
345345
jsonElement.Deserialize(McpJsonUtilities.JsonContext.Default.PrimitiveSchemaDefinition);
346346

347347
if (primitiveSchemaDefinition is null)
348-
throw new McpException($"Type '{type.FullName}' is not a supported property type for elicitation requests.");
348+
throw new McpProtocolException($"Type '{type.FullName}' is not a supported property type for elicitation requests.");
349349

350350
return primitiveSchemaDefinition;
351351
}

0 commit comments

Comments
 (0)