
1. 为什么要在 C# 里折腾 Stdio 版 MCP Server如果你正在做本地 AI 工具链集成大概率会遇到一个绕不开的问题模型怎么安全、稳定地读写你本机的文件、调用你本机的程序。SSE 那套适合远程服务但一旦涉及本地文件、本地数据库、本地命令行工具Stdio标准输入输出才是更自然的选择。MCP Server 用 Stdio 通信本质上是把「一个可执行程序」变成「模型能调用的工具集」模型通过 stdin 发 JSON-RPC 请求你的程序通过 stdout 回响应中间不经过任何网络端口。这篇要解决的就是用 C# 写一个最小可用的 Stdio MCP Server能读文件、能写文件并且把模型调用通道统一到 TaoToken 的 Key 上避免你在多个工具里反复填不同的 API Key。适合谁适合已经会用 C# 写控制台程序、想把自己的本地能力暴露给 AI 工具链的开发者也适合刚接触 MCP、想找一个能直接跑通的骨架的人。我试过把文件操作、时间查询、简单命令执行都塞进一个 Stdio Server实测下来最稳的还是「一个工具类 明确描述 异步方法」这个结构。下面从项目创建开始一步步把骨架搭出来最后用一次真实的请求-响应验证它确实能跑。2. TaoToken 前置统一 Key 与通道准备在写代码之前先把「模型从哪来」这件事定下来。Stdio MCP Server 本身不负责调用大模型它只负责暴露工具真正调用模型的是 MCP Client 或者你本地的 AI 工具链。但为了让整个链路可复现我们需要一个统一的 API 通道这样 Client 侧配置一次后面换模型、换工具都不用改代码。TaoToken 在这里的角色就是统一 Key 和 API 通道。你不需要在 C# 代码里硬编码任何模型地址只需要在 Client 的配置文件里写一次 base_url 和 api_key。官网入口在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 注意 API 地址后面不加 UTM 参数直接用于程序请求。具体要准备的东西只有两样一个 API Key以及确认你的 Client 支持自定义 base_url。Key 在控制台里生成入口是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 生成后复制出来后面写进 settings.json 或 config.toml。如果你还没决定用哪个模型可以先到模型对话页面看看当前可用的模型列表地址是 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。注意API Key 只放在本地配置文件里不要提交到 Git也不要在代码里写死。Stdio Server 本身不接触 KeyKey 是 Client 侧的事这样职责更清晰。3. 可复制配置项目骨架与 Stdio 消息循环3.1 创建控制台项目并添加依赖打开 Visual Studio 或直接用 dotnet CLI创建一个控制台应用目标框架选 net8.0。项目名可以叫 McpServer.Stdio。创建完成后添加 NuGet 包 ModelContextProtocolServer.Stdio版本选 0.0.1-preview-05记得勾选「包括预发行版」否则搜不到。dotnet new console -n McpServer.Stdio -f net8.0 cd McpServer.Stdio dotnet add package ModelContextProtocolServer.Stdio --version 0.0.1-preview-05这一步做完项目里会自动带上 Stdio 通信所需的运行时。你不需要自己写 stdin 读取循环框架已经封装好了你只需要在 Program.cs 里调用 RunAsync。3.2 Program.cs 的最小启动代码Program.cs 里只做一件事启动 Stdio Server。代码如下注意 using 和 await 的写法。using ModelContextProtocolServer.Stdio; await StdioServer.RunAsync(args);这两行就是整个 Stdio 消息循环的入口。RunAsync 内部会持续监听 stdin解析 JSON-RPC 请求找到对应的工具方法执行后把结果写到 stdout。你不需要手动处理换行、缓冲区、编码这些细节框架默认用 UTF-8按行分隔消息。3.3 工具类 FileTool读文件与写文件新建一个类文件 FileTool.cs放在项目根目录即可。这个类用特性标记让框架能自动发现并注册工具。关键点有三个类上加 [McpServerToolType]方法上加 [McpServerTool]参数和返回值用 [Description] 描述清楚这样模型才知道每个工具是干什么的、参数怎么填。using ModelContextProtocol.Server; using System.ComponentModel; namespace McpServer.Stdio { [McpServerToolType] public static class FileTool { [McpServerTool, Description(读取文件)] public static async Taskstring ReadFile( [Description(文件路径)] string path) { if (!File.Exists(path)) throw new FileNotFoundException(文件不存在); return await File.ReadAllTextAsync(path); } [McpServerTool, Description(保存文件)] public static async Taskstring SaveFile( [Description(文件路径)] string path, [Description(内容)] string content) { try { var directory Path.GetDirectoryName(path); if (!string.IsNullOrEmpty(directory) !Directory.Exists(directory)) { Directory.CreateDirectory(directory); } await File.WriteAllTextAsync(path, content); return $文件已成功保存至:{path}; } catch (Exception ex) { return $保存文件时发生错误:{ex.Message}; } } } }ReadFile 直接抛异常因为读不到文件本身就是错误让框架把错误信息回传给 Client 更合理。SaveFile 用 try-catch 包住返回错误字符串而不是抛异常这样模型能拿到可读的失败原因不会因为一次写入失败就中断整个会话。3.4 发布为可执行文件Stdio Server 必须是一个可执行文件Client 才能启动它。发布命令如下win-x64 按你的平台改。dotnet publish -c Release -r win-x64 --self-contained false发布完成后在 bin/release/net8.0/publish/win-x64/ 目录下会生成 McpServer.Stdio.exe。这个路径后面要填到 Client 的配置里先记下来。3.5 Client 侧 settings.json 与 config.toml 配置片段不同 AI 工具的配置文件名不一样但结构类似。以 settings.json 为例把 Stdio Server 注册进去同时把模型通道指向 TaoToken。{ mcpServers: { local-file-tool: { command: E:\\project\\mcpdemo\\McpServer.Stdio\\bin\\release\\net8.0\\publish\\win-x64\\McpServer.Stdio.exe, args: [] } }, llm: { base_url: https://taotoken.net/api, api_key: 你的_TaoToken_Key, model: 你选定的模型名 } }如果你用的工具是 config.toml 格式等价写法如下。[mcp_servers.local-file-tool] command E:\\project\\mcpdemo\\McpServer.Stdio\\bin\\release\\net8.0\\publish\\win-x64\\McpServer.Stdio.exe args [] [llm] base_url https://taotoken.net/api api_key 你的_TaoToken_Key model 你选定的模型名提示command 路径里的反斜杠在 JSON 里要写成双反斜杠TOML 里单反斜杠即可。路径写错是后面最常见的连接失败原因。4. 验证请求一次真实的读文件-写文件往返配置写好后启动你的 MCP Client。Client 会读取 settings.json启动 McpServer.Stdio.exe然后通过 stdin/stdout 和它握手。握手成功后Client 会打印出工具列表你应该能看到 ReadFile 和 SaveFile 两个工具。先准备一个本地文件比如 E:\本地文件.txt里面随便写点内容。然后在 Client 的对话输入框里输入读取 E:\本地文件.txt 的内容模型会识别出这是 ReadFile 工具的调用参数 path 为 E:\本地文件.txt。Client 把请求通过 stdin 发给 ServerServer 执行后把文件内容写到 stdoutClient 拿到结果再交给模型模型最终把内容展示给你。整个过程你不需要手动敲任何 JSON。接着验证写入将内容这是MCP Server实例保存到文件路径E:\stdio.txt模型会调用 SaveFile参数 path 为 E:\stdio.txtcontent 为「这是MCP Server实例」。执行成功后Server 返回「文件已成功保存至:E:\stdio.txt」你可以打开这个文件确认内容确实写进去了。再让模型读一次 E:\stdio.txt如果能读出刚才写的内容说明读-写闭环完全跑通。这一步的成功标志有三个Client 打印出工具列表、读文件返回正确内容、写文件后能再次读出。三个都满足最小可用 MCP Server 就算落地了。5. 本篇常见错排查5.1 Client 启动 Server 失败提示找不到可执行文件最常见的原因是 command 路径写错。检查发布目录下是否真的有 McpServer.Stdio.exe以及 JSON 里的双反斜杠是否正确。如果你用的是相对路径Client 的工作目录可能和你预期不一致建议一律用绝对路径。5.2 工具列表为空模型看不到 ReadFile 和 SaveFile先确认 FileTool 类上的 [McpServerToolType] 和方法上的 [McpServerTool] 都加了并且 using 了 ModelContextProtocol.Server。如果特性加了但列表还是空检查方法是否是 public static框架只注册公开静态方法。另外Description 特性里的文字不要留空空描述有时会导致注册被跳过。5.3 读文件报「文件不存在」但文件明明在Stdio Server 是以 Client 启动的进程身份运行的它的工作目录和当前用户可能和你手动打开文件时不同。路径里如果有中文或空格确保 JSON 转义正确。建议先用绝对路径测试排除相对路径带来的歧义。5.4 写文件成功但内容为空检查 SaveFile 的 content 参数是否被模型正确填充。有时候模型会把内容放到错误的参数里或者把路径和内容搞反。你可以在 SaveFile 里加一行日志写到临时文件确认实际收到的参数值。另外如果目标文件被其他程序占用WriteAllTextAsync 会抛异常但我们的 catch 会返回错误字符串注意看返回信息。5.5 模型通道返回 401 或连接超时这通常是 Client 侧的 base_url 或 api_key 配置问题。确认 base_url 是 https://taotoken.net/api 不要多加路径也不要在 API 地址后面拼 UTM 参数。Key 是否复制完整、是否有多余空格都检查一遍。如果还是不通到接入文档页面核对最新的配置示例地址是 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。6. 把 Key 和工具链固定下来Stdio MCP Server 跑通之后你会发现真正省事的地方在于工具在本地Key 在统一通道两者解耦。以后你加一个新工具只需要在 FileTool 旁边再写一个类重新发布Client 配置里的 command 不用改。换模型或者换 Client也只需要改 llm 那一段Server 代码完全不动。如果你打算长期做本地编码辅助或者 Agent 类工具建议把 Key 管理放到 Coding Plan 里统一规划入口是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 这样多个工具共用一套配额和通道排查问题也方便。需要单独管理 Key 的时候API Keys 页面在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 可以按工具拆分不同 Key出问题能快速定位是哪个环节。最后留一个实用习惯每次改完 Server 代码先手动跑一次 exe确认它能正常启动不报错再去 Client 里测。Stdio 程序如果启动就崩Client 那边只会显示连接失败看不到具体异常手动跑一次能省很多排查时间。