使用.NET实现自带思考的Tool 并且提供mcp streamable http服务
大家好,我是老K。今天我们来聊一个比较"潮"的话题:如何用.NET实现一个自带"思考"能力的工具(Tool),并且通过MCP(Model Context Protocol)的streamable HTTP服务把它暴露给AI模型使用。如果你对MCP还不太熟悉,简单理解就是:MCP是AI模型(比如Claude、GPT)与外部工具之间的"USB接口",让模型能调用你写的代码。而"自带思考"的意思是,我们的Tool在执行前,可以先"想"一下怎么做,再返回结果。这听起来有点玄,但实现起来并不复杂。### 为什么需要"自带思考"的Tool?传统的Tool是"死"的:输入什么,返回什么。比如一个加法器,输入1和2,返回3。但真实场景中,模型可能给出的指令很模糊,比如"帮我计算一下这个月的销售总额",这时候Tool如果直接执行,可能会因为参数不完整而报错。"自带思考"的Tool会先对输入进行"推理",比如:- 解析出"这个月"是哪个月?- 找到"销售数据"的表或接口;- 决定用什么算法计算总额。这种设计让Tool更智能,也更符合人类使用工具的习惯------先想后做。### 技术栈准备- .NET 8+(建议使用最新LTS)- MCP SDK for .NET(目前官方有实验性包,我们用社区方案也行)- 一个支持streamable HTTP的服务框架(ASP.NET Core)我们先从最简单的部分开始:写一个"思考型"的Tool。### 第一步:定义一个"思考"Tool我们以"文本情感分析"为例。普通Tool直接接收文本返回"正面/负面",但我们的Tool会先"思考":先检查文本长度、是否包含否定词、是否有多重情绪等,再给出结论。csharpusing System;using System.Collections.Generic;using System.Linq;using System.Text.Json;using System.Threading.Tasks;// 定义一个"自带思考"的工具public class SentimentAnalysisTool{ // 工具的名称和描述,MCP会用它来让模型理解工具用途 public string Name => "sentiment_analyzer"; public string Description => "分析文本情感,返回正面/负面/中性,并附带思考过程"; // 实际的执行方法,参数是模型传入的JSON字符串 public async Task<string> ExecuteAsync(string inputJson) { // 解析输入 var input = JsonSerializer.Deserialize<SentimentInput>(inputJson); if (input == null || string.IsNullOrEmpty(input.Text)) return JsonSerializer.Serialize(new { error = "文本不能为空" }); // ---- 思考阶段:分析文本特征 ---- var thoughts = new List<string>(); var text = input.Text.ToLower(); // 思考1:检查长度 if (text.Length < 10) thoughts.Add("文本较短,可能为口语化表达"); else thoughts.Add("文本长度正常,适合分析"); // 思考2:检查是否有否定词 bool hasNegation = text.Contains("not") || text.Contains("never") || text.Contains("no"); if (hasNegation) thoughts.Add("检测到否定词,情感可能反转"); // 思考3:简单的情感词库 var positiveWords = new[] { "good", "great", "happy", "love", "excellent" }; var negativeWords = new[] { "bad", "terrible", "sad", "hate", "awful" }; int posCount = positiveWords.Count(w => text.Contains(w)); int negCount = negativeWords.Count(w => text.Contains(w)); // 思考4:根据词频和否定词判断情感 string sentiment; if (hasNegation && posCount > 0) sentiment = "negative"; // 有否定词且出现正面词,可能反转 else if (posCount > negCount) sentiment = "positive"; else if (negCount > posCount) sentiment = "negative"; else sentiment = "neutral"; thoughts.Add($"正面词出现{posCount}次,负面词出现{negCount}次"); thoughts.Add($"最终判断:{sentiment}"); // ---- 返回结果,包括思考过程和结论 ---- var result = new { sentiment = sentiment, confidence = Math.Min(0.95, 0.5 + (Math.Abs(posCount - negCount) * 0.15)), thinking = thoughts.ToArray() }; return JsonSerializer.Serialize(result); }}// 输入模型public class SentimentInput{ public string Text { get; set; }}看到没?这个Tool在执行前,先做了一系列"思考",并且把思考过程也返回给模型了。这样模型就能理解为什么得出这个结论,而不是黑盒。### 第二步:把Tool挂到MCP streamable HTTP服务上MCP协议支持多种传输方式,streamable HTTP是其中一种,适合远程调用。在.NET中,我们用ASP.NET Core来写一个最小的MCP服务。我们需要一个MCP服务器类,负责注册Tool并处理请求。MCP的SDK目前还在迭代,但我们可以用官方示例或第三方库(如ModelContextProtocol.NET)。这里我手写一个简化版,展示核心逻辑。csharpusing Microsoft.AspNetCore.Builder;using Microsoft.AspNetCore.Http;using Microsoft.Extensions.DependencyInjection;using System.Text.Json;using System.Threading.Tasks;var builder = WebApplication.CreateBuilder(args);var app = builder.Build();// 注册我们的Toolvar tools = new Dictionary<string, SentimentAnalysisTool>{ { "sentiment_analyzer", new SentimentAnalysisTool() }};// MCP streamable HTTP 端点app.MapPost("/mcp", async (HttpContext context) =>{ // 读取请求体(假设是JSON-RPC格式) using var reader = new StreamReader(context.Request.Body); var body = await reader.ReadToEndAsync(); var request = JsonSerializer.Deserialize<McpRequest>(body); // 根据方法名分发 switch (request.Method) { case "tools/list": // 返回工具列表(供模型发现) var toolList = tools.Values.Select(t => new { name = t.Name, description = t.Description, inputSchema = new { type = "object", properties = new { text = new { type = "string" } } } }); await context.Response.WriteAsJsonAsync(new { jsonrpc = "2.0", result = toolList, id = request.Id }); break; case "tools/call": // 调用工具 var toolName = request.Params.GetProperty("name").GetString(); var args = request.Params.GetProperty("arguments").GetRawText(); if (tools.TryGetValue(toolName, out var tool)) { var output = await tool.ExecuteAsync(args); await context.Response.WriteAsJsonAsync(new { jsonrpc = "2.0", result = new { content = new[] { new { type = "text", text = output } } }, id = request.Id }); } else { await context.Response.WriteAsJsonAsync(new { jsonrpc = "2.0", error = new { code = -32601, message = "Tool not found" }, id = request.Id }); } break; default: await context.Response.WriteAsJsonAsync(new { jsonrpc = "2.0", error = new { code = -32601, message = "Method not found" }, id = request.Id }); break; }});// 支持流式响应(streamable)------这里简化,实际可以用SSE或chunkedapp.MapGet("/mcp/sse", async (HttpContext context) =>{ context.Response.Headers.Add("Content-Type", "text/event-stream"); await context.Response.WriteAsync("data: {\"message\":\"MCP SSE stream ready\"}\n\n"); await context.Response.Body.FlushAsync();});app.Run();// 简单的MCP请求模型public class McpRequest{ public string Jsonrpc { get; set; } = "2.0"; public string? Method { get; set; } public JsonElement Params { get; set; } public string? Id { get; set; }}这段代码实现了两个核心端点:- /mcp:处理JSON-RPC请求,包括tools/list(列出工具)和tools/call(调用工具)。- /mcp/sse:一个简单的Server-Sent Events端点,用于流式推送(比如工具执行过程中的中间结果,或者思考过程的实时输出)。### 第三步:测试与集成启动这个服务后,你可以在任何支持MCP的客户端(比如Claude Desktop、Cursor等)中配置这个HTTP端点。模型会先调用tools/list看到我们的sentiment_analyzer,然后根据用户请求调用它。例如,用户问"你觉得这句话怎么样:I love this product, but the shipping was terrible",模型会调用我们的Tool,Tool会"思考"并返回:json{ "sentiment": "neutral", "confidence": 0.65, "thinking": [ "文本长度正常,适合分析", "检测到否定词,情感可能反转", "正面词出现1次,负面词出现1次", "最终判断:neutral" ]}模型拿到这个结果后,会结合思考过程给用户一个更自然的回答,比如"这句话既有正面也有负面,所以整体偏中性,但负面情绪可能影响购买体验"。### 进阶:让"思考"更真实上面的思考过程是硬编码的规则,但你可以让Tool调用一个LLM来"思考",比如用一个小模型先做个预分析。这样Tool就更像"人"了------先想后做。csharp// 伪代码:在ExecuteAsync里调用LLM做思考public async Task<string> ExecuteAsync(string inputJson){ var input = JsonSerializer.Deserialize<SentimentInput>(inputJson); // 调用LLM进行思考(比如用OpenAI API) var prompt = $"请分析以下文本的情感,并给出思考步骤:{input.Text}"; var llmResponse = await CallLLMAsync(prompt); // 然后基于LLM的思考结果执行实际逻辑... // 返回结果包含思考过程和最终结论}这样,你的Tool就成了一个"自带大脑"的工具,而且通过MCP streamable HTTP,任何AI模型都能随时调用它。### 总结这篇文章我们实现了:1. 一个"自带思考"的Tool,它在执行前会分析输入特征,并输出思考过程。2. 一个基于ASP.NET Core的MCP streamable HTTP服务,暴露了tools/list和tools/call端点,并支持SSE流式响应。3. 简单测试了模型如何通过MCP协议调用这个Tool。这个架构非常适合做"智能代理"系统:模型负责决策,Tool负责执行,而且执行过程透明可追溯。未来你可以把任何复杂的业务逻辑封装成"思考型"Tool,比如数据分析、代码生成、甚至自动化运维。MCP的生态还在成长,.NET社区也有不错的库,建议大家多去试试。如果你也想让你的AI应用更"聪明",不妨试试这个模式------先思考,再行动。代码已经给你了,跑起来吧!