Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ McpServerOptions options = new()
{
if (request.Params.Arguments?.TryGetValue("message", out var message) is not true)
{
throw new McpException("Missing required argument 'message'");
throw new McpProtocolException("Missing required argument 'message'", McpErrorCode.InvalidParams);
}

return ValueTask.FromResult(new CallToolResult
Expand All @@ -216,7 +216,7 @@ McpServerOptions options = new()
});
}

throw new McpException($"Unknown tool: '{request.Params?.Name}'");
throw new McpProtocolException($"Unknown tool: '{request.Params?.Name}'", McpErrorCode.InvalidRequest);
}
}
};
Expand Down
2 changes: 1 addition & 1 deletion samples/EverythingServer/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ await ctx.Server.SampleAsync([
{
if (ctx.Params?.Level is null)
{
throw new McpException("Missing required argument 'level'", McpErrorCode.InvalidParams);
throw new McpProtocolException("Missing required argument 'level'", McpErrorCode.InvalidParams);
}

_minimumLoggingLevel = ctx.Params.Level;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ private void ConfigureCallToolFilter(McpServerOptions options)
var authResult = await GetAuthorizationResultAsync(context.User, context.MatchedPrimitive, context.Services, context);
if (!authResult.Succeeded)
{
throw new McpException("Access forbidden: This tool requires authorization.", McpErrorCode.InvalidRequest);
throw new McpProtocolException("Access forbidden: This tool requires authorization.", McpErrorCode.InvalidRequest);
}

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

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

return await next(context, cancellationToken);
Expand Down
60 changes: 17 additions & 43 deletions src/ModelContextProtocol.Core/McpException.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
using ModelContextProtocol.Protocol;

namespace ModelContextProtocol;

/// <summary>
/// Represents an exception that is thrown when an Model Context Protocol (MCP) error occurs.
/// </summary>
/// <remarks>
/// This exception is used to represent failures to do with protocol-level concerns, such as invalid JSON-RPC requests,
/// invalid parameters, or internal errors. It is not intended to be used for application-level errors.
/// <see cref="Exception.Message"/> or <see cref="ErrorCode"/> from a <see cref="McpException"/> may be
/// propagated to the remote endpoint; sensitive information should not be included. If sensitive details need
/// to be included, a different exception type should be used.
/// The <see cref="Exception.Message"/> from a <see cref="McpException"/> may be propagated to the remote
/// endpoint; sensitive information should not be included. If sensitive details need to be included,
/// a different exception type should be used.
///
/// This exception type can be thrown by MCP tools or tool call filters to propogate detailed error messages
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// This exception type can be thrown by MCP tools or tool call filters to propogate detailed error messages
/// This exception type can be thrown by MCP tools or tool call filters to propagate detailed error messages

/// from <see cref="Exception.Message"/> when a tool execution fails via a <see cref="CallToolResult"/>.
/// For non-tool calls, this exception controls the message propogated via a <see cref="JsonRpcError"/>.
///
/// <see cref="McpProtocolException"/> is a derived type that can be used to also specify the
/// <see cref="McpErrorCode"/> that should be used for the resulting <see cref="JsonRpcError"/>.
/// </remarks>
public class McpException : Exception
{
Expand All @@ -28,46 +35,13 @@ public McpException(string message) : base(message)
}

/// <summary>
/// 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.
/// 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.
/// </summary>
/// <param name="message">The message that describes the error.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null
/// reference if no inner exception is specified.</param>
public McpException(string message, Exception? innerException) : base(message, innerException)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="McpException"/> class with a specified error message and JSON-RPC error code.
/// </summary>
/// <param name="message">The message that describes the error.</param>
/// <param name="errorCode">A <see cref="McpErrorCode"/>.</param>
public McpException(string message, McpErrorCode errorCode) : this(message, null, errorCode)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="McpException"/> class with a specified error message, inner exception, and JSON-RPC error code.
/// </summary>
/// <param name="message">The message that describes the error.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
/// <param name="errorCode">A <see cref="McpErrorCode"/>.</param>
public McpException(string message, Exception? innerException, McpErrorCode errorCode) : base(message, innerException)
{
ErrorCode = errorCode;
}

/// <summary>
/// Gets the error code associated with this exception.
/// </summary>
/// <remarks>
/// This property contains a standard JSON-RPC error code as defined in the MCP specification. Common error codes include:
/// <list type="bullet">
/// <item><description>-32700: Parse error - Invalid JSON received</description></item>
/// <item><description>-32600: Invalid request - The JSON is not a valid Request object</description></item>
/// <item><description>-32601: Method not found - The method does not exist or is not available</description></item>
/// <item><description>-32602: Invalid params - Invalid method parameters</description></item>
/// <item><description>-32603: Internal error - Internal JSON-RPC error</description></item>
/// </list>
/// </remarks>
public McpErrorCode ErrorCode { get; } = McpErrorCode.InternalError;
}
}
73 changes: 73 additions & 0 deletions src/ModelContextProtocol.Core/McpProtocolException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
namespace ModelContextProtocol;

/// <summary>
/// Represents an exception that is thrown when an Model Context Protocol (MCP) error occurs.
/// </summary>
/// <remarks>
/// This exception is used to represent failures to do with protocol-level concerns, such as invalid JSON-RPC requests,
/// invalid parameters, or internal errors. It is not intended to be used for application-level errors.
/// <see cref="Exception.Message"/> or <see cref="ErrorCode"/> from a <see cref="McpProtocolException"/> may be
/// propagated to the remote endpoint; sensitive information should not be included. If sensitive details need
/// to be included, a different exception type should be used.
/// </remarks>
public sealed class McpProtocolException : McpException
{
/// <summary>
/// Initializes a new instance of the <see cref="McpProtocolException"/> class.
/// </summary>
public McpProtocolException()
{
}

/// <summary>
/// Initializes a new instance of the <see cref="McpProtocolException"/> class with a specified error message.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public McpProtocolException(string message) : base(message)
{
}

/// <summary>
/// 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.
/// </summary>
/// <param name="message">The message that describes the error.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
public McpProtocolException(string message, Exception? innerException) : base(message, innerException)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="McpProtocolException"/> class with a specified error message and JSON-RPC error code.
/// </summary>
/// <param name="message">The message that describes the error.</param>
/// <param name="errorCode">A <see cref="McpErrorCode"/>.</param>
public McpProtocolException(string message, McpErrorCode errorCode) : this(message, null, errorCode)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="McpProtocolException"/> class with a specified error message, inner exception, and JSON-RPC error code.
/// </summary>
/// <param name="message">The message that describes the error.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
/// <param name="errorCode">A <see cref="McpErrorCode"/>.</param>
public McpProtocolException(string message, Exception? innerException, McpErrorCode errorCode) : base(message, innerException)
{
ErrorCode = errorCode;
}

/// <summary>
/// Gets the error code associated with this exception.
/// </summary>
/// <remarks>
/// This property contains a standard JSON-RPC error code as defined in the MCP specification. Common error codes include:
/// <list type="bullet">
/// <item><description>-32700: Parse error - Invalid JSON received</description></item>
/// <item><description>-32600: Invalid request - The JSON is not a valid Request object</description></item>
/// <item><description>-32601: Method not found - The method does not exist or is not available</description></item>
/// <item><description>-32602: Invalid params - Invalid method parameters</description></item>
/// <item><description>-32603: Internal error - Internal JSON-RPC error</description></item>
/// </list>
/// </remarks>
public McpErrorCode ErrorCode { get; } = McpErrorCode.InternalError;
}
18 changes: 12 additions & 6 deletions src/ModelContextProtocol.Core/McpSessionHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,11 +181,17 @@ ex is OperationCanceledException &&
{
LogRequestHandlerException(EndpointName, request.Method, ex);

JsonRpcErrorDetail detail = ex is McpException mcpe ?
JsonRpcErrorDetail detail = ex is McpProtocolException mcpProtocolException ?
new()
{
Code = (int)mcpe.ErrorCode,
Message = mcpe.Message,
Code = (int)mcpProtocolException.ErrorCode,
Message = mcpProtocolException.Message,
} : ex is McpException mcpException ?
new()
{

Code = (int)McpErrorCode.InternalError,
Message = mcpException.Message,
} :
new()
{
Expand Down Expand Up @@ -336,7 +342,7 @@ private void HandleMessageWithId(JsonRpcMessage message, JsonRpcMessageWithId me
if (!_requestHandlers.TryGetValue(request.Method, out var handler))
{
LogNoHandlerFoundForRequest(EndpointName, request.Method);
throw new McpException($"Method '{request.Method}' is not available.", McpErrorCode.MethodNotFound);
throw new McpProtocolException($"Method '{request.Method}' is not available.", McpErrorCode.MethodNotFound);
}

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

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

int? intErrorCode =
(int?)((e as McpException)?.ErrorCode) is int errorCode ? errorCode :
(int?)((e as McpProtocolException)?.ErrorCode) is int errorCode ? errorCode :
e is JsonException ? (int)McpErrorCode.ParseError :
null;

Expand Down
14 changes: 7 additions & 7 deletions src/ModelContextProtocol.Core/Server/McpServer.Methods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ public async ValueTask<ElicitResult<T>> ElicitAsync<T>(
/// <param name="type">The type of the schema being built.</param>
/// <param name="serializerOptions">The serializer options to use.</param>
/// <returns>The built request schema.</returns>
/// <exception cref="McpException"></exception>
/// <exception cref="McpProtocolException"></exception>
private static ElicitRequestParams.RequestSchema BuildRequestSchema(Type type, JsonSerializerOptions serializerOptions)
{
var schema = new ElicitRequestParams.RequestSchema();
Expand All @@ -301,7 +301,7 @@ private static ElicitRequestParams.RequestSchema BuildRequestSchema(Type type, J

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

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

var typeInfo = serializerOptions.GetTypeInfo(type);

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

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

if (!TryValidateElicitationPrimitiveSchema(jsonElement, type, out var error))
{
throw new McpException(error);
throw new McpProtocolException(error);
}

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

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

return primitiveSchemaDefinition;
}
Expand Down
Loading
Loading