Clean up (use of) McpException and McpTransportException (#321)

* Clean up (use of) McpException

* Replace McpTransportException with InvalidOperationException

* Add cancellation test
This commit is contained in:
Stephen Toub 2025-04-18 12:25:29 -04:00 • committed by GitHub
commit 7d77be68cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 231 additions and 219 deletions

View file

@ -176,7 +176,7 @@ builder.Services
{
if (ctx.Params?.Level is null)
{
throw new McpException("Missing required argument 'level'");
throw new McpException("Missing required argument 'level'", McpErrorCode.InvalidParams);
}
_minimumLoggingLevel = ctx.Params.Level;

View file

@ -155,10 +155,10 @@ internal sealed partial class McpClient : McpEndpoint, IMcpClient
new JsonRpcNotification { Method = NotificationMethods.InitializedNotification },
initializationCts.Token).ConfigureAwait(false);
}
catch (OperationCanceledException oce) when (initializationCts.IsCancellationRequested)
catch (OperationCanceledException oce) when (initializationCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested)
{
LogClientInitializationTimeout(EndpointName);
throw new McpException("Initialization timed out", oce);
throw new TimeoutException("Initialization timed out", oce);
}
}
catch (Exception e)

View file

@ -0,0 +1,49 @@
namespace ModelContextProtocol;
/// <summary>
/// Represents standard JSON-RPC error codes as defined in the MCP specification.
/// </summary>
public enum McpErrorCode
{
/// <summary>
/// Indicates that the JSON received could not be parsed.
/// </summary>
/// <remarks>
/// This error occurs when the input contains malformed JSON or incorrect syntax.
/// </remarks>
ParseError = -32700,
/// <summary>
/// Indicates that the JSON payload does not conform to the expected Request object structure.
/// </summary>
/// <remarks>
/// The request is considered invalid if it lacks required fields or fails to follow the JSON-RPC protocol.
/// </remarks>
InvalidRequest = -32600,
/// <summary>
/// Indicates that the requested method does not exist or is not available on the server.
/// </summary>
/// <remarks>
/// This error is returned when the method name specified in the request cannot be found.
/// </remarks>
MethodNotFound = -32601,
/// <summary>
/// Indicates that one or more parameters provided in the request are invalid.
/// </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.
/// </remarks>
InvalidParams = -32602,
/// <summary>
/// Indicates that an internal error occurred while processing the request.
/// </summary>
/// <remarks>
/// This error is used when the endpoint encounters an unexpected condition that prevents it from fulfilling the request.
/// </remarks>
InternalError = -32603,
}

View file

@ -1,8 +1,15 @@
namespace ModelContextProtocol;
/// <summary>
/// Represents an exception that is thrown when a Model Context Protocol (MCP) error occurs.
/// 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.
/// </remarks>
public class McpException : Exception
{
/// <summary>
@ -33,8 +40,8 @@ public class McpException : Exception
/// 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 JSON-RPC error code from <see cref="Protocol.Messages.ErrorCodes"/> class.</param>
public McpException(string message, int? errorCode) : this(message, null, errorCode)
/// <param name="errorCode">A <see cref="McpErrorCode"/>.</param>
public McpException(string message, McpErrorCode errorCode) : this(message, null, errorCode)
{
}
@ -43,18 +50,15 @@ public class McpException : 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="errorCode">A JSON-RPC error code from <see cref="Protocol.Messages.ErrorCodes"/> class.</param>
public McpException(string message, Exception? innerException, int? errorCode) : base(message, innerException)
/// <param name="errorCode">A <see cref="McpErrorCode"/>.</param>
public McpException(string message, Exception? innerException, McpErrorCode errorCode) : base(message, innerException)
{
ErrorCode = errorCode;
}
/// <summary>
/// Gets the JSON-RPC error code associated with this exception.
/// Gets the error code associated with this exception.
/// </summary>
/// <value>
/// A standard JSON-RPC error code, or <see langword="null"/> if the exception wasn't caused by a JSON-RPC error.
/// </value>
/// <remarks>
/// This property contains a standard JSON-RPC error code as defined in the MCP specification. Common error codes include:
/// <list type="bullet">
@ -65,5 +69,5 @@ public class McpException : Exception
/// <item><description>-32603: Internal error - Internal JSON-RPC error</description></item>
/// </list>
/// </remarks>
public int? ErrorCode { get; }
public McpErrorCode ErrorCode { get; } = McpErrorCode.InternalError;
}

