Make client config optional to McpClientFactory.CreateAsync (#65)
* Make client config optional to McpClientFactory.CreateAsync Unless you're specifying capabilities (sampling/rooting) or need a specific client name/version, the client information can be inferred from the assembly, simplifying the getting-started experience. * Cache default options * Update src/ModelContextProtocol/Client/McpClientFactory.cs Co-authored-by: Eirik Tsarpalis <eirik.tsarpalis@gmail.com> --------- Co-authored-by: Eirik Tsarpalis <eirik.tsarpalis@gmail.com>
This commit is contained in:
parent
36d5019de0
commit
729e688b25
4 changed files with 50 additions and 23 deletions
14
README.MD
14
README.MD
|
|
@ -20,16 +20,10 @@ For more information about MCP:
|
||||||
## Getting Started (Client)
|
## Getting Started (Client)
|
||||||
|
|
||||||
To get started writing a client, the `McpClientFactory.CreateAsync` method is used to instantiate and connect an `IMcpClient`
|
To get started writing a client, the `McpClientFactory.CreateAsync` method is used to instantiate and connect an `IMcpClient`
|
||||||
to a server, with details about the client and server specified in `McpClientOptions` and `McpServerConfig` objects.
|
to a server. Once you have an `IMcpClient`, you can interact with it, such as to enumerate all available tools and invoke tools.
|
||||||
Once you have an `IMcpClient`, you can interact with it, such as to enumerate all available tools and invoke tools.
|
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
McpClientOptions options = new()
|
var client = await McpClientFactory.CreateAsync(new()
|
||||||
{
|
|
||||||
ClientInfo = new() { Name = "TestClient", Version = "1.0.0" }
|
|
||||||
};
|
|
||||||
|
|
||||||
McpServerConfig config = new()
|
|
||||||
{
|
{
|
||||||
Id = "everything",
|
Id = "everything",
|
||||||
Name = "Everything",
|
Name = "Everything",
|
||||||
|
|
@ -39,9 +33,7 @@ McpServerConfig config = new()
|
||||||
["command"] = "npx",
|
["command"] = "npx",
|
||||||
["arguments"] = "-y @modelcontextprotocol/server-everything",
|
["arguments"] = "-y @modelcontextprotocol/server-everything",
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
var client = await McpClientFactory.CreateAsync(config, options);
|
|
||||||
|
|
||||||
// Print the list of tools available from the server.
|
// Print the list of tools available from the server.
|
||||||
await foreach (var tool in client.ListToolsAsync())
|
await foreach (var tool in client.ListToolsAsync())
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,7 @@ var mcpClient = await McpClientFactory.CreateAsync(
|
||||||
{
|
{
|
||||||
["command"] = "npx", ["arguments"] = "-y @modelcontextprotocol/server-everything",
|
["command"] = "npx", ["arguments"] = "-y @modelcontextprotocol/server-everything",
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
new() { ClientInfo = new() { Name = "ChatClient", Version = "1.0.0" } });
|
|
||||||
|
|
||||||
// Get all available tools
|
// Get all available tools
|
||||||
Console.WriteLine("Tools available:");
|
Console.WriteLine("Tools available:");
|
||||||
|
|
|
||||||
|
|
@ -6,15 +6,36 @@ using ModelContextProtocol.Protocol.Transport;
|
||||||
using ModelContextProtocol.Utils;
|
using ModelContextProtocol.Utils;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
namespace ModelContextProtocol.Client;
|
namespace ModelContextProtocol.Client;
|
||||||
|
|
||||||
/// <summary>Provides factory methods for creating MCP clients.</summary>
|
/// <summary>Provides factory methods for creating MCP clients.</summary>
|
||||||
public static class McpClientFactory
|
public static class McpClientFactory
|
||||||
{
|
{
|
||||||
|
/// <summary>Default client options to use when none are supplied.</summary>
|
||||||
|
private static readonly McpClientOptions s_defaultClientOptions = CreateDefaultClientOptions();
|
||||||
|
|
||||||
|
/// <summary>Creates default client options to use when no options are supplied.</summary>
|
||||||
|
private static McpClientOptions CreateDefaultClientOptions()
|
||||||
|
{
|
||||||
|
var asmName = (Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly()).GetName();
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
ClientInfo = new()
|
||||||
|
{
|
||||||
|
Name = asmName.Name ?? "McpClient",
|
||||||
|
Version = asmName.Version?.ToString() ?? "1.0.0",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Creates an <see cref="IMcpClient"/>, connecting it to the specified server.</summary>
|
/// <summary>Creates an <see cref="IMcpClient"/>, connecting it to the specified server.</summary>
|
||||||
/// <param name="serverConfig">Configuration for the target server to which the client should connect.</param>
|
/// <param name="serverConfig">Configuration for the target server to which the client should connect.</param>
|
||||||
/// <param name="clientOptions">A client configuration object which specifies client capabilities and protocol version.</param>
|
/// <param name="clientOptions">
|
||||||
|
/// A client configuration object which specifies client capabilities and protocol version.
|
||||||
|
/// If <see langword="null"/>, details based on the current process will be employed.
|
||||||
|
/// </param>
|
||||||
/// <param name="createTransportFunc">An optional factory method which returns transport implementations based on a server configuration.</param>
|
/// <param name="createTransportFunc">An optional factory method which returns transport implementations based on a server configuration.</param>
|
||||||
/// <param name="loggerFactory">A logger factory for creating loggers for clients.</param>
|
/// <param name="loggerFactory">A logger factory for creating loggers for clients.</param>
|
||||||
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||||
|
|
@ -25,14 +46,14 @@ public static class McpClientFactory
|
||||||
/// <exception cref="InvalidOperationException"><paramref name="createTransportFunc"/> returns an invalid transport.</exception>
|
/// <exception cref="InvalidOperationException"><paramref name="createTransportFunc"/> returns an invalid transport.</exception>
|
||||||
public static async Task<IMcpClient> CreateAsync(
|
public static async Task<IMcpClient> CreateAsync(
|
||||||
McpServerConfig serverConfig,
|
McpServerConfig serverConfig,
|
||||||
McpClientOptions clientOptions,
|
McpClientOptions? clientOptions = null,
|
||||||
Func<McpServerConfig, ILoggerFactory?, IClientTransport>? createTransportFunc = null,
|
Func<McpServerConfig, ILoggerFactory?, IClientTransport>? createTransportFunc = null,
|
||||||
ILoggerFactory? loggerFactory = null,
|
ILoggerFactory? loggerFactory = null,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
Throw.IfNull(serverConfig);
|
Throw.IfNull(serverConfig);
|
||||||
Throw.IfNull(clientOptions);
|
|
||||||
|
|
||||||
|
clientOptions ??= s_defaultClientOptions;
|
||||||
createTransportFunc ??= CreateTransport;
|
createTransportFunc ??= CreateTransport;
|
||||||
|
|
||||||
string endpointName = $"Client ({serverConfig.Id}: {serverConfig.Name})";
|
string endpointName = $"Client ({serverConfig.Id}: {serverConfig.Name})";
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,6 @@ public class McpClientFactoryTests
|
||||||
{
|
{
|
||||||
await Assert.ThrowsAsync<ArgumentNullException>("serverConfig", () => McpClientFactory.CreateAsync((McpServerConfig)null!, _defaultOptions, cancellationToken: TestContext.Current.CancellationToken));
|
await Assert.ThrowsAsync<ArgumentNullException>("serverConfig", () => McpClientFactory.CreateAsync((McpServerConfig)null!, _defaultOptions, cancellationToken: TestContext.Current.CancellationToken));
|
||||||
|
|
||||||
await Assert.ThrowsAsync<ArgumentNullException>("clientOptions", () => McpClientFactory.CreateAsync(new McpServerConfig()
|
|
||||||
{
|
|
||||||
Name = "name",
|
|
||||||
Id = "id",
|
|
||||||
TransportType = TransportTypes.StdIo,
|
|
||||||
}, (McpClientOptions)null!, cancellationToken: TestContext.Current.CancellationToken));
|
|
||||||
|
|
||||||
await Assert.ThrowsAsync<ArgumentException>("serverConfig", () => McpClientFactory.CreateAsync(new McpServerConfig()
|
await Assert.ThrowsAsync<ArgumentException>("serverConfig", () => McpClientFactory.CreateAsync(new McpServerConfig()
|
||||||
{
|
{
|
||||||
Name = "name",
|
Name = "name",
|
||||||
|
|
@ -41,6 +34,28 @@ public class McpClientFactoryTests
|
||||||
}, _defaultOptions, (_, __) => null!, cancellationToken: TestContext.Current.CancellationToken));
|
}, _defaultOptions, (_, __) => null!, cancellationToken: TestContext.Current.CancellationToken));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateAsync_NullOptions_EntryAssemblyInferred()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var serverConfig = new McpServerConfig
|
||||||
|
{
|
||||||
|
Id = "test-server",
|
||||||
|
Name = "Test Server",
|
||||||
|
TransportType = TransportTypes.StdIo,
|
||||||
|
Location = "/path/to/server",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await using var client = await McpClientFactory.CreateAsync(
|
||||||
|
serverConfig,
|
||||||
|
null,
|
||||||
|
(_, __) => new NopTransport(),
|
||||||
|
cancellationToken: TestContext.Current.CancellationToken);
|
||||||
|
|
||||||
|
Assert.NotNull(client);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CreateAsync_WithValidStdioConfig_CreatesNewClient()
|
public async Task CreateAsync_WithValidStdioConfig_CreatesNewClient()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue