公司动态
Slnmap:用Roslyn构建.NET代码图谱MCP Server
接手过大型 .NET 解决方案的开发者应该都有过这种经历项目数量动辄几十个类型多到连 IDE 的导航都显得有些吃力。AI 编程助手确实能帮你写代码、解释代码但一旦问题变成“这个接口被谁实现”“修改 ProductService 会影响哪些模块”“两个项目之间有没有循环依赖”它就显得力不从心——不是模型不够聪明而是它看不到项目级的全局结构。让 AI 看到“全貌”的前提是把代码关系变成一种可以按需查询的数据。Slnmap 就是解决这件事的工具一个基于 Roslyn 的代码图谱 MCP Server面向 .NET 代码库可以把解决方案里的工程、类型、方法调用、引用关系组织成一张可查询的图并通过 MCP 协议交给 Claude Desktop、Cursor、VS Code 等 AI 客户端使用。这篇文章会围绕 Slnmap 从四个层面展开先解释 Roslyn、代码图谱、MCP Server 三个关键概念再给出一套可落地的环境准备和安装方式接着拆解它背后的工作原理并完成接入 AI 客户端的实战最后整理高频问题排查表和工程化建议。无论你是在评估 MCP 在 .NET 场景的价值还是想立刻把 Slnmap 接入日常开发这篇文章都能提供直接参考。1. 背景为什么 .NET 代码库需要一张“代码地图”1.1 AI 编程助手面临的结构性问题大型 .NET 解决方案通常由多个 csproj 组成一个业务系统里可能同时包含 API 层、领域层、基础设施层、测试项目以及若干类库。代码之间不仅有“文件在哪个目录”这种物理关系还有“方法调用链”“类型继承”“接口实现”“项目引用”等逻辑关系。大语言模型在处理单个文件时表现出色主要是因为上下文窗口可以容纳但一旦面对整个解决方案它就无法把几百个文件全部读进来。于是模型容易犯两类错误找不到定义位置凭经验猜测命名空间或方法签名。无法判断改动影响范围把“看起来像”的引用当作真实依赖。这就是为什么只靠“把代码喂给模型”还不够需要先把代码图谱提取出来让模型通过工具按需查询。1.2 代码图谱解决什么问题代码图谱Code Graph是一种把程序结构表达为图的数据模型。节点可以是解决方案、项目、文件、类、方法、属性、字段边可以表达“调用”“引用”“继承”“实现”“包含”等关系。有了这张图AI 就能回答一些非常具体的问题PaymentService.Process被哪些方法调用IOrderRepository有哪些实现类Order.Api项目间接依赖了Shared.Infrastructure吗如果重命名Customer.Name哪些文件会受影响这些问题本质上都是图查询而不是文本搜索。传统 IDE 里的“查看所有引用”也是在做类似的事但 IDE 的结果是给人看的MCP Server 的结果是给 AI 客户端消费的因此需要更结构化、更轻量。1.3 Slnmap 在技术生态中的位置Slnmap 以 “Show HN” 的形式发布本质上是一个独立开发者把内部工具开放出来供社区评估。它的定位非常清晰不是一个完整 IDE 插件也不是代码检索引擎而是专门面向 MCP 生态的 .NET 代码图谱服务。它内部使用 Roslyn 做语义分析把 .NET 解决方案转换成图数据再通过 MCP 协议暴露查询能力。你完全可以把 Slnmap 理解为 AI 开发助手和 .NET 代码库之间的“结构层翻译官”AI 负责理解和生成Slnmap 负责告诉 AI 代码之间的真实关系。2. 三个核心概念先说清楚2.1 Roslyn.NET 编译器平台Roslyn 是 .NET 官方的开源编译器平台它不只是编译器还提供了一整套代码分析 API。传统编译器的处理过程是黑盒源代码进去程序集出来。Roslyn 把这个过程拆成了可访问的中间层语法树Syntax Tree源代码的完整语法结构包含类、方法、语句等语法节点。语义模型Semantic Model在语法树基础上知道每个标识符真正引用的是哪个符号。符号Symbol类型、方法、属性、字段等编译单元带完整的全限定名和元数据。编译对象Compilation一个项目的完整编译上下文可以跨文件查找引用。正是因为 Roslyn 提供了这些 API开发者才能在 Visual Studio 之外写出分析代码工具比如代码生成器、静态分析器、重构工具以及 Slnmap 这种代码图谱服务。2.2 Code Graph把“关系”变成可查询数据代码图谱重点关注的是“关系”。拿一个简单的调用场景举例public class OrderService { private readonly IOrderRepository _repository; public Order GetOrder(int id) { return _repository.FindById(id); } }这张代码图谱里至少包含节点/边说明OrderService类节点订单服务类型IOrderRepository接口节点仓储接口_repository.FindById(id)调用边OrderService.GetOrder调用IOrderRepository.FindById实现边某个具体仓储类实现了该接口依赖边OrderService依赖IOrderRepository这些关系在源码里是隐式的但一旦提取成图谱就可以做全局分析。比如发现循环依赖、找调用链、评估改动影响范围本质上都是对这张图做定向查询。2.3 MCP ServerAI 的“外置工具接口”MCPModel Context Protocol是一个用于连接 AI 模型与外部数据、工具的开放协议由 Anthropic 提出并开源2024 年开始被大量开发工具采用。它解决的核心问题是每次集成一个新数据源都要为某个 AI 客户端写专用适配器而 MCP 用统一标准替代了这些零散接入。MCP 架构里有三个角色MCP HostAI 应用本身比如 Claude Desktop、Cursor。MCP ClientHost 内部的连接器负责与 Server 通信。MCP Server提供工具、资源、提示词的独立服务比如 Slnmap。通信方式有两种常见形态stdioServer 以本地子进程方式运行通过标准输入输出传输 JSON-RPC 消息最简单也最安全。HTTP/SSEServer 作为远程服务运行适合部署在远端或多人共享场景。MCP Server 可以暴露三类能力Tools可调用的工具、Resources可读取的数据资源、Prompts预置的提示词模板。Slnmap 这类代码图谱服务主要走 Tools 路线给 AI 提供类似get_symbol_references、find_callers的查询接口。2.4 三者如何串联Slnmap 的完整工作流程并不复杂读取 Visual Studio 解决方案文件.sln。用 Roslyn 的 Workspace API 加载所有项目还原 NuGet 引用获取每个项目的 Compilation。遍历语法树和语义模型提取类型、方法、字段等符号以及调用、继承、实现、项目依赖等关系。把上述信息组织成图谱数据。将图谱查询能力包装成 MCP Tools供 AI 客户端调用。这样一来AI 在回答 .NET 代码库的问题时不只是“看到”当前文件而是可以按需向 Slnmap 查询全解决方案的结构关系。查出来的结果可能是 JSON 片段 AI 再结合这些片段做判断准确率会明显高于凭空猜测。3. 环境准备与版本说明3.1 需要的运行环境Slnmap 是一个 .NET 程序因此本机需要安装 .NET SDK。具体版本要求应以项目 README 为准一般来说现代版本的 .NET SDK.NET 8 或 .NET 9都能满足。建议先确认 SDK 版本dotnet --version如果提示找不到dotnet命令说明 SDK 还没有安装或者没有加入 PATH。Windows 环境还需要注意区分 .NET Framework 和 .NET SDKSlnmap 依赖的是现代 .NET而不是 .NET Framework 4.8 这类传统运行时。另一个需要注意的是很多 MCP 客户端本身是传统桌面应用它们启动本地 MCP Server 时环境变量可能和你的终端不一样。如果你在终端没问题但客户端连接失败优先检查客户端是否能找到dotnet和全局工具路径。3.2 获取 Slnmap 的两种方式Slnmap 的获取方式通常是下面两种之一不同版本的安装命令可能会有差异方式一通过 dotnet tool 安装全局工具。这是 .NET 生态最常见的发布形式安装后的命令可以直接在终端调用dotnet tool install --global Slnmap如果包名和发布平台不同请以项目 README 或 NuGet 页面为准。方式二从源码构建。这种方式适合需要调试或改造的场景先在 GitHub 克隆仓库然后在仓库根目录执行dotnet build -c Release dotnet run --project src/Slnmap -- --help源码构建的好处是可以看到 Slnmap 内部如何使用 Roslyn如果你想学习实现细节这是一条不错的路径。3.3 准备一个可用的 MCP 客户端要真正用上 Slnmap还需要一个支持 MCP 的客户端。目前主流的选项有三个Claude DesktopMCP 原生支持最完整的桌面应用配置简单适合快速验证。Cursor支持在项目级配置 MCP Server适合日常开发环境。VS Code / GitHub Copilot新版 VS Code 已经逐步开放 MCP 支持配置方式随版本变化。建议先用 Claude Desktop 完成验证跑通之后再切到日常主力编辑器。4. 核心原理拆解用 Roslyn 构建代码关系理解 Slnmap 最好的方式是自己动手写一段 Roslyn 分析代码。下面这个 Demo 不做复杂功能只实现三件事加载解决方案、统计每个项目的类数量、统计方法调用点数量。但它的思路和 Slnmap 完全一致。4.1 创建示例项目并添加依赖先创建一个控制台项目然后添加两个关键 NuGet 包。mkdir RoslynGraphDemo cd RoslynGraphDemo dotnet new console dotnet add package Microsoft.Build.Locator dotnet add package Microsoft.CodeAnalysis.Workspaces.MSBuildMicrosoft.Build.Locator负责在非 Visual Studio 环境中定位和注册 MSBuild而Microsoft.CodeAnalysis.Workspaces.MSBuild让我们可以用MSBuildWorkspace直接打开.sln或.csproj。版本号需要结合你本机的 .NET SDK 环境调整示例项目如果使用 .NET 8这两个包的常用版本在 4.11.0 和 1.7.x 附近。4.2 加载解决方案并遍历项目修改Program.cs写入下面这段代码// 文件路径RoslynGraphDemo/Program.cs using Microsoft.Build.Locator; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.MSBuild; MSBuildLocator.RegisterDefaults(); var solutionPath args.Length 0 ? args[0] : Sample.sln; using var workspace MSBuildWorkspace.Create(); workspace.WorkspaceFailed (_, e) { Console.WriteLine([Workspace 警告] e.Diagnostic.Message); }; var solution await workspace.OpenSolutionAsync(solutionPath); Console.WriteLine(解决方案 solution.FilePath); Console.WriteLine(项目数量 solution.Projects.Count()); foreach (var project in solution.Projects) { var compilation await project.GetCompilationAsync(); if (compilation is null) { continue; } var classCount 0; var methodCallCount 0; foreach (var tree in compilation.SyntaxTrees) { var semanticModel compilation.GetSemanticModel(tree); var root await tree.GetRootAsync(); classCount root.DescendantNodes() .OfTypeClassDeclarationSyntax() .Count(); foreach (var invocation in root.DescendantNodes().OfTypeInvocationExpressionSyntax()) { var symbol semanticModel.GetSymbolInfo(invocation).Symbol; if (symbol is IMethodSymbol) { methodCallCount; } } } Console.WriteLine($[{project.Name}] 类数量{classCount}方法调用点{methodCallCount}); }这段代码有几个关键点需要说明MSBuildLocator.RegisterDefaults()必须放在MSBuildWorkspace.Create()之前否则可能抛出“无法定位 MSBuild”的异常。OpenSolutionAsync会触发项目加载加载失败不一定会抛异常而是通过WorkspaceFailed事件通知所以事件处理器里打印诊断信息很有用。GetCompilationAsync()提供语义分析入口只有拿到 Compilation 才能拿到SemanticModel。GetSymbolInfo(invocation).Symbol能拿到被调用方法的符号这是提取调用关系的核心 API。运行命令dotnet run -- ./YourSolution.sln预期输出类似解决方案C:\work\MyApp\MyApp.sln 项目数量8 [Order.Api] 类数量32方法调用点214 [Order.Domain] 类数量18方法调用点96 [Order.Infrastructure] 类数量27方法调用点1884.3 从语法树到调用图上面的 Demo 只统计了数量真正的代码图谱还需要记录调用方和被调用方。做法是在InvocationExpressionSyntax中找到调用方法所在的类和方法再结合被调用符号IMethodSymbol的全限定名形成一条边。伪代码如下var callerClass invocation.Ancestors() .OfTypeClassDeclarationSyntax() .FirstOrDefault()?.Identifier.Text; var callerMethod invocation.Ancestors() .OfTypeMethodDeclarationSyntax() .FirstOrDefault()?.Identifier.Text; var calleeSymbol semanticModel.GetSymbolInfo(invocation).Symbol as IMethodSymbol; var calleeName calleeSymbol?.ToDisplayString();ToDisplayString()会把符号转换成带命名空间的方法签名例如Order.Domain.OrderService.GetOrder(int)这种字符串天然适合作为图中的节点 ID。4.4 图谱数据如何暴露给 MCPSlnmap 做的事情本质上就是上面这些步骤的工业化版本它处理更大的解决方案、并发编译多个项目、缓存语义模型并把结果组织成 MCP Tools。由于我无法确定 Slnmap 具体暴露了哪些工具名这里给出一个常见形态示意实际使用时以项目 README 或--help输出为准{ name: get_symbol_references, arguments: { symbol: Order.Domain.OrderService } }AI 客户端只需要传入符号名Slnmap 返回符号的定义位置、引用列表、调用者列表等结构化 JSON。查询结果不用太大返回“小而准”的切片比一次性导出整个图谱更适合 AI 上下文窗口。5. 实战把 Slnmap 接入你的 AI 工具5.1 启动服务并手动验证在接入 MCP 客户端之前先在终端手动执行一次服务启动命令确认没有运行时错误。注意 Slnmap 是 MCP Server启动后通常会阻塞等待输入这是正常现象不要误以为卡死。slnmap serve --solution ./MyApp.sln如果服务能静默运行不报错说明它已经在通过 stdio 等待 MCP 消息了。立刻用CtrlC退出进入客户端配置环节。5.2 在 Claude Desktop 中接入Claude Desktop 的配置文件路径Windows%APPDATA%\Claude\claude_desktop_config.jsonmacOS~/Library/Application Support/Claude/claude_desktop_config.json在配置文件的mcpServers字段中新增一项{ mcpServers: { slnmap: { command: slnmap, args: [ serve, --solution, C:/work/MyApp/MyApp.sln ] } } }这里把--solution参数直接写在args里路径建议统一用正斜杠避免 Windows 反斜杠在 JSON 中需要双重转义。如果你在终端测试时用的是dotnet run启动可以把command改成dotnetargs改成{ command: dotnet, args: [run, --project, D:/dev/slnmap/src/Slnmap, --, serve, --solution, C:/work/MyApp/MyApp.sln] }修改配置文件后必须完全退出并重启 Claude DesktopMCP Server 才会重新加载。5.3 在 Cursor 中接入Cursor 支持项目级 MCP 配置在项目根目录创建.cursor/mcp.json{ mcpServers: { slnmap: { command: slnmap, args: [serve, --solution, /work/MyApp/MyApp.sln] } } }保存后在 Cursor 的 Chat 面板中找到 MCP 工具列表确认slnmap已经出现。如果列表为空检查.cursor路径是否正确以及 Cursor 版本是否支持 MCP。5.4 实际提问示例接入成功后可以在聊天窗口里尝试这些提问“列出这个方案里所有项目及其之间的依赖关系。”“查找OrderService.GetOrder的所有调用者并告诉我它们分别在哪个项目。”“IOrderRepository被哪些类实现这些实现类都依赖哪些外部服务”“如果我把Customer.Id从int改成long哪些项目需要重新编译”第一类问题可以验证 MCP 连接是否正常第二、三类可以验证代码图谱的引用分析是否准确第四类则是代码图谱在“影响面分析”上的典型应用。如果模型没有自动调用工具可以通过“请使用代码图谱工具查询……”这类提示