mcpField
.
.NET Debugger for MCP

.NET Debugger for MCP

本地服务
github.com开发工具93 天前更新

关于 .NET Debugger for MCP

.NET Debugger for MCP 是面向 AI 助手的 .NET 调试 MCP 服务,可启动或附加进程,并检查执行状态。它通过 Debug Adapter Protocol 驱动 clrdbg,还提供可搜索的调试器原理参考。

主要功能

  1. 01启动或附加 .NET 进程
  2. 02按文件行或方法设置断点
  3. 03单步执行并查看调用栈
  4. 04检查局部变量并计算 C# 表达式
  5. 05按异常类型设置暂停规则
  6. 06搜索 .NET 调试文档

适用场景

  1. 01定位程序启动早期抛出的异常
  2. 02检查特定线程的调用栈和变量
  3. 03调试 .NET MAUI 移动应用

MCP 相关

它作为 MCP 服务向兼容客户端提供 .NET 调试工具,包括进程管理、断点、执行控制和变量检查。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

暂未收录安装方式

我们还没有获取到这个服务的安装包或远程地址,请按项目 README 中的说明安装。

代码仓库
安装包
nugetDotnetDebugger.Mcp0.7.1

工具

还没有获取到工具列表

本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。

详细介绍

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 和 InnerException getter 来报告这些信息。
  • 读取尚未初始化类型的静态字段时,会运行该类型的静态 构造函数;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_LEVELTrace、Debug、Information、Warning、Error 或 CriticalInformation
DOTNET_DEBUGGER_MCP_MAX_SESSIONS可同时打开的会话数,每个会话调试一个独立进程1
DOTNET_DEBUGGER_MCP_OPERATION_TIMEOUT_SECONDS限制除求值外的每个调试器请求的时长:附加、启动、暂停、单步执行、读取状态和关闭会话。调试器一次只响应一个请求,因此,如果某个请求仍在处理中,后续所有请求都会被阻塞,直到此时限用尽30
DOTNET_DEBUGGER_MCP_EVAL_TIMEOUT_MS限制 evaluate_expression 和 get_exception_info 的时长。最小值为 1005000
DOTNET_DEBUGGER_MCP_BREAKPOINT_BIND_TIMEOUT_MS等待断点绑定后再将其报告为未经验证的最长时间,最小值为 1002000
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 可信发布策略。

相关项目

许可证

MIT

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。