找回密码
 立即注册
首页 业界区 业界 抛开官方库,手撸一个轻量级 MCP 服务端 ...

抛开官方库,手撸一个轻量级 MCP 服务端

马璞玉 13 小时前
大家好!在昨天的文章 《官方文档没告诉你的:通过抓包,深入揭秘MCP协议底层通信》 中,我们通过Fiddler工具,像侦探一样,一步步揭开了MCP(Model Context Protocol)在无状态HTTP模式下的神秘面纱。我们搞清楚了它的两步握手、SSE(Server-Sent Events)响应机制以及精巧的两种错误处理方式。
1.png

然而,仅仅停留在理论分析层面总感觉意犹未尽。更重要的是,当我们审视官方提供的 ModelContextProtocol.AspNetCore 这个NuGet包时(当前版本0.3.0-preview.3),会发现它目前引入了相当多的依赖项:

  • Microsoft.Bcl.Memory (>= 9.0.5)
  • Microsoft.Extensions.AI.Abstractions (>= 9.7.1)
  • Microsoft.Extensions.Logging.Abstractions (>= 8.0.3)
  • System.Diagnostics.DiagnosticSource (>= 8.0.1)
  • System.IO.Pipelines (>= 8.0.0)
  • System.Net.ServerSentEvents (>= 10.0.0-preview.4.25258.110)
  • System.Text.Json (>= 8.0.6)
  • System.Threading.Channels (>= 8.0.0)
  • Microsoft.Extensions.Hosting.Abstractions (>= 8.0.1)
  • ModelContextProtocol.Core (>= 0.3.0-preview.3)
其中,最令人不安的莫过于 System.Net.ServerSentEvents,它竟然是一个 .NET 10 的预览版包!在生产环境中使用预览版包,通常是大忌。
既然我们已经通过抓包掌握了协议的全部细节,那么,何不自己动手,实现一个轻量级、零预览版依赖的MCP服务端呢?这不仅是一次绝佳的学习实践,也能让我们对协议的理解更上一层楼。
今天,我们就来完成这个挑战:不依赖官方服务端库,直接用纯粹的ASP.NET Core代码,实现一个功能完备的MCP服务端。
我们的目标:保持工具定义的简洁性

在动手之前,我们先定一个目标。我们希望定义工具(Tools)的方式能够尽可能地简洁和直观,几乎和昨天的代码保持一致:
  1. using System.ComponentModel;
  2. public class Tools(IHttpContextAccessor http)
  3. {
  4.     [Description("Echoes the message back to the client.")]
  5.     public string Echo(string message) => $"hello {message}";
  6.     [Description("Returns the IP address of the client.")]
  7.     public string EchoIP() => http.HttpContext?.Connection.RemoteIpAddress?.ToString() ?? "Unknown";
  8.     [Description("Counts from 0 to n, reporting progress at each step.")]
  9.     public async Task<int> Count(int n, IProgress<ProgressNotificationValue> progress)
  10.     {
  11.         for (int i = 0; i < n; ++i)
  12.         {
  13.             progress.Report(new ProgressNotificationValue()
  14.             {
  15.                 Progress = i,
  16.                 Total = n,
  17.                 Message = $"Step {i} of {n}",
  18.             });
  19.             await Task.Delay(100);
  20.         }
  21.         return n;
  22.     }
  23.     [Description("Throws an exception for testing purposes.")]
  24.     public string TestThrow()
  25.     {
  26.         throw new Exception("This is a test exception");
  27.     }
  28. }
复制代码
注意到变化了吗?我们去掉了官方库定义的 [McpServerToolType] 和 [McpServerTool] 特性。取而代之的是一种更符合ASP.NET Core直觉的方式:任何 public 方法都自动成为一个工具,并使用标准的 System.ComponentModel.DescriptionAttribute 来提供工具描述。
理想中的使用方式

我们期望最终的使用方式能像下面这样优雅:
  1. WebApplicationBuilder builder = WebApplication.CreateBuilder();
  2. // 1. 注册原生服务和我们的工具类
  3. builder.Services.AddHttpContextAccessor();
  4. builder.Services.AddTransient<Tools>();
  5. WebApplication app = builder.Build();
  6. // 2. 映射 MCP 端点,自动发现并使用 Tools 类
  7. app.MapMcpEndpoint<Tools>("/");
  8. // 3. 启动应用
  9. app.Run();