View file

@ -1,32 +0,0 @@
namespace ModelContextProtocol.Protocol.Messages;
/// <summary>
/// Standard JSON-RPC error codes as defined in the MCP specification.
/// </summary>
internal static class ErrorCodes
{
/// <summary>
/// Invalid JSON was received by the server.
/// </summary>
public const int ParseError = -32700;
/// <summary>
/// The JSON sent is not a valid Request object.
/// </summary>
public const int InvalidRequest = -32600;
/// <summary>
/// The method does not exist / is not available.
/// </summary>
public const int MethodNotFound = -32601;
/// <summary>
/// Invalid method parameter(s).
/// </summary>
public const int InvalidParams = -32602;
/// <summary>
/// Internal JSON-RPC error.
/// </summary>
public const int InternalError = -32603;
}

View file

@ -42,6 +42,6 @@ public interface IClientTransport
/// This method is used by <see cref="McpClientFactory"/> to initialize the connection.
/// </para>
/// </remarks>
/// <exception cref="McpTransportException">The transport connection could not be established.</exception>
/// <exception cref="InvalidOperationException">The transport connection could not be established.</exception>
Task<ITransport> ConnectAsync(CancellationToken cancellationToken = default);
}

View file

@ -1,34 +0,0 @@
namespace ModelContextProtocol.Protocol.Transport;
/// <summary>
/// Represents errors that occur in MCP transport operations.
/// </summary>
public class McpTransportException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="McpTransportException"/> class.
/// </summary>
public McpTransportException()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="McpTransportException"/> class with a specified error message.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public McpTransportException(string message)
: base(message)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="McpTransportException"/> 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.</param>
public McpTransportException(string message, Exception? innerException)
: base(message, innerException)
{
}
}

View file

@ -58,11 +58,11 @@ internal sealed partial class SseClientSessionTransport : TransportBase
await _connectionEstablished.Task.WaitAsync(_options.ConnectionTimeout, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex) when (ex is not McpTransportException) // propagate transport exceptions
catch (Exception ex)
{
LogTransportConnectFailed(Name, ex);
await CloseAsync().ConfigureAwait(false);
throw new McpTransportException("Failed to connect transport", ex);
throw new InvalidOperationException("Failed to connect transport", ex);
}
}
@ -110,7 +110,7 @@ internal sealed partial class SseClientSessionTransport : TransportBase
else
{
JsonRpcResponse initializeResponse = JsonSerializer.Deserialize(responseContent, McpJsonUtilities.JsonContext.Default.JsonRpcResponse) ??
throw new McpTransportException("Failed to initialize client");
throw new InvalidOperationException("Failed to initialize client");
LogTransportReceivedMessage(Name, messageId);
await WriteMessageAsync(initializeResponse, cancellationToken).ConfigureAwait(false);
@ -136,7 +136,7 @@ internal sealed partial class SseClientSessionTransport : TransportBase
LogRejectedPost(Name, messageId);
}
throw new McpTransportException("Failed to send message");
throw new InvalidOperationException("Failed to send message");
}
}
@ -273,34 +273,18 @@ internal sealed partial class SseClientSessionTransport : TransportBase
private void HandleEndpointEvent(string data)
{
try
if (string.IsNullOrEmpty(data))
{
if (string.IsNullOrEmpty(data))
{
LogTransportEndpointEventInvalid(Name);
return;
}
// If data is an absolute URL, the Uri will be constructed entirely from it and not the _sseEndpoint.
_messageEndpoint = new Uri(_sseEndpoint, data);
// Set connected state
SetConnected(true);
_connectionEstablished.TrySetResult(true);
LogTransportEndpointEventInvalid(Name);
return;
}
catch (JsonException ex)
{
if (_logger.IsEnabled(LogLevel.Trace))
{
LogTransportEndpointEventParseFailedSensitive(Name, data, ex);
}
else
{
LogTransportEndpointEventParseFailed(Name, ex);
}
throw new McpTransportException("Failed to parse endpoint event", ex);
}
// If data is an absolute URL, the Uri will be constructed entirely from it and not the _sseEndpoint.
_messageEndpoint = new Uri(_sseEndpoint, data);
// Set connected state
SetConnected(true);
_connectionEstablished.TrySetResult(true);
}
private void CopyAdditionalHeaders(HttpRequestHeaders headers)

