diff --git a/src/mcpdotnet/Client/IMcpClient.cs b/src/mcpdotnet/Client/IMcpClient.cs index 6d2daa4..05bd6b1 100644 --- a/src/mcpdotnet/Client/IMcpClient.cs +++ b/src/mcpdotnet/Client/IMcpClient.cs @@ -5,6 +5,8 @@ using McpDotNet.Protocol.Messages; /// /// Represents an instance of the MCP client connecting to a specific server. +/// Use PingAsync, ListToolsAsync and CallToolAsync instead of SendRequestAsync for common operations. +/// SendRequestAsync is provided for custom request and those not yet covered explicitly by the client. /// public interface IMcpClient : IAsyncDisposable { @@ -14,44 +16,58 @@ public interface IMcpClient : IAsyncDisposable bool IsInitialized { get; } /// - /// Gets the capabilities that the server supports. + /// Gets the capabilities supported by the server. /// ServerCapabilities? ServerCapabilities { get; } /// - /// Gets the version information of the server. + /// Gets the version and implementation information of the server. /// Implementation? ServerInfo { get; } /// - /// Connects to the server. + /// Establishes a connection to the server. /// + /// A token to cancel the operation. + /// A task representing the asynchronous operation. Task ConnectAsync(CancellationToken cancellationToken = default); /// - /// A simple ping to the server to check if it is alive. Task completes when the ping is successful. + /// Sends a ping request to verify server connectivity. /// + /// A token to cancel the operation. + /// A task that completes when the ping is successful. Task PingAsync(CancellationToken cancellationToken = default); /// - /// Queries the server for a list of tools that are available (if any) + /// Retrieves a list of available tools from the server. /// + /// A token to cancel the operation. + /// A task containing the server's response with tool information. Task ListToolsAsync(CancellationToken cancellationToken = default); /// - /// Calls a tool on the server. + /// Invokes a tool on the server with optional arguments. /// - /// The name of the tool as given when listing tool definitions. - /// Arguments for the tool (if any) + /// The name of the tool to call. + /// Optional arguments for the tool. + /// A token to cancel the operation. + /// A task containing the tool's response. Task CallToolAsync(string toolName, Dictionary? arguments = null, CancellationToken cancellationToken = default); /// - /// A generic method to send a request to the server and get a response. + /// Sends a generic JSON-RPC request to the server. /// + /// The expected response type. + /// The JSON-RPC request to send. + /// A token to cancel the operation. + /// A task containing the server's response. Task SendRequestAsync(JsonRpcRequest request, CancellationToken cancellationToken) where T : class; /// - /// Registers a handler for a specific notification method. + /// Registers a handler for server notifications of a specific method. /// + /// The notification method to handle. + /// The async handler function to process notifications. void OnNotification(string method, Func handler); -} +} \ No newline at end of file diff --git a/src/mcpdotnet/Client/McpClient.cs b/src/mcpdotnet/Client/McpClient.cs index f0b56fb..6a6dc98 100644 --- a/src/mcpdotnet/Client/McpClient.cs +++ b/src/mcpdotnet/Client/McpClient.cs @@ -1,5 +1,4 @@ -// Client/McpClientOptions.cs -namespace McpDotNet.Client; +namespace McpDotNet.Client; using McpDotNet.Protocol.Types; using McpDotNet.Protocol.Transport; @@ -9,10 +8,8 @@ using System.Diagnostics; using System.Text.Json; using McpDotNet.Utils.Json; -/// -/// Main MCP client implementation that handles protocol lifecycle and message routing. -/// -public class McpClient : IMcpClient +/// +internal class McpClient : IMcpClient { private readonly IMcpTransport _transport; private readonly McpClientOptions _options; @@ -24,21 +21,20 @@ public class McpClient : IMcpClient private CancellationTokenSource? _cts; private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions().ConfigureForMcp(); - /// - /// Gets whether the client is connected and initialized. - /// + /// public bool IsInitialized => _isInitialized; - /// - /// Gets the server's capabilities after initialization. - /// + /// public ServerCapabilities? ServerCapabilities { get; private set; } - /// - /// Gets information about the server implementation after initialization. - /// + /// public Implementation? ServerInfo { get; private set; } + /// + /// Initializes a new instance of the class. + /// + /// An MCP transport implementation. + /// Options for the client, defining protocol version and capabilities. public McpClient(IMcpTransport transport, McpClientOptions options) { _transport = transport; @@ -48,9 +44,7 @@ public class McpClient : IMcpClient _nextRequestId = 1; } - /// - /// Connects to the server and performs the initialization sequence. - /// + /// public async Task ConnectAsync(CancellationToken cancellationToken = default) { if (_isInitialized) @@ -128,6 +122,7 @@ public class McpClient : IMcpClient } } + /// public async Task PingAsync(CancellationToken cancellationToken) { await SendRequestAsync( @@ -139,6 +134,7 @@ public class McpClient : IMcpClient ).ConfigureAwait(false); } + /// public async Task ListToolsAsync(CancellationToken cancellationToken) { return await SendRequestAsync( @@ -151,6 +147,7 @@ public class McpClient : IMcpClient ).ConfigureAwait(false); } + /// public async Task CallToolAsync(string toolName, Dictionary? arguments = null, CancellationToken cancellationToken = default) { @@ -218,6 +215,7 @@ public class McpClient : IMcpClient } } + /// public async Task SendRequestAsync(JsonRpcRequest request, CancellationToken cancellationToken) where T : class { if (!_transport.IsConnected) @@ -275,9 +273,7 @@ public class McpClient : IMcpClient await _transport.SendMessageAsync(notification, cancellationToken).ConfigureAwait(false); } - /// - /// Registers a handler for notifications of a specific method. - /// + /// public void OnNotification(string method, Func handler) { var handlers = _notificationHandlers.GetOrAdd(method, _ => new()); diff --git a/src/mcpdotnet/Client/McpClientException.cs b/src/mcpdotnet/Client/McpClientException.cs index 900542f..f6692d5 100644 --- a/src/mcpdotnet/Client/McpClientException.cs +++ b/src/mcpdotnet/Client/McpClientException.cs @@ -1,5 +1,4 @@ -// Client/McpClientOptions.cs -namespace McpDotNet.Client; +namespace McpDotNet.Client; /// /// Represents errors that occur in the MCP client. @@ -11,15 +10,29 @@ public class McpClientException : Exception /// public int? ErrorCode { get; } + /// + /// 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) { } diff --git a/src/mcpdotnet/Client/McpClientFactory.cs b/src/mcpdotnet/Client/McpClientFactory.cs index 2561907..7b9200f 100644 --- a/src/mcpdotnet/Client/McpClientFactory.cs +++ b/src/mcpdotnet/Client/McpClientFactory.cs @@ -1,11 +1,13 @@ -// Client/McpClientFactory.cs -namespace McpDotNet.Client; +namespace McpDotNet.Client; using McpDotNet.Configuration; using McpDotNet.Protocol.Transport; /// -/// Factory for creating MCP clients based on configuration. +/// Factory for creating MCP clients based on configuration. It caches clients for reuse, so it is safe to call GetClientAsync multiple times. +/// Call GetClientAsync to get a client for a specific server (by ID), which will create a new client and connect if it doesn't already exist. +/// All server configurations must be passed in the constructor. +/// Capabilities (as defined in client options) are shared across all clients, as the client host can always decide not to use certain capabilities. /// public class McpClientFactory { @@ -15,6 +17,15 @@ public class McpClientFactory private readonly Func _transportFactoryMethod; private readonly Func _clientFactoryMethod; + /// + /// Initializes a new instance of the class. + /// It is not necessary to pass factory methods for creating transports and clients, as default implementations are provided. + /// Custom factory methods can be provided for mocking or to use custom transport or client implementations. + /// + /// Configuration objects for each server the factory should support. + /// A configuration object which specifies client capabilities and protocol version. + /// An optional factory method which returns transport implementations based on a server configuration. + /// An optional factory method which creates a client based on client options and transport implementation. public McpClientFactory( IEnumerable serverConfigs, McpClientOptions clientOptions, @@ -28,8 +39,12 @@ public class McpClientFactory } /// - /// Gets or creates a client for the specified server. + /// Gets or creates a client for the specified server. The first time a server is requested, a new client is created and connected. + /// Note that this will often spawn the server process during connection, so in some cases you want to call this method only when needed. + /// In other cases, you may want to call it ahead of time to ensure the server is ready when needed, as it may take some time to start up. /// + /// The ID of the server to connect to. It must have been passed in the serverConfigs when constructing the factory. + /// A token to cancel the operation. public async Task GetClientAsync(string serverId, CancellationToken cancellationToken = default) { if (!_serverConfigs.TryGetValue(serverId, out var config)) diff --git a/src/mcpdotnet/Client/McpClientOptions.cs b/src/mcpdotnet/Client/McpClientOptions.cs index 00d6475..8bc7902 100644 --- a/src/mcpdotnet/Client/McpClientOptions.cs +++ b/src/mcpdotnet/Client/McpClientOptions.cs @@ -1,10 +1,11 @@ -// Client/McpClientOptions.cs +using McpDotNet.Protocol.Types; + namespace McpDotNet.Client; -using global::McpDotNet.Protocol.Types; - /// -/// Configuration options for the MCP client. +/// Configuration options for the MCP client. This is passed to servers during the initialization sequence, letting them know about the client's capabilities and +/// protocol version. +/// See the protocol specification for details on capability negotiation /// public record McpClientOptions { diff --git a/src/mcpdotnet/Configuration/McpServerConfig.cs b/src/mcpdotnet/Configuration/McpServerConfig.cs index f245abd..3f9d735 100644 --- a/src/mcpdotnet/Configuration/McpServerConfig.cs +++ b/src/mcpdotnet/Configuration/McpServerConfig.cs @@ -1,5 +1,4 @@ -// Configuration/McpServerConfig.cs -namespace McpDotNet.Configuration; +namespace McpDotNet.Configuration; /// /// Configuration for an MCP server connection. diff --git a/src/mcpdotnet/Features/LlmIntegration/DefaultMcpToolProvider.cs b/src/mcpdotnet/Features/LlmIntegration/DefaultMcpToolProvider.cs deleted file mode 100644 index ba92f9c..0000000 --- a/src/mcpdotnet/Features/LlmIntegration/DefaultMcpToolProvider.cs +++ /dev/null @@ -1,188 +0,0 @@ -// Features/LlmIntegration/DefaultMcpToolProvider.cs -namespace McpDotNet.Features.LlmIntegration; - -using System.Text.Json; -using mcpdotnet.Features.Tools; -using McpDotNet.Client; -using McpDotNet.Features.Tools; - -/// -/// Default implementation of IMcpToolProvider that handles conversion between MCP and LLM-friendly formats. -/// -public class DefaultMcpToolProvider : IMcpToolProvider -{ - private readonly McpClient _client; - private readonly JsonSerializerOptions _jsonOptions; - - public DefaultMcpToolProvider(McpClient client, JsonSerializerOptions? jsonOptions = null) - { - _client = client; - _jsonOptions = jsonOptions ?? new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }; - } - - /// - public async Task> GetToolsAsync(CancellationToken cancellationToken = default) - { - return await _client.ListToolDefsAsync(cancellationToken); - } - - /// - public async Task ExecuteToolCallAsync( - string toolName, - Dictionary arguments, - CancellationToken cancellationToken = default) - { - try - { - // Execute tool call - var result = await _client.CallToolAsync(toolName, arguments, cancellationToken); - if (result != null) - { - return new ToolResult() - { - Succeeded = !result.IsError, - Content = result.Content.Select(c => new ToolContent() - { - Type = c.Type, - Value = (c.Data ?? c.Text) ?? "", - MimeType = c.MimeType - }).ToList() - }; - } - else - { - return new ToolResult - { - Succeeded = false, - Content = [new ToolContent - { - Type = "error", - Value = "Tool execution failed with no error message" - }], - }; - } - } - catch (Exception ex) - { - return new ToolResult - { - Succeeded = false, - Content = [new ToolContent - { - Type = "error", - Value = ex.Message - }], - }; - } - } - - - - //private static (Dictionary Arguments, List Required) ParseInputSchema(ToolInputSchema schema) - //{ - // var arguments = new Dictionary(); - // var required = new List(); - - // if (schema.Properties == null) - // { - // return (arguments, required); - // } - - // foreach (var (name, propObj) in schema.Properties) - // { - // // Convert property to JsonElement for easier parsing - // var prop = (propObj as JsonElement?) ?? JsonSerializer.SerializeToElement(propObj); - - // if (prop.TryGetProperty("type", out var typeElement)) - // { - // var type = typeElement.GetString() ?? "unknown"; - // var description = prop.TryGetProperty("description", out var descElement) - // ? descElement.GetString() - // : string.Empty; - - // var isRequired = prop.TryGetProperty("required", out var reqElement) && reqElement.GetBoolean(); - // if (isRequired) - // { - // required.Add(name); - // } - - // string[]? enumValues = null; - // if (type == "string" && prop.TryGetProperty("enum", out var enumElement)) - // { - // enumValues = enumElement.EnumerateArray() - // .Select(e => e.GetString()) - // .Where(s => s != null) - // .Cast() - // .ToArray(); - // } - - // arguments[name] = new ToolArgument - // { - // Name = name, - // Type = type, - // Description = description ?? string.Empty, - // Required = isRequired, - // EnumValues = enumValues - // }; - // } - // } - - // // Also check for required array at schema root - // if (schema.Properties.TryGetValue("required", out var rootRequired)) - // { - // var requiredArray = rootRequired as JsonElement?; - // if (requiredArray?.ValueKind == JsonValueKind.Array) - // { - // foreach (var item in requiredArray.Value.EnumerateArray()) - // { - // var name = item.GetString(); - // if (name != null && !required.Contains(name)) - // { - // required.Add(name); - - // // Update the argument's required flag if it exists - // if (arguments.TryGetValue(name, out var arg)) - // { - // arguments[name] = arg with { Required = true }; - // } - // } - // } - // } - // } - - // return (arguments, required); - //} - - //private static ToolResultContent ConvertContent(IContent content) - //{ - // return content switch - // { - // TextContent text => new ToolResultContent - // { - // Type = "text", - // Value = text.Text, - // }, - // ImageContent image => new ToolResultContent - // { - // Type = "image", - // Value = image.Data, - // MimeType = image.MimeType - // }, - // _ => new ToolResultContent - // { - // Type = "unknown", - // Value = content.ToString() ?? string.Empty - // } - // }; - //} - - //private static string ExtractErrorMessage(IReadOnlyList content) - //{ - // // Try to extract error message from content, defaulting to first text content if available - // var textContent = content.OfType().FirstOrDefault(); - // return textContent?.Text ?? "Tool execution failed with no error message"; - //} -} \ No newline at end of file diff --git a/src/mcpdotnet/Features/LlmIntegration/IMcpToolProvider.cs b/src/mcpdotnet/Features/LlmIntegration/IMcpToolProvider.cs deleted file mode 100644 index bab0503..0000000 --- a/src/mcpdotnet/Features/LlmIntegration/IMcpToolProvider.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Features/LlmIntegration/IMcpToolProvider.cs -using mcpdotnet.Features.Tools; - -namespace McpDotNet.Features.LlmIntegration; - -/// -/// Provides MCP tool information and execution capabilities to an LLM. -/// -public interface IMcpToolProvider -{ - /// - /// Gets the available tools and their descriptions. - /// - Task> GetToolsAsync(CancellationToken cancellationToken = default); - - /// - /// Executes a tool call. - /// - Task ExecuteToolCallAsync( - string toolName, - Dictionary arguments, - CancellationToken cancellationToken = default); -} \ No newline at end of file diff --git a/src/mcpdotnet/Features/Tools/ToolMapper.cs b/src/mcpdotnet/Features/Tools/ToolMapper.cs deleted file mode 100644 index 421253c..0000000 --- a/src/mcpdotnet/Features/Tools/ToolMapper.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System.Text.Json; -using mcpdotnet.Features.Tools; -using McpDotNet.Protocol.Types; - -namespace McpDotNet.Features.Tools; - -internal static class ToolMapper -{ - public static ToolDefinition ToToolDefinition(Tool protocolTool) - { - ArgumentNullException.ThrowIfNull(protocolTool); - ArgumentNullException.ThrowIfNull(protocolTool.InputSchema); - - var parameters = new Dictionary(); - - if (protocolTool.InputSchema.Properties != null) - { - foreach (var property in protocolTool.InputSchema.Properties) - { - // Convert the dynamic property value to a JsonElement to access its properties - var propertySchema = property.Value; - - parameters[property.Key] = new ToolParameter - { - Name = property.Key, - Type = propertySchema.Type, - Description = propertySchema.Description, - Required = IsPropertyRequired(protocolTool.InputSchema, property.Key), - AllowedValues = GetEnumValues(propertySchema) - }; - } - } - - return new ToolDefinition - { - Name = protocolTool.Name, - Description = protocolTool.Description ?? string.Empty, - Parameters = parameters - }; - } - - public static ToolResult ToToolResult(CallToolResponse protocolResult) - { - ArgumentNullException.ThrowIfNull(protocolResult); - - return new ToolResult - { - Succeeded = protocolResult.IsError, - Content = protocolResult.Content?.Select(ToToolContent).ToList(), - ErrorMessage = protocolResult.IsError == true ? - GetErrorMessage(protocolResult.Content) : null - }; - } - - private static mcpdotnet.Features.Tools.ToolContent ToToolContent(Protocol.Types.ToolContent c) - { - return new mcpdotnet.Features.Tools.ToolContent() - { - Type = c.Type, - Value = (c.Data ?? c.Text) ?? "", - MimeType = c.MimeType - }; - } - - private static string GetSchemaType(JsonElement schema) - { - if (!schema.TryGetProperty("type", out var typeElement)) - { - return "object"; - } - - return typeElement.GetString() ?? "object"; - } - - private static string GetSchemaDescription(JsonElement schema) - { - if (!schema.TryGetProperty("description", out var descElement)) - { - return string.Empty; - } - - return descElement.GetString() ?? string.Empty; - } - - private static bool IsPropertyRequired(JsonSchema? schema, string propertyName) - { - if (schema?.Required != null && schema.Required.Contains(propertyName)) - { - return true; - } - - return false; - } - - private static string[]? GetEnumValues(JsonSchemaProperty? schema) - { - // TODO - return null; - } - - private static string GetErrorMessage(IReadOnlyList? contents) - { - if (contents == null || contents.Count == 0) - { - return "Unknown error"; - } - - // Concatenate all text content as the error message - var errorMessages = contents - .Where(c => !string.IsNullOrEmpty(c.Text)) - .Select(c => c.Text); - - return string.Join(" ", errorMessages); - } -} \ No newline at end of file diff --git a/src/mcpdotnet/Features/Tools/ToolsExtensions.cs b/src/mcpdotnet/Features/Tools/ToolsExtensions.cs deleted file mode 100644 index efb65fe..0000000 --- a/src/mcpdotnet/Features/Tools/ToolsExtensions.cs +++ /dev/null @@ -1,61 +0,0 @@ -using mcpdotnet.Features.Tools; -using McpDotNet.Client; -using McpDotNet.Protocol.Messages; -using McpDotNet.Protocol.Types; - -namespace McpDotNet.Features.Tools; - -/// -/// Extension methods for working with MCP tools. -/// -public static class ToolsExtensions -{ - /// - /// Lists all available tools from the server. - /// - public static async Task> ListToolDefsAsync( - this McpClient client, - CancellationToken cancellationToken = default) - { - if (client.ServerCapabilities?.Tools == null) - { - throw new McpClientException("Server does not support tools"); - } - - var response = await client.ListToolsAsync(cancellationToken); - - return response.Tools - .Select(ToolMapper.ToToolDefinition) - .ToList(); - } - - /// - /// Calls a tool with the specified arguments. - /// - public static async Task CallToolAsync( - this McpClient client, - string toolName, - object? arguments = null, - CancellationToken cancellationToken = default) - { - if (client.ServerCapabilities?.Tools == null) - { - throw new McpClientException("Server does not support tools"); - } - - var response = await client.SendRequestAsync( - new JsonRpcRequest - { - Method = "tools/call", - Params = new - { - name = toolName, - arguments = arguments - } - }, - cancellationToken - ); - - return ToolMapper.ToToolResult(response); - } -} \ No newline at end of file diff --git a/src/mcpdotnet/Features/Tools/ToolsTypes.cs b/src/mcpdotnet/Features/Tools/ToolsTypes.cs deleted file mode 100644 index 740ccc1..0000000 --- a/src/mcpdotnet/Features/Tools/ToolsTypes.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Features/Tools/ToolTypes.cs -namespace mcpdotnet.Features.Tools; - -/// -/// Represents a tool available from an MCP server. -/// -public record ToolDefinition -{ - public required string Name { get; init; } - public required string Description { get; init; } - public required Dictionary Parameters { get; init; } -} - - -/// -/// Represents a tool parameter. -/// -public record ToolParameter -{ - public required string Name { get; init; } - public required string Type { get; init; } - public required string Description { get; init; } - public bool Required { get; init; } - public string[]? AllowedValues { get; init; } -} - -/// -/// Result of a tool execution, from server to client -public record McpToolCallResponse -{ - // TODO -} - -/// -/// Result of a tool execution, from client to host. -/// -public record ToolResult -{ - public required bool Succeeded { get; init; } - public List? Content { get; init; } - public string? ErrorMessage { get; init; } -} - -/// -/// Content returned by a tool execution. -/// -public record ToolContent -{ - /// - /// The type of content. - /// - public required string Type { get; init; } - - /// - /// The content value. - /// - public required object Value { get; init; } - - /// - /// Optional MIME type for binary content. - /// - public string? MimeType { get; init; } -} - -/// -/// A list of tools available from an MCP server. -/// -public record ToolDefinitionList -{ - /// - /// The list of tools. - /// - public required List Tools { get; init; } -} \ No newline at end of file diff --git a/src/mcpdotnet/Protocol/Types/BlobResourceContents.cs b/src/mcpdotnet/Protocol/Types/BlobResourceContents.cs new file mode 100644 index 0000000..325fbf9 --- /dev/null +++ b/src/mcpdotnet/Protocol/Types/BlobResourceContents.cs @@ -0,0 +1,21 @@ +namespace McpDotNet.Protocol.Types; + +using System.Text.Json.Serialization; + +/// +/// Represents binary resource contents. +/// +public record BlobResourceContents : IResourceContents +{ + [JsonPropertyName("uri")] + public required string Uri { get; init; } + + [JsonPropertyName("mimeType")] + public string? MimeType { get; init; } + + /// + /// The base64-encoded binary content of the resource. + /// + [JsonPropertyName("blob")] + public required string Blob { get; init; } +} \ No newline at end of file diff --git a/src/mcpdotnet/Protocol/Types/CallToolResponse.cs b/src/mcpdotnet/Protocol/Types/CallToolResponse.cs new file mode 100644 index 0000000..090a839 --- /dev/null +++ b/src/mcpdotnet/Protocol/Types/CallToolResponse.cs @@ -0,0 +1,18 @@ +namespace McpDotNet.Protocol.Types; + +/// +/// A response to a request to call a tool on the server. +/// See the schema for details +/// +public class CallToolResponse +{ + /// + /// The server's response to a tools/call request from the client. + /// + public List Content { get; set; } = new(); + + /// + /// Whether the tool call was unsuccessful. If true, the call was unsuccessful. + /// + public bool IsError { get; set; } +} diff --git a/src/mcpdotnet/Protocol/Types/Capabilities.cs b/src/mcpdotnet/Protocol/Types/Capabilities.cs index e6c7782..2c5273a 100644 --- a/src/mcpdotnet/Protocol/Types/Capabilities.cs +++ b/src/mcpdotnet/Protocol/Types/Capabilities.cs @@ -26,42 +26,6 @@ public record ClientCapabilities public SamplingCapability? Sampling { get; init; } } -/// -/// Represents the capabilities that a server may support. -/// -public record ServerCapabilities -{ - /// - /// Experimental, non-standard capabilities that the server supports. - /// - [JsonPropertyName("experimental")] - public Dictionary? Experimental { get; init; } - - /// - /// Present if the server supports sending log messages to the client. - /// - [JsonPropertyName("logging")] - public LoggingCapability? Logging { get; init; } - - /// - /// Present if the server offers any prompt templates. - /// - [JsonPropertyName("prompts")] - public PromptsCapability? Prompts { get; init; } - - /// - /// Present if the server offers any resources to read. - /// - [JsonPropertyName("resources")] - public ResourcesCapability? Resources { get; init; } - - /// - /// Present if the server offers any tools to call. - /// - [JsonPropertyName("tools")] - public ToolsCapability? Tools { get; init; } -} - /// /// Represents the roots capability configuration. /// diff --git a/src/mcpdotnet/Protocol/Types/IResourceContents.cs b/src/mcpdotnet/Protocol/Types/IResourceContents.cs new file mode 100644 index 0000000..a652966 --- /dev/null +++ b/src/mcpdotnet/Protocol/Types/IResourceContents.cs @@ -0,0 +1,10 @@ +namespace McpDotNet.Protocol.Types; + +/// +/// Interface for resource contents. +/// +public interface IResourceContents +{ + string Uri { get; } + string? MimeType { get; } +} diff --git a/src/mcpdotnet/Protocol/Types/Implementation.cs b/src/mcpdotnet/Protocol/Types/Implementation.cs index d4e730d..d93cd12 100644 --- a/src/mcpdotnet/Protocol/Types/Implementation.cs +++ b/src/mcpdotnet/Protocol/Types/Implementation.cs @@ -1,5 +1,4 @@ -// Protocol/Types/Implementation.cs -namespace McpDotNet.Protocol.Types; +namespace McpDotNet.Protocol.Types; using System.Text.Json.Serialization; diff --git a/src/mcpdotnet/Protocol/Types/JsonSchema.cs b/src/mcpdotnet/Protocol/Types/JsonSchema.cs new file mode 100644 index 0000000..d322f31 --- /dev/null +++ b/src/mcpdotnet/Protocol/Types/JsonSchema.cs @@ -0,0 +1,23 @@ +namespace McpDotNet.Protocol.Types; + +/// +/// Represents a JSON schema for a tool's input arguments. +/// See the schema for details +/// +public class JsonSchema +{ + /// + /// The type of the schema, should be "object". + /// + public string Type { get; set; } = string.Empty; + + /// + /// Map of property names to property definitions. + /// + public Dictionary? Properties { get; set; } + + /// + /// List of required property names. + /// + public List? Required { get; set; } +} diff --git a/src/mcpdotnet/Protocol/Types/JsonSchemaProperty.cs b/src/mcpdotnet/Protocol/Types/JsonSchemaProperty.cs new file mode 100644 index 0000000..35a5ede --- /dev/null +++ b/src/mcpdotnet/Protocol/Types/JsonSchemaProperty.cs @@ -0,0 +1,18 @@ +namespace McpDotNet.Protocol.Types; + +/// +/// Represents a property in a JSON schema. +/// See the schema for details +/// +public class JsonSchemaProperty +{ + /// + /// The type of the property. Should be a JSON Schema type and is required. + /// + public string Type { get; set; } = string.Empty; + + /// + /// A human-readable description of the property. + /// + public string? Description { get; set; } +} diff --git a/src/mcpdotnet/Protocol/Types/ListToolsResponse.cs b/src/mcpdotnet/Protocol/Types/ListToolsResponse.cs new file mode 100644 index 0000000..433c6c9 --- /dev/null +++ b/src/mcpdotnet/Protocol/Types/ListToolsResponse.cs @@ -0,0 +1,13 @@ +namespace McpDotNet.Protocol.Types; + +/// +/// A response to a request to list the tools available on the server. +/// See the schema for details +/// +public class ListToolsResponse +{ + /// + /// The server's response to a tools/list request from the client. + /// + public List Tools { get; set; } = new(); +} diff --git a/src/mcpdotnet/Protocol/Types/Resources.cs b/src/mcpdotnet/Protocol/Types/Resources.cs index 9f8e8c9..a746ff8 100644 --- a/src/mcpdotnet/Protocol/Types/Resources.cs +++ b/src/mcpdotnet/Protocol/Types/Resources.cs @@ -37,48 +37,3 @@ public record Resource [JsonPropertyName("annotations")] public Annotations? Annotations { get; init; } } - -/// -/// Interface for resource contents. -/// -public interface IResourceContents -{ - string Uri { get; } - string? MimeType { get; } -} - -/// -/// Represents text-based resource contents. -/// -public record TextResourceContents : IResourceContents -{ - [JsonPropertyName("uri")] - public required string Uri { get; init; } - - [JsonPropertyName("mimeType")] - public string? MimeType { get; init; } - - /// - /// The text content of the resource. - /// - [JsonPropertyName("text")] - public required string Text { get; init; } -} - -/// -/// Represents binary resource contents. -/// -public record BlobResourceContents : IResourceContents -{ - [JsonPropertyName("uri")] - public required string Uri { get; init; } - - [JsonPropertyName("mimeType")] - public string? MimeType { get; init; } - - /// - /// The base64-encoded binary content of the resource. - /// - [JsonPropertyName("blob")] - public required string Blob { get; init; } -} \ No newline at end of file diff --git a/src/mcpdotnet/Protocol/Types/ServerCapabilities.cs b/src/mcpdotnet/Protocol/Types/ServerCapabilities.cs new file mode 100644 index 0000000..50256d5 --- /dev/null +++ b/src/mcpdotnet/Protocol/Types/ServerCapabilities.cs @@ -0,0 +1,39 @@ +namespace McpDotNet.Protocol.Types; + +using System.Text.Json.Serialization; + +/// +/// Represents the capabilities that a server may support. +/// +public record ServerCapabilities +{ + /// + /// Experimental, non-standard capabilities that the server supports. + /// + [JsonPropertyName("experimental")] + public Dictionary? Experimental { get; init; } + + /// + /// Present if the server supports sending log messages to the client. + /// + [JsonPropertyName("logging")] + public LoggingCapability? Logging { get; init; } + + /// + /// Present if the server offers any prompt templates. + /// + [JsonPropertyName("prompts")] + public PromptsCapability? Prompts { get; init; } + + /// + /// Present if the server offers any resources to read. + /// + [JsonPropertyName("resources")] + public ResourcesCapability? Resources { get; init; } + + /// + /// Present if the server offers any tools to call. + /// + [JsonPropertyName("tools")] + public ToolsCapability? Tools { get; init; } +} diff --git a/src/mcpdotnet/Protocol/Types/TextResourceContents.cs b/src/mcpdotnet/Protocol/Types/TextResourceContents.cs new file mode 100644 index 0000000..b01a1db --- /dev/null +++ b/src/mcpdotnet/Protocol/Types/TextResourceContents.cs @@ -0,0 +1,21 @@ +namespace McpDotNet.Protocol.Types; + +using System.Text.Json.Serialization; + +/// +/// Represents text-based resource contents. +/// +public record TextResourceContents : IResourceContents +{ + [JsonPropertyName("uri")] + public required string Uri { get; init; } + + [JsonPropertyName("mimeType")] + public string? MimeType { get; init; } + + /// + /// The text content of the resource. + /// + [JsonPropertyName("text")] + public required string Text { get; init; } +} diff --git a/src/mcpdotnet/Protocol/Types/Tool.cs b/src/mcpdotnet/Protocol/Types/Tool.cs new file mode 100644 index 0000000..153cf17 --- /dev/null +++ b/src/mcpdotnet/Protocol/Types/Tool.cs @@ -0,0 +1,23 @@ +namespace McpDotNet.Protocol.Types; + +/// +/// Represents a tool that the server is capable of calling. Part of the ListToolsResponse. +/// See the schema for details +/// +public class Tool +{ + /// + /// he name of the tool. + /// + public string Name { get; set; } = string.Empty; + + /// + /// A human-readable description of the tool. + /// + public string? Description { get; set; } + + /// + /// A JSON Schema object defining the expected parameters for the tool. + /// + public JsonSchema? InputSchema { get; set; } +} diff --git a/src/mcpdotnet/Protocol/Types/ToolContent.cs b/src/mcpdotnet/Protocol/Types/ToolContent.cs new file mode 100644 index 0000000..a1efeb9 --- /dev/null +++ b/src/mcpdotnet/Protocol/Types/ToolContent.cs @@ -0,0 +1,58 @@ +namespace McpDotNet.Protocol.Types; + +/// +/// Represents the content of a tool response. +/// See the schema for details +/// There are multiple subtypes of content, depending on the "type" field, these are represented as separate classes. +/// +public class ToolContent +{ + /// + /// The type of content. This determines the structure of the content object. Can be "image", "text", "resource". + /// + public string Type { get; set; } = string.Empty; + + /// + /// The text content of the message. + /// + public string? Text { get; set; } + + /// + /// The base64-encoded image data. + /// + public string? Data { get; set; } + + /// + /// The MIME type of the image. + /// + public string? MimeType { get; set; } + + /// + /// One of BlobResourceContents or TextResourceContents. + /// + public object? Resource { get; set; } + + /// + /// Validates the content object. + /// + /// Whether the type of tool content has the correct data fields provided. + public bool Validate() + { + if (Type == "text") + { + return !string.IsNullOrEmpty(Text); + } + else if (Type == "image") + { + return !string.IsNullOrEmpty(Data) && !string.IsNullOrEmpty(MimeType); + } + else if (Type == "resource") + { + return Resource is BlobResourceContents || Resource is TextResourceContents; + } + else + { + return false; + } + } +} \ No newline at end of file diff --git a/src/mcpdotnet/Protocol/Types/Tools.cs b/src/mcpdotnet/Protocol/Types/Tools.cs deleted file mode 100644 index 6527907..0000000 --- a/src/mcpdotnet/Protocol/Types/Tools.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Protocol/Types/Tools.cs -namespace McpDotNet.Protocol.Types; - -using mcpdotnet.Features.Tools; -using System.Text.Json.Serialization; - -public class ListToolsResponse -{ - public List Tools { get; set; } = new(); -} - -public class Tool -{ - public string Name { get; set; } = string.Empty; - public string? Description { get; set; } - public JsonSchema? InputSchema { get; set; } -} - -public class JsonSchema -{ - public string Type { get; set; } = string.Empty; - public Dictionary? Properties { get; set; } - public List? Required { get; set; } -} - -public class JsonSchemaProperty -{ - public string Type { get; set; } = string.Empty; - public string? Description { get; set; } -} - -public class CallToolResponse -{ - public List Content { get; set; } = new(); - public bool IsError { get; set; } -} - -public class ToolContent -{ - public string Type { get; set; } = string.Empty; - public string? Text { get; set; } - public string? Data { get; set; } - public string? MimeType { get; set; } -} \ No newline at end of file diff --git a/src/mcpdotnet/Utils/Json/ContentJsonConverter.cs b/src/mcpdotnet/Utils/Json/ContentJsonConverter.cs index 0c8ca3d..79859e8 100644 --- a/src/mcpdotnet/Utils/Json/ContentJsonConverter.cs +++ b/src/mcpdotnet/Utils/Json/ContentJsonConverter.cs @@ -1,15 +1,15 @@ -// Utils/Json/ContentJsonConverter.cs -namespace McpDotNet.Utils.Json; - -using System.Text.Json; +using System.Text.Json; using System.Text.Json.Serialization; -using global::McpDotNet.Protocol.Types; +using McpDotNet.Protocol.Types; + +namespace McpDotNet.Utils.Json; /// /// JSON converter for IContent that handles polymorphic deserialization based on the "type" property. /// public class ContentJsonConverter : JsonConverter { + /// public override IContent? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType != JsonTokenType.StartObject) @@ -34,6 +34,7 @@ public class ContentJsonConverter : JsonConverter }; } + /// public override void Write(Utf8JsonWriter writer, IContent value, JsonSerializerOptions options) { switch (value) diff --git a/src/mcpdotnet/Utils/Json/JsonRpcMessageConverter.cs b/src/mcpdotnet/Utils/Json/JsonRpcMessageConverter.cs index 89931f4..f4712d7 100644 --- a/src/mcpdotnet/Utils/Json/JsonRpcMessageConverter.cs +++ b/src/mcpdotnet/Utils/Json/JsonRpcMessageConverter.cs @@ -1,15 +1,15 @@ -// Utils/Json/JsonRpcMessageConverter.cs (continued) -namespace McpDotNet.Utils.Json; - -using System.Text.Json; +using System.Text.Json; using System.Text.Json.Serialization; -using global::McpDotNet.Protocol.Messages; +using McpDotNet.Protocol.Messages; + +namespace McpDotNet.Utils.Json; /// /// JSON converter for IJsonRpcMessage that handles polymorphic deserialization of different message types. /// public class JsonRpcMessageConverter : JsonConverter { + /// public override IJsonRpcMessage? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType != JsonTokenType.StartObject) @@ -63,6 +63,7 @@ public class JsonRpcMessageConverter : JsonConverter throw new JsonException("Invalid JSON-RPC message format"); } + /// public override void Write(Utf8JsonWriter writer, IJsonRpcMessage value, JsonSerializerOptions options) { switch (value) diff --git a/src/mcpdotnet/Utils/Json/JsonSerializerOptionsExtensions.cs b/src/mcpdotnet/Utils/Json/JsonSerializerOptionsExtensions.cs index 51fce6a..0659b4d 100644 --- a/src/mcpdotnet/Utils/Json/JsonSerializerOptionsExtensions.cs +++ b/src/mcpdotnet/Utils/Json/JsonSerializerOptionsExtensions.cs @@ -1,8 +1,7 @@ -namespace McpDotNet.Utils.Json; - -using System.Text.Json; +using System.Text.Json; using System.Text.Json.Serialization; +namespace McpDotNet.Utils.Json; /// /// Extensions for configuring System.Text.Json serialization options for MCP. /// diff --git a/src/mcpdotnet/Utils/Json/ResourceContentsJsonConverter.cs b/src/mcpdotnet/Utils/Json/ResourceContentsJsonConverter.cs index c472d7a..d323506 100644 --- a/src/mcpdotnet/Utils/Json/ResourceContentsJsonConverter.cs +++ b/src/mcpdotnet/Utils/Json/ResourceContentsJsonConverter.cs @@ -1,14 +1,15 @@ -namespace McpDotNet.Utils.Json; - -using System.Text.Json; +using System.Text.Json; using System.Text.Json.Serialization; -using global::McpDotNet.Protocol.Types; +using McpDotNet.Protocol.Types; + +namespace McpDotNet.Utils.Json; /// /// JSON converter for IResourceContents that handles polymorphic deserialization based on content properties. /// public class ResourceContentsJsonConverter : JsonConverter { + /// public override IResourceContents? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType != JsonTokenType.StartObject) @@ -32,6 +33,7 @@ public class ResourceContentsJsonConverter : JsonConverter throw new JsonException("Resource contents must have either 'text' or 'blob' property"); } + /// public override void Write(Utf8JsonWriter writer, IResourceContents value, JsonSerializerOptions options) { switch (value) diff --git a/src/mcpdotnet/mcpdotnet.csproj b/src/mcpdotnet/mcpdotnet.csproj index c46b6f2..fa71b7a 100644 --- a/src/mcpdotnet/mcpdotnet.csproj +++ b/src/mcpdotnet/mcpdotnet.csproj @@ -6,8 +6,4 @@ enable - - - -