Skip to content
Merged
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
18 changes: 14 additions & 4 deletions src/ModelContextProtocol.Core/McpErrorCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,22 @@ public enum McpErrorCode
MethodNotFound = -32601,

/// <summary>
/// Indicates that one or more parameters provided in the request are invalid.
/// Indicates that the request parameters are invalid at the protocol level.
/// </summary>
/// <remarks>
/// This error is returned when the parameters do not match the expected method signature or constraints.
/// This includes cases where required parameters are missing or not understood, such as when a name for
/// a tool or prompt is not recognized.
/// <para>
/// This error is returned for protocol-level parameter issues, such as:
/// </para>
/// <list type="bullet">
/// <item><description>Malformed requests that fail to satisfy the request schema (e.g., CallToolRequest)</description></item>
/// <item><description>Unknown or unrecognized primitive names (e.g., tool, prompt, or resource names)</description></item>
/// <item><description>Missing required protocol-level parameters</description></item>
/// </list>
/// <para>
/// Note: Input validation errors within tool/prompt/resource arguments should be reported as execution errors
/// (e.g., via <see cref="Protocol.CallToolResult.IsError"/>) rather than as protocol errors, allowing language
/// models to receive error feedback and self-correct.
/// </para>
/// </remarks>
InvalidParams = -32602,

Expand Down
19 changes: 15 additions & 4 deletions src/ModelContextProtocol.Core/McpProtocolException.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
namespace ModelContextProtocol;

/// <summary>
/// Represents an exception that is thrown when an Model Context Protocol (MCP) error occurs.
/// Represents an exception that is thrown when a Model Context Protocol (MCP) protocol-level 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.
/// <para>
/// This exception is used to represent failures related to protocol-level concerns, such as malformed
/// JSON-RPC requests, unknown methods, unknown primitive names (tools/prompts/resources), or internal
/// server errors. It is not intended to be used for tool execution errors, including input validation failures.
/// </para>
/// <para>
/// Tool execution errors (including input validation errors, API failures, and business logic errors)
/// should be returned in the result object with <c>IsError</c> set to <see langword="true"/>, allowing
/// language models to see error details and self-correct. Only protocol-level issues should throw
/// <see cref="McpProtocolException"/>.
/// </para>
/// <para>
/// <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.
/// </para>
/// </remarks>
public sealed class McpProtocolException : McpException
{
Expand Down Expand Up @@ -65,7 +76,7 @@ public McpProtocolException(string message, Exception? innerException, McpErrorC
/// <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>-32602: Invalid params - Malformed request or unknown primitive name (tool/prompt/resource)</description></item>
/// <item><description>-32603: Internal error - Internal JSON-RPC error</description></item>
/// </list>
/// </remarks>
Expand Down
25 changes: 17 additions & 8 deletions src/ModelContextProtocol.Core/Protocol/CallToolResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ namespace ModelContextProtocol.Protocol;
/// </summary>
/// <remarks>
/// <para>
/// Any errors that originate from the tool should be reported inside the result
/// object, with <see cref="IsError"/> set to true, rather than as a <see cref="JsonRpcError"/>.
/// Tool execution errors (including input validation errors, API failures, and business logic errors)
/// should be reported inside the result object with <see cref="IsError"/> set to <see langword="true"/>,
/// rather than as a <see cref="JsonRpcError"/>. This allows language models to see error details
/// and potentially self-correct in subsequent requests.
/// </para>
/// <para>
/// However, any errors in finding the tool, an error indicating that the
/// server does not support tool calls, or any other exceptional conditions,
/// should be reported as an MCP error response.
/// Protocol-level errors (such as unknown tool names, malformed requests that fail schema validation,
/// or server errors) should be reported as MCP protocol error responses using <see cref="McpErrorCode"/>.
/// </para>
/// <para>
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
Expand All @@ -38,10 +39,18 @@ public sealed class CallToolResult : Result
/// Gets or sets an indication of whether the tool call was unsuccessful.
/// </summary>
/// <remarks>
/// <para>
/// When set to <see langword="true"/>, it signifies that the tool execution failed.
/// Tool errors are reported with this property set to <see langword="true"/> and details in the <see cref="Content"/>
/// property, rather than as protocol-level errors. This allows LLMs to see that an error occurred
/// and potentially self-correct in subsequent requests.
/// Tool execution errors (including input validation errors, API failures, and business logic errors)
/// are reported with this property set to <see langword="true"/> and details in the <see cref="Content"/>
/// property, rather than as protocol-level errors.
/// </para>
/// <para>
/// This allows language models to receive detailed error feedback and potentially self-correct
/// in subsequent requests. For example, if a date parameter is in the wrong format or out of range,
/// the error message in <see cref="Content"/> can explain the issue, enabling the model to retry
/// with corrected parameters.
/// </para>
/// </remarks>
[JsonPropertyName("isError")]
public bool? IsError { get; set; }
Expand Down
35 changes: 35 additions & 0 deletions tests/ModelContextProtocol.Tests/Server/McpServerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,41 @@ await Can_Handle_Requests(
});
}

[Fact]
public async Task Can_Handle_Call_Tool_Requests_With_InputValidationException()
{
// Test that input validation errors (like ArgumentException from JSON deserialization)
// are returned as tool execution errors (IsError=true) rather than protocol errors, per SEP-1303.
const string errorMessage = "Input validation failed: invalid date format";

await Can_Handle_Requests(
new ServerCapabilities
{
Tools = new()
},
method: RequestMethods.ToolsCall,
configureOptions: options =>
{
options.Handlers.CallToolHandler = async (request, ct) =>
{
// Simulate an input validation error (like what would happen with wrong argument types)
throw new ArgumentException(errorMessage);
};
options.Handlers.ListToolsHandler = (request, ct) => throw new NotImplementedException();
},
assertResult: (_, response) =>
{
var result = JsonSerializer.Deserialize<CallToolResult>(response, McpJsonUtilities.DefaultOptions);
Assert.NotNull(result);
Assert.True(result.IsError, "Input validation errors should be returned as tool execution errors (IsError=true), not protocol errors");
Assert.NotEmpty(result.Content);
var textContent = Assert.IsType<TextContentBlock>(result.Content[0]);
// ArgumentException should result in a generic error message that doesn't expose the exception details
Assert.DoesNotContain(errorMessage, textContent.Text);
Assert.Contains("An error occurred", textContent.Text);
});
}

[Fact]
public async Task Can_Handle_Call_Tool_Requests_With_McpProtocolException()
{
Expand Down
Loading