news 2026/9/23 9:54:38

MCP实现:.Net实现MCP服务端 + Ollama,MCP服务端工具调用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MCP实现:.Net实现MCP服务端 + Ollama,MCP服务端工具调用

1. 从零跑通 .NET MCP 服务端 + Ollama 工具调用

MCP(Model Context Protocol)是让大模型安全调用外部工具的开放协议,而 .NET 凭借强类型和成熟的依赖注入体系,非常适合用来写 MCP 服务端。这篇内容聚焦一个具体场景:用 .NET 8 搭一个 MCP 服务端,注册几个工具方法,再让本地 Ollama 模型通过 MCP 客户端把工具调起来,最终实现「用户问一句,模型自动选工具、执行、返回结果」的完整链路。适合正在搭本地 AI 工具链、想让本地模型具备函数调用能力的开发者,也适合已经写过 WebApi 但没接触过 MCP 的同学。整条链路我会拆成服务端骨架、工具注册、客户端对接、连通性验证和排障五块,每一步都给可复制的代码和参数,照着做基本能一次跑通。过程中如果本地模型或网络通道需要统一管理,我会顺带给出用 TaoToken 统一 Key 与 API 通道的配置片段,方便你把模型调用和工具调用放在同一套凭证体系里。

2. 前置准备:.NET 环境、Ollama 与 TaoToken 通道

2.1 环境清单

先把基础环境对齐,版本不一致是后面报错的高发区。

组件建议版本说明
.NET SDK8.0 及以上MCP 的 AspNetCore 包依赖较新运行时
Ollama最新稳定版本地跑模型,默认端口 11434
模型llama3.1:8b 或 qwen2.5:7b需要支持 tool calls 的模型
IDEVS 2022 / Rider / VS Code任意即可

Ollama 装好后先拉模型并确认服务在跑:

ollama pull llama3.1:8b ollama list curl http://localhost:11434/api/tags

curl能返回模型列表,说明本地推理服务正常。注意模型必须支持工具调用,纯对话模型即使接了 MCP 工具也不会触发ToolCalls,这是后面排查的重点之一。

2.2 用 TaoToken 统一模型通道

本地 Ollama 适合离线调试,但一旦你要把同一套工具调用逻辑接到云端模型,或者团队里多人共用额度,逐个管理 Key 会很乱。我的做法是用 TaoToken 做统一入口:官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 注册后,在控制台 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite 生成 Key,API 基址用 https://taotoken.net/api(不加 UTM)。这样 MCP 客户端里模型地址和 Key 都走同一套配置,切换本地/云端只改一个 base url。

在项目根目录建一个settings.json,把模型通道和 MCP 服务端地址都放进去:

{ "ModelProvider": { "BaseUrl": "https://taotoken.net/api", "ApiKey": "sk-你的TaoToken密钥", "Model": "llama3.1:8b" }, "McpServer": { "Name": "csharp-mcp-sse-server", "Command": "http://localhost:5069/", "TransportType": "Sse" } }

Key 建议放环境变量或用户机密,别硬编码进仓库。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,里面有各语言 SDK 的调用示例,配置字段对不上时对着查最快。

3. 可复制配置:.NET MCP 服务端骨架与工具注册

3.1 创建项目并引入包

用 WebApi 模板建项目,控制台项目也能跑,但 WebApi 方便用 SSE 传输和 Swagger 调试:

dotnet new webapi -n McpServerDemo cd McpServerDemo dotnet add package ModelContextProtocol.AspNetCore

包版本以 NuGet 最新为准,装完dotnet restore确认无冲突。

3.2 编写工具类

MCP 的工具靠特性标注,方法签名里的参数会被自动解析成 JSON Schema 暴露给模型。下面两个工具一个做字符串拼接,一个做加法,覆盖「文本类」和「数值类」两种典型入参:

using System.ComponentModel; using ModelContextProtocol.Server; [McpServerToolType] public static class EchoTool { [McpServerTool, Description("拼接后返回给客户端")] public static string Echo(string message) => $"你好你好 {message}"; [McpServerTool, Description("用于计算两个数字的和,接收两个整数参数 a 和 b")] public static int Add( [Description("第一个加数")] int a, [Description("第二个加数")] int b) => a + b; }

