使用MCP C# SDK开发MCP Server + Client

大家好,我是Edison。

近日被MCP刷屏了,刚好看到张队发了一篇文章提到MCP的官方C# SDK发布了预览版,于是手痒痒尝了一下鲜,写了一个DEMO分享给大家。

MCP是什么鬼?

MCP,全称是“模型上下文协议”(Model Context Protocol),是Anthropic开源的一个标准协议。打个比方,它就像是AI世界的“USB-C”接口。你知道USB-C吧?一根线就能连接手机、电脑、充电器,超级方便。MCP的作用也差不多,它让AI模型(比如Anthropic的Claude)可以轻松地跟外部的数据源和工具连接起来,比如数据库、文件系统、API等等。

以前,如果想让AI访问你的数据库或者调用某个工具,得专门写一堆代码,特别麻烦。现在有了MCP,就像是插上USB-C线那么简单,AI模型通过这个标准协议就能直接获取数据或执行操作,不用每次都重新开发连接方式。这样,开发AI应用就变得更快、更省事了。

图片

MCP是如何工作的?

MCP是一个典型的C/S架构模式,即客户端 和 服务端,它们之间采用一种标准的消息格式(JSON-RPC)进行通信,大模型可以通过这些消息进行:

(1)获取数据:例如通过SQL从DB中查询订单数据;

(2)执行操作:例如通过API调用发个消息通知;

(3)理解指令:例如通过一些提示词模板,LLM可以知道如何使用数据和工具;

图片

简单来说,MCP就是AI的“万能接口”。有了它,AI模型就能像插上USB-C线一样,轻松连接到各种外部数据源和工具,变得更聪明、更实用。不管是开发者还是普通用户,都能通过MCP让AI干更多事,而且过程简单又安全。未来随着MCP的普及,我们可能会看到更多酷炫的AI应用冒出来!

创建一个MCP Server

这里我们使用MCP C# SDK来实现,使用标准的IO传输方式。

(1)创建一个.NET 8.0控制台应用,假设命名为:EDT.McpServer.ConsoleHost

(2)安装MCP SDK

ModelContextProtocol: 0.1.0-preview.2

(3)创建一个Tools目录,然后添加一个TimeTool.cs

这个TimeTool就是我们定义的基于MCP的Tool,可以看到基于SDK提供的Attribute,可以方便地将其指定为MCP Server Tools。

using ModelContextProtocol.Server;using System.ComponentModel;
namespace EDT.McpServer.Tools.ConsoleHost;
[McpServerToolType]public static class TimeTool{    [McpServerTool, Description("Get the current time for a city")]    public static string GetCurrentTime(string city) =>        $"It is {DateTime.Now.Hour}:{DateTime.Now.Minute} in {city}.";}

(3)修改Program.cs设置为启动MCP Server

同样,也是很方便地就完成了MCP Server的创建,重点关注WithToolsFromAssembly这个扩展方法,它会扫描程序集中添加了McpServerTool标签的类进行注册。

using Microsoft.Extensions.Hosting;using ModelContextProtocol;using EDT.McpServer.Tools.ConsoleHost;
try{    Console.WriteLine("Starting MCP Server...");
    var builder = Host.CreateEmptyApplicationBuilder(settings: null);    builder.Services        .AddMcpServer()        .WithStdioServerTransport()        .WithToolsFromAssembly();
    await builder.Build().RunAsync();    return 0;}catch (Exception ex){    Console.WriteLine($"Host terminated unexpectedly : {ex.Message}");    return 1;}
这时我们已经完成了MCP Server的创建,可以把它启动起来了。

但是,要完成测试,我们还得实现一个Client来调用Server。

创建一个MCP Client

(1)创建一个.NET 8.0控制台应用,假设命名为:EDT.McpServer.Client

(2)安装MCP SDK

ModelContextProtocol: 0.1.0-preview.2

(3)修改Program.cs,实现以下步骤:

创建MCP Client:

await using var mcpClient = await McpClientFactory.CreateAsync(new(){    Id = "time",    Name = "Time MCP Server",    TransportType = TransportTypes.StdIo,    TransportOptions = new()    {        ["command"] = @"..\..\..\..\EDT.McpServer\bin\Debug\net8.0\EDT.McpServer.exe"    }});

需要注意的是:这里我们MCP Server使用的是标准IO传输方式,因此指定TransportType为StdIo,同时指定command为MCP Server应用程序所在的exe的目录位置。当然,这里的这种方式有点不是很规范,但你只需要了解它是需要访问MCP Server的程序地址就行了。

列出可用的Tools:

var tools = await mcpClient.ListToolsAsync();foreach (var tool in tools){    Console.WriteLine($"{tool.Name} ({tool.Description})");}

直接执行Tool:(一般情况下不会这样用,而是在LLM中来调用)

var result = await mcpClient.CallToolAsync(    "GetCurrentTime",    new Dictionary<string, object?>() { ["city"] = "Chengdu" },    CancellationToken.None);Console.WriteLine(result.Content.First(c => c.Type == "text").Text);

通过LLM来调用Tool:

这里基于Microsoft.Extensions.AI核心库来实现的,你也可以用Semantic Kernel库来做这个事,都行!

var apiKeyCredential = new ApiKeyCredential(config["LLM:ApiKey"]);var aiClientOptions = new OpenAIClientOptions();aiClientOptions.Endpoint = new Uri(config["LLM:EndPoint"]);var aiClient = new OpenAIClient(apiKeyCredential, aiClientOptions)    .AsChatClient(config["LLM:ModelId"]);var chatClient = new ChatClientBuilder(aiClient)    .UseFunctionInvocation()    .Build();IList<ChatMessage> chatHistory =[    new(ChatRole.System, """       You are a helpful assistant delivering time in one sentence       in a short format, like 'It is 10:08 in Paris, France.'       """),];// Core Part: Get AI Tools from MCP Servervar mcpTools = await mcpClient.ListToolsAsync();var chatOptions = new ChatOptions(){    Tools = [..mcpTools]};// Prompt the user for a question.Console.ForegroundColor = ConsoleColor.Green;Console.WriteLine($"Assistant> How can I assist you today?");while (true){    // Read the user question.    Console.ForegroundColor = ConsoleColor.White;    Console.Write("User> ");    var question = Console.ReadLine();    // Exit the application if the user didn't type anything.    if (!string.IsNullOrWhiteSpace(question) && question.ToUpper() == "EXIT")        break;
    chatHistory.Add(new ChatMessage(ChatRole.User, question));    Console.ForegroundColor = ConsoleColor.Green;    var response = await chatClient.GetResponseAsync(chatHistory, chatOptions);    var content = response.ToString();    Console.WriteLine($"Assistant> {content}");    chatHistory.Add(new ChatMessage(ChatRole.Assistant, content));
    Console.WriteLine();}

最后的效果如下图所示:

创建一个ASP.NET MCP SSE Server

除了使用标准的IO协议,我们还可以实现一个基于ASP.NET Core的MCP SSE Server,顾名思义它就是使用SSE传输方式。

(1)创建一个.NET 8.0 ASP.NET WebAPI应用,假设命名为:EDT.McpServer.WebHost

(2)安装MCP SDK

ModelContextProtocol: 0.1.0-preview.2

(3)创建一个Tools目录,然后添加一个TimeTool.cs

这里和上面的一样,不再赘述。

(4)创建一个McpEndpointRouteBuilderExtensions,参考下面的示例代码,它来自MCP C# SDK的Sample Code