View file

@ -23,14 +23,14 @@ internal sealed class StdioClientSessionTransport : StreamClientSessionTransport
/// <para>
/// For stdio-based transports, this implementation first verifies that the underlying process
/// is still running before attempting to send the message. If the process has exited or cannot
/// be accessed, a <see cref="McpTransportException"/> is thrown with details about the failure.
/// be accessed, a <see cref="InvalidOperationException"/> is thrown with details about the failure.
/// </para>
/// <para>
/// After verifying the process state, this method delegates to the base class implementation
/// to handle the actual message serialization and transmission to the process's standard input stream.
/// </para>
/// </remarks>
/// <exception cref="McpTransportException">
/// <exception cref="InvalidOperationException">
/// Thrown when the underlying process has exited or cannot be accessed.
/// </exception>
public override async Task SendMessageAsync(IJsonRpcMessage message, CancellationToken cancellationToken = default)
@ -49,7 +49,7 @@ internal sealed class StdioClientSessionTransport : StreamClientSessionTransport
if (hasExited)
{
throw new McpTransportException("Transport is not connected", processException);
throw new InvalidOperationException("Transport is not connected", processException);
}
await base.SendMessageAsync(message, cancellationToken).ConfigureAwait(false);

View file

@ -154,7 +154,7 @@ public sealed partial class StdioClientTransport : IClientTransport
if (!processStarted)
{
LogTransportProcessStartFailed(logger, endpointName);
throw new McpTransportException("Failed to start MCP server process");
throw new InvalidOperationException("Failed to start MCP server process");
}
LogTransportProcessStarted(logger, endpointName, process.Id);
@ -176,7 +176,7 @@ public sealed partial class StdioClientTransport : IClientTransport
LogTransportShutdownFailed(logger, endpointName, ex2);
}
throw new McpTransportException("Failed to connect transport", ex);
throw new InvalidOperationException("Failed to connect transport", ex);
}
}

View file

@ -57,28 +57,11 @@ internal class StreamClientSessionTransport : TransportBase
}
/// <inheritdoc/>
/// <remarks>
/// <para>
/// For stream-based transports, this implementation serializes the JSON-RPC message to the
/// underlying output stream. The specific serialization format includes:
/// <list type="bullet">
/// <item>A Content-Length header that specifies the byte length of the JSON message</item>
/// <item>A blank line separator</item>
/// <item>The UTF-8 encoded JSON representation of the message</item>
/// </list>
/// </para>
/// <para>
/// This implementation first checks if the transport is connected and throws a <see cref="McpTransportException"/>
/// if it's not. It then extracts the message ID (if present) for logging purposes, serializes the message,
/// and writes it to the output stream.
/// </para>
/// </remarks>
/// <exception cref="McpTransportException">Thrown when the transport is not connected.</exception>
public override async Task SendMessageAsync(IJsonRpcMessage message, CancellationToken cancellationToken = default)
{
if (!IsConnected)
{
throw new McpTransportException("Transport is not connected");
throw new InvalidOperationException("Transport is not connected");
}
string id = "(no id)";
@ -99,7 +82,7 @@ internal class StreamClientSessionTransport : TransportBase
catch (Exception ex)
{
LogTransportSendFailed(Name, id, ex);
throw new McpTransportException("Failed to send message", ex);
throw new InvalidOperationException("Failed to send message", ex);
}
}

