現(xiàn) Stdio 通信的 MCP Server:TaoToken 統(tǒng)一 Key 接入與配置骨架)
1. 為什么要在 C# 里折騰 Stdio 版 MCP Server如果你正在做本地 AI 工具鏈集成大概率會(huì)遇到一個(gè)繞不開(kāi)的問(wèn)題模型怎么安全、穩(wěn)定地讀寫你本機(jī)的文件、調(diào)用你本機(jī)的程序。SSE 那套適合遠(yuǎn)程服務(wù)但一旦涉及本地文件、本地?cái)?shù)據(jù)庫(kù)、本地命令行工具Stdio標(biāo)準(zhǔn)輸入輸出才是更自然的選擇。MCP Server 用 Stdio 通信本質(zhì)上是把「一個(gè)可執(zhí)行程序」變成「模型能調(diào)用的工具集」模型通過(guò) stdin 發(fā) JSON-RPC 請(qǐng)求你的程序通過(guò) stdout 回響應(yīng)中間不經(jīng)過(guò)任何網(wǎng)絡(luò)端口。這篇要解決的就是用 C# 寫一個(gè)最小可用的 Stdio MCP Server能讀文件、能寫文件并且把模型調(diào)用通道統(tǒng)一到 TaoToken 的 Key 上避免你在多個(gè)工具里反復(fù)填不同的 API Key。適合誰(shuí)適合已經(jīng)會(huì)用 C# 寫控制臺(tái)程序、想把自己的本地能力暴露給 AI 工具鏈的開(kāi)發(fā)者也適合剛接觸 MCP、想找一個(gè)能直接跑通的骨架的人。我試過(guò)把文件操作、時(shí)間查詢、簡(jiǎn)單命令執(zhí)行都塞進(jìn)一個(gè) Stdio Server實(shí)測(cè)下來(lái)最穩(wěn)的還是「一個(gè)工具類 明確描述 異步方法」這個(gè)結(jié)構(gòu)。下面從項(xiàng)目創(chuàng)建開(kāi)始一步步把骨架搭出來(lái)最后用一次真實(shí)的請(qǐng)求-響應(yīng)驗(yàn)證它確實(shí)能跑。2. TaoToken 前置統(tǒng)一 Key 與通道準(zhǔn)備在寫代碼之前先把「模型從哪來(lái)」這件事定下來(lái)。Stdio MCP Server 本身不負(fù)責(zé)調(diào)用大模型它只負(fù)責(zé)暴露工具真正調(diào)用模型的是 MCP Client 或者你本地的 AI 工具鏈。但為了讓整個(gè)鏈路可復(fù)現(xiàn)我們需要一個(gè)統(tǒng)一的 API 通道這樣 Client 側(cè)配置一次后面換模型、換工具都不用改代碼。TaoToken 在這里的角色就是統(tǒng)一 Key 和 API 通道。你不需要在 C# 代碼里硬編碼任何模型地址只需要在 Client 的配置文件里寫一次 base_url 和 api_key。官網(wǎng)入口在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 注意 API 地址后面不加 UTM 參數(shù)直接用于程序請(qǐng)求。具體要準(zhǔn)備的東西只有兩樣一個(gè) API Key以及確認(rèn)你的 Client 支持自定義 base_url。Key 在控制臺(tái)里生成入口是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 生成后復(fù)制出來(lái)后面寫進(jìn) settings.json 或 config.toml。如果你還沒(méi)決定用哪個(gè)模型可以先到模型對(duì)話頁(yè)面看看當(dāng)前可用的模型列表地址是 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。注意API Key 只放在本地配置文件里不要提交到 Git也不要在代碼里寫死。Stdio Server 本身不接觸 KeyKey 是 Client 側(cè)的事這樣職責(zé)更清晰。3. 可復(fù)制配置項(xiàng)目骨架與 Stdio 消息循環(huán)3.1 創(chuàng)建控制臺(tái)項(xiàng)目并添加依賴打開(kāi) Visual Studio 或直接用 dotnet CLI創(chuàng)建一個(gè)控制臺(tái)應(yīng)用目標(biāo)框架選 net8.0。項(xiàng)目名可以叫 McpServer.Stdio。創(chuàng)建完成后添加 NuGet 包 ModelContextProtocolServer.Stdio版本選 0.0.1-preview-05記得勾選「包括預(yù)發(fā)行版」否則搜不到。dotnet new console -n McpServer.Stdio -f net8.0 cd McpServer.Stdio dotnet add package ModelContextProtocolServer.Stdio --version 0.0.1-preview-05這一步做完項(xiàng)目里會(huì)自動(dòng)帶上 Stdio 通信所需的運(yùn)行時(shí)。你不需要自己寫 stdin 讀取循環(huán)框架已經(jīng)封裝好了你只需要在 Program.cs 里調(diào)用 RunAsync。3.2 Program.cs 的最小啟動(dòng)代碼Program.cs 里只做一件事啟動(dòng) Stdio Server。代碼如下注意 using 和 await 的寫法。using ModelContextProtocolServer.Stdio; await StdioServer.RunAsync(args);這兩行就是整個(gè) Stdio 消息循環(huán)的入口。RunAsync 內(nèi)部會(huì)持續(xù)監(jiān)聽(tīng) stdin解析 JSON-RPC 請(qǐng)求找到對(duì)應(yīng)的工具方法執(zhí)行后把結(jié)果寫到 stdout。你不需要手動(dòng)處理?yè)Q行、緩沖區(qū)、編碼這些細(xì)節(jié)框架默認(rèn)用 UTF-8按行分隔消息。3.3 工具類 FileTool讀文件與寫文件新建一個(gè)類文件 FileTool.cs放在項(xiàng)目根目錄即可。這個(gè)類用特性標(biāo)記讓框架能自動(dòng)發(fā)現(xiàn)并注冊(cè)工具。關(guān)鍵點(diǎn)有三個(gè)類上加 [McpServerToolType]方法上加 [McpServerTool]參數(shù)和返回值用 [Description] 描述清楚這樣模型才知道每個(gè)工具是干什么的、參數(shù)怎么填。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(內(nèi)容)] 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 $保存文件時(shí)發(fā)生錯(cuò)誤:{ex.Message}; } } } }ReadFile 直接拋異常因?yàn)樽x不到文件本身就是錯(cuò)誤讓框架把錯(cuò)誤信息回傳給 Client 更合理。SaveFile 用 try-catch 包住返回錯(cuò)誤字符串而不是拋異常這樣模型能拿到可讀的失敗原因不會(huì)因?yàn)橐淮螌懭胧【椭袛嗾麄€(gè)會(huì)話。3.4 發(fā)布為可執(zhí)行文件Stdio Server 必須是一個(gè)可執(zhí)行文件Client 才能啟動(dòng)它。發(fā)布命令如下win-x64 按你的平臺(tái)改。dotnet publish -c Release -r win-x64 --self-contained false發(fā)布完成后在 bin/release/net8.0/publish/win-x64/ 目錄下會(huì)生成 McpServer.Stdio.exe。這個(gè)路徑后面要填到 Client 的配置里先記下來(lái)。3.5 Client 側(cè) settings.json 與 config.toml 配置片段不同 AI 工具的配置文件名不一樣但結(jié)構(gòu)類似。以 settings.json 為例把 Stdio Server 注冊(cè)進(jìn)去同時(shí)把模型通道指向 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 格式等價(jià)寫法如下。[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 里單反斜杠即可。路徑寫錯(cuò)是后面最常見(jiàn)的連接失敗原因。4. 驗(yàn)證請(qǐng)求一次真實(shí)的讀文件-寫文件往返配置寫好后啟動(dòng)你的 MCP Client。Client 會(huì)讀取 settings.json啟動(dòng) McpServer.Stdio.exe然后通過(guò) stdin/stdout 和它握手。握手成功后Client 會(huì)打印出工具列表你應(yīng)該能看到 ReadFile 和 SaveFile 兩個(gè)工具。先準(zhǔn)備一個(gè)本地文件比如 E:\本地文件.txt里面隨便寫點(diǎn)內(nèi)容。然后在 Client 的對(duì)話輸入框里輸入讀取 E:\本地文件.txt 的內(nèi)容模型會(huì)識(shí)別出這是 ReadFile 工具的調(diào)用參數(shù) path 為 E:\本地文件.txt。Client 把請(qǐng)求通過(guò) stdin 發(fā)給 ServerServer 執(zhí)行后把文件內(nèi)容寫到 stdoutClient 拿到結(jié)果再交給模型模型最終把內(nèi)容展示給你。整個(gè)過(guò)程你不需要手動(dòng)敲任何 JSON。接著驗(yàn)證寫入將內(nèi)容這是MCP Server實(shí)例保存到文件路徑E:\stdio.txt模型會(huì)調(diào)用 SaveFile參數(shù) path 為 E:\stdio.txtcontent 為「這是MCP Server實(shí)例」。執(zhí)行成功后Server 返回「文件已成功保存至:E:\stdio.txt」你可以打開(kāi)這個(gè)文件確認(rèn)內(nèi)容確實(shí)寫進(jìn)去了。再讓模型讀一次 E:\stdio.txt如果能讀出剛才寫的內(nèi)容說(shuō)明讀-寫閉環(huán)完全跑通。這一步的成功標(biāo)志有三個(gè)Client 打印出工具列表、讀文件返回正確內(nèi)容、寫文件后能再次讀出。三個(gè)都滿足最小可用 MCP Server 就算落地了。5. 本篇常見(jiàn)錯(cuò)排查5.1 Client 啟動(dòng) Server 失敗提示找不到可執(zhí)行文件最常見(jiàn)的原因是 command 路徑寫錯(cuò)。檢查發(fā)布目錄下是否真的有 McpServer.Stdio.exe以及 JSON 里的雙反斜杠是否正確。如果你用的是相對(duì)路徑Client 的工作目錄可能和你預(yù)期不一致建議一律用絕對(duì)路徑。5.2 工具列表為空模型看不到 ReadFile 和 SaveFile先確認(rèn) FileTool 類上的 [McpServerToolType] 和方法上的 [McpServerTool] 都加了并且 using 了 ModelContextProtocol.Server。如果特性加了但列表還是空檢查方法是否是 public static框架只注冊(cè)公開(kāi)靜態(tài)方法。另外Description 特性里的文字不要留空空描述有時(shí)會(huì)導(dǎo)致注冊(cè)被跳過(guò)。5.3 讀文件報(bào)「文件不存在」但文件明明在Stdio Server 是以 Client 啟動(dòng)的進(jìn)程身份運(yùn)行的它的工作目錄和當(dāng)前用戶可能和你手動(dòng)打開(kāi)文件時(shí)不同。路徑里如果有中文或空格確保 JSON 轉(zhuǎn)義正確。建議先用絕對(duì)路徑測(cè)試排除相對(duì)路徑帶來(lái)的歧義。5.4 寫文件成功但內(nèi)容為空檢查 SaveFile 的 content 參數(shù)是否被模型正確填充。有時(shí)候模型會(huì)把內(nèi)容放到錯(cuò)誤的參數(shù)里或者把路徑和內(nèi)容搞反。你可以在 SaveFile 里加一行日志寫到臨時(shí)文件確認(rèn)實(shí)際收到的參數(shù)值。另外如果目標(biāo)文件被其他程序占用WriteAllTextAsync 會(huì)拋異常但我們的 catch 會(huì)返回錯(cuò)誤字符串注意看返回信息。5.5 模型通道返回 401 或連接超時(shí)這通常是 Client 側(cè)的 base_url 或 api_key 配置問(wèn)題。確認(rèn) base_url 是 https://taotoken.net/api 不要多加路徑也不要在 API 地址后面拼 UTM 參數(shù)。Key 是否復(fù)制完整、是否有多余空格都檢查一遍。如果還是不通到接入文檔頁(yè)面核對(duì)最新的配置示例地址是 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。6. 把 Key 和工具鏈固定下來(lái)Stdio MCP Server 跑通之后你會(huì)發(fā)現(xiàn)真正省事的地方在于工具在本地Key 在統(tǒng)一通道兩者解耦。以后你加一個(gè)新工具只需要在 FileTool 旁邊再寫一個(gè)類重新發(fā)布Client 配置里的 command 不用改。換模型或者換 Client也只需要改 llm 那一段Server 代碼完全不動(dòng)。如果你打算長(zhǎng)期做本地編碼輔助或者 Agent 類工具建議把 Key 管理放到 Coding Plan 里統(tǒng)一規(guī)劃入口是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 這樣多個(gè)工具共用一套配額和通道排查問(wèn)題也方便。需要單獨(dú)管理 Key 的時(shí)候API Keys 頁(yè)面在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 可以按工具拆分不同 Key出問(wèn)題能快速定位是哪個(gè)環(huán)節(jié)。最后留一個(gè)實(shí)用習(xí)慣每次改完 Server 代碼先手動(dòng)跑一次 exe確認(rèn)它能正常啟動(dòng)不報(bào)錯(cuò)再去 Client 里測(cè)。Stdio 程序如果啟動(dòng)就崩Client 那邊只會(huì)顯示連接失敗看不到具體異常手動(dòng)跑一次能省很多排查時(shí)間。