public static class McpEndpointRouteBuilderExtensions{    public static IEndpointConventionBuilder MapMcpSse(this IEndpointRouteBuilder endpoints)    {        SseResponseStreamTransport? transport = null;        var loggerFactory = endpoints.ServiceProvider.GetRequiredService<ILoggerFactory>();        var mcpServerOptions = endpoints.ServiceProvider.GetRequiredService<IOptions<McpServerOptions>>();
        var routeGroup = endpoints.MapGroup("");
        routeGroup.MapGet("/sse", async (HttpResponse response, CancellationToken requestAborted) =>        {            response.Headers.ContentType = "text/event-stream";            response.Headers.CacheControl = "no-cache";
            await using var localTransport = transport = new SseResponseStreamTransport(response.Body);            await using var server = McpServerFactory.Create(transport, mcpServerOptions.Value, loggerFactory, endpoints.ServiceProvider);
            try            {                var transportTask = transport.RunAsync(cancellationToken: requestAborted);                await server.StartAsync(cancellationToken: requestAborted);                await transportTask;            }            catch (OperationCanceledException) when (requestAborted.IsCancellationRequested)            {                // RequestAborted always triggers when the client disconnects before a complete response body is written,                // but this is how SSE connections are typically closed.            }        });
        routeGroup.MapPost("/message", async context =>        {            if (transport is null)            {                await Results.BadRequest("Connect to the /sse endpoint before sending messages.").ExecuteAsync(context);                return;            }
            var message = await context.Request.ReadFromJsonAsync<IJsonRpcMessage>(McpJsonUtilities.DefaultOptions, context.RequestAborted);            if (message is null)            {                await Results.BadRequest("No message in request body.").ExecuteAsync(context);                return;            }
            await transport.OnMessageReceivedAsync(message, context.RequestAborted);            context.Response.StatusCode = StatusCodes.Status202Accepted;            await context.Response.WriteAsync("Accepted");        });
        return routeGroup;    }}

(5)修改Program.cs完成MCP Server配置:

using EDT.McpServer.WebHost.Extensions;using EDT.McpServer.WebHost.Tools;using ModelContextProtocol;
try{    Console.WriteLine("Starting MCP Server...");
    var builder = WebApplication.CreateBuilder(args);    builder.Services.AddMcpServer().WithToolsFromAssembly();    builder.Services.AddWeatherToolService();    var app = builder.Build();
    app.UseHttpsRedirection();    app.MapGet("/", () => "Hello MCP Server!");    app.MapMcpSse();
    app.Run();    return 0;}catch (Exception ex){    Console.WriteLine($"Host terminated unexpectedly : {ex.Message}");    return 1;}

这时,你就可以把这个ASP.NET WebAPI应用启动起来,假设我们这里是https://localhost:8843,你就可以通过下面的一点点修改,让MCP Client连接上这个SSE Server:

await using var mcpClient = await McpClientFactory.CreateAsync(new(){    Id = "time",    Name = "Time MCP Server",    TransportType = TransportTypes.Sse,    Location = "https://localhost:8443/sse"});

可以看到,仅仅修改TransportType为SSE,然后指定Server的BaseUrl即可。

OK,让我们再来运行一下Client看看能否再次成功调用Tool:

看来这次使用SSE传输方式也能调用成功了!Perfect!

小结

本文介绍了MCP的基本概念和工作模式,然后演示了如何通过MCP C# SDK创建MCP Server和Client,以及基于ASP.NET WebAPI创建SSE Server,相信会对你有所帮助。

如果你也是.NET程序员希望参与AI应用的开发,那就快快了解和使用基于Microsoft.Extensioins.AI + MCP C# SDK 的生态组件库吧。

示例源码

GitHub: https://github.com/edisontalk/EdisonTalk.AI.Agents

>> 点击本文底部“阅读原文”即可直达

参考内容

MCP C# SDK Samples: https://github.com/modelcontextprotocol/csharp-sdk/tree/main/samples

推荐内容

Microsoft Learn: https://learn.microsoft.com/zh-cn/dotnet/ai/ai-extensions?wt.mc_id=MVP_397012

eShopSupport:  https://github.com/dotnet/eShopSupport?wt.mc_id=MVP_397012

devblogs: https://devblogs.microsoft.com/dotnet/e-shop-infused-with-ai-comprehensive-intelligent-dotnet-app-sample?wt.mc_id=MVP_397012

年终总结:Edison的2024年终总结

数字化转型:我在传统企业做数字化转型

C#刷算法题:C#刷剑指Offer算法题系列文章目录

C#刷设计模式:C#刷23种设计模式系列文章目录

.NET面试:.NET开发面试知识体系

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值