Clean up and add more comments (#2)
This commit is contained in:
parent
79af38cf21
commit
9a244f5770
30 changed files with 349 additions and 653 deletions
|
|
@ -5,6 +5,8 @@ using McpDotNet.Protocol.Messages;
|
|||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public interface IMcpClient : IAsyncDisposable
|
||||
{
|
||||
|
|
@ -14,44 +16,58 @@ public interface IMcpClient : IAsyncDisposable
|
|||
bool IsInitialized { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the capabilities that the server supports.
|
||||
/// Gets the capabilities supported by the server.
|
||||
/// </summary>
|
||||
ServerCapabilities? ServerCapabilities { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the version information of the server.
|
||||
/// Gets the version and implementation information of the server.
|
||||
/// </summary>
|
||||
Implementation? ServerInfo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Connects to the server.
|
||||
/// Establishes a connection to the server.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
Task ConnectAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||
/// <returns>A task that completes when the ping is successful.</returns>
|
||||
Task PingAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Queries the server for a list of tools that are available (if any)
|
||||
/// Retrieves a list of available tools from the server.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||
/// <returns>A task containing the server's response with tool information.</returns>
|
||||
Task<ListToolsResponse> ListToolsAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Calls a tool on the server.
|
||||
/// Invokes a tool on the server with optional arguments.
|
||||
/// </summary>
|
||||
/// <param name="toolName">The name of the tool as given when listing tool definitions.</param>
|
||||
/// <param name="arguments">Arguments for the tool (if any)</param>
|
||||
/// <param name="toolName">The name of the tool to call.</param>
|
||||
/// <param name="arguments">Optional arguments for the tool.</param>
|
||||
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||
/// <returns>A task containing the tool's response.</returns>
|
||||
Task<CallToolResponse> CallToolAsync(string toolName, Dictionary<string, object>? arguments = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// A generic method to send a request to the server and get a response.
|
||||
/// Sends a generic JSON-RPC request to the server.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The expected response type.</typeparam>
|
||||
/// <param name="request">The JSON-RPC request to send.</param>
|
||||
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||
/// <returns>A task containing the server's response.</returns>
|
||||
Task<T> SendRequestAsync<T>(JsonRpcRequest request, CancellationToken cancellationToken) where T : class;
|
||||
|
||||
/// <summary>
|
||||
/// Registers a handler for a specific notification method.
|
||||
/// Registers a handler for server notifications of a specific method.
|
||||
/// </summary>
|
||||
/// <param name="method">The notification method to handle.</param>
|
||||
/// <param name="handler">The async handler function to process notifications.</param>
|
||||
void OnNotification(string method, Func<JsonRpcNotification, Task> handler);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Main MCP client implementation that handles protocol lifecycle and message routing.
|
||||
/// </summary>
|
||||
public class McpClient : IMcpClient
|
||||
/// </inheritdoc>
|
||||
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();
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the client is connected and initialized.
|
||||
/// </summary>
|
||||
/// </inheritdoc>
|
||||
public bool IsInitialized => _isInitialized;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the server's capabilities after initialization.
|
||||
/// </summary>
|
||||
/// </inheritdoc>
|
||||
public ServerCapabilities? ServerCapabilities { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets information about the server implementation after initialization.
|
||||
/// </summary>
|
||||
/// </inheritdoc>
|
||||
public Implementation? ServerInfo { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="McpClient"/> class.
|
||||
/// </summary>
|
||||
/// <param name="transport">An MCP transport implementation.</param>
|
||||
/// <param name="options">Options for the client, defining protocol version and capabilities.</param>
|
||||
public McpClient(IMcpTransport transport, McpClientOptions options)
|
||||
{
|
||||
_transport = transport;
|
||||
|
|
@ -48,9 +44,7 @@ public class McpClient : IMcpClient
|
|||
_nextRequestId = 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connects to the server and performs the initialization sequence.
|
||||
/// </summary>
|
||||
/// </inheritdoc>
|
||||
public async Task ConnectAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_isInitialized)
|
||||
|
|
@ -128,6 +122,7 @@ public class McpClient : IMcpClient
|
|||
}
|
||||
}
|
||||
|
||||
/// </inheritdoc>
|
||||
public async Task PingAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await SendRequestAsync<dynamic>(
|
||||
|
|
@ -139,6 +134,7 @@ public class McpClient : IMcpClient
|
|||
).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// </inheritdoc>
|
||||
public async Task<ListToolsResponse> ListToolsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return await SendRequestAsync<ListToolsResponse>(
|
||||
|
|
@ -151,6 +147,7 @@ public class McpClient : IMcpClient
|
|||
).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// </inheritdoc>
|
||||
public async Task<CallToolResponse> CallToolAsync(string toolName, Dictionary<string, object>? arguments = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
|
@ -218,6 +215,7 @@ public class McpClient : IMcpClient
|
|||
}
|
||||
}
|
||||
|
||||
/// </inheritdoc>
|
||||
public async Task<T> SendRequestAsync<T>(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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a handler for notifications of a specific method.
|
||||
/// </summary>
|
||||
/// </inheritdoc>
|
||||
public void OnNotification(string method, Func<JsonRpcNotification,Task> handler)
|
||||
{
|
||||
var handlers = _notificationHandlers.GetOrAdd(method, _ => new());
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
// Client/McpClientOptions.cs
|
||||
namespace McpDotNet.Client;
|
||||
namespace McpDotNet.Client;
|
||||
|
||||
/// <summary>
|
||||
/// Represents errors that occur in the MCP client.
|
||||
|
|
@ -11,15 +10,29 @@ public class McpClientException : Exception
|
|||
/// </summary>
|
||||
public int? ErrorCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="McpClientException"/> class with a specified error message.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
public McpClientException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="McpClientException"/> class with a specified error message and error code.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
/// <param name="errorCode">The error code associated with the JSON-RPC error response.</param>
|
||||
public McpClientException(string message, int errorCode) : base(message)
|
||||
{
|
||||
ErrorCode = errorCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="McpClientException"/> class with a specified error message and a reference to the inner exception that is the cause of this exception.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
|
||||
public McpClientException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
// Client/McpClientFactory.cs
|
||||
namespace McpDotNet.Client;
|
||||
namespace McpDotNet.Client;
|
||||
|
||||
using McpDotNet.Configuration;
|
||||
using McpDotNet.Protocol.Transport;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public class McpClientFactory
|
||||
{
|
||||
|
|
@ -15,6 +17,15 @@ public class McpClientFactory
|
|||
private readonly Func<McpServerConfig, IMcpTransport> _transportFactoryMethod;
|
||||
private readonly Func<IMcpTransport, McpClientOptions, IMcpClient> _clientFactoryMethod;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="McpClientFactory"/> 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.
|
||||
/// </summary>
|
||||
/// <param name="serverConfigs">Configuration objects for each server the factory should support.</param>
|
||||
/// <param name="clientOptions">A configuration object which specifies client capabilities and protocol version.</param>
|
||||
/// <param name="transportFactoryMethod">An optional factory method which returns transport implementations based on a server configuration.</param>
|
||||
/// <param name="clientFactoryMethod">An optional factory method which creates a client based on client options and transport implementation. </param>
|
||||
public McpClientFactory(
|
||||
IEnumerable<McpServerConfig> serverConfigs,
|
||||
McpClientOptions clientOptions,
|
||||
|
|
@ -28,8 +39,12 @@ public class McpClientFactory
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="serverId">The ID of the server to connect to. It must have been passed in the serverConfigs when constructing the factory.</param>
|
||||
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||
public async Task<IMcpClient> GetClientAsync(string serverId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!_serverConfigs.TryGetValue(serverId, out var config))
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
// Client/McpClientOptions.cs
|
||||
using McpDotNet.Protocol.Types;
|
||||
|
||||
namespace McpDotNet.Client;
|
||||
|
||||
using global::McpDotNet.Protocol.Types;
|
||||
|
||||
/// <summary>
|
||||
/// 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 href="https://spec.modelcontextprotocol.io/specification/basic/lifecycle/">See the protocol specification for details on capability negotiation</see>
|
||||
/// </summary>
|
||||
public record McpClientOptions
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
// Configuration/McpServerConfig.cs
|
||||
namespace McpDotNet.Configuration;
|
||||
namespace McpDotNet.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for an MCP server connection.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation of IMcpToolProvider that handles conversion between MCP and LLM-friendly formats.
|
||||
/// </summary>
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<ToolDefinition>> GetToolsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _client.ListToolDefsAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ToolResult> ExecuteToolCallAsync(
|
||||
string toolName,
|
||||
Dictionary<string, object> 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<string, ToolArgument> Arguments, List<string> Required) ParseInputSchema(ToolInputSchema schema)
|
||||
//{
|
||||
// var arguments = new Dictionary<string, ToolArgument>();
|
||||
// var required = new List<string>();
|
||||
|
||||
// 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<string>()
|
||||
// .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<IContent> content)
|
||||
//{
|
||||
// // Try to extract error message from content, defaulting to first text content if available
|
||||
// var textContent = content.OfType<TextContent>().FirstOrDefault();
|
||||
// return textContent?.Text ?? "Tool execution failed with no error message";
|
||||
//}
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
// Features/LlmIntegration/IMcpToolProvider.cs
|
||||
using mcpdotnet.Features.Tools;
|
||||
|
||||
namespace McpDotNet.Features.LlmIntegration;
|
||||
|
||||
/// <summary>
|
||||
/// Provides MCP tool information and execution capabilities to an LLM.
|
||||
/// </summary>
|
||||
public interface IMcpToolProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the available tools and their descriptions.
|
||||
/// </summary>
|
||||
Task<List<ToolDefinition>> GetToolsAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Executes a tool call.
|
||||
/// </summary>
|
||||
Task<ToolResult> ExecuteToolCallAsync(
|
||||
string toolName,
|
||||
Dictionary<string, object> arguments,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
|
@ -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<string, ToolParameter>();
|
||||
|
||||
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<Protocol.Types.ToolContent>? 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
using mcpdotnet.Features.Tools;
|
||||
using McpDotNet.Client;
|
||||
using McpDotNet.Protocol.Messages;
|
||||
using McpDotNet.Protocol.Types;
|
||||
|
||||
namespace McpDotNet.Features.Tools;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for working with MCP tools.
|
||||
/// </summary>
|
||||
public static class ToolsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Lists all available tools from the server.
|
||||
/// </summary>
|
||||
public static async Task<List<ToolDefinition>> 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls a tool with the specified arguments.
|
||||
/// </summary>
|
||||
public static async Task<ToolResult> 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<CallToolResponse>(
|
||||
new JsonRpcRequest
|
||||
{
|
||||
Method = "tools/call",
|
||||
Params = new
|
||||
{
|
||||
name = toolName,
|
||||
arguments = arguments
|
||||
}
|
||||
},
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
return ToolMapper.ToToolResult(response);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
// Features/Tools/ToolTypes.cs
|
||||
namespace mcpdotnet.Features.Tools;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a tool available from an MCP server.
|
||||
/// </summary>
|
||||
public record ToolDefinition
|
||||
{
|
||||
public required string Name { get; init; }
|
||||
public required string Description { get; init; }
|
||||
public required Dictionary<string, ToolParameter> Parameters { get; init; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Represents a tool parameter.
|
||||
/// </summary>
|
||||
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; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of a tool execution, from server to client
|
||||
public record McpToolCallResponse
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of a tool execution, from client to host.
|
||||
/// </summary>
|
||||
public record ToolResult
|
||||
{
|
||||
public required bool Succeeded { get; init; }
|
||||
public List<ToolContent>? Content { get; init; }
|
||||
public string? ErrorMessage { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Content returned by a tool execution.
|
||||
/// </summary>
|
||||
public record ToolContent
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of content.
|
||||
/// </summary>
|
||||
public required string Type { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The content value.
|
||||
/// </summary>
|
||||
public required object Value { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional MIME type for binary content.
|
||||
/// </summary>
|
||||
public string? MimeType { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A list of tools available from an MCP server.
|
||||
/// </summary>
|
||||
public record ToolDefinitionList
|
||||
{
|
||||
/// <summary>
|
||||
/// The list of tools.
|
||||
/// </summary>
|
||||
public required List<ToolDefinition> Tools { get; init; }
|
||||
}
|
||||
21
src/mcpdotnet/Protocol/Types/BlobResourceContents.cs
Normal file
21
src/mcpdotnet/Protocol/Types/BlobResourceContents.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
namespace McpDotNet.Protocol.Types;
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Represents binary resource contents.
|
||||
/// </summary>
|
||||
public record BlobResourceContents : IResourceContents
|
||||
{
|
||||
[JsonPropertyName("uri")]
|
||||
public required string Uri { get; init; }
|
||||
|
||||
[JsonPropertyName("mimeType")]
|
||||
public string? MimeType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The base64-encoded binary content of the resource.
|
||||
/// </summary>
|
||||
[JsonPropertyName("blob")]
|
||||
public required string Blob { get; init; }
|
||||
}
|
||||
18
src/mcpdotnet/Protocol/Types/CallToolResponse.cs
Normal file
18
src/mcpdotnet/Protocol/Types/CallToolResponse.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
namespace McpDotNet.Protocol.Types;
|
||||
|
||||
/// <summary>
|
||||
/// A response to a request to call a tool on the server.
|
||||
/// <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/schema.json">See the schema for details</see>
|
||||
/// </summary>
|
||||
public class CallToolResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// The server's response to a tools/call request from the client.
|
||||
/// </summary>
|
||||
public List<ToolContent> Content { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Whether the tool call was unsuccessful. If true, the call was unsuccessful.
|
||||
/// </summary>
|
||||
public bool IsError { get; set; }
|
||||
}
|
||||
|
|
@ -26,42 +26,6 @@ public record ClientCapabilities
|
|||
public SamplingCapability? Sampling { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the capabilities that a server may support.
|
||||
/// </summary>
|
||||
public record ServerCapabilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Experimental, non-standard capabilities that the server supports.
|
||||
/// </summary>
|
||||
[JsonPropertyName("experimental")]
|
||||
public Dictionary<string, object>? Experimental { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Present if the server supports sending log messages to the client.
|
||||
/// </summary>
|
||||
[JsonPropertyName("logging")]
|
||||
public LoggingCapability? Logging { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Present if the server offers any prompt templates.
|
||||
/// </summary>
|
||||
[JsonPropertyName("prompts")]
|
||||
public PromptsCapability? Prompts { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Present if the server offers any resources to read.
|
||||
/// </summary>
|
||||
[JsonPropertyName("resources")]
|
||||
public ResourcesCapability? Resources { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Present if the server offers any tools to call.
|
||||
/// </summary>
|
||||
[JsonPropertyName("tools")]
|
||||
public ToolsCapability? Tools { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the roots capability configuration.
|
||||
/// </summary>
|
||||
|
|
|
|||
10
src/mcpdotnet/Protocol/Types/IResourceContents.cs
Normal file
10
src/mcpdotnet/Protocol/Types/IResourceContents.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
namespace McpDotNet.Protocol.Types;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for resource contents.
|
||||
/// </summary>
|
||||
public interface IResourceContents
|
||||
{
|
||||
string Uri { get; }
|
||||
string? MimeType { get; }
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
// Protocol/Types/Implementation.cs
|
||||
namespace McpDotNet.Protocol.Types;
|
||||
namespace McpDotNet.Protocol.Types;
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
|
|
|
|||
23
src/mcpdotnet/Protocol/Types/JsonSchema.cs
Normal file
23
src/mcpdotnet/Protocol/Types/JsonSchema.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
namespace McpDotNet.Protocol.Types;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a JSON schema for a tool's input arguments.
|
||||
/// <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/schema.json">See the schema for details</see>
|
||||
/// </summary>
|
||||
public class JsonSchema
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of the schema, should be "object".
|
||||
/// </summary>
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Map of property names to property definitions.
|
||||
/// </summary>
|
||||
public Dictionary<string, JsonSchemaProperty>? Properties { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of required property names.
|
||||
/// </summary>
|
||||
public List<string>? Required { get; set; }
|
||||
}
|
||||
18
src/mcpdotnet/Protocol/Types/JsonSchemaProperty.cs
Normal file
18
src/mcpdotnet/Protocol/Types/JsonSchemaProperty.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
namespace McpDotNet.Protocol.Types;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a property in a JSON schema.
|
||||
/// <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/schema.json">See the schema for details</see>
|
||||
/// </summary>
|
||||
public class JsonSchemaProperty
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of the property. Should be a JSON Schema type and is required.
|
||||
/// </summary>
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// A human-readable description of the property.
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
}
|
||||
13
src/mcpdotnet/Protocol/Types/ListToolsResponse.cs
Normal file
13
src/mcpdotnet/Protocol/Types/ListToolsResponse.cs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
namespace McpDotNet.Protocol.Types;
|
||||
|
||||
/// <summary>
|
||||
/// A response to a request to list the tools available on the server.
|
||||
/// <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/schema.json">See the schema for details</see>
|
||||
/// </summary>
|
||||
public class ListToolsResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// The server's response to a tools/list request from the client.
|
||||
/// </summary>
|
||||
public List<Tool> Tools { get; set; } = new();
|
||||
}
|
||||
|
|
@ -37,48 +37,3 @@ public record Resource
|
|||
[JsonPropertyName("annotations")]
|
||||
public Annotations? Annotations { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for resource contents.
|
||||
/// </summary>
|
||||
public interface IResourceContents
|
||||
{
|
||||
string Uri { get; }
|
||||
string? MimeType { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents text-based resource contents.
|
||||
/// </summary>
|
||||
public record TextResourceContents : IResourceContents
|
||||
{
|
||||
[JsonPropertyName("uri")]
|
||||
public required string Uri { get; init; }
|
||||
|
||||
[JsonPropertyName("mimeType")]
|
||||
public string? MimeType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The text content of the resource.
|
||||
/// </summary>
|
||||
[JsonPropertyName("text")]
|
||||
public required string Text { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents binary resource contents.
|
||||
/// </summary>
|
||||
public record BlobResourceContents : IResourceContents
|
||||
{
|
||||
[JsonPropertyName("uri")]
|
||||
public required string Uri { get; init; }
|
||||
|
||||
[JsonPropertyName("mimeType")]
|
||||
public string? MimeType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The base64-encoded binary content of the resource.
|
||||
/// </summary>
|
||||
[JsonPropertyName("blob")]
|
||||
public required string Blob { get; init; }
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue