关于 .NET Debugger for MCP
.NET Debugger for MCP 是面向 AI 助手的 .NET 调试 MCP 服务,可启动或附加进程,并检查执行状态。它通过 Debug Adapter Protocol 驱动 clrdbg,还提供可搜索的调试器原理参考。
主要功能
- 01启动或附加 .NET 进程
- 02按文件行或方法设置断点
- 03单步执行并查看调用栈
- 04检查局部变量并计算 C# 表达式
- 05按异常类型设置暂停规则
- 06搜索 .NET 调试文档
适用场景
- 01定位程序启动早期抛出的异常
- 02检查特定线程的调用栈和变量
- 03调试 .NET MAUI 移动应用
MCP 相关
它作为 MCP 服务向兼容客户端提供 .NET 调试工具,包括进程管理、断点、执行控制和变量检查。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
我们还没有获取到这个服务的安装包或远程地址,请按项目 README 中的说明安装。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
DotnetDebugger.Mcp
一个用于帮助 AI 助手调试 .NET 程序的 MCP 服务:启动进程或附加到进程、设置断点、逐步执行代码、检查变量和计算表达式。它还附带可搜索的参考资料,介绍 .NET 调试器的内部工作原理。
已发布到 NuGet,包名为 DotnetDebugger.Mcp。该服务基于 clrdbg 构建,通过 Debug Adapter Protocol 驱动。调试器随包一起分发,并作为子进程运行,因此原生调试代码崩溃时,受影响的是调试会话,而不是服务。
起源。 这是 decriptor/SharpDbg.MCP 的一个分支,如今与原项目的差异已足够大,因此拥有了自己的名称:调试器层通过 Debug Adapter Protocol 通信,而不是调用调试器的内部 API;底层调试器也已从 SharpDbg 更换为 clrdbg;新增了在调试器下启动程序的功能;并且两者自此再未共享过任何提交。此仓库在首次发布前名为
SharpDbg.MCP,GitHub 会将旧名称重定向到此仓库。SHARPDBG_*设置在 0.6.3 之后的版本中更名为DOTNET_DEBUGGER_MCP_*;旧名称不再读取。
功能
- 附加到正在运行的 .NET 进程,或启动进程并在执行第一行代码前暂停
- 按文件和行号或方法名设置断点,并指定条件和命中次数
- 单步跳过、单步进入和单步跳出代码,并读取任意线程的调用堆栈
- 检查局部变量、逐成员展开对象,并计算 C# 表达式
- 在发生异常时中断——所有异常、仅未处理异常,或仅指定类型的异常——并读取抛出的内容
- 读取被调试程序的 stdout 和 stderr
- 同时调试多个进程,每个进程都有各自的断点和暂停状态
- 调试 .NET MAUI 应用:可在 Android 模拟器或手机、iOS 模拟器或设备上运行,也可在此 Mac 上作为 Mac Catalyst 应用运行;构建后在执行第一行代码前暂停,与桌面程序相同
- 搜索有关 ICorDebug、Debug Adapter Protocol 和表达式求值的内嵌文档
不支持的功能
其中一些功能需要修改底层调试器,而非此项目。
- 报告附加到的进程的退出代码。 此时
exit_code为null。调试器可以读取自行启动的进程的退出代码,但无法读取指定给它的进程的退出代码,因此对于附加到的进程没有可报告的内容——而协议无法表示“未知”,只能表示0,这会是此服务编造的数字。通过launch_program启动的程序会报告实际退出代码。 - 调试自包含的单文件发布版本。 运行时被打包在可执行文件中,因此调试器 shim 无法找到它并加载匹配的组件。此尝试会立即失败,错误为
CORDBG_E_DEBUG_COMPONENT_MISSING(0x80131C3C),报告为Attempting to register for runtime startup failed: -2146231236。单独使用自包含发布可以正常工作,单独使用单文件发布也可以正常工作;两者结合则不行。 - 监视表达式、热重载和数据断点尚未实现。
要求
- .NET 10 SDK 或更高版本
- 兼容 MCP 的客户端,例如 Claude Code 或 Claude Desktop
- Windows、macOS 或 Linux
安装
一个软件包包含适用于所有平台的调试器及其原生垫片,因此相同配置在任何平台上都能使用。无需克隆或构建。
Claude Code,用于当前项目:
claude mcp add dotnet-debugger -- dotnet tool exec DotnetDebugger.Mcp --version 0.8.0 --yes
添加 --scope user 可让它在每个项目中使用,或添加 --scope project 来写入 .mcp.json,并将其提交并与团队共享。
VS Code,在工作区的 .vscode/mcp.json 中配置,或使用 MCP: Open User Configuration 使其适用于每个工作区。VS Code 将该部分称为 servers,而不是 mcpServers:
{
"servers": {
"dotnet-debugger": {
"type": "stdio",
"command": "dotnet",
"args": ["tool", "exec", "DotnetDebugger.Mcp", "--version", "0.8.0", "--yes"]
}
}
}
Claude Desktop,在 macOS 和 Linux 上编辑 ~/.config/Claude/claude_desktop_config.json,或在 Windows 上编辑
%APPDATA%\Claude\claude_desktop_config.json:
{
"mcpServers": {
"dotnet-debugger": {
"command": "dotnet",
"args": ["tool", "exec", "DotnetDebugger.Mcp", "--version", "0.8.0", "--yes"]
}
}
}
dnx DotnetDebugger.Mcp --version 0.8.0 --yes 是更简短的等效命令,也是 NuGet.org 推荐的形式。对于从桌面环境启动的客户端,优先使用 dotnet tool exec,而不是从 shell 启动的形式:dnx 位于 SDK 目录中,此类客户端通常不会将该目录加入 PATH,而 dotnet 则通常可以找到。
客户端只会在会话启动时连接其 MCP 服务,因此更改配置后请重启客户端。要确认服务已连接,请运行 claude mcp list,或让客户端列出 .NET 进程。
为什么要固定版本
这里的每条命令都指定了版本。如果不使用 --version,dotnet tool exec 会在每次启动时解析软件包,因此新发布的版本会在下一个会话中运行,而无需任何人主动选择。调试器可以读取和更改它所附加的进程中的所有内容,因此应由人来决定运行哪个构建版本,而不是让它意外发生。要升级,请更改命令中的版本;最新版本见 nuget.org。
改为一次性安装工具,也可以避免每次启动时都进行解析:
dotnet tool install -g DotnetDebugger.Mcp --version 0.8.0
之后,命令就是 dotnet-debugger-mcp,无需任何参数。这要求将 ~/.dotnet/tools 加入 PATH,因此默认不推荐这种方式:客户端找不到该命令时,会像路径错误一样失败。
检查软件包的来源
0.6.1 之后的版本都附带构建来源证明:一份通过 GitHub 签名的声明,证明此仓库的发布工作流从指定提交构建了该软件包。在镜像或安装之前,可以从 nuget.org 下载 .nupkg 并运行:
gh attestation verify DotnetDebugger.Mcp.<version>.nupkg --repo nevse/dotnet-debugger-mcp
nuget.org 会为其接受的每个软件包添加自己的仓库签名,这会更改文件,因此发布工作流会为两份文件提供证明:构建完成后附加到 GitHub Release 的软件包,以及 nuget.org 上的副本(前提是已检查该副本与构建产物完全一致)。0.6.2 只有第一份文件的证明:请使用 gh release download v0.6.2 -R nevse/dotnet-debugger-mcp -p '*.nupkg' 验证该副本。
升级
即使发布并索引了更新版本,dotnet tool exec 仍可能继续运行已下载的版本。出现这种情况时一切看起来都正常:服务启动,客户端报告已连接,唯一的迹象是新版本中的工具并未出现。
以下数据是在发布 0.1.1 时测得的。本地软件包目录中只有 0.1.0 时,未固定版本的命令仍运行 0.1.0,清除 NuGet HTTP 缓存也没有改变这一情况。指定版本后,命令获取了新软件包;此后,未固定版本的命令也开始使用该版本:
dotnet tool exec DotnetDebugger.Mcp --version 0.1.1 --yes
固定版本后,升级只需更改命令中的版本,不会出现上述问题。已安装的工具也需要显式升级:
dotnet tool update -g DotnetDebugger.Mcp --version <new version>
要查看实际运行的版本,请让客户端列出此服务的工具,或读取服务写入 stderr 的第一行——其中会显示启动时的版本。客户端自身的健康检查只会报告进程已启动,不会报告其实际运行的版本。
示例详解
在程序尚未运行任何一行代码之前捕获它,这是附加进程无法实现的情况:
User: "My app throws before it prints anything. Find out why."
Claude: [launch_program("/path/to/bin/Debug/net10.0/MyApp.dll")]
"Prepared, not running yet. Setting a breakpoint on the first line of Main."
Claude: [set_breakpoint("/path/to/Program.cs", 12)]
"Breakpoint set. It is unverified for now — nothing can bind before the program has
loaded its modules — and takes effect when the program starts."
Claude: [start_program()]
Claude: [wait_for_stop()]
"Stopped at Program.cs:12, before a single line has run."
Claude: [step_over(thread_id: 1)]
Claude: [get_variables(frame_id: 0)]
"configPath is null, and the next line passes it to File.ReadAllText."
Claude: [get_program_output()]
"The program printed nothing before the throw, which matches what you saw."
工具
每个工具都会声明 MCP 注解,以便能够读取这些注解的客户端无需询问即可批准只读工具,同时仍可对其余工具进行询问。最后一列说明了各工具的注解:
- 只读 - 读取会话或机器信息,不做任何更改。
- 会话 - 仅更改调试会话:单步执行、恢复执行、移除断点。不会造成破坏。
- 运行 getter - 读取被调试程序自身代码中的内容:调试器会调用
ToString、属性 getter 和DebuggerDisplay来显示值。此类工具不会标记为只读,但也不会造成破坏。 - 结束已启动的程序 - 会造成破坏:终止由会话启动的程序。
- 运行代码 - 会造成破坏且影响范围不可预知:启动、构建或计算由 Agent 选择的代码。断点条件是每次命中时都会计算的 C# 表达式,因此设置断点条件也属于此类。
注解仅供参考,客户端可以选择忽略它们。
调试
| 工具 | 功能 | 注解 |
|---|---|---|
list_dotnet_processes | 列出此机器上运行的 .NET 进程 | 只读 |
attach_to_process | 将调试器附加到正在运行的 .NET 进程 | 运行代码 |
launch_program | 准备在调试器下运行的程序,并在启动前暂停 | 运行代码 |
start_program | 运行由 launch_program 准备的程序 | 运行代码 |
get_process_status | 报告会话是否正在运行、已暂停,以及所在位置 | 只读 |
wait_for_stop | 阻塞,直到被调试程序停止,而不是轮询 | 只读 |
get_program_output | 读取被调试程序写入 stdout 和 stderr 的内容 | 只读 |
detach_from_process | 分离调试器,同时让进程继续运行 | 结束已启动的程序 |
list_sessions | 列出打开的会话及其各自正在调试的对象 | 只读 |
close_session | 关闭会话,必要时先分离调试器 | 结束已启动的程序 |
.NET MAUI
需要一次性设置——请参阅调试 .NET MAUI 应用。
| 工具 | 功能 | 注解 |
|---|---|---|
list_mobile_devices | 列出 Android 模拟器和手机、iOS 模拟器和设备,以及此 Mac | 只读 |
build_mobile_app | 为其中一台设备构建 MAUI 项目,使用 CoreCLR,并包含调试库 | 运行代码 |
launch_mobile_app | 准备已构建的应用以供调试,并在启动前暂停;使用 start_program 运行应用 | 运行代码 |
断点
| 工具 | 功能 | 注解 |
|---|---|---|
set_breakpoint | 在文件的指定行设置或更新断点,可选设置条件或命中次数 | 运行代码 |
set_function_breakpoint | 在不知道文件和行号时,按方法名称设置断点 | 运行代码 |
remove_breakpoint | 移除任意类型的断点 | 会话 |
list_breakpoints | 列出此会话的断点,以及每个断点是否已验证 | 只读 |
set_exception_break_mode | 选择哪些异常会使程序暂停:所有异常、未处理的异常或指定类型的异常 | 会话 |
执行与检查
| 工具 | 功能 | 注解 |
|---|---|---|
continue_execution | 恢复执行,直到遇到下一个断点或程序退出 | 会话 |
pause_execution | 在程序当前所在位置进入调试器 | 会话 |
step_over、step_into、step_out | 按行单步执行、进入调用或跳出当前方法 | 会话 |
get_threads | 列出被调试程序中的线程 | 只读 |
get_stack_trace | 读取线程的调用堆栈 | 只读 |
get_variables | 读取堆栈帧中的局部变量 | 运行 getter |
expand_variable | 展开对象以查看其成员,这些成员也可能进一步展开 | 运行 getter |
evaluate_expression | 在某个帧的上下文中计算 C# 表达式 | 运行代码 |
get_exception_info | 读取所抛出异常的类型、消息、堆栈跟踪和内部异常 | 运行 getter |
文档
| 工具 | 功能 | 注释 |
|---|---|---|
search_debugging_concepts | 搜索内嵌文档 | 只读 |
explain_icordebug_interface | 说明特定的 ICorDebug 接口 | 只读 |
get_debugging_flow | 逐步讲解调试操作 | 只读 |
list_debugging_concepts | 按类别浏览概念目录 | 只读 |
值得了解的事项
断点需要可移植 PDB
断点通过目标程序的符号绑定,因此调试目标必须使用可移植 PDB 构建,并将 PDB 文件放在程序集旁边。PDB 文件缺失或不匹配,是 set_breakpoint 返回 verified: false 和 No symbols have been loaded for this document 最常见的原因。
调试构建已默认启用此设置。对于 Release 构建,或任何更改默认设置的项目:
<PropertyGroup>
<DebugType>portable</DebugType>
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
优化后的代码也会让局部变量无法访问,因此只有设置为 <Optimize>false</Optimize> 时,get_variables 才能充分发挥作用。
macOS:无头环境可能需要授权
macOS 不允许调试器获取另一个进程的任务端口,除非目标程序具有 com.apple.security.get-task-allow 权限。通过 dotnet 多路复用器运行的程序不受影响,因为该多路复用器自带此权限;而 dotnet publish 生成的 apphost 则使用临时签名,不带任何权限。
调试器本身无需你进行任何设置:调试适配器也通过 dotnet 多路复用器启动,因此它继承了进行调试所需的权限。
在桌面会话中,这不会影响你。 已验证在桌面环境中,调试不带任何权限的自包含发布版本可以正常工作。
但在 CI 运行器等无头环境中,这一点很重要,而且出错时后果严重:macOS 会阻止操作,而不是返回错误,因此调试器会停止响应,调用也永远不会返回。如果在 macOS 上启动或附加调试时卡住,且没有任何错误,请为目标程序签名,然后重试:
codesign -s - -f --entitlements get-task-allow.entitlements ./MyApp
其中 get-task-allow.entitlements 包含:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.get-task-allow</key>
<true/>
</dict>
</plist>
“仅我的代码”对单步执行的影响
DOTNET_DEBUGGER_MCP_JUST_MY_CODE=false 并不会让单步执行停在没有符号的代码中。无论该设置如何,情况都一样:如果单步执行进入没有符号的模块,就会直接步出,因为没有源代码可用于报告位置。单步执行经过插值字符串时,会经过 System.Private.CoreLib,然后无论设置如何,都会返回到你自己的方法中的下一条语句。
此设置改变的是搜索哪些模块的符号。启用“仅我的代码”时,只搜索你构建的程序集;关闭时,则会搜索所有模块,因此如果你碰巧有某个依赖项的符号,单步执行就会进入该依赖项。这就是关闭此设置的用途。
launch_program 将运行什么
调试器会启动传入的任何内容,因此 launch_program 只接受 .NET 应用:其旁边带有 .runtimeconfig.json 的 .dll,或者同名 .dll 和 .runtimeconfig.json 位于其旁边的 apphost。这涵盖普通构建和独立部署。Shell、系统工具或任何其他原生可执行文件都会被拒绝,单文件部署也会被拒绝,因为它本来就无法调试。
会将其他代码加载到程序中的环境变量也会被拒绝,因为它们中的任何一个都会让启动合法应用变成运行其他内容:DOTNET_STARTUP_HOOKS、DOTNET_ADDITIONAL_DEPS、DOTNET_SHARED_STORE、DOTNET_ROOT*、CoreCLR 分析器变量(CORECLR_ENABLE_PROFILING、CORECLR_PROFILER*、COR_ENABLE_PROFILING、COR_PROFILER*)、ASP.NET Core 的托管启动程序集(ASPNETCORE_HOSTINGSTARTUPASSEMBLIES、DOTNET_HOSTINGSTARTUPASSEMBLIES)、LD_PRELOAD、LD_LIBRARY_PATH、LD_AUDIT 以及所有 DYLD_*。
此服务会读取 launchSettings.json 配置文件,而不会将其交给调试器。传入 project_path(项目文件或其目录),并可选择传入 launch_profile。如果未指定名称,则使用 https 配置文件;若没有,则使用第一个 commandName: "Project" 配置文件。其中的 commandLineArgs、workingDirectory 和 environmentVariables 会分别成为启动参数、工作目录和环境变量,applicationUrl 则会成为 ASPNETCORE_URLS。随后,这些值会按照与上述参数相同的方式进行检查。传给 launch_program 的参数和工作目录会替换配置文件中的对应值。传入的环境变量会在配置文件的基础上添加。只接受 Project 配置文件,因为 Executable 配置文件会指定自己的程序,而 executablePath 会被忽略,因此程序始终是传入的那个。值会按原样使用:既不展开 $(...),也不展开 %VAR%。配置文件随仓库一同提供,因此其可信度不高于 Agent:其中设置的启动挂钩会像其他情况一样被拒绝,错误信息也会指出配置文件名称。项目没有 launchSettings.json 时,除非指定了配置文件,否则会在不使用配置文件的情况下启动。
这并不是沙箱,也无法阻止 launch_program 被用作 Shell。.NET 应用可以执行其用户能够执行的任何操作;能够编写和构建代码的 Agent 可以启动自己构建的内容;机器上也已经有可直接使用、能够运行其他程序的 .NET 应用:SDK 自带的 sdk/<version>/dotnet.dll 旁边就有其 .runtimeconfig.json,因此启动它就能访问整个 dotnet CLI,构建并运行任何项目。evaluate_expression 可以在正在调试的对象中调用 Process.Start。这项检查阻止的是 Agent 直接指定原生程序。应将允许使用此服务工具的客户端视为允许使用 Shell,并确认 launch_program、evaluate_expression、build_mobile_app 和 attach_to_process 调用,而不是自动批准;set_breakpoint 和 set_function_breakpoint 也一样,因为它们的条件代码会在每次命中时运行。所有这些工具以及 start_program 和 launch_mobile_app 都标注为具有破坏性且面向开放世界(参见工具),因此可以将遵循这些标注的客户端设置为针对这些工具询问确认,并批准读取操作。
调试器显示内容中的秘密
从被调试程序读取的值会发送给模型,因此 get_variables、expand_variable、
evaluate_expression、get_exception_info 和 get_program_output 会先将看起来像秘密的内容
屏蔽为 [redacted: N chars]。具体取决于两条规则:
- 按名称判断。 变量名或成员名包含
password、passwd、pwd、secret、token、apiKey、connectionString、credential、privateKey、accessKey或authorization的字符串会被 整体屏蔽。仅限字符串:int tokenCount或CancellationToken token会照常显示。对象 不会被屏蔽,其成员在展开时会接受检查。对于evaluate_expression,名称取表达式读取的最后一个成员,因此config.Db.Password符合条件。 - 按格式判断。 在任何文本中,无论是否有名称:
Bearer和Basic凭据、JWT、Password=、Pwd=、ApiKey=、sig=等连接字符串和 URL 中的内容、带有常见前缀的密钥(sk-、ghp_、github_pat_、AKIA、xoxb-、AIza),以及 PEM 私钥。只会屏蔽秘密本身,因此连接字符串的其余部分仍可读。
null 和空字符串会照常显示,因为是否设置了秘密本身往往就是要确认的问题。每个响应都会在 redacted 中
说明屏蔽了多少个值。
服务自身的日志(MCP 客户端通常会将其保存在磁盘上)只记录程序输出每行的长度。文本本身会在
DOTNET_DEBUGGER_MCP_LOG_LEVEL=Debug 及更低级别时记录,并逐行屏蔽,因此,像 PEM 密钥这样跨多行
输出的秘密,在日志中可能只会被部分屏蔽。调试器在请求等待超时后报告的错误,以及调试器自身的诊断输出,也会被屏蔽。
这能减少泄露,但无法完全防止泄露。名为 s 的变量中的秘密、不符合已知格式的秘密,或分段读取的秘密——例如对
password.Length 或 password[0] 使用 evaluate_expression——都会通过。设置
DOTNET_DEBUGGER_MCP_REDACT_SECRETS=false 可取消屏蔽值;Agent 无法自行关闭此功能。
在程序中运行代码
evaluate_expression 会运行表达式调用的所有内容——属性 getter、ToString、任何方法,包括
Process.Start——并使用程序的权限。列出变量时也会运行属性 getter、ToString 重写以及
DebuggerTypeProxy 构造函数,以显示值。这是调试器展示程序所持有内容的方式;默认情况下,唯一的限制是
DOTNET_DEBUGGER_MCP_EVAL_TIMEOUT_MS。
设置 DOTNET_DEBUGGER_MCP_ALLOW_FUNC_EVAL=false 可阻止调试器运行程序代码,无论是为了
显示值,还是因为表达式要求运行代码。字段、局部变量和参数仍会照常读取。属性会显示为
[not evaluated: function evaluation is off],对象会显示为花括号内的类型名称,而调用任何内容——getter、方法、
构造函数——的表达式都会失败。Agent 无法重新启用此功能。以下两种情况仍会运行代码:
get_exception_info会调用异常的Message和InnerExceptiongetter 来报告这些信息。- 读取尚未初始化类型的静态字段时,会运行该类型的静态
构造函数;
evaluate_expression和展开静态成员时都一样。
断点条件也是表达式,因此,调用 getter 或方法的条件——例如 items.Count > 3
——在关闭此功能时无法求值。此类断点会在每次命中时停止,就像没有条件一样,
停止信息中也不会说明原因。可以改为比较字段,或者省略条件并在停止后检查。
这限制了调试时可以运行的代码,但并不意味着可以放心将服务交给不可信的 Agent。
launch_program 仍会启动任意 .NET 程序。
附加到其他用户的进程
调试器可以读取和更改所附加进程中的所有内容,因此此服务默认只附加到与其运行身份相同的用户所属的进程。attach_to_process 会在查看进程之前拒绝其他进程,而 list_dotnet_processes 会为每个条目标记 owner,其值为 current_user、other_user 或 unknown。
unknown 也会被拒绝。在 Windows 上,系统进程或提升权限的进程会显示为 unknown,因为无法打开其令牌;如果将其视为自己的进程,那么在无法进行查询时,这项检查就形同虚设。
DOTNET_DEBUGGER_MCP_ALLOW_OTHER_USER_PROCESSES=true 会解除此限制。操作系统仍会自行限制:在 Linux 和 macOS 上,即使启用了此设置,普通用户也无法附加到其他用户的进程,因此实际上只有在服务以提升权限运行或以 root 身份运行时,此设置才有用。
同时调试多个进程
默认情况下,服务一次只调试一个进程,DOTNET_DEBUGGER_MCP_MAX_SESSIONS 可以提高此限制。每个会话都有各自的进程、断点和暂停状态。
attach_to_process 和 launch_program 会返回一个 session_id。如果只打开了一个会话,可以忽略它,因为每个工具默认使用唯一的会话。接管第二个进程时,会打开第二个会话,而不是失败;从此之后,session_id 就成为必需项:如果同时打开了两个进程,猜测 continue_execution 应该作用于哪个进程,还不如直接询问。
detach_from_process 会让会话保持打开并释放出来,因此下一次附加或启动会复用此会话,而不会占用另一个名额。
默认值设为 1 是有意为之。每次附加都可能导致调试器 shim 内部发生原生崩溃,因此会话越多,风险越大。这种崩溃造成的影响是有限的:每个会话都在独立进程中运行自己的调试适配器,因此发生崩溃的会话会随之终止,而服务和其他会话仍会继续运行。
调试 .NET MAUI 应用
调试 MAUI 应用的方式与桌面程序相同:准备、设置断点、启动——但在此之前必须满足两个条件,而普通方式构建的应用都不满足这两个条件。
应用必须在 CoreCLR 上运行。 此调试器只能附加到 CoreCLR,而 MAUI 应用默认仍针对 Mono 构建。net10.0-android 和适用于 -ios 与 -maccatalyst 的 net11.0 才包含 CoreCLR;低于这些版本时,应用可以构建并启动,但永远无法连接回调试器,因此 build_mobile_app 会拒绝这些版本,并告知可用的版本。
应用必须包含远程调试库。 要调试手机上的运行时,应用内必须有一个原生库,用于打开调试器附加所需的连接;本机也需要配套的库。两者均为开源项目,属于 clrdbg,并随本软件包提供,版本与构建此调试器所用的 clrdbg 版本一致,因此无需安装或配置:
remote-coreclr/
remote-host/ osx-arm64/, osx-x64/, win-x64/, win-arm64/, linux-x64/, linux-arm64/
remote-target/ android/, ios/, maccatalyst/
如果要改用自行构建的副本(例如编译时启用了跟踪功能的副本),请将 DOTNET_DEBUGGER_MCP_REMOTE_CORECLR_HOST 指向 remote-host 目录,将 DOTNET_DEBUGGER_MCP_REMOTE_CORECLR_TARGET 指向 remote-target 目录,或同时设置两者。未设置的任一部分都会使用随软件包提供的副本。
无论来源如何,都不会使用 Visual Studio 自带的远程调试库(vsdbg*):如果某个目录中包含文件名含 vsdbg 的文件,无论该目录是随软件包提供还是自行配置,都会被拒绝。
随后会话如下所示:
list_mobile_devices() → ids to choose from
build_mobile_app(project_path, device_id) → the .apk or .app, built to be debuggable
launch_mobile_app(project_path, device_id) → prepared, not started
set_breakpoint(file, line) → in place before the app's first line
start_program() → boots, installs, launches: minutes, not seconds
wait_for_stop() → and from here it is an ordinary debug session
build_mobile_app 会在构建时添加自己的 MSBuild 文件——该文件随服务器一起提供,路径为 Resources/CopyRemoteCoreclrTargetLibrary.targets,且内容可读——以便将目标库放入应用捆绑包或 Android 软件包中。你的项目不会有任何更改。
各平台的行为说明:
- Android。 通过 AVD 名称选择模拟器;模拟器无需处于运行状态,因为调试器会自行启动冷启动的模拟器。通过 adb 序列号选择手机。应用会一次性为所有 ABI 构建,因此无需选择运行时标识符。应用的 logcat 输出会传入
get_program_output。uninstall_app: true会先清除先前的安装;签名或权限发生变化时,可以使用此选项。 - iOS。 通过 UDID 选择模拟器;如果模拟器尚未运行,则会启动它。实体设备必须像往常一样为应用配置预配;调试器会安装并启动应用,但不会为应用签名。
- Mac Catalyst。 设备 ID 为
maccatalyst;无需启动或安装,因此这是检查设置是否正常工作的最快方式。
process_id 始终为 null:应用运行在设备上,因此无法将此机器上的进程 ID 用作目标。其他功能——断点、单步执行、局部变量、表达式、异常——与本地调试完全相同。
如何报告失败
每个工具都会以相同方式报告失败:
{
"success": false,
"error": "Returned from a call to Continue that was not matched with a stopping event. (0x8013132F)",
"explanation": "The process was already running, so there was nothing to resume. Check get_process_status before continuing."
}
error 是调试器返回的内容,原样保留。对于调试器引发的 CORDBG_E_* 结果,explanation 会说明失败的含义以及应如何处理:进程已退出、操作要求被调试进程处于停止状态、变量在当前指令处不处于活动状态、帧 ID 来自较早的一次停止,或已有其他调试器附加。对于非调试器导致的失败(例如参数无效),该字段为 null。
配置
在客户端的配置中将以下内容设为环境变量:
{
"mcpServers": {
"dotnet-debugger": {
"command": "dotnet",
"args": ["tool", "exec", "DotnetDebugger.Mcp", "--version", "0.8.0", "--yes"],
"env": {
"DOTNET_DEBUGGER_MCP_LOG_LEVEL": "Debug"
}
}
}
}
| 变量 | 描述 | 默认值 |
|---|---|---|
DOTNET_DEBUGGER_MCP_LOG_LEVEL | Trace、Debug、Information、Warning、Error 或 Critical | Information |
DOTNET_DEBUGGER_MCP_MAX_SESSIONS | 可同时打开的会话数,每个会话调试一个独立进程 | 1 |
DOTNET_DEBUGGER_MCP_OPERATION_TIMEOUT_SECONDS | 限制除求值外的每个调试器请求的时长:附加、启动、暂停、单步执行、读取状态和关闭会话。调试器一次只响应一个请求,因此,如果某个请求仍在处理中,后续所有请求都会被阻塞,直到此时限用尽 | 30 |
DOTNET_DEBUGGER_MCP_EVAL_TIMEOUT_MS | 限制 evaluate_expression 和 get_exception_info 的时长。最小值为 100 | 5000 |
DOTNET_DEBUGGER_MCP_BREAKPOINT_BIND_TIMEOUT_MS | 等待断点绑定后再将其报告为未经验证的最长时间,最小值为 100 | 2000 |
DOTNET_DEBUGGER_MCP_JUST_MY_CODE | 将调试范围限制为你自己的代码。关闭此选项前请参阅上文 | true |
DOTNET_DEBUGGER_MCP_ALLOW_FUNC_EVAL | 允许调试器在程序中运行代码:属性 getter、ToString、evaluate_expression 中的方法调用。请参阅上文 | true |
DOTNET_DEBUGGER_MCP_REDACT_SECRETS | 在模型看到值之前,遮蔽看起来像机密的内容。请参阅上文 | true |
DOTNET_DEBUGGER_MCP_ALLOW_OTHER_USER_PROCESSES | 允许附加到非当前用户所有的进程 | false |
DOTNET_DEBUGGER_MCP_ENABLE_DIAGNOSTICS | 详细诊断日志记录 | false |
DOTNET_DEBUGGER_MCP_REMOTE_CORECLR_HOST | 用于调试 MAUI 应用的 remote-host 目录,以替代捆绑目录。请参阅上文 | bundled |
DOTNET_DEBUGGER_MCP_REMOTE_CORECLR_TARGET | 用于替代捆绑目录的 remote-target 目录 | bundled |
DOTNET_DEBUGGER_MCP_MOBILE_START_TIMEOUT_SECONDS | 限制移动应用的 start_program 时长,包括启动模拟器、安装和启动应用 | 600 |
DOTNET_DEBUGGER_MCP_BUILD_TIMEOUT_SECONDS | 限制 build_mobile_app 的时长 | 900 |
故障排除
客户端中没有显示此服务。 请检查配置中的路径是否为绝对路径且正确,然后彻底退出并重启客户端。Claude Desktop 在 macOS 上将日志写入 ~/Library/Logs/Claude/,在 Windows 上将日志写入 %APPDATA%\Claude\logs\。
附加失败。 常见原因包括:进程由其他用户拥有(见上文)、进程已退出、不是 .NET 进程,或已有调试器附加。list_dotnet_processes 会显示服务能够看到哪些进程,以及这些进程由谁拥有。
未命中断点。 请检查它是否返回了 verified: true。如果没有,首先要检查的是 PDB。如果已验证但仍未命中,请确认代码确实执行到了该行,并且文件路径与 PDB 中记录的路径一致。
在 macOS 或 Linux 上,list_dotnet_processes 返回空结果。 这些系统上的模块枚举需要权限,服务会回退到按进程名称检测,因此无法识别 apphost 名称已被更改的程序。
在 macOS 上,启动或附加操作卡住且没有错误信息。 请参阅上文的权限部分。
MAUI 应用启动后一直无法在断点处暂停。 几乎可以肯定它运行在 Mono 上,而不是 CoreCLR 上——如果应用不是通过 build_mobile_app 构建的,就会出现这种情况,因为普通的 dotnet build 不会生成调试器所需的运行时或库。请使用 build_mobile_app 重新构建,并启动该命令报告的应用。
list_mobile_devices 未显示 Android 设备。 它从 AVD 目录读取模拟器,并通过 adb 读取已连接的手机,因此需要 Android SDK:如果 SDK 位于非默认位置,请设置 ANDROID_HOME 或 ANDROID_SDK_ROOT。响应中的 warnings 会说明无法读取哪个来源以及原因。
如需查看服务正在执行的操作,请设置 DOTNET_DEBUGGER_MCP_LOG_LEVEL=Trace 和 DOTNET_DEBUGGER_MCP_ENABLE_DIAGNOSTICS=true,或者直接运行服务并查看 stderr:
dotnet tool exec DotnetDebugger.Mcp --version 0.8.0 --yes
开发
git clone https://github.com/nevse/dotnet-debugger-mcp.git
cd dotnet-debugger-mcp
dotnet build
dotnet test
构建过程会根据 clrdbg 发布版中的 ClrdbgVersion 名称获取调试适配器、检查其哈希值,并将其放在服务旁边,因此无需单独执行其他步骤。若要改为从 clrdbg 检出目录构建适配器——例如使用包含修复的分支——请将 ClrdbgSourcePath 指向该目录:
ClrdbgSourcePath=~/work/clrdbg dotnet build
集成测试使用真实断点驱动真正的被调试程序,因此比其他测试耗时更长,并通过 TestCategory=Integration 单独分类。
若要让客户端使用你的工作副本,而不是已发布的包:
claude mcp add dotnet-debugger -- dotnet run --project "$(pwd)/src/DotnetDebugger.Mcp/DotnetDebugger.Mcp.csproj"
CONTRIBUTING.md 介绍了目录结构和约定。 docs/RELEASING.md 介绍了如何发布版本,包括工作流所依赖但未展示的 nuget.org 可信发布策略。
相关项目
- clrdbg — 此服务所驱动的 .NET 调试器
- SharpDbg — 迁移之前使用的调试器
- ClrDebug — ICorDebug API 包装器
- Model Context Protocol — 规范和 SDK
许可证
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。