View file

@ -60,7 +60,7 @@ public class StreamServerTransport : TransportBase
{
if (!IsConnected)
{
throw new McpTransportException("Transport is not connected");
throw new InvalidOperationException("Transport is not connected");
}
using var _ = await _sendLock.LockAsync(cancellationToken).ConfigureAwait(false);
@ -80,7 +80,7 @@ public class StreamServerTransport : TransportBase
catch (Exception ex)
{
LogTransportSendFailed(Name, id, ex);
throw new McpTransportException("Failed to send message", ex);
throw new InvalidOperationException("Failed to send message", ex);
}
}

View file

@ -73,7 +73,7 @@ public abstract partial class TransportBase : ITransport
{
if (!IsConnected)
{
throw new McpTransportException("Transport is not connected");
throw new InvalidOperationException("Transport is not connected");
}
await _messageChannel.Writer.WriteAsync(message, cancellationToken).ConfigureAwait(false);

View file

@ -101,7 +101,7 @@ internal sealed class AIFunctionMcpServerPrompt : McpServerPrompt
BindParameter = (pi, args) =>
GetRequestContext(args)?.Services?.GetService(pi.ParameterType) ??
(pi.HasDefaultValue ? null :
throw new ArgumentException("No service of the requested type was found.")),
throw new InvalidOperationException("No service of the requested type was found.")),
};
}
@ -113,7 +113,7 @@ internal sealed class AIFunctionMcpServerPrompt : McpServerPrompt
BindParameter = (pi, args) =>
(GetRequestContext(args)?.Services as IKeyedServiceProvider)?.GetKeyedService(pi.ParameterType, keyedAttr.Key) ??
(pi.HasDefaultValue ? null :
throw new ArgumentException("No service of the requested type was found.")),
throw new InvalidOperationException("No service of the requested type was found.")),
};
}

View file

@ -122,7 +122,7 @@ internal sealed class AIFunctionMcpServerTool : McpServerTool
BindParameter = (pi, args) =>
GetRequestContext(args)?.Services?.GetService(pi.ParameterType) ??
(pi.HasDefaultValue ? null :
throw new ArgumentException("No service of the requested type was found.")),
throw new InvalidOperationException("No service of the requested type was found.")),
};
}
@ -134,7 +134,7 @@ internal sealed class AIFunctionMcpServerTool : McpServerTool
BindParameter = (pi, args) =>
(GetRequestContext(args)?.Services as IKeyedServiceProvider)?.GetKeyedService(pi.ParameterType, keyedAttr.Key) ??
(pi.HasDefaultValue ? null :
throw new ArgumentException("No service of the requested type was found.")),
throw new InvalidOperationException("No service of the requested type was found.")),
};
}
@ -265,10 +265,14 @@ internal sealed class AIFunctionMcpServerTool : McpServerTool
}
catch (Exception e) when (e is not OperationCanceledException)
{
return new CallToolResponse()
string errorMessage = e is McpException ?
$"An error occurred invoking '{request.Params?.Name}': {e.Message}" :
$"An error occurred invoking '{request.Params?.Name}'.";
return new()
{
IsError = true,
Content = [new() { Text = $"An error occurred invoking '{request.Params?.Name}'.", Type = "text" }],
Content = [new() { Text = errorMessage, Type = "text" }],
};
}

View file

@ -196,7 +196,9 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
}
var completeHandler = completionsCapability.CompleteHandler ??
throw new McpException("Completions capability was enabled, but Complete handler was not specified.");
throw new InvalidOperationException(
$"{nameof(ServerCapabilities)}.{nameof(ServerCapabilities.Completions)} was enabled, " +
$"but {nameof(CompletionsCapability.CompleteHandler)} was not specified.");
// This capability is not optional, so return an empty result if there is no handler.
RequestHandlers.Set(
@ -219,7 +221,9 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
if ((listResourcesHandler is not { } && listResourceTemplatesHandler is not { }) ||
resourcesCapability.ReadResourceHandler is not { } readResourceHandler)
{
throw new McpException("Resources capability was enabled, but ListResources and/or ReadResource handlers were not specified.");
throw new InvalidOperationException(
$"{nameof(ServerCapabilities)}.{nameof(ServerCapabilities.Resources)} was enabled, " +
$"but {nameof(ResourcesCapability.ListResourcesHandler)} or {nameof(ResourcesCapability.ReadResourceHandler)} was not specified.");
}
listResourcesHandler ??= static async (_, _) => new ListResourcesResult();
@ -252,7 +256,9 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
var unsubscribeHandler = resourcesCapability.UnsubscribeFromResourcesHandler;
if (subscribeHandler is null || unsubscribeHandler is null)
{
throw new McpException("Resources capability was enabled with subscribe support, but SubscribeToResources and/or UnsubscribeFromResources handlers were not specified.");
throw new InvalidOperationException(
$"{nameof(ServerCapabilities)}.{nameof(ServerCapabilities.Resources)}.{nameof(ResourcesCapability.Subscribe)} is set, " +
$"but {nameof(ResourcesCapability.SubscribeToResourcesHandler)} or {nameof(ResourcesCapability.UnsubscribeFromResourcesHandler)} was not specified.");
}
RequestHandlers.Set(
@ -277,7 +283,10 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
if (listPromptsHandler is null != getPromptHandler is null)
{
throw new McpException("ListPrompts and GetPrompt handlers should be specified together.");
throw new InvalidOperationException(
$"{nameof(PromptsCapability)}.{nameof(promptsCapability.ListPromptsHandler)} or " +
$"{nameof(PromptsCapability)}.{nameof(promptsCapability.GetPromptHandler)} was specified without the other. " +
$"Both or neither must be provided.");
}
// Handle prompts provided via DI.
@ -310,7 +319,7 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
return originalGetPromptHandler(request, cancellationToken);
}
throw new McpException($"Unknown prompt '{request.Params?.Name}'");
throw new McpException($"Unknown prompt: '{request.Params?.Name}'", McpErrorCode.InvalidParams);
}
return prompt.GetAsync(request, cancellationToken);
@ -344,7 +353,9 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
// Make sure the handlers are provided if the capability is enabled.
if (listPromptsHandler is null || getPromptHandler is null)
{
throw new McpException("ListPrompts and/or GetPrompt handlers were not specified but the Prompts capability was enabled.");
throw new InvalidOperationException(
$"{nameof(ServerCapabilities)}.{nameof(ServerCapabilities.Prompts)} was enabled, " +
$"but {nameof(PromptsCapability.ListPromptsHandler)} or {nameof(PromptsCapability.GetPromptHandler)} was not specified.");
}
}
@ -370,7 +381,10 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
if (listToolsHandler is null != callToolHandler is null)
{
throw new McpException("ListTools and CallTool handlers should be specified together.");
throw new InvalidOperationException(
$"{nameof(ToolsCapability)}.{nameof(ToolsCapability.ListToolsHandler)} or " +
$"{nameof(ToolsCapability)}.{nameof(ToolsCapability.CallToolHandler)} was specified without the other. " +
$"Both or neither must be provided.");
}
// Handle tools provided via DI.
@ -403,7 +417,7 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
return originalCallToolHandler(request, cancellationToken);
}
throw new McpException($"Unknown tool '{request.Params?.Name}'");
throw new McpException($"Unknown tool: '{request.Params?.Name}'", McpErrorCode.InvalidParams);
}
return tool.InvokeAsync(request, cancellationToken);
@ -437,7 +451,9 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
// Make sure the handlers are provided if the capability is enabled.
if (listToolsHandler is null || callToolHandler is null)
{
throw new McpException("ListTools and/or CallTool handlers were not specified but the Tools capability was enabled.");
throw new InvalidOperationException(
$"{nameof(ServerCapabilities)}.{nameof(ServerCapabilities.Tools)} was enabled, " +
$"but {nameof(ToolsCapability.ListToolsHandler)} or {nameof(ToolsCapability.CallToolHandler)} was not specified.");
}
}

