diff --git a/src/ModelContextProtocol/Client/IMcpClient.cs b/src/ModelContextProtocol/Client/IMcpClient.cs index 357ce384..7e85b9d9 100644 --- a/src/ModelContextProtocol/Client/IMcpClient.cs +++ b/src/ModelContextProtocol/Client/IMcpClient.cs @@ -8,19 +8,21 @@ namespace ModelContextProtocol.Client; public interface IMcpClient : IMcpEndpoint { /// - /// Gets the capabilities supported by the server. + /// Gets the capabilities supported by the connected server. /// - ServerCapabilities? ServerCapabilities { get; } + ServerCapabilities ServerCapabilities { get; } /// - /// Gets the version and implementation information of the server. + /// Gets the implementation information of the connected server. /// - Implementation? ServerInfo { get; } + Implementation ServerInfo { get; } /// - /// Instructions describing how to use the server and its features. - /// This can be used by clients to improve the LLM's understanding of available tools, resources, etc. - /// It can be thought of like a "hint" to the model. For example, this information MAY be added to the system prompt. + /// Gets any instructions describing how to use the connected server and its features. /// + /// + /// This can be used by clients to improve an LLM's understanding of available tools, prompts, and resources. + /// It can be thought of like a "hint" to the model and may be added to a system prompt. + /// string? ServerInstructions { get; } } \ No newline at end of file diff --git a/src/ModelContextProtocol/Client/McpClient.cs b/src/ModelContextProtocol/Client/McpClient.cs index cf27a6b5..a38edf6d 100644 --- a/src/ModelContextProtocol/Client/McpClient.cs +++ b/src/ModelContextProtocol/Client/McpClient.cs @@ -18,6 +18,10 @@ internal sealed class McpClient : McpEndpoint, IMcpClient private ITransport? _sessionTransport; private CancellationTokenSource? _connectCts; + private ServerCapabilities? _serverCapabilities; + private Implementation? _serverInfo; + private string? _serverInstructions; + /// /// Initializes a new instance of the class. /// @@ -66,13 +70,13 @@ public McpClient(IClientTransport clientTransport, McpClientOptions options, Mcp } /// - public ServerCapabilities? ServerCapabilities { get; private set; } + public ServerCapabilities ServerCapabilities => _serverCapabilities ?? throw new InvalidOperationException("The client is not connected."); /// - public Implementation? ServerInfo { get; private set; } + public Implementation ServerInfo => _serverInfo ?? throw new InvalidOperationException("The client is not connected."); /// - public string? ServerInstructions { get; private set; } + public string? ServerInstructions => _serverInstructions; /// public override string EndpointName { get; } @@ -112,15 +116,15 @@ public async Task ConnectAsync(CancellationToken cancellationToken = default) capabilities: JsonSerializer.Serialize(initializeResponse.Capabilities, McpJsonUtilities.JsonContext.Default.ServerCapabilities), serverInfo: JsonSerializer.Serialize(initializeResponse.ServerInfo, McpJsonUtilities.JsonContext.Default.Implementation)); - ServerCapabilities = initializeResponse.Capabilities; - ServerInfo = initializeResponse.ServerInfo; - ServerInstructions = initializeResponse.Instructions; + _serverCapabilities = initializeResponse.Capabilities; + _serverInfo = initializeResponse.ServerInfo; + _serverInstructions = initializeResponse.Instructions; // Validate protocol version if (initializeResponse.ProtocolVersion != _options.ProtocolVersion) { _logger.ServerProtocolVersionMismatch(EndpointName, _options.ProtocolVersion, initializeResponse.ProtocolVersion); - throw new McpClientException($"Server protocol version mismatch. Expected {_options.ProtocolVersion}, got {initializeResponse.ProtocolVersion}"); + throw new McpException($"Server protocol version mismatch. Expected {_options.ProtocolVersion}, got {initializeResponse.ProtocolVersion}"); } // Send initialized notification @@ -128,10 +132,10 @@ await SendMessageAsync( new JsonRpcNotification { Method = NotificationMethods.InitializedNotification }, initializationCts.Token).ConfigureAwait(false); } - catch (OperationCanceledException) when (initializationCts.IsCancellationRequested) + catch (OperationCanceledException oce) when (initializationCts.IsCancellationRequested) { _logger.ClientInitializationTimeout(EndpointName); - throw new McpClientException("Initialization timed out"); + throw new McpException("Initialization timed out", oce); } } catch (Exception e) diff --git a/src/ModelContextProtocol/Client/McpClientException.cs b/src/ModelContextProtocol/Client/McpClientException.cs deleted file mode 100644 index 8f19862c..00000000 --- a/src/ModelContextProtocol/Client/McpClientException.cs +++ /dev/null @@ -1,46 +0,0 @@ -namespace ModelContextProtocol.Client; - -/// -/// Represents errors that occur in the MCP client. -/// -public class McpClientException : Exception -{ - /// - /// Gets the error code if this exception was caused by a JSON-RPC error response. - /// - public int? ErrorCode { get; } - - /// - /// Initializes a new instance of the class. - /// - public McpClientException() - { - } - - /// - /// Initializes a new instance of the class with a specified error message. - /// - /// The message that describes the error. - public McpClientException(string message) : base(message) - { - } - - /// - /// Initializes a new instance of the class with a specified error message and error code. - /// - /// The message that describes the error. - /// The error code associated with the JSON-RPC error response. - public McpClientException(string message, int errorCode) : base(message) - { - ErrorCode = errorCode; - } - - /// - /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. - /// - /// The message that describes the error. - /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified. - public McpClientException(string message, Exception innerException) : base(message, innerException) - { - } -} \ No newline at end of file diff --git a/src/ModelContextProtocol/Client/McpClientExtensions.cs b/src/ModelContextProtocol/Client/McpClientExtensions.cs index c98f7619..37ffb135 100644 --- a/src/ModelContextProtocol/Client/McpClientExtensions.cs +++ b/src/ModelContextProtocol/Client/McpClientExtensions.cs @@ -15,7 +15,7 @@ public static class McpClientExtensions /// Sends a ping request to verify server connectivity. /// /// The client. - /// A token to cancel the operation. + /// The to monitor for cancellation requests. The default is . /// A task that completes when the ping is successful. public static Task PingAsync(this IMcpClient client, CancellationToken cancellationToken = default) { @@ -34,7 +34,7 @@ public static Task PingAsync(this IMcpClient client, CancellationToken cancellat /// /// The client. /// The serializer options governing tool parameter serialization. - /// A token to cancel the operation. + /// The to monitor for cancellation requests. The default is . /// A list of all available tools. public static async Task> ListToolsAsync( this IMcpClient client, @@ -75,7 +75,7 @@ public static async Task> ListToolsAsync( /// /// The client. /// The serializer options governing tool parameter serialization. - /// A token to cancel the operation. + /// The to monitor for cancellation requests. The default is . /// An asynchronous sequence of all available tools. /// /// Every iteration through the returned @@ -115,7 +115,7 @@ public static async IAsyncEnumerable EnumerateToolsAsync( /// Retrieves a list of available prompts from the server. /// /// The client. - /// A token to cancel the operation. + /// The to monitor for cancellation requests. The default is . /// A list of all available prompts. public static async Task> ListPromptsAsync( this IMcpClient client, CancellationToken cancellationToken = default) @@ -150,7 +150,7 @@ public static async Task> ListPromptsAsync( /// Creates an enumerable for asynchronously enumerating all available prompts from the server. /// /// The client. - /// A token to cancel the operation. + /// The to monitor for cancellation requests. The default is . /// An asynchronous sequence of all available prompts. /// /// Every iteration through the returned @@ -188,7 +188,7 @@ public static async IAsyncEnumerable EnumeratePromptsAsync( /// The name of the prompt to retrieve /// Optional arguments for the prompt /// The serialization options governing argument serialization. - /// A token to cancel the operation. + /// The to monitor for cancellation requests. The default is . /// A task containing the prompt's content and messages. public static Task GetPromptAsync( this IMcpClient client, @@ -216,7 +216,7 @@ public static Task GetPromptAsync( /// Retrieves a list of available resource templates from the server. /// /// The client. - /// A token to cancel the operation. + /// The to monitor for cancellation requests. The default is . /// A list of all available resource templates. public static async Task> ListResourceTemplatesAsync( this IMcpClient client, CancellationToken cancellationToken = default) @@ -255,7 +255,7 @@ public static async Task> ListResourceTemplatesAsync( /// Creates an enumerable for asynchronously enumerating all available resource templates from the server. /// /// The client. - /// A token to cancel the operation. + /// The to monitor for cancellation requests. The default is . /// An asynchronous sequence of all available resource templates. /// /// Every iteration through the returned @@ -290,7 +290,7 @@ public static async IAsyncEnumerable EnumerateResourceTemplate /// Retrieves a list of available resources from the server. /// /// The client. - /// A token to cancel the operation. + /// The to monitor for cancellation requests. The default is . /// A list of all available resources. public static async Task> ListResourcesAsync( this IMcpClient client, CancellationToken cancellationToken = default) @@ -329,7 +329,7 @@ public static async Task> ListResourcesAsync( /// Creates an enumerable for asynchronously enumerating all available resources from the server. /// /// The client. - /// A token to cancel the operation. + /// The to monitor for cancellation requests. The default is . /// An asynchronous sequence of all available resources. /// /// Every iteration through the returned @@ -365,7 +365,7 @@ public static async IAsyncEnumerable EnumerateResourcesAsync( /// /// The client. /// The uri of the resource. - /// A token to cancel the operation. + /// The to monitor for cancellation requests. The default is . public static Task ReadResourceAsync( this IMcpClient client, string uri, CancellationToken cancellationToken = default) { @@ -387,7 +387,7 @@ public static Task ReadResourceAsync( /// A resource (uri) or prompt (name) reference /// Name of argument. Must be non-null and non-empty. /// Value of argument. Must be non-null. - /// A token to cancel the operation. + /// The to monitor for cancellation requests. The default is . public static Task GetCompletionAsync(this IMcpClient client, Reference reference, string argumentName, string argumentValue, CancellationToken cancellationToken = default) { Throw.IfNull(client); @@ -416,7 +416,7 @@ public static Task GetCompletionAsync(this IMcpClient client, Re /// /// The client. /// The uri of the resource. - /// A token to cancel the operation. + /// The to monitor for cancellation requests. The default is . public static Task SubscribeToResourceAsync(this IMcpClient client, string uri, CancellationToken cancellationToken = default) { Throw.IfNull(client); @@ -435,7 +435,7 @@ public static Task SubscribeToResourceAsync(this IMcpClient client, string uri, /// /// The client. /// The uri of the resource. - /// A token to cancel the operation. + /// The to monitor for cancellation requests. The default is . public static Task UnsubscribeFromResourceAsync(this IMcpClient client, string uri, CancellationToken cancellationToken = default) { Throw.IfNull(client); @@ -456,7 +456,7 @@ public static Task UnsubscribeFromResourceAsync(this IMcpClient client, string u /// The name of the tool to call. /// Optional arguments for the tool. /// The serialization options governing argument serialization. - /// A token to cancel the operation. + /// The to monitor for cancellation requests. The default is . /// A task containing the tool's response. public static Task CallToolAsync( this IMcpClient client, @@ -622,7 +622,7 @@ internal static CreateMessageResult ToCreateMessageResult(this ChatResponse chat /// /// The client. /// The minimum log level of messages to be generated. - /// A token to cancel the operation. + /// The to monitor for cancellation requests. The default is . public static Task SetLoggingLevel(this IMcpClient client, LoggingLevel level, CancellationToken cancellationToken = default) { Throw.IfNull(client); diff --git a/src/ModelContextProtocol/Client/McpClientFactory.cs b/src/ModelContextProtocol/Client/McpClientFactory.cs index 97c2371f..c1bb3770 100644 --- a/src/ModelContextProtocol/Client/McpClientFactory.cs +++ b/src/ModelContextProtocol/Client/McpClientFactory.cs @@ -37,7 +37,7 @@ private static McpClientOptions CreateDefaultClientOptions() /// /// An optional factory method which returns transport implementations based on a server configuration. /// A logger factory for creating loggers for clients. - /// A token to cancel the operation. + /// The to monitor for cancellation requests. The default is . /// An that's connected to the specified server. /// is . /// is . diff --git a/src/ModelContextProtocol/Client/McpClientPrompt.cs b/src/ModelContextProtocol/Client/McpClientPrompt.cs index 71d6a4e6..4d4329db 100644 --- a/src/ModelContextProtocol/Client/McpClientPrompt.cs +++ b/src/ModelContextProtocol/Client/McpClientPrompt.cs @@ -22,7 +22,7 @@ internal McpClientPrompt(IMcpClient client, Prompt prompt) /// /// Optional arguments for the prompt /// The serialization options governing argument serialization. - /// A token to cancel the operation. + /// The to monitor for cancellation requests. The default is . /// A task containing the prompt's content and messages. public async ValueTask GetAsync( IEnumerable>? arguments = null, diff --git a/src/ModelContextProtocol/IMcpEndpoint.cs b/src/ModelContextProtocol/IMcpEndpoint.cs index 6643e02a..a053ad64 100644 --- a/src/ModelContextProtocol/IMcpEndpoint.cs +++ b/src/ModelContextProtocol/IMcpEndpoint.cs @@ -7,13 +7,13 @@ public interface IMcpEndpoint : IAsyncDisposable { /// Sends a JSON-RPC request to the connected endpoint. /// The JSON-RPC request to send. - /// A token to cancel the operation. + /// The to monitor for cancellation requests. The default is . /// A task containing the client's response. Task SendRequestAsync(JsonRpcRequest request, CancellationToken cancellationToken = default); /// Sends a message to the connected endpoint. /// The message. - /// A token to cancel the operation. + /// The to monitor for cancellation requests. The default is . Task SendMessageAsync(IJsonRpcMessage message, CancellationToken cancellationToken = default); /// diff --git a/src/ModelContextProtocol/McpEndpointExtensions.cs b/src/ModelContextProtocol/McpEndpointExtensions.cs index d2a6a952..b099019e 100644 --- a/src/ModelContextProtocol/McpEndpointExtensions.cs +++ b/src/ModelContextProtocol/McpEndpointExtensions.cs @@ -20,7 +20,7 @@ public static class McpEndpointExtensions /// Object representing the request parameters. /// The request id for the request. /// The options governing request serialization. - /// A token to cancel the operation. + /// The to monitor for cancellation requests. The default is . /// A task that represents the asynchronous operation. The task result contains the deserialized result. public static Task SendRequestAsync( this IMcpEndpoint endpoint, @@ -50,7 +50,7 @@ public static Task SendRequestAsync( /// The type information for request parameter serialization. /// The type information for request parameter deserialization. /// The request id for the request. - /// A token to cancel the operation. + /// The to monitor for cancellation requests. The default is . /// A task that represents the asynchronous operation. The task result contains the deserialized result. internal static async Task SendRequestAsync( this IMcpEndpoint endpoint, @@ -87,7 +87,7 @@ internal static async Task SendRequestAsync( /// /// The client. /// The notification method name. - /// A token to cancel the operation. + /// The to monitor for cancellation requests. The default is . public static Task SendNotificationAsync(this IMcpEndpoint client, string method, CancellationToken cancellationToken = default) { Throw.IfNull(client); @@ -102,7 +102,7 @@ public static Task SendNotificationAsync(this IMcpEndpoint client, string method /// The JSON-RPC method name to invoke. /// Object representing the request parameters. /// The options governing request serialization. - /// A token to cancel the operation. + /// The to monitor for cancellation requests. The default is . public static Task SendNotificationAsync( this IMcpEndpoint endpoint, string method, @@ -124,7 +124,7 @@ public static Task SendNotificationAsync( /// The JSON-RPC method name to invoke. /// Object representing the request parameters. /// The type information for request parameter serialization. - /// A token to cancel the operation. + /// The to monitor for cancellation requests. The default is . internal static Task SendNotificationAsync( this IMcpEndpoint endpoint, string method, @@ -144,7 +144,7 @@ internal static Task SendNotificationAsync( /// The endpoint issuing the notification. /// The identifying the operation. /// The progress update to send. - /// A token to cancel the operation. + /// The to monitor for cancellation requests. The default is . /// A task representing the completion of the operation. /// is . public static Task NotifyProgressAsync( diff --git a/src/ModelContextProtocol/McpException.cs b/src/ModelContextProtocol/McpException.cs new file mode 100644 index 00000000..3fea1128 --- /dev/null +++ b/src/ModelContextProtocol/McpException.cs @@ -0,0 +1,56 @@ +namespace ModelContextProtocol; + +/// +/// An exception that is thrown when an MCP configuration or protocol error occurs. +/// +public class McpException : Exception +{ + /// + /// Initializes a new instance of the class. + /// + public McpException() + { + } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The message that describes the error. + public McpException(string message) : base(message) + { + } + + /// + /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. + /// + /// The message that describes the error. + /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified. + public McpException(string message, Exception? innerException) : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. + /// + /// The message that describes the error. + /// A JSON-RPC error code, if this exception represents a JSON-RPC error. + public McpException(string message, int? errorCode) : this(message, null, errorCode) + { + } + + /// + /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. + /// + /// The message that describes the error. + /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified. + /// A JSON-RPC error code, if this exception represents a JSON-RPC error. + public McpException(string message, Exception? innerException, int? errorCode) : base(message, innerException) + { + ErrorCode = errorCode; + } + + /// + /// Gets the error code if this exception was caused by a JSON-RPC error response. + /// + public int? ErrorCode { get; } +} \ No newline at end of file diff --git a/src/ModelContextProtocol/Protocol/Transport/IClientTransport.cs b/src/ModelContextProtocol/Protocol/Transport/IClientTransport.cs index c68f6cef..48ec1dbb 100644 --- a/src/ModelContextProtocol/Protocol/Transport/IClientTransport.cs +++ b/src/ModelContextProtocol/Protocol/Transport/IClientTransport.cs @@ -8,7 +8,7 @@ public interface IClientTransport /// /// Asynchronously establishes a transport session with an MCP server and returns an interface for the duplex JSON-RPC message stream. /// - /// Token to cancel the operation. + /// The to monitor for cancellation requests. The default is . /// Returns an interface for the duplex JSON-RPC message stream. Task ConnectAsync(CancellationToken cancellationToken = default); } diff --git a/src/ModelContextProtocol/Protocol/Transport/ITransport.cs b/src/ModelContextProtocol/Protocol/Transport/ITransport.cs index cb4fcc36..76174212 100644 --- a/src/ModelContextProtocol/Protocol/Transport/ITransport.cs +++ b/src/ModelContextProtocol/Protocol/Transport/ITransport.cs @@ -22,6 +22,6 @@ public interface ITransport : IAsyncDisposable /// Sends a message through the transport. /// /// The message to send. - /// Token to cancel the operation. + /// The to monitor for cancellation requests. The default is . Task SendMessageAsync(IJsonRpcMessage message, CancellationToken cancellationToken = default); } diff --git a/src/ModelContextProtocol/Protocol/Transport/SseResponseStreamTransport.cs b/src/ModelContextProtocol/Protocol/Transport/SseResponseStreamTransport.cs index d4e39c8a..de1cb711 100644 --- a/src/ModelContextProtocol/Protocol/Transport/SseResponseStreamTransport.cs +++ b/src/ModelContextProtocol/Protocol/Transport/SseResponseStreamTransport.cs @@ -28,7 +28,7 @@ public sealed class SseResponseStreamTransport(Stream sseResponseStream, string /// Starts the transport and writes the JSON-RPC messages sent via /// to the SSE response stream until cancelled or disposed. /// - /// A token to cancel writing to the SSE response stream. + /// The to monitor for cancellation requests. The default is . /// A task representing the send loop that writes JSON-RPC messages to the SSE response stream. public Task RunAsync(CancellationToken cancellationToken) { @@ -84,7 +84,7 @@ public async Task SendMessageAsync(IJsonRpcMessage message, CancellationToken ca /// Handles incoming JSON-RPC messages received on the /message endpoint. /// /// The JSON-RPC message received. - /// A token to cancel the operation. + /// The to monitor for cancellation requests. The default is . /// A task representing the potentially asynchronous operation to buffer or process the JSON-RPC message. /// Thrown when there is an attempt to process a message before calling . public async Task OnMessageReceivedAsync(IJsonRpcMessage message, CancellationToken cancellationToken) diff --git a/src/ModelContextProtocol/Protocol/Transport/TransportBase.cs b/src/ModelContextProtocol/Protocol/Transport/TransportBase.cs index 35576168..75a9a9b5 100644 --- a/src/ModelContextProtocol/Protocol/Transport/TransportBase.cs +++ b/src/ModelContextProtocol/Protocol/Transport/TransportBase.cs @@ -45,7 +45,7 @@ protected TransportBase(ILoggerFactory? loggerFactory) /// Writes a message to the message channel. /// /// The message to write. - /// Token to cancel the operation. + /// The to monitor for cancellation requests. The default is . protected async Task WriteMessageAsync(IJsonRpcMessage message, CancellationToken cancellationToken = default) { if (!_isConnected) diff --git a/src/ModelContextProtocol/Server/McpServer.cs b/src/ModelContextProtocol/Server/McpServer.cs index 499f2efa..764736e2 100644 --- a/src/ModelContextProtocol/Server/McpServer.cs +++ b/src/ModelContextProtocol/Server/McpServer.cs @@ -25,7 +25,7 @@ internal sealed class McpServer : McpEndpoint, IMcpServer /// Make sure to accurately reflect exactly what capabilities the server supports and does not support. /// Logger factory to use for logging /// Optional service provider to use for dependency injection - /// + /// The server was incorrectly configured. public McpServer(ITransport transport, McpServerOptions options, ILoggerFactory? loggerFactory, IServiceProvider? serviceProvider) : base(loggerFactory) { @@ -185,7 +185,7 @@ private void SetResourcesHandler(McpServerOptions options) if ((listResourcesHandler is not { } && listResourceTemplatesHandler is not { }) || resourcesCapability.ReadResourceHandler is not { } readResourceHandler) { - throw new McpServerException("Resources capability was enabled, but ListResources and/or ReadResource handlers were not specified."); + throw new McpException("Resources capability was enabled, but ListResources and/or ReadResource handlers were not specified."); } listResourcesHandler ??= (static (_, _) => Task.FromResult(new ListResourcesResult())); @@ -218,7 +218,7 @@ private void SetResourcesHandler(McpServerOptions options) var unsubscribeHandler = resourcesCapability.UnsubscribeFromResourcesHandler; if (subscribeHandler is null || unsubscribeHandler is null) { - throw new McpServerException("Resources capability was enabled with subscribe support, but SubscribeToResources and/or UnsubscribeFromResources handlers were not specified."); + throw new McpException("Resources capability was enabled with subscribe support, but SubscribeToResources and/or UnsubscribeFromResources handlers were not specified."); } SetRequestHandler( @@ -243,7 +243,7 @@ private void SetPromptsHandler(McpServerOptions options) if (listPromptsHandler is null != getPromptHandler is null) { - throw new McpServerException("ListPrompts and GetPrompt handlers should be specified together."); + throw new McpException("ListPrompts and GetPrompt handlers should be specified together."); } // Handle prompts provided via DI. @@ -276,7 +276,7 @@ await originalListPromptsHandler(request, cancellationToken).ConfigureAwait(fals return originalGetPromptHandler(request, cancellationToken); } - throw new McpServerException($"Unknown prompt '{request.Params?.Name}'"); + throw new McpException($"Unknown prompt '{request.Params?.Name}'"); } return prompt.GetAsync(request, cancellationToken); @@ -310,7 +310,7 @@ await originalListPromptsHandler(request, cancellationToken).ConfigureAwait(fals // Make sure the handlers are provided if the capability is enabled. if (listPromptsHandler is null || getPromptHandler is null) { - throw new McpServerException("ListPrompts and/or GetPrompt handlers were not specified but the Prompts capability was enabled."); + throw new McpException("ListPrompts and/or GetPrompt handlers were not specified but the Prompts capability was enabled."); } } @@ -336,7 +336,7 @@ private void SetToolsHandler(McpServerOptions options) if (listToolsHandler is null != callToolHandler is null) { - throw new McpServerException("ListTools and CallTool handlers should be specified together."); + throw new McpException("ListTools and CallTool handlers should be specified together."); } // Handle tools provided via DI. @@ -369,7 +369,7 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false) return originalCallToolHandler(request, cancellationToken); } - throw new McpServerException($"Unknown tool '{request.Params?.Name}'"); + throw new McpException($"Unknown tool '{request.Params?.Name}'"); } return tool.InvokeAsync(request, cancellationToken); @@ -403,7 +403,7 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false) // Make sure the handlers are provided if the capability is enabled. if (listToolsHandler is null || callToolHandler is null) { - throw new McpServerException("ListTools and/or CallTool handlers were not specified but the Tools capability was enabled."); + throw new McpException("ListTools and/or CallTool handlers were not specified but the Tools capability was enabled."); } } @@ -429,7 +429,7 @@ private void SetSetLoggingLevelHandler(McpServerOptions options) if (loggingCapability.SetLoggingLevelHandler is not { } setLoggingLevelHandler) { - throw new McpServerException("Logging capability was enabled, but SetLoggingLevelHandler was not specified."); + throw new McpException("Logging capability was enabled, but SetLoggingLevelHandler was not specified."); } SetRequestHandler( diff --git a/src/ModelContextProtocol/Server/McpServerException.cs b/src/ModelContextProtocol/Server/McpServerException.cs deleted file mode 100644 index 7d6a6c0a..00000000 --- a/src/ModelContextProtocol/Server/McpServerException.cs +++ /dev/null @@ -1,48 +0,0 @@ -using ModelContextProtocol.Client; - -namespace ModelContextProtocol.Server; - -/// -/// Represents errors that occur in the MCP server. -/// -public class McpServerException : Exception -{ - /// - /// Gets the error code if this exception was caused by a JSON-RPC error response. - /// - public int? ErrorCode { get; } - - /// - /// Initializes a new instance of the class with a specified error message. - /// - public McpServerException() - { - } - - /// - /// Initializes a new instance of the class with a specified error message. - /// - /// The message that describes the error. - public McpServerException(string message) : base(message) - { - } - - /// - /// Initializes a new instance of the class with a specified error message and error code. - /// - /// The message that describes the error. - /// The error code associated with the JSON-RPC error response. - public McpServerException(string message, int errorCode) : base(message) - { - ErrorCode = errorCode; - } - - /// - /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. - /// - /// The message that describes the error. - /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified. - public McpServerException(string message, Exception innerException) : base(message, innerException) - { - } -} \ No newline at end of file diff --git a/src/ModelContextProtocol/Server/McpServerExtensions.cs b/src/ModelContextProtocol/Server/McpServerExtensions.cs index 9b160d4c..b59992c9 100644 --- a/src/ModelContextProtocol/Server/McpServerExtensions.cs +++ b/src/ModelContextProtocol/Server/McpServerExtensions.cs @@ -41,7 +41,7 @@ public static Task RequestSamplingAsync( /// The server issueing the request. /// The messages to send as part of the request. /// The options to use for the request. - /// A token to cancel the operation. + /// The to monitor for cancellation requests. The default is . /// A task containing the response from the client. /// is . /// is . diff --git a/src/ModelContextProtocol/Shared/McpSession.cs b/src/ModelContextProtocol/Shared/McpSession.cs index d5e4f930..dae92686 100644 --- a/src/ModelContextProtocol/Shared/McpSession.cs +++ b/src/ModelContextProtocol/Shared/McpSession.cs @@ -1,6 +1,5 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using ModelContextProtocol.Client; using ModelContextProtocol.Logging; using ModelContextProtocol.Protocol.Messages; using ModelContextProtocol.Protocol.Transport; @@ -147,7 +146,7 @@ await _transport.SendMessageAsync(new JsonRpcError JsonRpc = "2.0", Error = new JsonRpcErrorDetail { - Code = (ex as McpServerException)?.ErrorCode ?? ErrorCodes.InternalError, + Code = (ex as McpException)?.ErrorCode ?? ErrorCodes.InternalError, Message = ex.Message } }, cancellationToken).ConfigureAwait(false); @@ -298,7 +297,7 @@ private async Task HandleRequest(JsonRpcRequest request, CancellationToken cance if (!_requestHandlers.TryGetValue(request.Method, out var handler)) { _logger.NoHandlerFoundForRequest(EndpointName, request.Method); - throw new McpServerException("The method does not exist or is not available.", ErrorCodes.MethodNotFound); + throw new McpException("The method does not exist or is not available.", ErrorCodes.MethodNotFound); } _logger.RequestHandlerCalled(EndpointName, request.Method); @@ -318,14 +317,14 @@ await _transport.SendMessageAsync(new JsonRpcResponse /// Use this method for custom requests or those not yet covered explicitly by the endpoint implementation. /// /// The JSON-RPC request to send. - /// A token to cancel the operation. + /// The to monitor for cancellation requests. The default is . /// A task containing the server's response. public async Task SendRequestAsync(JsonRpcRequest request, CancellationToken cancellationToken) { if (!_transport.IsConnected) { _logger.EndpointNotConnected(EndpointName); - throw new McpClientException("Transport is not connected"); + throw new McpException("Transport is not connected"); } Histogram durationMetric = _isServer ? s_serverRequestDuration : s_clientRequestDuration; @@ -372,7 +371,7 @@ public async Task SendRequestAsync(JsonRpcRequest request, Canc if (response is JsonRpcError error) { _logger.RequestFailed(EndpointName, request.Method, error.Error.Message, error.Error.Code); - throw new McpClientException($"Request failed (server side): {error.Error.Message}", error.Error.Code); + throw new McpException($"Request failed (server side): {error.Error.Message}", error.Error.Code); } if (response is JsonRpcResponse success) @@ -384,7 +383,7 @@ public async Task SendRequestAsync(JsonRpcRequest request, Canc // Unexpected response type _logger.RequestInvalidResponseType(EndpointName, request.Method); - throw new McpClientException("Invalid response type"); + throw new McpException("Invalid response type"); } catch (Exception ex) when (addTags) { @@ -405,7 +404,7 @@ public async Task SendMessageAsync(IJsonRpcMessage message, CancellationToken ca if (!_transport.IsConnected) { _logger.ClientNotConnected(EndpointName); - throw new McpClientException("Transport is not connected"); + throw new McpException("Transport is not connected"); } Histogram durationMetric = _isServer ? s_serverRequestDuration : s_clientRequestDuration; @@ -529,8 +528,7 @@ private static void AddExceptionTags(ref TagList tags, Exception e) { tags.Add("error.type", e.GetType().FullName); tags.Add("rpc.jsonrpc.error_code", - (e as McpClientException)?.ErrorCode is int clientError ? clientError : - (e as McpServerException)?.ErrorCode is int serverError ? serverError : + (e as McpException)?.ErrorCode is int errorCode ? errorCode : e is JsonException ? ErrorCodes.ParseError : ErrorCodes.InternalError); } diff --git a/tests/ModelContextProtocol.TestServer/Program.cs b/tests/ModelContextProtocol.TestServer/Program.cs index 6e5655a3..830a042a 100644 --- a/tests/ModelContextProtocol.TestServer/Program.cs +++ b/tests/ModelContextProtocol.TestServer/Program.cs @@ -164,7 +164,7 @@ private static ToolsCapability ConfigureTools() { if (request.Params?.Arguments is null || !request.Params.Arguments.TryGetValue("message", out var message)) { - throw new McpServerException("Missing required argument 'message'"); + throw new McpException("Missing required argument 'message'"); } return new CallToolResponse() { @@ -177,7 +177,7 @@ private static ToolsCapability ConfigureTools() !request.Params.Arguments.TryGetValue("prompt", out var prompt) || !request.Params.Arguments.TryGetValue("maxTokens", out var maxTokens)) { - throw new McpServerException("Missing required arguments 'prompt' and 'maxTokens'"); + throw new McpException("Missing required arguments 'prompt' and 'maxTokens'"); } var sampleResult = await request.Server.RequestSamplingAsync(CreateRequestSamplingParams(prompt.ToString(), "sampleLLM", Convert.ToInt32(maxTokens.GetRawText())), cancellationToken); @@ -189,7 +189,7 @@ private static ToolsCapability ConfigureTools() } else { - throw new McpServerException($"Unknown tool: {request.Params?.Name}"); + throw new McpException($"Unknown tool: {request.Params?.Name}"); } } }; @@ -283,7 +283,7 @@ private static PromptsCapability ConfigurePrompts() } else { - throw new McpServerException($"Unknown prompt: {request.Params?.Name}"); + throw new McpException($"Unknown prompt: {request.Params?.Name}"); } return Task.FromResult(new GetPromptResult() @@ -304,7 +304,7 @@ private static LoggingCapability ConfigureLogging() { if (request.Params?.Level is null) { - throw new McpServerException("Missing required argument 'level'"); + throw new McpException("Missing required argument 'level'"); } _minimumLoggingLevel = request.Params.Level; @@ -384,9 +384,9 @@ private static ResourcesCapability ConfigureResources() var startIndexAsString = Encoding.UTF8.GetString(Convert.FromBase64String(request.Params.Cursor)); startIndex = Convert.ToInt32(startIndexAsString); } - catch + catch (Exception e) { - throw new McpServerException("Invalid cursor"); + throw new McpException("Invalid cursor.", e); } } @@ -408,7 +408,7 @@ private static ResourcesCapability ConfigureResources() { if (request.Params?.Uri is null) { - throw new McpServerException("Missing required argument 'uri'"); + throw new McpException("Missing required argument 'uri'"); } if (request.Params.Uri.StartsWith("test://dynamic/resource/")) @@ -416,7 +416,7 @@ private static ResourcesCapability ConfigureResources() var id = request.Params.Uri.Split('/').LastOrDefault(); if (string.IsNullOrEmpty(id)) { - throw new McpServerException("Invalid resource URI"); + throw new McpException("Invalid resource URI"); } return Task.FromResult(new ReadResourceResult() { @@ -432,7 +432,7 @@ private static ResourcesCapability ConfigureResources() } ResourceContents contents = resourceContents.FirstOrDefault(r => r.Uri == request.Params.Uri) - ?? throw new McpServerException("Resource not found"); + ?? throw new McpException("Resource not found"); return Task.FromResult(new ReadResourceResult() { @@ -444,12 +444,12 @@ private static ResourcesCapability ConfigureResources() { if (request?.Params?.Uri is null) { - throw new McpServerException("Missing required argument 'uri'"); + throw new McpException("Missing required argument 'uri'"); } if (!request.Params.Uri.StartsWith("test://static/resource/") && !request.Params.Uri.StartsWith("test://dynamic/resource/")) { - throw new McpServerException("Invalid resource URI"); + throw new McpException("Invalid resource URI"); } _subscribedResources.TryAdd(request.Params.Uri, true); @@ -461,12 +461,12 @@ private static ResourcesCapability ConfigureResources() { if (request?.Params?.Uri is null) { - throw new McpServerException("Missing required argument 'uri'"); + throw new McpException("Missing required argument 'uri'"); } if (!request.Params.Uri.StartsWith("test://static/resource/") && !request.Params.Uri.StartsWith("test://dynamic/resource/")) { - throw new McpServerException("Invalid resource URI"); + throw new McpException("Invalid resource URI"); } _subscribedResources.Remove(request.Params.Uri, out _); @@ -511,7 +511,7 @@ private static Func, CancellationToken, Ta return Task.FromResult(new CompleteResult() { Completion = new() { Values = values, HasMore = false, Total = values.Length } }); } - throw new McpServerException($"Unknown reference type: {request.Params?.Ref.Type}"); + throw new McpException($"Unknown reference type: {request.Params?.Ref.Type}"); }; } diff --git a/tests/ModelContextProtocol.TestSseServer/Program.cs b/tests/ModelContextProtocol.TestSseServer/Program.cs index d5a24c99..e4bd996b 100644 --- a/tests/ModelContextProtocol.TestSseServer/Program.cs +++ b/tests/ModelContextProtocol.TestSseServer/Program.cs @@ -154,13 +154,13 @@ static CreateMessageRequestParams CreateRequestSamplingParams(string context, st { if (request.Params is null) { - throw new McpServerException("Missing required parameter 'name'"); + throw new McpException("Missing required parameter 'name'"); } if (request.Params.Name == "echo") { if (request.Params.Arguments is null || !request.Params.Arguments.TryGetValue("message", out var message)) { - throw new McpServerException("Missing required argument 'message'"); + throw new McpException("Missing required argument 'message'"); } return new CallToolResponse() { @@ -173,7 +173,7 @@ static CreateMessageRequestParams CreateRequestSamplingParams(string context, st !request.Params.Arguments.TryGetValue("prompt", out var prompt) || !request.Params.Arguments.TryGetValue("maxTokens", out var maxTokens)) { - throw new McpServerException("Missing required arguments 'prompt' and 'maxTokens'"); + throw new McpException("Missing required arguments 'prompt' and 'maxTokens'"); } var sampleResult = await request.Server.RequestSamplingAsync(CreateRequestSamplingParams(prompt.ToString(), "sampleLLM", Convert.ToInt32(maxTokens.ToString())), cancellationToken); @@ -185,7 +185,7 @@ static CreateMessageRequestParams CreateRequestSamplingParams(string context, st } else { - throw new McpServerException($"Unknown tool: {request.Params.Name}"); + throw new McpException($"Unknown tool: {request.Params.Name}"); } } }, @@ -219,7 +219,7 @@ static CreateMessageRequestParams CreateRequestSamplingParams(string context, st } catch { - throw new McpServerException("Invalid cursor"); + throw new McpException("Invalid cursor"); } } @@ -240,7 +240,7 @@ static CreateMessageRequestParams CreateRequestSamplingParams(string context, st { if (request.Params?.Uri is null) { - throw new McpServerException("Missing required argument 'uri'"); + throw new McpException("Missing required argument 'uri'"); } if (request.Params.Uri.StartsWith("test://dynamic/resource/")) @@ -248,7 +248,7 @@ static CreateMessageRequestParams CreateRequestSamplingParams(string context, st var id = request.Params.Uri.Split('/').LastOrDefault(); if (string.IsNullOrEmpty(id)) { - throw new McpServerException("Invalid resource URI"); + throw new McpException("Invalid resource URI"); } return Task.FromResult(new ReadResourceResult() { @@ -264,7 +264,7 @@ static CreateMessageRequestParams CreateRequestSamplingParams(string context, st } ResourceContents? contents = resourceContents.FirstOrDefault(r => r.Uri == request.Params.Uri) ?? - throw new McpServerException("Resource not found"); + throw new McpException("Resource not found"); return Task.FromResult(new ReadResourceResult() { @@ -311,7 +311,7 @@ static CreateMessageRequestParams CreateRequestSamplingParams(string context, st { if (request.Params is null) { - throw new McpServerException("Missing required parameter 'name'"); + throw new McpException("Missing required parameter 'name'"); } List messages = new(); if (request.Params.Name == "simple_prompt") @@ -361,7 +361,7 @@ static CreateMessageRequestParams CreateRequestSamplingParams(string context, st } else { - throw new McpServerException($"Unknown prompt: {request.Params.Name}"); + throw new McpException($"Unknown prompt: {request.Params.Name}"); } return Task.FromResult(new GetPromptResult() diff --git a/tests/ModelContextProtocol.Tests/ClientIntegrationTests.cs b/tests/ModelContextProtocol.Tests/ClientIntegrationTests.cs index a6d0a9b6..705a0779 100644 --- a/tests/ModelContextProtocol.Tests/ClientIntegrationTests.cs +++ b/tests/ModelContextProtocol.Tests/ClientIntegrationTests.cs @@ -175,7 +175,7 @@ public async Task GetPrompt_NonExistent_ThrowsException(string clientId) // act await using var client = await _fixture.CreateClientAsync(clientId); - await Assert.ThrowsAsync(() => + await Assert.ThrowsAsync(() => client.GetPromptAsync("non_existent_prompt", null, cancellationToken: TestContext.Current.CancellationToken)); } diff --git a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsPromptsTests.cs b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsPromptsTests.cs index 26d834b9..bbbbb05d 100644 --- a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsPromptsTests.cs +++ b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsPromptsTests.cs @@ -217,7 +217,7 @@ public async Task Throws_When_Prompt_Fails() { IMcpClient client = await CreateMcpClientForServer(); - await Assert.ThrowsAsync(async () => await client.GetPromptAsync( + await Assert.ThrowsAsync(async () => await client.GetPromptAsync( nameof(SimplePrompts.ThrowsException), cancellationToken: TestContext.Current.CancellationToken)); } @@ -227,7 +227,7 @@ public async Task Throws_Exception_On_Unknown_Prompt() { IMcpClient client = await CreateMcpClientForServer(); - var e = await Assert.ThrowsAsync(async () => await client.GetPromptAsync( + var e = await Assert.ThrowsAsync(async () => await client.GetPromptAsync( "NotRegisteredPrompt", cancellationToken: TestContext.Current.CancellationToken)); @@ -239,7 +239,7 @@ public async Task Throws_Exception_Missing_Parameter() { IMcpClient client = await CreateMcpClientForServer(); - var e = await Assert.ThrowsAsync(async () => await client.GetPromptAsync( + var e = await Assert.ThrowsAsync(async () => await client.GetPromptAsync( nameof(SimplePrompts.ReturnsChatMessages), cancellationToken: TestContext.Current.CancellationToken)); diff --git a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsToolsTests.cs b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsToolsTests.cs index 3c8981b6..79ae117f 100644 --- a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsToolsTests.cs +++ b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsToolsTests.cs @@ -435,7 +435,7 @@ public async Task Throws_Exception_On_Unknown_Tool() { IMcpClient client = await CreateMcpClientForServer(); - var e = await Assert.ThrowsAsync(async () => await client.CallToolAsync( + var e = await Assert.ThrowsAsync(async () => await client.CallToolAsync( "NotRegisteredTool", cancellationToken: TestContext.Current.CancellationToken)); diff --git a/tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs b/tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs index ff946333..1c7e6613 100644 --- a/tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs +++ b/tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs @@ -95,6 +95,6 @@ public void CreatingReadHandlerWithNoListHandlerFails() }); }); var sp = services.BuildServiceProvider(); - Assert.Throws(() => sp.GetRequiredService()); + Assert.Throws(() => sp.GetRequiredService()); } } diff --git a/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs b/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs index e0a4a6e4..619dcfde 100644 --- a/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs +++ b/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs @@ -95,7 +95,7 @@ public async Task RunAsync_Should_Throw_InvalidOperationException_If_Already_Run } [Fact] - public async Task RequestSamplingAsync_Should_Throw_McpServerException_If_Client_Does_Not_Support_Sampling() + public async Task RequestSamplingAsync_Should_Throw_McpException_If_Client_Does_Not_Support_Sampling() { // Arrange await using var transport = new TestServerTransport(); @@ -131,7 +131,7 @@ public async Task RequestSamplingAsync_Should_SendRequest() } [Fact] - public async Task RequestRootsAsync_Should_Throw_McpServerException_If_Client_Does_Not_Support_Roots() + public async Task RequestRootsAsync_Should_Throw_McpException_If_Client_Does_Not_Support_Roots() { // Arrange await using var transport = new TestServerTransport(); @@ -523,7 +523,7 @@ private async Task Throws_Exception_If_No_Handler_Assigned(ServerCapabilities se await using var transport = new TestServerTransport(); var options = CreateOptions(serverCapabilities); - Assert.Throws(() => McpServerFactory.Create(transport, options, LoggerFactory)); + Assert.Throws(() => McpServerFactory.Create(transport, options, LoggerFactory)); } [Fact] diff --git a/tests/ModelContextProtocol.Tests/SseServerIntegrationTests.cs b/tests/ModelContextProtocol.Tests/SseServerIntegrationTests.cs index b73a9c06..070bfd1c 100644 --- a/tests/ModelContextProtocol.Tests/SseServerIntegrationTests.cs +++ b/tests/ModelContextProtocol.Tests/SseServerIntegrationTests.cs @@ -209,7 +209,7 @@ public async Task GetPrompt_Sse_NonExistent_ThrowsException() // act await using var client = await GetClientAsync(); - await Assert.ThrowsAsync(() => + await Assert.ThrowsAsync(() => client.GetPromptAsync("non_existent_prompt", null, cancellationToken: TestContext.Current.CancellationToken)); }