同一产品做多个网站,17网站一起做网店普宁池尾,建设方案包括哪些内容,标书制作员这工作好吗潜油抗坦根据 MCP 协议的规定#xff0c;在 MCP 协议中有以下对象#xff1a; MCP Hosts: 如 Claude Desktop、IDE 或 AI 工具#xff0c;希望通过 MCP 访问数据的程序#xff1b; MCP Clients: 维护与服务器一对一连接的协议客户端#xff1b; MCP Servers: 轻量级程序}然后创建 MCP Server 服务并使用 WithStdioServerTransport() 暴露接口能力。using Microsoft.Extensions.DependencyInjection;using Microsoft.Extensions.Hosting;using Microsoft.Extensions.Logging;using TransportStdioServer;var builder Host.CreateApplicationBuilder(args);builder.Services.AddMcpServer().WithStdioServerTransport().WithTools();builder.Logging.AddConsole(options {options.LogToStandardErrorThreshold LogLevel.Trace;});await builder.Build().RunAsync();编译 TransportStdioServer 项目在 Windows 下会生成 .exe 文件复制 .exe 文件的绝对路径在编写 Client 时要用。1745042567544C# 编写 Client 时需要通过命令行参数导入 .exe 文件示例如下using Microsoft.Extensions.Configuration;using Microsoft.Extensions.Hosting;using ModelContextProtocol.Client;using ModelContextProtocol.Protocol.Transport;var builder Host.CreateApplicationBuilder(args);builder.Configuration.AddEnvironmentVariables().AddUserSecrets();var clientTransport new StdioClientTransport(new(){Name Demo Server,// 要使用绝对路径这里笔者省略了Command E:/../../TransportStdioServer.exe});await using var mcpClient await McpClientFactory.CreateAsync(clientTransport);var tools await mcpClient.ListToolsAsync();foreach (var tool in tools){Console.WriteLine($Connected to server with tools: {tool.Name});}启动 TransportStdioClient控制台会打印 TransportStdioServer 中的所有 Mcp tool。image-20250419140917673StdioClientTransport 原理是基于命令行参数启动 TransportStdioServerStdioClient 会将命令行参数拼接起来然后以子进程方式启动 MCP Server命令行示例cmd.exe/c E:/../TransportStdioServer.exeStdioClientTransport 核心代码启动子进程image-20250419135453777SSE本节参考示例项目TransportSseServer、TransportSseClient。SSE 是通过 HTTP 长连接实现远程通信的在使用各种 AI 对话应用时AI 会像打字机一样逐个输出字符这种通过 HTTP 长连接、由 HTTP Server 服务器持续推送内容的方式就叫 sse。SSE Server 需提供两个端点/sseGET请求建立长连接接收服务器推送的事件流。/messagesPOST请求客户端发送请求至该端点。image-20250419142510347在 TransportSseServer 实现简单的 EchoTool。[McpServerToolType]public sealed class EchoTool{[McpServerTool, Description(Echoes the input back to the client.)]public static string Echo(string message){return hello message;}}配置 MCP Server 支持 SSEusing TransportSseServer.Tools;var builder WebApplication.CreateBuilder(args);builder.Services.AddMcpServer().WithHttpTransport().WithTools().WithTools();var app builder.Build();app.MapMcp();app.Run(http://0.0.0.0:5000);TransportSseClient 实现客户端连接 Mcp Server其代码非常简单连接到 MCP Server 后将对方支持的 Tool 列出来。using Microsoft.Extensions.Logging;using Microsoft.Extensions.Logging.Abstractions;using ModelContextProtocol.Client;using ModelContextProtocol.Protocol.Transport;var defaultOptions new McpClientOptions{ClientInfo new() { Name IntegrationTestClient, Version 1.0.0 }};var defaultConfig new SseClientTransportOptions{Endpoint new Uri($http://localhost:5000/sse),Name Everything,};// Create client and run testsawait using var client await McpClientFactory.CreateAsync(new SseClientTransport(defaultConfig),defaultOptions,loggerFactory: NullLoggerFactory.Instance);var tools await client.ListToolsAsync();foreach (var tool in tools){Console.WriteLine($Connected to server with tools: {tool.Name});}StreamableStreamable HTTP 是 SSE 的升级方案完全基于标准 HTTP 协议移除了专用 SSE 端点所有消息通过 /message 端点传输。本节不讲解 Streamable 。MCP Tool 说明目前社区有两大主流 LLM 开发框架分别是 Microsoft.SemanticKernel、LangChain它们都支持 Plugin 能够将本地函数、Swagger 等转换为函数将 Function 提交给 LLMAI 返回要调用的 Function 后由框架引擎实现动态调用这样功能叫 Function call。注意MCP 有很多功能其中一个叫 MCP Tool可以视为跟 Plugin 实现类似功能的东西。MCP Tool 对标 Plugin MCP 不止包含 Tool 这一功能。但是每个 LLM 框架的 Plugin 实现方式不一样其使用和实现机制跟语言特性深度绑定不能实现跨服务跨平台使用所以出现了 MCP Tool MCP Tool 是对标 Plugin 的一类功能主要目的跟 Plugin 一样提供 Function但是 MCP 有统一协议标准跟语言无关、跟平台无关但是 MCP 也不是完全替换 Plugin Plugin 依然具有很大的用武之地。MCP Tool、Plugin 最后都是转换为 Function call 的有很多人会把 MCP 、MCP Tool 和 Function call 搞混认为 MCP 是替换 Function call 的所以要注意对标 Plugin 的是 MCP Tool而两者都是转换为 Function 给 AI 使用的。MCP Tool以 TransportSseClient 为例如果要在 Client 调用 TransportSseServer 的 Tool需要指定 Tool 名字和参数。后续将会讲解如何通过 SK 将 mcp tool 提供给 AI 模型。var echoTool tools.First(x x.Name Echo);var result await client.CallToolAsync(Echo, new Dictionary{{ message,痴者工良}});foreach (var item in result.Content){Console.WriteLine($type: {item.Type},text: {item.Text});}让我们再回顾 MCP Server 是怎么提供 Tool 的。首先服务端需要定义 Tool 类和函数。[McpServerToolType]public sealed class EchoTool{[McpServerTool, Description(Echoes the input back to the client.)]public static string Echo(string message){return hello message;}}Mcp server 可以通过以下两种方式暴露 tool。// 直接指定 Tool 类builder.Services.AddMcpServer().WithHttpTransport().WithTools().WithTools();// 扫描程序集builder.Services.AddMcpServer().WithHttpTransport().WithStdioServerTransport().WithToolsFromAssembly();Client 识别服务端的 Tool 列表时可以使用 McpClientTool.ProtocolTool.InputSchema 获取 tool 的输入参数格式image-20250419152644657其内容格式示例如下Annotations: nullDescription: Echoes the input back to the client.Name: EchoInputSchema: {title:Echo,description:Echoes the input back to the client.,type:object,properties:{message:{type:string}},required:[message]}[McpServerToolType] 用于将包含应该作为ModelContextProtocol.Server.McpServerTools公开的方法的类型属性化。[McpServerTool]用于指示应该将方法视为 ModelContextProtocol.Server.McpServerTool。[Description] 则用于添加注释。依赖注入在实现 Tool 函数时服务端是可以通过函数实现依赖注入的。参考示例项目 InjectServer、InjectClient。image-20250419160655239添加一个服务类并注册到容器中。public class MyService{public string Echo(string message){return hello message;}}builder.Services.AddScoped();在 Tool 函数中注入该服务[McpServerToolType]public sealed class MyTool{[McpServerTool, Description(Echoes the input back to the client.)]public static string Echo(MyService myService, string message){return myService.Echo(message);}}将 MCP Tool 提交到 AI 对话中前面提到MCP Tool 和 Plugin 都是实现 Function call 的一种方式当在 AI 对话中使用 Tool 时其主要过程如下当你提出问题时client 将你的问题发送给 LLM LLM 分析可用的 tools 并决定使用哪些 toolclient 通过 MCP server 执行选择的 tool结果被发回给 LLMLLM 制定自然语言响应响应显示给你这个过程并不是只有一两次可能发生多次具体细节将会在 高德地图 MCP 实战 中讲解这里只是简单提及。将 Tool 提交到对话上下文的伪代码// Get available functions.IList tools await client.ListToolsAsync();// Call the chat client using the tools.IChatClient chatClient ...;var response await chatClient.GetResponseAsync(your prompt here,new() { Tools [.. tools] },高德地图 MCP 实战聊了这么久终于到了实战对接环节本节将会通过高德地图案例讲解 MCP Tool 的逻辑细节和对接使用方式。代码参考示例项目 amap。高德地图 MCP Server 目前主要提供的功能地理编码逆地理编码IP 定位天气查询骑行路径规划步行路径规划驾车路径规划公交路径规划距离测量关键词搜索周边搜索详情搜索其 Tool 名称如下maps_direction_bicyclingmaps_direction_drivingmaps_direction_transit_integratedmaps_direction_walkingmaps_distancemaps_geomaps_regeocodemaps_ip_locationmaps_around_searchmaps_search_detailmaps_text_searchmaps_weather高德地图每天都给开发者提供了免费额度所以做该实验时不需要担心需要付费。打开 https://console.amap.com/dev/key/app 创建一个新的应用然后复制应用 key。高德 mcp 服务器地址https://mcp.amap.com/sse?key{在高德官网上申请的key}在 amap 项目的 appsettings.json 添加以下 json替换里面的部分参数。笔者注除了 gpt-4o 模型其它注册 Function call 的模型也可以使用。McpServers: {amap-amap-sse: {url: https://mcp.amap.com/sse?key{在高德官网上申请的key}}},AIModel: {ModelId: gpt-4o,DeploymentName: gpt-4o,Endpoint: https://openai.com/,Key: aaaaaaaa}image-20250419170432902导入配置并创建日志var configuration new ConfigurationBuilder().AddJsonFile(appsettings.json).AddJsonFile(appsettings.Development.json).Build();using ILoggerFactory factory LoggerFactory.Create(builder builder.AddConsole());第一步创建 mcp 客户端连接高德 MCP Server并获取 Tool 列表。var defaultOptions new McpClientOptions{ClientInfo new() { Name 地图规划, Version 1.0.0 }};var defaultConfig new SseClientTransportOptions{Endpoint new Uri(configuration[McpServers:amap-amap-sse:url]!),Name amap-amap-sse,};await using var client await McpClientFactory.CreateAsync(new SseClientTransport(defaultConfig),defaultOptions,loggerFactory: factory);var tools await client.ListToolsAsync();foreach (var tool in tools){Console.WriteLine($Connected to server with tools: {tool.Name});}image-20250419170702770第二步连接 AI 模型和配置 MCP使用 SemanticKernel 框架对接 LLM将 MCP Tool 转换为 Function 添加到对话上下文中。var aiModel configuration.GetSection(AIModel);var builder Kernel.CreateBuilder().AddAzureOpenAIChatCompletion(deploymentName: aiModel[ModelId],endpoint: aiModel[Endpoint],apiKey: aiModel[Key]);builder.Services.AddLogging(s {s.AddConsole();});Kernel kernel builder.Build();// 这里将 mcp 转换为 functaion callkernel.Plugins.AddFromFunctions(amap, tools.Select(aiFunction aiFunction.AsKernelFunction()));var chatCompletionService kernel.GetRequiredService();OpenAIPromptExecutionSettings openAIPromptExecutionSettings new(){Temperature 0,FunctionChoiceBehavior FunctionChoiceBehavior.Auto(options: new() { RetainArgumentTypes true })};image-20250419170714853第三步对话交互编写控制台与用户对话交互。var history new ChatHistory();string? userInput;do{Console.Write(用户提问 );userInput Console.ReadLine();history.AddUserMessage(userInput!);var result await chatCompletionService.GetChatMessageContentAsync(history,executionSettings: openAIPromptExecutionSettings,kernel: kernel);Console.WriteLine(AI 回答 result);history.AddMessage(result.Role, result.Content ?? string.Empty);} while (userInput is not null);image-20250419170840789演示地图规划注意由于高德地图免费额度限流而 AI 对话时可能有多次对 MCP Server 请求因此有时候效果并不是那么好。1. 智能旅游路线规划最多支持16个途经点的旅游路线规划自动计算最优顺序并提供可视化地图链接。使用示例请帮我规划一条上海三日游路线包括外滩、东方明珠、迪士尼、豫园、南京路并提供可视化地图image-202504191723483762. 景点搜索与详情查询查询景点的详细信息包括评分、开放时间、门票价格等。使用示例请查询黄山风景区的开放时间、门票价格和旅游季节推荐image-20250419172058217AI 是怎么识别调用 MCP在编写高德地图规划时有一段代码是将 MCP 服务器的接口转换为 Function 的代码如下kernel.Plugins.AddFromFunctions(amap, tools.Select(aiFunction aiFunction.AsKernelFunction()))其实在这里就可以下结论并不是 AI 模型直接调用 MCP Server 的依然 Client 进行是 Function call 。通过拦截 http 请求可以发现当用户输入 请帮我规划一条上海三日游路线包括外滩、东方明珠、迪士尼、豫园、南京路并提供可视化地图 时客户端首先将用户提问和 mcp 服务所提供的 function call 一起发送到 AI 模型服务器。对话时Client 提供给 LLM 的 Function MCP Tool列表。image-20250419173936573image-20250419173957048然后 AI 回答要调用的 Function call 步骤和参数接着由客户端实现将 Function 定位 MCP Server并顺序调用每个 Tool。LLM 返回要顺序调用的 Function 列表以及参数image-20250419174037025客户端将每个 Function 的执行结果和用户的提问等信息一起再次提交给 AI 模型服务器。image-20250419174630396由于高德接口并发限制有部分接口调用失败那么客户端可能会来回请求多次最后输出 AI 的回答。image-20250419174824315到这里读者应该明白 MCP Tool、Plugin、Function Call 的关系了吧实现 Mcp Server前面笔者介绍了 MCP Tool但是 MCP Server 还可以提供很多很有用的功能MCP 协议定义了以下核心模块Core architectureResourcesPromptsToolsSamplingRootsTransports作为当前社区中最关注的 Tools本文已经单独介绍接下来将会以继续讲解其它功能模块。实现 Resources示例项目参考ResourceServer、ResourceClient。Resources 定义Resources 是 Model Context Protocol (MCP) 中的一个核心原语它允许服务器暴露可以被 clients 读取并用作 LLM 交互上下文的数据和内容。Resources 代表 MCP server 想要提供给 clients 的任何类型的数据在使用上MCP Server 可以给每种资源定义一个 Uri这个 Uri 的协议格式可以是虚拟的这不重要只要是能够定位资源的一段 Uri 字符串即可。只看定义读者可能不理解什么意思没关系等后面动手做的时候就知道了。Resources 可以包括文件内容数据库记录API 响应实时系统数据屏幕截图和图像日志文件等等每个 resource 都由一个唯一的 URI 标识并且可以包含文本或二进制数据。Resources 使用以下格式的 URIs 进行标识[protocol]://[host]/[path]例如file:///home/user/documents/report.pdfpostgres://database/customers/schemascreen://localhost/display1Resources 的文件类型主要是文本资源和二进制资源。文本资源文本资源包含 UTF-8 编码的文本数据。这些适用于源代码配置文件日志文件JSON/XML 数据纯文本二进制资源二进制资源包含以 base64 编码的原始二进制数据。这些适用于图像PDFs音频文件视频文件其他非文本格式Resources Server、Client 实现客户端使用 Resources 服务时有以下 Api那么在本节的学习中将会围绕这这些接口讲解如何在服务段实现对应的功能。image-20250419194249066实现 Resources 时主要有两种提供 Resources 的方式一种是通过模板动态提供 Resource Uri 的格式一种是直接提供具体的 Resource Uri。Resource Uri 格式示例test://static/resource/{README.txt}MCP Server 提供的 Resource Uri 格式是可以随意自定义的这些 Uri 并不是直接给 Client 读取的Client 在需要读取 Resource 是把 Uri 发送给 MCP ServerMCP Server 自行解析 Uri 并定位对应的资源然后把资源内容返回给 Client。也就是说该 Uri 的协议其实就是字符串只要在当前 MCP Server 和 Client 之间能用即可。MCP Server 可以通过模板提供某类资源这类资源的的地址是动态的要根据 id 实时获取。builder.Services.AddMcpServer().WithListResourceTemplatesHandler(async (ctx, ct) {return new ListResourceTemplatesResult{ResourceTemplates [new ResourceTemplate { Name Static Resource, Description A static resource with a numeric ID, UriTemplate test://static/resource/{id} }]};});对于地址固定的 Resource可以通过这种方式暴露出去比如有个使用必读的文件只需要固定暴露地址。builder.Services.AddMcpServer().WithListResourcesHandler(async (ctx, ct) {await Task.CompletedTask;var readmeResource new Resource{Uri test://static/resource/README.txt,Name Resource README.txt,MimeType application/octet-stream,Description Convert.ToBase64String(Encoding.UTF8.GetBytes(这是一个必读文件))};return new ListResourcesResult{Resources new List{readmeResource}};})Client 读取资源模板和静态资源列表var defaultOptions new McpClientOptions{ClientInfo new() { Name ResourceClient, Version 1.0.0 }};var defaultConfig new SseClientTransportOptions{Endpoint new Uri($http://localhost:5000/sse),Name Everything,};// Create client and run testsawait using var client await McpClientFactory.CreateAsync(new SseClientTransport(defaultConfig),defaultOptions,loggerFactory: NullLoggerFactory.Instance);var resourceTemplates await client.ListResourceTemplatesAsync();var resources await client.ListResourcesAsync();foreach (var template in resourceTemplates){Console.WriteLine($Connected to server with resource templates: {template.Name});}foreach (var resource in resources){Console.WriteLine($Connected to server with resources: {resource.Name});}那么客户端如果从 MCP 服务器读取资源只需要将 Resource Uri 传递即可。var readmeResource await client.ReadResourceAsync(resources.First().Uri);这里只介绍了 MCP Server 提供 Resource Uri那么当 Client 要获取某个 Resource Uri 的内容时MCP Server 要怎么处理呢ModelContextProtocol CSharp 目前提供了两种实现TextResourceContentsBlobResourceContents比如说当 Client 访问 test://static/resource/README.txt 时可以将 README.txt 文件直接以文本的形式返回.WithReadResourceHandler(async (ctx, ct) {var uri ctx.Params?.Uri;if (uri is null || !uri.StartsWith(test://static/resource/)){throw new NotSupportedException($Unknown resource: {uri});}if(uri test://static/resource/README.txt){var readmeResource new Resource{Uri test://static/resource/README.txt,Name Resource README.txt,MimeType application/octet-stream,Description 这是一个必读文件};return new ReadResourceResult{Contents [new TextResourceContents{Text File.ReadAllText(README.txt),MimeType readmeResource.MimeType,Uri readmeResource.Uri,}]};}})image-20250419201835608如果 Client 访问了其它 Resource则以二进制的形式返回.WithReadResourceHandler(async (ctx, ct) {var uri ctx.Params?.Uri;if (uri is null || !uri.StartsWith(test://static/resource/)){throw new NotSupportedException($Unknown resource: {uri});}int index int.Parse(uri[test://static/resource/.Length..]) - 1;if (index 0 || index ResourceGenerator.Resources.Count){throw new NotSupportedException($Unknown resource: {uri});}var resource ResourceGenerator.Resources[index];return new ReadResourceResult{Contents [new TextResourceContents{Text resource.Description!,MimeType resource.MimeType,Uri resource.Uri,}]};})客户端读取 test://static/resource/README.txt 示例var readmeResource await client.ReadResourceAsync(resources.First().Uri);var textContent readmeResource.Contents.First() as TextResourceContents;Console.WriteLine(textContent.Text));image-20250420151630678Resource 订阅Clients 可以订阅特定 resources 的更新Client 使用 resource URI 发送 resources/subscribe当 resource 更改时服务器发送 notifications/resources/updatedClient 可以使用 resources/read 获取最新内容Client 可以使用 resources/unsubscribe 取消订阅一般来说MCP Server 要实现工厂模式以便动态记录有哪些 Resource Uri 是被订阅的那么当这些 Uri 的资源发生变化时才需要推送否则即使发送变化也没有推送更新的必要。但是目前来说只有 WithStdioServerTransport() 才能起效笔者在 WithHttpTransport() 实验失败。