复制代码
是的,你没看错。核心就在于 builder.Services.AddTransient(); 和 app.MapMcpEndpoint("/"); 这两行。前者负责将我们的工具类注册到依赖注入容器,后者则是我们即将创建的魔法扩展方法,它会自动处理所有MCP协议的细节。
第一步:定义协议的“语言” - DTOs

要实现协议,首先要定义好通信双方所使用的“语言”,也就是数据传输对象(DTOs)。根据昨天的抓包分析,我们用C#的 record 类型来精确描述这些JSON结构。
  1. using System.Text.Json.Serialization;
  2. // --- JSON-RPC Base Structures ---
  3. public record JsonRpcRequest(
  4.     [property: JsonPropertyName("jsonrpc")] string JsonRpc,
  5.     [property: JsonPropertyName("method")] string Method,
  6.     [property: JsonPropertyName("params")] object? Params,
  7.     [property: JsonPropertyName("id")] int? Id
  8. );
  9. public record JsonRpcResponse(
  10.     [property: JsonPropertyName("jsonrpc")] string JsonRpc,
  11.     [property: JsonPropertyName("result")] object? Result,
  12.     [property: JsonPropertyName("error")] object? Error,
  13.     [property: JsonPropertyName("id")] int? Id
  14. );
  15. public record JsonRpcError(
  16.     [property: JsonPropertyName("code")] int Code,
  17.     [property: JsonPropertyName("message")] string Message
  18. );
  19. // --- MCP Specific Payloads ---
  20. // For initialize method
  21. public record InitializeParams(
  22.     [property: JsonPropertyName("protocolVersion")] string ProtocolVersion,
  23.     [property: JsonPropertyName("clientInfo")] ClientInfo ClientInfo
  24. );
  25. public record ClientInfo([property: JsonPropertyName("name")] string Name, [property: JsonPropertyName("version")] string Version);
  26. public record InitializeResult(
  27.     [property: JsonPropertyName("protocolVersion")] string ProtocolVersion,
  28.     [property: JsonPropertyName("capabilities")] ServerCapabilities Capabilities,
  29.     [property: JsonPropertyName("serverInfo")] ClientInfo ServerInfo
  30. );
  31. public record ServerCapabilities([property: JsonPropertyName("tools")] object Tools);
  32. // For tools/call method
  33. public record ToolCallParams(
  34.     [property: JsonPropertyName("name")] string Name,
  35.     [property: JsonPropertyName("arguments")] Dictionary<string, object?> Arguments,
  36.     [property: JsonPropertyName("_meta")] ToolCallMeta? Meta
  37. );
  38. public record ToolCallMeta([property: JsonPropertyName("progressToken")] string ProgressToken);
  39. // For tool call results
  40. public record ToolCallResult(
  41.     [property: JsonPropertyName("content")] List<ContentItem> Content,
  42.     [property: JsonPropertyName("isError")] bool IsError = false
  43. );
  44. public record ContentItem([property: JsonPropertyName("type")] string Type, [property: JsonPropertyName("text")] string Text);
  45. // For tools/list results
  46. public record ToolListResult(
  47.     [property: JsonPropertyName("tools")] List<ToolDefinition> Tools
  48. );
  49. public record ToolDefinition(
  50.     [property: JsonPropertyName("name")] string Name,
  51.     [property: JsonPropertyName("description")] string Description,
  52.     [property: JsonPropertyName("inputSchema")] object InputSchema
  53. );
  54. // For progress notifications
  55. public record ProgressNotification(
  56.     [property: JsonPropertyName("jsonrpc")] string JsonRpc,
  57.     [property: JsonPropertyName("method")] string Method,
  58.     [property: JsonPropertyName("params")] ProgressParams Params
  59. );
  60. public record ProgressParams(
  61.     [property: JsonPropertyName("progressToken")] string ProgressToken,
  62.     [property: JsonPropertyName("progress")] int Progress,
  63.     [property: JsonPropertyName("total")] int Total,
  64.     [property: JsonPropertyName("message")] string Message
  65. );
  66. // This class is for the IProgress<T> interface in our Tools methods
  67. public class ProgressNotificationValue
  68. {
  69.     public int Progress { get; set; }
  70.     public int Total { get; set; }
  71.     public string Message { get; set; } = string.Empty;
  72. }
复制代码
第二步:打造核心引擎 - McpEndpointExtensions

接下来,就是实现我们魔法的源泉:一个IEndpointRouteBuilder的扩展方法。我们将所有逻辑都封装在一个静态类 McpEndpointExtensions 中。
这个类将负责:

  • 路由映射:监听指定路径的 POST 和 GET 请求。
  • 请求分发:根据JSON-RPC请求中的method字段,调用不同的处理函数。
  • 工具发现与调用:使用反射来查找和执行TTools类中的工具方法。
  • 响应构建:手动构建符合SSE规范的响应流。
  • 错误处理:精确复现抓包分析中发现的两种错误模型。
  1. using Microsoft.AspNetCore.Mvc;
  2. using Microsoft.AspNetCore.WebUtilities;
  3. using Microsoft.Extensions.Primitives;
  4. using System.ComponentModel;
  5. using System.Reflection;
  6. using System.Text;
  7. using System.Text.Json;
  8. using System.Text.Json.Serialization;
  9. public static class McpEndpointExtensions
  10. {
  11.     // JSON-RPC Error Codes from your article's findings
  12.     private const int InvalidParamsErrorCode = -32602; // Invalid params
  13.     private const int MethodNotFoundErrorCode = -32601; // Method not found
  14.     private static readonly JsonSerializerOptions s_jsonOptions = new()
  15.     {
  16.         DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
  17.         PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
  18.     };
  19.     /// <summary>
  20.     /// Maps an endpoint that speaks the Model Context Protocol.
  21.     /// </summary>
  22.     public static IEndpointRouteBuilder MapMcpEndpoint<TTools>(this IEndpointRouteBuilder app, string pattern) where TTools : class
  23.     {
  24.         // 预先通过反射发现所有工具方法,并转换为snake_case以匹配MCP命名习惯
  25.         Dictionary<string, MethodInfo> methods = typeof(TTools).GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
  26.             .ToDictionary(k => ToSnakeCase(k.Name), v => v);
  27.         app.MapPost(pattern, async (HttpContext context, [FromServices] IServiceProvider sp) =>
  28.         {
  29.             JsonRpcRequest? request = await JsonSerializer.DeserializeAsync<JsonRpcRequest>(context.Request.Body, s_jsonOptions);
  30.             if (request == null)
  31.             {
  32.                 context.Response.StatusCode = 400; // Bad Request
  33.                 return;
  34.             }
  35.             // 核心:处理不同的MCP方法
  36.             switch (request.Method)
  37.             {
  38.                 case "initialize":
  39.                     await HandleInitialize(context, request);
  40.                     break;
  41.                 case "notifications/initialized":
  42.                     // 在无状态模式下,这个请求只是一个确认,我们返回与initialize类似的信息
  43.                     await HandleInitialize(context, request);
  44.                     break;
  45.                 case "tools/list":
  46.                     await HandleToolList<TTools>(context, request);
  47.                     break;
  48.                 case "tools/call":
  49.                     await HandleToolCall<TTools>(context, request, sp, methods);
  50.                     break;
  51.                 default:
  52.                     JsonRpcResponse errorResponse = new("2.0", null, new JsonRpcError(MethodNotFoundErrorCode, "Method not found"), request.Id);
  53.                     await WriteSseMessageAsync(context.Response, errorResponse);
  54.                     break;
  55.             }
  56.         });
  57.         // 旧版SDK会发送GET请求,我们明确返回405
  58.         app.MapGet(pattern, context =>
  59.         {
  60.             context.Response.StatusCode = StatusCodes.Status405MethodNotAllowed;
  61.             context.Response.Headers.Allow = "POST";
  62.             return Task.CompletedTask;
  63.         });
  64.         return app;
  65.     }
  66.     private static string ToSnakeCase(string name)
  67.     {
  68.         if (string.IsNullOrEmpty(name)) return name;
  69.         var sb = new StringBuilder(name.Length);
  70.         for (int i = 0; i < name.Length; i++)
  71.         {
  72.             char c = name[i];
  73.             if (char.IsUpper(c))
  74.             {
  75.                 if (sb.Length > 0 && i > 0 && !char.IsUpper(name[i-1])) sb.Append('_');
  76.                 sb.Append(char.ToLowerInvariant(c));
  77.             }
  78.             else
  79.             {
  80.                 sb.Append(c);
  81.             }
  82.         }
  83.         return sb.ToString();
  84.     }
  85.     private static async Task HandleInitialize(HttpContext context, JsonRpcRequest request)
  86.     {
  87.         // 复用或创建 Session ID
  88.         string sessionId = context.Request.Headers.TryGetValue("Mcp-Session-Id", out StringValues existingSessionId)
  89.             ? existingSessionId.ToString()
  90.             : WebEncoders.Base64UrlEncode(Guid.NewGuid().ToByteArray());
  91.         context.Response.Headers["Mcp-Session-Id"] = sessionId;
  92.         // 构建与抓包一致的响应
  93.         InitializeResult result = new(
  94.             "2025-06-18", // Echo the protocol version
  95.             new ServerCapabilities(new { listChanged = true }), // Mimic the capabilities
  96.             new ClientInfo("PureAspNetCoreMcpServer", "1.0.0")
  97.         );
  98.         JsonRpcResponse response = new("2.0", result, null, request.Id);
  99.         await WriteSseMessageAsync(context.Response, response);
  100.     }
  101.     private static async Task HandleToolList<TTools>(HttpContext context, JsonRpcRequest request) where TTools : class
  102.     {
  103.         EchoSessionId(context);
  104.         List<ToolDefinition> toolDefs = [];
  105.         MethodInfo[] toolMethods = typeof(TTools).GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
  106.         foreach (MethodInfo method in toolMethods)
  107.         {
  108.             string description = method.GetCustomAttribute<DescriptionAttribute>()?.Description ?? "No description.";
  109.             // 简化的动态Schema生成
  110.             Dictionary<string, object> properties = [];
  111.             List<string> required = [];
  112.             foreach (ParameterInfo param in method.GetParameters())
  113.             {
  114.                 if (param.ParameterType == typeof(IProgress<ProgressNotificationValue>)) continue; // 忽略进度报告参数
  115.                 properties[param.Name!] = new { type = GetJsonType(param.ParameterType) };
  116.                 if (!param.IsOptional)
  117.                 {
  118.                     required.Add(param.Name!);
  119.                 }
  120.             }
  121.             var schema = new { type = "object", properties, required };
  122.             toolDefs.Add(new ToolDefinition(ToSnakeCase(method.Name), description, schema));
  123.         }
  124.         ToolListResult result = new(toolDefs);
  125.         JsonRpcResponse response = new("2.0", result, null, request.Id);
  126.         await WriteSseMessageAsync(context.Response, response);
  127.     }
  128.     private static async Task HandleToolCall<TTools>(HttpContext context, JsonRpcRequest request, IServiceProvider sp, Dictionary<string, MethodInfo> methods) where TTools : class
  129.     {
  130.         EchoSessionId(context);
  131.         ToolCallParams? toolCallParams = JsonSerializer.Deserialize<ToolCallParams>(JsonSerializer.Serialize(request.Params, s_jsonOptions), s_jsonOptions);
  132.         if (toolCallParams == null) return;
  133.         string toolName = toolCallParams.Name;
  134.         methods.TryGetValue(toolName, out MethodInfo? method);
  135.         // 场景1: 调用不存在的工具 -> 返回标准JSON-RPC错误
  136.         if (method == null)
  137.         {
  138.             JsonRpcError error = new(InvalidParamsErrorCode, $"Unknown tool: '{toolName}'");
  139.             JsonRpcResponse response = new("2.0", null, error, request.Id);
  140.             await WriteSseMessageAsync(context.Response, response);
  141.             return;
  142.         }
  143.         // 使用DI容器创建工具类的实例
  144.         using IServiceScope scope = sp.CreateScope();
  145.         TTools toolInstance = scope.ServiceProvider.GetRequiredService<TTools>();
  146.         object? resultValue;
  147.         bool isError = false;
  148.         try
  149.         {
  150.             // 通过反射准备方法参数
  151.             ParameterInfo[] methodParams = method.GetParameters();
  152.             object?[] args = new object?[methodParams.Length];
  153.             for (int i = 0; i < methodParams.Length; i++)
  154.             {
  155.                 ParameterInfo p = methodParams[i];
  156.                 if (p.ParameterType == typeof(IProgress<ProgressNotificationValue>))
  157.                 {
  158.                     // 创建一个IProgress<T>的实现,它会将进度作为SSE消息发回客户端
  159.                     args[i] = new ProgressReporter(context.Response, toolCallParams.Meta!.ProgressToken);
  160.                 }
  161.                 else if (toolCallParams.Arguments.TryGetValue(p.Name!, out object? argValue) && argValue is JsonElement element)
  162.                 {
  163.                     args[i] = element.Deserialize(p.ParameterType, s_jsonOptions);
  164.                 }
  165.                 else if (p.IsOptional)
  166.                 {
  167.                     args[i] = p.DefaultValue;
  168.                 }
  169.                 else
  170.                 {
  171.                      // 场景2a: 缺少必要参数 -> 抛出异常,进入catch块
  172.                     throw new TargetParameterCountException($"Tool '{toolName}' requires parameter '{p.Name}' but it was not provided.");
  173.                 }
  174.             }
  175.             object? invokeResult = method.Invoke(toolInstance, args);
  176.             // 处理异步方法
  177.             if (invokeResult is Task task)
  178.             {
  179.                 await task;
  180.                 resultValue = task.GetType().IsGenericType ? task.GetType().GetProperty("Result")?.GetValue(task) : null;
  181.             }
  182.             else
  183.             {
  184.                 resultValue = invokeResult;
  185.             }
  186.         }
  187.         // 场景2b: 工具执行时内部抛出异常 -> isError: true
  188.         catch (Exception ex)
  189.         {
  190.             isError = true;
  191.             // 将异常信息包装在result中,而不是顶层error
  192.             resultValue = $"An error occurred invoking '{toolName}'. Details: {ex.InnerException?.Message ?? ex.Message}";
  193.         }
  194.         List<ContentItem> content = [new("text", resultValue?.ToString() ?? string.Empty)];
  195.         ToolCallResult result = new(content, isError);
  196.         JsonRpcResponse finalResponse = new("2.0", result, null, request.Id);
  197.         await WriteSseMessageAsync(context.Response, finalResponse);
  198.     }
  199.     // 手动实现SSE消息写入,告别预览版包
  200.     private static async Task WriteSseMessageAsync(HttpResponse response, object data)
  201.     {
  202.         if (!response.Headers.ContainsKey("Content-Type"))
  203.         {
  204.             response.ContentType = "text/event-stream";
  205.             response.Headers.CacheControl = "no-cache,no-store";
  206.             response.Headers.ContentEncoding = "identity";
  207.             response.Headers.KeepAlive = "true";
  208.         }
  209.         string json = JsonSerializer.Serialize(data, s_jsonOptions);
  210.         string message = $"event: message\ndata: {json}\n\n";
  211.         await response.WriteAsync(message);
  212.         await response.Body.FlushAsync();
  213.     }
  214.     private static void EchoSessionId(HttpContext context)
  215.     {
  216.         if (context.Request.Headers.TryGetValue("Mcp-Session-Id", out StringValues sessionId))
  217.         {
  218.             context.Response.Headers["Mcp-Session-Id"] = sessionId;
  219.         }
  220.     }
  221.     private static string GetJsonType(Type type) => Type.GetTypeCode(type) switch
  222.     {
  223.         TypeCode.String => "string",
  224.         TypeCode.Int32 or TypeCode.Int64 or TypeCode.Int16 or TypeCode.UInt32 => "integer",
  225.         TypeCode.Double or TypeCode.Single or TypeCode.Decimal => "number",
  226.         TypeCode.Boolean => "boolean",
  227.         _ => "object"
  228.     };
  229.     // 专门用于处理进度报告的辅助类
  230.     private class ProgressReporter(HttpResponse response, string token) : IProgress<ProgressNotificationValue>
  231.     {
  232.         public void Report(ProgressNotificationValue value)
  233.         {
  234.             ProgressParams progressParams = new(token, value.Progress, value.Total, value.Message);
  235.             ProgressNotification notification = new("2.0", "notifications/progress", progressParams);
  236.             // 警告: 在同步方法中调用异步代码,在真实生产环境中需要更优雅的处理
  237.             WriteSseMessageAsync(response, notification).GetAwaiter().GetResult();
  238.         }
  239.     }
  240. }
复制代码
完整代码已备好!

为了方便大家动手实践,我已经将上述所有可直接运行的示例代码上传到了 GitHub Gist。您可以通过以下链接访问:

  • https://gist.github.com/sdcb/80353c3273cd1a89b839c6d40fb1adbc
该Gist中包含了两个文件:

  • mcp-server-raw.linq: 我们刚刚从零开始构建的轻量级MCP服务端。
  • mcp-client.linq: 用于测试的客户端。
这两个文件都可以直接在最新版的 LINQPad 中打开并运行,让您能够立即体验和调试,如果您访问 Github Gist 有困难,则可以访问这个备用地址:https://github.com/sdcb/blog-data/tree/master/2025
2.png

第三步:见证奇迹的时刻

现在,我们所有的准备工作都已就绪。我们可以用和昨天一模一样的客户端代码来测试我们的新服务端了:
  1. // 客户端代码完全不变!
  2. var clientTransport = new SseClientTransport(new SseClientTransportOptions()
  3. {
  4.     Name = "MyServer",
  5.     Endpoint = new Uri("http://localhost:5000"), // 注意端口可能不同
  6. });
  7. var client = await McpClientFactory.CreateAsync(clientTransport);
  8. // 1. 列出工具
  9. (await client.ListToolsAsync()).Select(x => new { x.Name, Desc = JsonObject.Parse(x.JsonSchema.ToString()) }).Dump();
  10. // 2. 调用简单工具
  11. (await client.CallToolAsync(
  12.     "echo",
  13.     new Dictionary<string, object?>() { ["message"] = ".NET is awesome!" },
  14.     cancellationToken: CancellationToken.None)).Dump();
  15. // 3. 调用带进度的工具
  16. (await client.CallToolAsync(
  17.     "count",
  18.     new Dictionary<string, object?>() { ["n"] = 5 },
  19.     new Reporter(),
  20.     cancellationToken: CancellationToken.None)).Dump();
  21.    
  22. // 4. 调用会抛出异常的工具
  23. (await client.CallToolAsync("test_throw", cancellationToken: CancellationToken.None)).Dump();
  24. // 5. 调用不存在的工具
  25. (await client.CallToolAsync("not-existing-tool", cancellationToken: CancellationToken.None)).Dump();
  26. // ... Reporter class as before ...
