* Overhaul tool handling
- Renames [McpTool{Type}] to [McpServerTool{Type}], in order to distinguish representations of tools on the server from tools on the client.
- Enables [McpServerTool] methods to have arguments injected from DI, as well as a specific set of types from the implementation directly, like IMcpServer.
- Renames WithTools to WithToolsFromAssembly.
- All of the WithToolsXx methods now publish each individual McpServerTool into DI; other code can do so as well. The options setup code gathers all of the tools from DI and combines them into a collection which is then stored in McpServerOptions and used to construct the server.
- The server tools specified via DI as well as manually-provided handlers, using CallToolHandler as a fallback in case the requested tool doesn't exist in the tools collection, and ListToolsHandler augments the information for whatever tools exist.
- The tools are stored in McpServerOptions in a new McpServerToolCollection type, which is a thread-safe container that exposes add/removal notification. Adding/removing tools triggers a change notification that in turn sends a notification to the client about a tools update.
- The ServerOptions are exposed from the server instance.
- Removed cursor-based APIs from McpClientExtensions.
- Changed McpClientExtensions APIs to return `Task<IList<...>` rather than `IAsyncEnumerable<...>`.
I'm sure this will need to evolve further before we're "done", but it's a significant improvement from where we are now. One area I'm not super happy with is the notifying collection; that might work ok for a stdio server, where you can grab the created collection from the server's options and mutate it, but I don't know how well that's going to work for sse servers.
* Make McpServerTool{Collection} extensible
* Fix README.md example
* Add notifications test
* Address feedback
51 lines
No EOL
1.6 KiB
C#
51 lines
No EOL
1.6 KiB
C#
using ModelContextProtocol.Client;
|
|
using ModelContextProtocol.Protocol.Transport;
|
|
using Microsoft.Extensions.AI;
|
|
using OpenAI;
|
|
|
|
// Connect to an MCP server
|
|
Console.WriteLine("Connecting client to MCP 'everything' server");
|
|
var mcpClient = await McpClientFactory.CreateAsync(
|
|
new()
|
|
{
|
|
Id = "everything",
|
|
Name = "Everything",
|
|
TransportType = TransportTypes.StdIo,
|
|
TransportOptions = new()
|
|
{
|
|
["command"] = "npx", ["arguments"] = "-y @modelcontextprotocol/server-everything",
|
|
}
|
|
});
|
|
|
|
// Get all available tools
|
|
Console.WriteLine("Tools available:");
|
|
var tools = await mcpClient.ListToolsAsync();
|
|
foreach (var tool in tools)
|
|
{
|
|
Console.WriteLine($" {tool}");
|
|
}
|
|
Console.WriteLine();
|
|
|
|
// Create an IChatClient. (This shows using OpenAIClient, but it could be any other IChatClient implementation.)
|
|
// Provide your own OPENAI_API_KEY via an environment variable.
|
|
using IChatClient chatClient =
|
|
new OpenAIClient(Environment.GetEnvironmentVariable("OPENAI_API_KEY")).AsChatClient("gpt-4o-mini")
|
|
.AsBuilder().UseFunctionInvocation().Build();
|
|
|
|
// Have a conversation, making all tools available to the LLM.
|
|
List<ChatMessage> messages = [];
|
|
while (true)
|
|
{
|
|
Console.Write("Q: ");
|
|
messages.Add(new(ChatRole.User, Console.ReadLine()));
|
|
|
|
List<ChatResponseUpdate> updates = [];
|
|
await foreach (var update in chatClient.GetStreamingResponseAsync(messages, new() { Tools = [.. tools] }))
|
|
{
|
|
Console.Write(update);
|
|
updates.Add(update);
|
|
}
|
|
Console.WriteLine();
|
|
|
|
messages.AddMessages(updates);
|
|
} |