View file

@ -36,7 +36,7 @@ public static class McpServerExtensions
if (server.ClientCapabilities?.Sampling is null)
{
throw new ArgumentException("Client connected to the server does not support sampling.", nameof(server));
throw new InvalidOperationException("Client does not support sampling.");
}
return server.SendRequestAsync(
@ -166,7 +166,7 @@ public static class McpServerExtensions
if (server.ClientCapabilities?.Sampling is null)
{
throw new ArgumentException("Client connected to the server does not support sampling.", nameof(server));
throw new InvalidOperationException("Client does not support sampling.");
}
return new SamplingChatClient(server);
@ -204,7 +204,7 @@ public static class McpServerExtensions
if (server.ClientCapabilities?.Roots is null)
{
throw new ArgumentException("Client connected to the server does not support roots.", nameof(server));
throw new InvalidOperationException("Client does not support roots.");
}
return server.SendRequestAsync(

View file

@ -142,15 +142,24 @@ internal sealed partial class McpSession : IDisposable
if (!isUserCancellation && message is JsonRpcRequest request)
{
LogRequestHandlerException(EndpointName, request.Method, ex);
JsonRpcErrorDetail detail = ex is McpException mcpe ?
new()
{
Code = (int)mcpe.ErrorCode,
Message = mcpe.Message,
} :
new()
{
Code = (int)McpErrorCode.InternalError,
Message = "An error occurred.",
};
await _transport.SendMessageAsync(new JsonRpcError
{
Id = request.Id,
JsonRpc = "2.0",
Error = new JsonRpcErrorDetail
{
Code = (ex as McpException)?.ErrorCode ?? ErrorCodes.InternalError,
Message = ex.Message
}
Error = detail,
}, cancellationToken).ConfigureAwait(false);
}
else if (ex is not OperationCanceledException)
@ -287,7 +296,7 @@ internal sealed partial class McpSession : IDisposable
if (!_requestHandlers.TryGetValue(request.Method, out var handler))
{
LogNoHandlerFoundForRequest(EndpointName, request.Method);
throw new McpException("The method does not exist or is not available.", ErrorCodes.MethodNotFound);
throw new McpException($"Method '{request.Method}' is not available.", McpErrorCode.MethodNotFound);
}
LogRequestHandlerCalled(EndpointName, request.Method);
@ -342,7 +351,7 @@ internal sealed partial class McpSession : IDisposable
{
if (!_transport.IsConnected)
{
throw new McpException("Transport is not connected");
throw new InvalidOperationException("Transport is not connected");
}
cancellationToken.ThrowIfCancellationRequested();
@ -399,7 +408,7 @@ internal sealed partial class McpSession : IDisposable
if (response is JsonRpcError error)
{
LogSendingRequestFailed(EndpointName, request.Method, error.Error.Message, error.Error.Code);
throw new McpException($"Request failed (remote): {error.Error.Message}", error.Error.Code);
throw new McpException($"Request failed (remote): {error.Error.Message}", (McpErrorCode)error.Error.Code);
}
if (response is JsonRpcResponse success)
@ -443,7 +452,7 @@ internal sealed partial class McpSession : IDisposable
if (!_transport.IsConnected)
{
throw new McpException("Transport is not connected");
throw new InvalidOperationException("Transport is not connected");
}
cancellationToken.ThrowIfCancellationRequested();
@ -591,13 +600,16 @@ internal sealed partial class McpSession : IDisposable
e = ae.InnerException;
}
int? intErrorCode = (e as McpException)?.ErrorCode is int errorCode ? errorCode :
e is JsonException ? ErrorCodes.ParseError : null;
int? intErrorCode =
(int?)((e as McpException)?.ErrorCode) is int errorCode ? errorCode :
e is JsonException ? (int)McpErrorCode.ParseError :
null;
tags.Add("error.type", intErrorCode == null ? e.GetType().FullName : intErrorCode.ToString());
string? errorType = intErrorCode?.ToString() ?? e.GetType().FullName;
tags.Add("error.type", errorType);
if (intErrorCode is not null)
{
tags.Add("rpc.jsonrpc.error_code", intErrorCode.ToString());
tags.Add("rpc.jsonrpc.error_code", errorType);
}
if (activity is { IsAllDataRequested: true })

View file

@ -166,7 +166,7 @@ internal static class Program
{
if (request.Params?.Arguments is null || !request.Params.Arguments.TryGetValue("message", out var message))
{
throw new McpException("Missing required argument 'message'");
throw new McpException("Missing required argument 'message'", McpErrorCode.InvalidParams);
}
return new CallToolResponse()
{
@ -179,7 +179,7 @@ internal static class Program
!request.Params.Arguments.TryGetValue("prompt", out var prompt) ||
!request.Params.Arguments.TryGetValue("maxTokens", out var maxTokens))
{
throw new McpException("Missing required arguments 'prompt' and 'maxTokens'");
throw new McpException("Missing required arguments 'prompt' and 'maxTokens'", McpErrorCode.InvalidParams);
}
var sampleResult = await request.Server.RequestSamplingAsync(CreateRequestSamplingParams(prompt.ToString(), "sampleLLM", Convert.ToInt32(maxTokens.GetRawText())),
cancellationToken);
@ -191,7 +191,7 @@ internal static class Program
}
else
{
throw new McpException($"Unknown tool: {request.Params?.Name}");
throw new McpException($"Unknown tool: {request.Params?.Name}", McpErrorCode.InvalidParams);
}
}
};
@ -285,7 +285,7 @@ internal static class Program
}
else
{
throw new McpException($"Unknown prompt: {request.Params?.Name}");
throw new McpException($"Unknown prompt: {request.Params?.Name}", McpErrorCode.InvalidParams);
}
return new GetPromptResult()
@ -306,7 +306,7 @@ internal static class Program
{
if (request.Params?.Level is null)
{
throw new McpException("Missing required argument 'level'");
throw new McpException("Missing required argument 'level'", McpErrorCode.InvalidParams);
}
_minimumLoggingLevel = request.Params.Level;
@ -388,7 +388,7 @@ internal static class Program
}
catch (Exception e)
{
throw new McpException("Invalid cursor.", e);
throw new McpException($"Invalid cursor: '{request.Params.Cursor}'", e, McpErrorCode.InvalidParams);
}
}
@ -410,7 +410,7 @@ internal static class Program
{
if (request.Params?.Uri is null)
{
throw new McpException("Missing required argument 'uri'");
throw new McpException("Missing required argument 'uri'", McpErrorCode.InvalidParams);
}
if (request.Params.Uri.StartsWith("test://dynamic/resource/"))
@ -418,7 +418,7 @@ internal static class Program
var id = request.Params.Uri.Split('/').LastOrDefault();
if (string.IsNullOrEmpty(id))
{
throw new McpException("Invalid resource URI");
throw new McpException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
}
return new ReadResourceResult()
@ -435,7 +435,7 @@ internal static class Program
}
ResourceContents contents = resourceContents.FirstOrDefault(r => r.Uri == request.Params.Uri)
?? throw new McpException("Resource not found");
?? throw new McpException($"Resource not found: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
return new ReadResourceResult()
{
@ -447,12 +447,12 @@ internal static class Program
{
if (request?.Params?.Uri is null)
{
throw new McpException("Missing required argument 'uri'");
throw new McpException("Missing required argument 'uri'", McpErrorCode.InvalidParams);
}
if (!request.Params.Uri.StartsWith("test://static/resource/")
&& !request.Params.Uri.StartsWith("test://dynamic/resource/"))
{
throw new McpException("Invalid resource URI");
throw new McpException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
}
_subscribedResources.TryAdd(request.Params.Uri, true);
@ -464,12 +464,12 @@ internal static class Program
{
if (request?.Params?.Uri is null)
{
throw new McpException("Missing required argument 'uri'");
throw new McpException("Missing required argument 'uri'", McpErrorCode.InvalidParams);
}
if (!request.Params.Uri.StartsWith("test://static/resource/")
&& !request.Params.Uri.StartsWith("test://dynamic/resource/"))
{
throw new McpException("Invalid resource URI");
throw new McpException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
}
_subscribedResources.TryRemove(request.Params.Uri, out _);
@ -514,7 +514,7 @@ internal static class Program
return new CompleteResult() { Completion = new() { Values = values, HasMore = false, Total = values.Length } };
}
throw new McpException($"Unknown reference type: {request.Params?.Ref.Type}");
throw new McpException($"Unknown reference type: '{request.Params?.Ref.Type}'", McpErrorCode.InvalidParams);
};
return new() { CompleteHandler = handler };

View file

@ -157,13 +157,13 @@ public class Program
{
if (request.Params is null)
{
throw new McpException("Missing required parameter 'name'");
throw new McpException("Missing required parameter 'name'", McpErrorCode.InvalidParams);
}
if (request.Params.Name == "echo")
{
if (request.Params.Arguments is null || !request.Params.Arguments.TryGetValue("message", out var message))
{
throw new McpException("Missing required argument 'message'");
throw new McpException("Missing required argument 'message'", McpErrorCode.InvalidParams);
}
return new CallToolResponse()
{
@ -176,7 +176,7 @@ public class Program
!request.Params.Arguments.TryGetValue("prompt", out var prompt) ||
!request.Params.Arguments.TryGetValue("maxTokens", out var maxTokens))
{
throw new McpException("Missing required arguments 'prompt' and 'maxTokens'");
throw new McpException("Missing required arguments 'prompt' and 'maxTokens'", McpErrorCode.InvalidParams);
}
var sampleResult = await request.Server.RequestSamplingAsync(CreateRequestSamplingParams(prompt.ToString(), "sampleLLM", Convert.ToInt32(maxTokens.ToString())),
cancellationToken);
@ -188,7 +188,7 @@ public class Program
}
else
{
throw new McpException($"Unknown tool: {request.Params.Name}");
throw new McpException($"Unknown tool: '{request.Params.Name}'", McpErrorCode.InvalidParams);
}
}
},
@ -220,9 +220,9 @@ public class Program
var startIndexAsString = Encoding.UTF8.GetString(Convert.FromBase64String(requestParams.Cursor));
startIndex = Convert.ToInt32(startIndexAsString);
}
catch
catch (Exception e)
{
throw new McpException("Invalid cursor");
throw new McpException($"Invalid cursor: '{requestParams.Cursor}'", e, McpErrorCode.InvalidParams);
}
}
@ -244,7 +244,7 @@ public class Program
{
if (request.Params?.Uri is null)
{
throw new McpException("Missing required argument 'uri'");
throw new McpException("Missing required argument 'uri'", McpErrorCode.InvalidParams);
}
if (request.Params.Uri.StartsWith("test://dynamic/resource/"))
@ -252,7 +252,7 @@ public class Program
var id = request.Params.Uri.Split('/').LastOrDefault();
if (string.IsNullOrEmpty(id))
{
throw new McpException("Invalid resource URI");
throw new McpException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
}
return new ReadResourceResult()
@ -269,7 +269,7 @@ public class Program
}
ResourceContents? contents = resourceContents.FirstOrDefault(r => r.Uri == request.Params.Uri) ??
throw new McpException("Resource not found");
throw new McpException($"Resource not found: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
return new ReadResourceResult()
{
@ -316,7 +316,7 @@ public class Program
{
if (request.Params is null)
{
throw new McpException("Missing required parameter 'name'");
throw new McpException("Missing required parameter 'name'", McpErrorCode.InvalidParams);
}
List<PromptMessage> messages = new();
if (request.Params.Name == "simple_prompt")
@ -366,7 +366,7 @@ public class Program
}
else
{
throw new McpException($"Unknown prompt: {request.Params.Name}");
throw new McpException($"Unknown prompt: {request.Params.Name}", McpErrorCode.InvalidParams);
}
return new GetPromptResult()

Some files were not shown because too many files have changed in this diff Show more