复制代码
启动我们的新服务端,再运行客户端代码。打开抓包工具,你会发现,所有HTTP请求和SSE响应的格式、内容和行为,都与昨天分析的官方库实现完全一致!我们成功了!
对错误处理的进一步思考

值得一提的是,昨天的文章没有深入探讨参数错误的情况。比如 count 工具需要一个名为 n 的 int 类型参数,如果客户端错误地传递了一个 n2 参数,会发生什么?
在我今天实现的 HandleToolCall 方法中,参数匹配逻辑会因为找不到名为 n 的键而抛出 TargetParameterCountException。这个异常会被 try-catch 块捕获,然后和 test_throw 的情况一样,返回一个调用“成功”(HTTP 200)、但在 result 载荷中包含 "isError": true 和详细错误信息的响应。这恰好证明了MCP这种错误处理设计的健壮性:它能统一处理业务逻辑层面(工具内部异常)和参数绑定层面(调用约定不匹配)的多种失败情况。
总结

通过本次实践,我们不仅重温了MCP协议的通信原理,更重要的是,我们亲手实现了一个轻量级、无预览版依赖的MCP服务端。这次旅程的核心收获是:

  • 协议是根基:一旦深刻理解了协议本身,即使没有官方SDK,我们也能在任何支持HTTP的环境中实现它。
  • 化繁为简:我们用一个扩展方法和一些辅助类,就替代了官方库及其繁杂的依赖,代码清晰且易于掌控。
  • 反射与元编程的威力:通过巧妙运用反射,我们实现了工具的自动发现和动态调用,大大提高了代码的灵活性和可扩展性。
  • 知其然,知其所以然:现在,我们不仅知道MCP如何工作,更通过自己动手理解了它为何如此设计,比如两步握手、SSE流式响应以及分层的错误处理机制。
希望本文能帮助你彻底搞懂并掌握MCP协议的实现细节。现在,你拥有了完全控制MCP通信的能力,无论是进行二次开发、跨语言实现,还是仅仅为了满足那份技术探索的好奇心。
感谢您的阅读,如果您有任何问题或想法,欢迎在评论区留言讨论。
也欢迎加入我们的 .NET骚操作 QQ群一起探讨:495782587

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册