Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
/// 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