
如果你維護過大型 .NET 解決方案一定遇到過這樣的場景項目里有幾十個 csproj類與類之間的依賴關系盤根錯節方法調用鏈跨越了三四個程序集。本地調試還能靠 IDE 的“轉到定義”一點點摸但當你試圖讓 AI 編程助手幫你改代碼、做重構、解釋業務邏輯時它卻常常答非所問——因為 AI 看到的只是你貼給它的那幾個文件它看不到整個解決方案的依賴關系和調用上下文。這個問題的本質是傳統 AI 編程工具缺少對代碼庫全局結構的感知能力。最近在 Hacker News 看到的一個項目很有意思Slnmap一個基于 Roslyn 的 code graph MCP server專門為 .NET 代碼庫服務。它把 Roslyn 的語法樹和符號分析能力與 MCPModel Context Protocol結合起來讓 AI 編程助手可以按需查詢代碼庫的依賴圖、調用關系、類型結構等語義信息。這篇文章會從實際工程角度拆解 Slnmap它解決的是什么問題、核心原理是什么、怎么配置和啟動、如何在 Cursor / Claude Desktop / 自研 Agent 中接入它以及真實項目中的坑和最佳實踐。1. 這篇文章真正要解決的問題先說一個經常被忽略的事實給 AI 編程助手“喂文件”和讓它“理解代碼庫”是兩回事。如果你用過 Copilot Chat、Cursor 或 Claude Code 處理大型解決方案大概率遇到過以下幾種情況上下文窗口不夠用。一個大型 .NET 解決方案可能有幾百個源文件全部塞進上下文既不可能也沒必要但 AI 需要的那幾個關鍵文件恰恰不在上下文里。缺少依賴關系信息。AI 看到一個OrderService類但它不知道OrderService依賴了哪些倉儲接口、被哪個控制器調用、實現了哪個抽象基類。這些信息不直接寫在當前文件里卻決定了重構時會不會破壞其他模塊。符號解析不準確。同名類、Partial 類、通過依賴注入注冊的接口實現只看源碼文本很難判斷運行時真正用的是哪個實現。人工挑選上下文太累。讓開發者手動把相關文件拖進對話在大型代碼庫里篩選成本極高。Slnmap 的思路是把代碼庫的圖譜信息通過 MCP 協議暴露給 AI 助手讓 AI 按需查詢而不是靠猜。Roslyn 在這里起到的作用是“語義分析引擎”。它不是簡單讀取文本而是能夠解析出完整的符號信息類型在哪里定義、方法被誰調用、項目之間誰引用誰。Slnmap 把 Roslyn 分析出的這些信息構造成一張 code graph再通過 MCP server 提供給 AI 客戶端查詢。換句話說Slnmap 讓 AI 從“讀文件”升級為“讀代碼庫”。這是本篇要講的核心判斷。2. 三個繞不開的概念Roslyn、代碼圖與 MCP在動手操作之前需要先弄清楚三個基礎概念。如果這三個概念沒有對齊后面配置和調試的時候會很難受。2.1 Roslyn不只是編譯器Roslyn 是微軟開源的 C# 和 Visual Basic 編譯器平臺但它和傳統編譯器最大的區別是Roslyn 把自己做成了一組 API。傳統編譯器是“源碼進、程序集出”的黑盒你很難在編譯過程中拿到中間信息。Roslyn 則把編譯過程拆成多個可訪問的階段Syntax Tree語法樹代碼的語法結構比如類聲明、方法聲明、語句塊。Symbol符號語法樹背后的語義信息比如OrderService這個類名對應哪個類型、GetOrderById這個方法的完整簽名是什么。Semantic Model語義模型把語法樹和符號關聯起來告訴你某個標識符在特定上下文里到底引用了哪個類型或方法。Slnmap 用 Roslyn 不是為了編譯出 DLL而是利用 Roslyn 的語義分析能力構建一個可供查詢的代碼關系網絡。2.2 代碼圖Code Graph把代碼變成關系數據“代碼圖”可以理解為代碼庫的“關系型抽象”。它關注的是實體之間的關系而不是具體代碼內容。在 .NET 代碼庫中常見的圖關系包括項目之間的 ProjectReference。類型之間的繼承關系和接口實現。方法之間的調用關系Caller / Callee。類型之間的屬性引用、方法參數引用、字段類型引用。舉個例子CustomerController依賴ICustomerServiceCustomerService實現了ICustomerService并依賴ICustomerRepositorySqlCustomerRepository實現了ICustomerRepository。這段依賴鏈如果靠人肉梳理需要打開三四個文件來回跳。而代碼圖可以把這些關系直接輸出為結構化數據{ type: CustomerController, dependsOn: [ICustomerService], implementedBy: [], references: [CustomerService, CustomerRepository] }2.3 MCPAI 世界的“USB-C 接口”MCPModel Context Protocol是 Anthropic 提出的開放協議目的是統一 AI 應用與外部數據、工具之間的連接方式。可以把它理解成 AI 領域的 USB-C 標準不同的工具通過統一協議接入不同的 AI 客戶端不需要為每一個 AI 產品單獨定制集成。MCP 協議中有幾個核心角色MCP Server提供服務的一方把某個能力封裝成協議接口。Slnmap 就是一個 MCP Server。MCP Client消費服務的一方比如 Claude Desktop、Cursor或者你自己寫的 Agent。ToolServer 暴露給 Client 調用的具體能力比如“查詢類型依賴”“查找方法調用鏈”。Slnmap 屬于 MCP Server它把 Roslyn 分析結果包裝成若干 ToolAI 客戶端通過標準協議調用這些 Tool就能拿到結構化的代碼圖譜信息。2.4 三者的關系用一句話概括Roslyn 負責分析代碼圖負責存儲MCP 負責傳輸。Slnmap 啟動時先用 Roslyn 加載目標解決方案或項目分析出符號和關系構建代碼圖然后啟動一個 MCP ServerAI 編程助手通過 MCP 協議向 Server 發起查詢請求Server 返回圖譜數據。整個過程不需要把整個代碼庫塞進上下文。3. Slnmap 工作原理與架構拆解從架構上看Slnmap 可以分為三個層次分析層、圖構建層、服務層。3.1 分析層Roslyn 項目加載與符號解析Slnmap 首先需要定位目標代碼庫這一步通常通過傳入.sln或.csproj文件路徑來完成。Roslyn 提供了MSBuildWorkspace類可以從解決方案文件加載項目并獲取每個項目的Compilation對象。Compilation是所有語義分析的入口// 示意代碼使用 MSBuildWorkspace 加載解決方案 using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.MSBuild; var workspace MSBuildWorkspace.Create(); var solution await workspace.OpenSolutionAsync(path/to/YourSolution.sln); foreach (var project in solution.Projects) { var compilation await project.GetCompilationAsync(); // 拿到 compilation 后可以遍歷語法樹、獲取符號、分析依賴 }這里的關鍵點是Slnmap 分析的是Roslyn 的語義模型Semantic Model而不是簡單的文本匹配。這意味著它能準確區分兩個同名但來自不同命名空間的類型。通過別名引用的類型。泛型實例化后的實際類型參數。擴展方法實際調用的靜態類。這些信息僅靠 GPT 的文本理解是拿不到的而 Roslyn 可以精確給出。3.2 圖構建層提取并存儲關系拿到Compilation之后Slnmap 會遍歷所有語法樹提取兩類信息類型級信息類型名稱、命名空間、文件路徑。基類、實現的接口。類型中聲明的成員屬性、方法、字段。每個成員的類型引用。依賴級信息類 A 是否在方法體內調用了類 B 的方法。類 A 是否在屬性類型中引用了類 B。項目 X 是否引用了項目 Y。方法 M 被哪些方法調用。這些關系會組織成一個圖數據結構。在實際存儲上可以選擇內存數據結構適合單次會話分析也可以序列化成 JSON、圖數據庫或內存數據庫以便反復查詢。3.3 服務層MCP 工具暴露Slnmap 啟動 MCP Server 后會暴露一組查詢工具。根據項目定位常見工具包括get_project_structure獲取解決方案的項目列表和依賴關系。get_type_info按名稱查詢類型的定義位置、基類、接口和成員。get_dependencies查詢某個類型的直接依賴和反向依賴。get_callers查詢某個方法被誰調用。get_callees查詢某個方法調用了哪些方法。AI 客戶端拿到這些工具后就能在對話中按需調用。例如用戶問“OrderService 的 ExecuteAsync 被哪些地方調用了”AI 不會自己瞎猜而是調用get_callers工具拿到 Slnmap 返回的結構化方法調用列表。3.4 為什么這個設計比“貼文件”更好這里做一個對比更容易理解對比維度傳統方式把文件貼給 AISlnmap 方式MCP 查詢上下文占用隨文件數量線性增長只占用查詢結果關系準確性依賴 AI 推斷容易出錯Roslyn 語義分析精確大型代碼庫支持很差窗口很快打滿按需查詢天然支持信息完整度只看到貼進去的文件能看到全庫關系交互體驗用戶手動挑選文件AI 自主調用工具這個設計的核心價值是上下文窗口不再是代碼庫大小的瓶頸。4. 環境準備與前置條件Slnmap 是面向 .NET 生態的工具所以環境準備以 .NET SDK 為核心。4.1 運行環境清單依賴項說明.NET SDK建議使用當前 LTS 版本實際項目請以官方 README 要求為準操作系統Windows、macOS、Linux 均可.NET 本身跨平臺目標代碼庫需要分析解決方案中包含 .NET 項目.NET Framework 或 .NET / .NET Core 均可MCP 客戶端Claude Desktop、Cursor、自研 MCP Client 等4.2 安裝與啟動如果目標是快速體驗一般方式是從源碼運行或安裝發布包。這里給出通用的啟動方式# 克隆代碼庫如果 Slnmap 是開放源碼項目 git clone https://github.com/your-repo/slnmap.git cd slnmap # 還原依賴并構建 dotnet restore dotnet build # 啟動 MCP Server 并指定要分析的目標解決方案 dotnet run --project src/Slnmap -- --solution /path/to/YourSolution.sln --transport stdio另一種常見方式是通過npx或其他包管理工具直接運行具體取決于項目發布形式。無論哪種方式核心啟動參數都會包括--solution或--project指定要分析的代碼庫入口。--transportMCP 傳輸方式常見有stdio標準輸入輸出和sseHTTP 流式。4.3 驗證 MCP Server 是否啟動成功對于stdio傳輸模式啟動后進程會等待標準輸入上的 JSON-RPC 消息。你可以在終端里手動輸入一條 MCPinitialize請求來驗證{jsonrpc:2.0,id:1,method:initialize,params:{protocolVersion:2024-11-05,capabilities:{},clientInfo:{name:test,version:1.0.0}}}如果服務正常你會收到包含 server 信息和 capabilities 的響應。如果沒有任何輸出說明啟動階段就失敗了優先檢查路徑參數和依賴還原是否成功。5. 核心流程拆解讓 Slnmap 跑起來這一節我們拆解一個完整的最小流程從準備測試代碼庫到啟動 Slnmap再到通過 MCP 調用工具獲取代碼圖。5.1 準備一個示例解決方案為了演示我們創建一個最簡但包含依賴關系的解決方案MyShop/ ├── MyShop.sln ├── src/ │ ├── MyShop.Domain/ │ │ └── Customer.cs │ ├── MyShop.Application/ │ │ └── CustomerService.cs │ └── MyShop.Api/ │ ├── Controllers/ │ │ └── CustomerController.cs │ └── MyShop.Api.csproj各文件內容如下// 文件路徑src/MyShop.Domain/Customer.cs namespace MyShop.Domain; public class Customer { public int Id { get; set; } public string Name { get; set; } string.Empty; }// 文件路徑src/MyShop.Application/CustomerService.cs using MyShop.Domain; namespace MyShop.Application; public interface ICustomerRepository { TaskCustomer? GetByIdAsync(int id); } public class CustomerService { private readonly ICustomerRepository _repository; public CustomerService(ICustomerRepository repository) { _repository repository; } public async TaskCustomer? GetCustomerAsync(int id) { return await _repository.GetByIdAsync(id); } }// 文件路徑src/MyShop.Api/Controllers/CustomerController.cs using Microsoft.AspNetCore.Mvc; using MyShop.Application; namespace MyShop.Api.Controllers; [ApiController] [Route(api/customers)] public class CustomerController : ControllerBase { private readonly CustomerService _service; public CustomerController(CustomerService service) { _service service; } [HttpGet({id})] public async TaskActionResultCustomer? Get(int id) { var customer await _service.GetCustomerAsync(id); if (customer is null) return NotFound(); return Ok(customer); } }三個項目之間的依賴關系很清晰MyShop.Api引用MyShop.Application。MyShop.Application引用MyShop.Domain。CustomerController依賴CustomerService。CustomerService依賴ICustomerRepository。這個例子雖然小但已經包含項目引用、接口依賴、類依賴、方法調用四種圖關系。5.2 啟動 Slnmap 并指向示例解決方案dotnet run --project src/Slnmap -- \ --solution /path/to/MyShop/MyShop.sln \ --transport stdio啟動日志如果顯示類似“MCP server started”的信息說明服務已經就緒。在stdio模式下你會看到進程掛起等待輸入這是正常行為。5.3 調用工具查詢項目依賴MCP 協議中調用工具使用tools/call方法。假設 Slnmap 暴露了get_project_dependencies工具請求格式如下{jsonrpc:2.0,id:2,method:tools/call,params:{name:get_project_dependencies,arguments:{projectName:MyShop.Api}}}預期響應中會包含 MyShop.Api 直接和間接依賴的項目信息類似{ jsonrpc: 2.0, id: 2, result: { content: [ { type: text, text: [{\Project\:\MyShop.Api\,\DependsOn\:[\MyShop.Application\,\MyShop.Domain\]}] } ] } }這個查詢結果說明MyShop.Api不僅直接引用了MyShop.Application還通過傳遞引用依賴了MyShop.Domain。5.4 查詢類型反向依賴假設你想知道CustomerService被誰用到可以調用對應的反向依賴查詢工具{jsonrpc:2.0,id:3,method:tools/call,params:{name:get_reverse_dependencies,arguments:{typeName:CustomerService}}}從代碼庫結構看CustomerController構造時注入了CustomerService所以查詢結果會指向CustomerController。這個能力在重構時非常有價值改一個構造函數簽名前先查一下誰在用它。6. 在 AI 編程助手中接入 SlnmapSlnmap 的價值要在 AI 客戶端中才能真正體現。這里分別說明在兩種場景下的接入方式。6.1 在 Claude Desktop 中注冊 MCP ServerClaude Desktop 支持通過配置文件注冊 MCP Server。如果你把 Slnmap 編譯成可執行文件可以將其注冊為一個command類型的 MCP Server。配置位置通常為claude_desktop_config.json示例{ mcpServers: { slnmap: { command: dotnet, args: [ /path/to/Slnmap.dll, --solution, /path/to/MyShop/MyShop.sln, --transport, stdio ] } } }重啟 Claude Desktop 后會話中就能看到 Slnmap 提供的工具列表。之后你可以直接在對話里提問Claude 會在需要時自動調用工具。6.2 在 Cursor 中配置Cursor 的 MCP 配置入口在 Settings → MCP選擇 Add New MCP Server填寫NameslnmapTypecommandCommand啟動命令例如dotnet /path/to/Slnmap.dll --solution /path/to/MyShop/MyShop.sln --transport stdio保存后Cursor 會自動發現 Slnmap 暴露的工具。在寫代碼時AI 就能調用工具查詢代碼庫關系而不是只依賴當前打開的文件。6.3 在自研 Agent 中調用如果你在寫自己的 AI Agent可以用 C# 或其他語言構造 MCP 標準 JSON-RPC 請求通過標準輸入輸出與 Slnmap 通信。核心邏輯如下// 示意代碼發送 MCP initialize 請求 var process new Process { StartInfo new ProcessStartInfo { FileName dotnet, Arguments Slnmap.dll --solution /path/to/MyShop/MyShop.sln --transport stdio, RedirectStandardInput true, RedirectStandardOutput true, UseShellExecute false } }; process.Start(); var request {\jsonrpc\:\2.0\,\id\:1,\method\:\initialize\,\params\:{}}; await process.StandardInput.WriteLineAsync(request); var response await process.StandardOutput.ReadLineAsync(); Console.WriteLine(response);自研接入時需要注意 MCP 協議要求先完成initialize握手再發送notifications/initialized通知最后才能調用工具。7. 運行結果與效果驗證7.1 驗證項目依賴查詢啟動 Slnmap 并完成一次initialize握手之后調用get_project_dependencies。如果返回結果中包含了 MyShop.Api 對 MyShop.Application 和 MyShop.Domain 的依賴說明 Roslyn 正確解析了解決方案中的ProjectReference。7.2 驗證類型信息查詢調用類型查詢工具傳入CustomerService{jsonrpc:2.0,id:4,method:tools/call,params:{name:get_type_info,arguments:{typeName:CustomerService}}}預期結果包括類型所在命名空間MyShop.Application定義文件路徑src/MyShop.Application/CustomerService.cs構造函數參數類型ICustomerRepository公開方法GetCustomerAsync這些信息說明 Slnmap 不只是做了文本索引而是真的解析了語義模型。如果 Slnmap 只是簡單做正則匹配它是無法告訴你構造函數參數類型的。7.3 驗證方法調用鏈如果你在示例代碼里讓CustomerService.GetCustomerAsync調用_repository.GetByIdAsync那么通過方法調用查詢工具可以得到CustomerService.GetCustomerAsync調用了ICustomerRepository.GetByIdAsync。這個信息對 AI 助手理解代碼執行路徑非常關鍵。沒有代碼圖AI 可能看到_repository.GetByIdAsync卻不知道調用的目標是接口方法。7.4 如何判斷 Slnmap 是否正常工作判斷標準有三條能返回結構化結果返回的是 JSON 而不是報錯信息。結果與代碼庫實際一致對照源碼項目依賴、類型信息、調用關系都正確。查詢響應及時MCP 調用沒有明顯超時。如果其中任何一條不滿足優先查看啟動日志和標準錯誤輸出。8. 常見問題與排查思路問題現象可能原因排查方式解決方案啟動后沒有任何輸出解決方案路徑錯誤或依賴還原失敗檢查--solution路徑在項目目錄手動執行dotnet restore確保路徑正確先還原依賴再啟動MCP initialize 請求無響應傳輸模式不匹配確認客戶端用 stdio 連接時服務端也用 stdio統一傳輸模式檢查啟動參數查詢類型時返回空結果類型名稱寫錯或代碼庫不是 Roslyn 可加載的 .NET 項目確認類型全名含命名空間檢查目標項目能否在命令行下編譯使用全名查詢先確認項目可以dotnet build分析大型解決方案時耗時過長代碼庫規模大Roslyn 全量分析需要時間查看啟動日志中的分析進度檢查是否重復分析同一項目明確分析范圍必要時先分析子集項目客戶端顯示工具不存在MCP Server 版本與客戶端不兼容或工具名稱變化查看 MCP Server 的tools/list響應按實際工具名稱調用不要寫死.NET Framework 項目加載失敗目標項目需要 MSBuild 相關組件檢查是否安裝了對應版本的 .NET Framework Developer Pack在具備構建該項目的環境中運行分析一個很關鍵的經驗是Slnmap 能不能正確分析首先取決于目標代碼庫能不能被 Roslyn 正確加載。如果命令行下連dotnet build都過不了Slnmap 大概率也會失敗。所以在排查 MCP 層問題之前先確認代碼庫本身可用。9. 最佳實踐與工程建議9.1 按需分析不要全量加載對大型企業級解決方案不要一次加載所有項目再查詢。Slnmap 這類工具必然受 Roslyn 編譯時間和內存的約束。如果你的解決方案包含幾十甚至上百個項目優先考慮啟動時指定要分析的子集例如只分析應用層和 API 層。按需增量加載先拿到項目列表再針對特定項目做深度分析。把分析結果緩存下來重復使用。9.2 使用全名查詢避免歧義在 MCP 調用時類型名盡量帶命名空間。因為不同命名空間下可能有同名類型只傳CustomerService可能命中多個結果。推薦格式{name:get_type_info,arguments:{typeName:MyShop.Application.CustomerService}}9.3 與代碼搜索工具配合使用Slnmap 提供的是語義圖譜信息它不一定包含“某個字符串出現在哪些文件”這種文本搜索能力。與 grep、ripgrep 等文本搜索工具配合才能覆蓋代碼理解和代碼定位的完整鏈路。9.4 安全邊界與權限控制MCP Server 本質上是本機代碼執行服務。在使用 Slnmap 時要注意不要在未授權的情況下分析敏感項目的完整依賴圖尤其是包含密鑰、連接字符串、內部實現細節的代碼庫。如果 Slnmap 部署為遠程服務必須做訪問控制避免任意 MCP 客戶端都能查詢代碼庫結構。在用 AI 助手時注意不要把不應暴露的代碼內容發送給云端大模型。9.5 制定 MCP 工具命名規范如果你在團隊內推廣 Slnmap 或自研類似的工具給 MCP 工具命名時盡量遵循統一模式get_前綴的查詢工具get_type_info、get_dependencies。find_前綴的探索工具find_callers、find_references。list_前綴的枚舉工具list_projects、list_members。規范命名不僅讓 AI 更容易理解工具用途也能減少把參數傳錯的概率。10. 總結與后續學習方向Slnmap 代表了一類新的 .NET 開發工具方向用編譯器級的語義分析能力為 AI 編程助手補上代碼庫全局視角。它的意義不在于“又一個 MCP Server”而在于它把 Roslyn 多年積累的代碼分析能力通過標準協議開放給了 AI 工具鏈。對于維護大型 .NET 解決方案的開發者這種能力意味著 AI 不再是一個“只看局部文件的實習生”而是一個“能隨時查詢全局關系的高級助手”。如果你正在做 .NET 開發并且頻繁使用 AI 編程助手建議按這篇文章的流程把 Slnmap 跑起來。先在小解決方案上驗證工具能力再逐步引入到真實項目中。理解 Roslyn 語義模型是 .NET 開發者被低估的一項技能Slnmap 恰好是理解 Roslyn 能力邊界的一個極好入口。后續可以繼續探索的方向包括基于代碼圖做架構守護、自動生成依賴文檔、將代碼圖導出到圖數據庫做更深度的分析或者把 Slnmap 集成到 CI 流水線中對每次變更做影響范圍分析。對這個領域有興趣的開發者值得持續關注。