Description不是装饰,模型就是靠这段文字判断该不该调这个工具、参数怎么填。描述写得含糊,模型选错工具或参数缺失的概率会明显上升。

3.3 注册 MCP 并启动

Program.cs里把 MCP 服务注册进依赖注入容器,并映射端点:

using ModelContextProtocol.AspNetCore; var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Logging.AddConsole(consoleLogOptions => { consoleLogOptions.LogToStandardErrorThreshold = LogLevel.Trace; }); builder.Services .AddMcpServer() .WithHttpTransport(options => { options.Stateless = false; // false 时保留 /sse 端点 }) .WithToolsFromAssembly(); // 自动扫描 [McpServerToolType] var app = builder.Build(); app.UseSwagger(); app.UseSwaggerUI(); app.UseAuthorization(); app.MapControllers(); app.MapMcp(); app.Run();

WithToolsFromAssembly()会扫描当前程序集里所有带[McpServerToolType]的类,工具多了也不用逐个注册。Stateless = false表示走有状态的 SSE 会话,客户端需要保持连接;如果你的场景是短连接无状态调用,可以改成true,但那样/sse端点会被禁用,客户端传输方式也要跟着换。

启动服务:

dotnet run --urls "http://localhost:5069"

浏览器打开http://localhost:5069/swagger能看到接口文档,说明服务端起来了。

4. 客户端对接 Ollama 与工具调用验证

4.1 引入客户端包并读取配置

客户端项目引入:

dotnet add package OllamaSharp.ModelContextProtocol

settings.json读进来,避免地址散落在代码里:

var config = new ConfigurationBuilder() .AddJsonFile("settings.json") .Build(); var modelBaseUrl = config["ModelProvider:BaseUrl"]; var apiKey = config["ModelProvider:ApiKey"]; var modelName = config["ModelProvider:Model"]; var mcpServerUrl = config["McpServer:Command"];

4.2 从 MCP 服务端拉取工具

MCP 客户端先连服务端,把工具列表取回来,再塞给模型:

using Microsoft.Extensions.Logging; using OllamaSharp; using OllamaSharp.ModelContextProtocol; var loggerFactory = LoggerFactory.Create(b => b.AddConsole()); var serverConfigs = new[] { new McpServerConfiguration { Name = "csharp-mcp-sse-server", Command = mcpServerUrl, TransportType = McpServerTransportType.Sse } }; var tools = await Tools.GetFromMcpServers( mcpServers: serverConfigs, clientOptions: new McpClientOptions { LoggerFactory = loggerFactory, InitializationTimeout = TimeSpan.FromSeconds(30) }); foreach (var tool in tools) { Console.WriteLine($"- {tool.Function.Name}: {tool.Function.Description}"); }

正常会打印出EchoAdd两个工具。如果列表为空,先确认服务端MapMcp()已调用、SSE 地址能访问。

4.3 发起对话并执行工具调用

把工具挂到ChatRequest.Tools上,模型返回ToolCalls时手动执行对应工具:

var ollama = new OllamaApiClient(new Uri(modelBaseUrl)); var chatRequest = new ChatRequest { Model = modelName, Stream = false, Think = false, Messages = new List<Message> { new Message { Role = ChatRole.User, Content = "请计算3加5的和是多少" } }, Tools = tools }; var resp = await ollama.ChatAsync(chatRequest, CancellationToken.None) .StreamToEndAsync(); if (resp.Message.ToolCalls.Any()) { var toolCall = resp.Message.ToolCalls.First(); Console.WriteLine($"调用的工具: {toolCall.Function.Name}"); var tool = tools.FirstOrDefault(t => t.Function.Name == toolCall.Function.Name); var toolResult = await tool.InvokeMethodAsync(toolCall.Function.Arguments); Console.WriteLine($"工具调用结果: {toolResult}"); return toolResult.ToString(); } return resp.Message.Content;

入参「请计算3和5的和是多少」,模型会选中Add工具,参数a=3, b=5,执行后返回8。控制台能看到「调用的工具: Add」和「工具调用结果: 8」两行,链路就算通了。

5. 本篇常见报错排查

5.1 工具列表为空或连接超时

最常见的是 SSE 地址写错或服务端没起。先curl http://localhost:5069/sse看有没有事件流返回;如果服务端在另一台机器,把localhost换成实际 IP,并确认防火墙放行 5069。InitializationTimeout默认偏短,跨机调用可以调到 30 秒以上。

5.2 模型不触发 ToolCalls

先确认模型本身支持工具调用,llama3.1:8bqwen2.5:7b这类可以,纯 chat 模型不行。其次检查Tools = tools是否真的传进去了,以及工具Description是否和用户问题语义匹配。如果模型返回的是自然语言答案而不是ToolCalls,多半是描述没写清楚,把「用于计算两个数字的和」这类意图写明确。

5.3 参数解析失败或类型不匹配

InvokeMethodAsync传的是 JSON 字符串,字段名要和 C# 参数名一致。比如Add的参数是ab,模型生成的 arguments 也必须是这两个键。如果模型生成了num1num2,就会解析失败。解决办法是在Description里把参数名和含义写死,减少模型自由发挥。

5.4 走 TaoToken 通道时 401 或模型不存在

检查settings.jsonBaseUrl是否为https://taotoken.net/api,Key 是否带多余空格。模型名要和通道支持的名称一致,不确定时在模型对话 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite 里确认可用列表。如果本地 Ollama 和云端通道混用,注意OllamaApiClient的 base url 要指向对应服务,别把本地地址和云端 Key 拼在一起。

6. 把工具调用接进长期编码工作流

单次跑通只是起点。如果你打算把 MCP 服务端常驻,让 IDE 或 Agent 长期调用,建议把模型通道固定成 TaoToken 的 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite ,额度和 Key 统一管理,本地调试和线上调用共用一套配置,省得每次切环境改代码。API Key 在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 生成,接入细节看文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。工具注册这块,我的经验是每个工具只做一件事,描述里把「什么时候用」写清楚,比堆一堆参数更能提升模型选对工具的概率;工具方法保持无副作用、可重复调用,排障时直接单测工具本身,比反复问模型快得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 9:53:16

数字化校友圈:智能平台如何连接全球校友网络

1. 从一场校友聚会聊起&#xff1a;为什么我们需要一个"数字化校友圈"去年冬天&#xff0c;我参加了一场毕业十年的校友聚会。组织者在微信群里吆喝了两个月&#xff0c;最终到场不到四十人。席间大家聊得热火朝天&#xff0c;散场时互加微信&#xff0c;说"以后…

作者头像 李华
网站建设 2026/9/23 9:47:41

RTX5060是假消息?2026游戏本选购避坑指南

1. 先泼一盆冷水&#xff1a;RTX5060与RTX5070Ti在2026年9月根本不会存在如果你刚在某电商页面看到“RTX5060游戏本首发预售”“RTX5070Ti性能暴涨70%”这类标题&#xff0c;点进去还配着炫酷渲染图和“限时早鸟价”&#xff0c;请立刻关掉页面——这不是新品预告&#xff0c;而…

作者头像 李华
网站建设 2026/9/23 9:45:56

心理咨询师证有必要报班吗?从报名学习到考试拿证,报考全攻略

考心理咨询师证&#xff0c;真的有必要报班吗&#xff1f;这是很多人报名前的第一道纠结。有人说网上资料一大堆自学就行&#xff0c;有人说没有系统培训很难考过——两种说法都有道理&#xff0c;关键看你的基础、时间和目标。本文从心理咨询师证的价值、自学与报班的真实差别…

作者头像 李华
网站建设 2026/9/23 9:44:46

Atlas 300V推理卡上部署YOLO:完整实践与避坑指南

最近群里有个话题被反复问起来&#xff1a;**Atlas 300V 24G这个卡&#xff0c;到底算不算运算加速卡&#xff1f;能不能在上面跑YOLO目标检测&#xff1f;**问题虽然短&#xff0c;但背后绕着的其实是昇腾推理卡在产品定位、软件栈和实际落地之间的那层窗户纸。我的答案是&…

作者头像 李华
网站建设 2026/9/23 9:44:39

智能工牌录音的隐私合规边界:风险不在采集在流转

智能工牌录音的合规问题&#xff0c;通常被简化成一句“征得客户同意了吗”。但同意只是起点。真正的风险发生在这之后&#xff1a;数据采回来之后谁能听、听多久、存在哪里、能拿去做什么。多数出问题的项目&#xff0c;不是倒在“没告知”&#xff0c;而是倒在“告知了却管不…

作者头像 李华