Repository navigation
Add a headless MCP host (stdio, no dnSpy window) - #26
Merged
Merged
Conversation
Groundwork for a headless host that serves the same tools without the dnSpy window. Behaviour inside dnSpy is unchanged. - McpTools depended directly on IDocumentTreeView, IDocumentTabService and IDecompilerService. It now takes an IMcpHost with exactly what the tools use: GetDocuments / OpenDocument, the decompiler, running a mutating tool on the UI thread, and the rename hooks (ApplyRename re-sorts and redraws the tree node and rebuilds open tabs; RefreshTreeNode / RefreshDecompiledViews for the rest). DnSpyMcpHost implements it with the same dnSpy calls the tools made before, so the tree/tab code now lives in one file. - JSON-RPC dispatch (initialize / tools / resources, error codes) moves from McpServer into McpDispatcher, which has no transport code; the HTTP server delegates to it and a stdio host can too. - McpSettings.Logged exposes each log line, so a host whose stdout is the protocol channel can mirror the log to stderr. - Removes RenameClassOrEnumSymbolCore and CreateRenameTypeResult: dead since rename_symbol_by_token routed types through RenameAnyTypeSymbol. - InternalsVisibleTo for the upcoming dnSpy.Extension.MCP.Headless, and headless/ is excluded from the extension's compile glob. E2E: net10 240 pass / 0 fail. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
dnSpy.Extension.MCP.Headless.exe serves the same 32 tools and 6 resources over the MCP stdio transport with no dnSpy window, so an MCP client (Claude Desktop, Claude Code, Chatbox, Cursor, codex, ...) can start it on demand instead of the user keeping dnSpy open with the server enabled. Follow-up to the headless request on #24. - headless/: Program finds the dnSpy installation it is deployed in, installs an assembly resolver (dnSpy's bin, bin\Extensions\...), then wires HeadlessMcpHost -> McpTools -> McpDispatcher -> StdioTransport. HeadlessMcpHost implements IMcpHost the way dnSpy's document service works with memory-mapped I/O off: files are read into memory (targets stay unlocked), wrapped as DsDotNetDocument, cached in the resolver, PDBs loaded; the C# decompiler comes from dnSpy.Decompiler.ILSpy.Core exactly as dnSpy.Console.exe gets it. stdout is kept for the protocol and Console.Out goes to stderr with the log. Positional arguments preload files/folders through open_files; --dnspy, --quiet, --version. - deploy-headless.ps1 lays it out like dnSpy.Console.exe in each bundle: the exe next to dnSpy.Console.exe, the net10 DLL next to dnSpy.Console.dll (AppHostPatcher -d bin for build.ps1's layout), and the runtime configuration copied from the bundle (net48 dnSpy.exe.config, net10 dnSpy.Console.runtimeconfig.json), so it is framework-dependent or self-contained exactly as the bundle is. The apphost must match the bundle's architecture (an x64 apphost can't load the win-x86 bundle's hostfxr): release.yml builds one per RID and the script refuses a mismatch. - release.yml deploys it into all three zips; build.yml builds it. - run-tests.ps1 -Headless runs the whole suite over stdio against the deployed host (skipping only the HTTP step [23]), plus a clean-exit check when stdin closes. - README (EN + zh-CN): a Headless mode section with client configs. It replaces the Claude Desktop example, which used a nonexistent "command": "http". CLAUDE.md documents the host and its rules. Verified: GUI E2E net10 240/240 and net48 240/240; headless E2E net10 239/239 and net48 239/239; the official @modelcontextprotocol/sdk stdio client against the net48, net10 and self-contained win-x64 / win-x86 bundles, built locally with dnSpy's build.ps1 (COREHOST_TRACE confirms each self-contained bundle runs on its own bundled hostfxr). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
CLAUDE.md: keep this branch's architecture bullets and slot #25's session adoption note under McpServer.cs; the test-fixture line names [36] too. run-tests.ps1: [36] tests Streamable HTTP sessions, so it is skipped under -Headless like [23]. E2E on this tree: GUI net10 245/245, net48 245/245; headless net10 239/239, net48 239/239. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The README grew around the in-dnSpy server, with headless bolted on as one section and client setup spread over four places. Rewrite both languages (489 -> 325 lines each) around the two ways to run it: - A headless vs. inside-dnSpy comparison up front, and a quick start that offers both. - One "Connecting a client" section: Claude Code (user / project scope, .mcp.json and its one-time approval, how to check), Claude Desktop step by step (Edit Config, full restart, where the host's log lands), and other clients, including codex's `mcp add`. Checked against Claude Code 2.1.280 (`claude mcp add` + `claude mcp list` report the net10 and net48 hosts as Connected) and codex-cli 0.142.5 (both config forms parse). - One line per tool instead of a paragraph; the curl walkthroughs, architecture notes and technical details (all in CLAUDE.md) cut down to a transport table and a pointer. - Development says changes must pass the end-to-end suite in both modes, and CLAUDE.md records that as a convention. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
新增 headless 宿主
dnSpy.Extension.MCP.Headless.exe:不开 dnSpy 窗口,通过 MCP stdio 传输提供同样的 32 个工具和 6 份资源。MCP 客户端(Claude Desktop、Claude Code、Chatbox、Cursor、codex 等)会按需启动它,用户不必一直开着 dnSpy、也不用手动启用服务器。对应 #24 评论里提的 headless 需求。{ "mcpServers": { "dnspy": { "command": "C:\\Tools\\dnSpy\\dnSpy.Extension.MCP.Headless.exe", "args": [] } } }Changes
主要提交:
McpTools不再直接依赖IDocumentTreeView/IDocumentTabService/IDecompilerService,改为通过IMcpHost(列出/打开文档、反编译器、在 UI 线程执行、重命名后的树/标签页刷新)。dnSpy 内由DnSpyMcpHost实现,行为不变。JSON-RPC 分派从McpServer抽到与传输无关的McpDispatcher。顺带删除了已经没有调用方的RenameClassOrEnumSymbolCore。headless/):dnSpy.Console.exe一样通过反射加载 dnSpy 的 C# 反编译器;目标文件读入内存(运行期间不锁定 DLL),并包装成与 dnSpy 相同的DsDotNetDocument类型,所以所有工具不用改动。open_files加载目标;可选开关--dnspy、--quiet、--version(传入路径也可预加载,但不是必需的)。deploy-headless.ps1按dnSpy.Console.exe的方式部署到每个发布包:复用该包自己的运行时配置(net48 用dnSpy.exe.config,net10 用dnSpy.Console.runtimeconfig.json),net10 用 AppHostPatcher 修补 apphost,因此它和所在的包一样是框架依赖或自包含。run-tests.ps1 -Headless:通过 stdio 对部署好的 headless 宿主跑完整套测试(只跳过 HTTP 专属的 [23] 和 [36]),并检查 stdin 关闭后以退出码 0 退出。--scope user、.mcp.json及其一次性批准、如何验证)、Claude Desktop(Edit Config → 配置 → 彻底重启,日志位置)、其他客户端和 codex;工具说明改成每个一行,curl 演示和架构细节精简为传输表格并指向 CLAUDE.md。顺带去掉了原来 Claude Desktop 示例里并不存在的"command": "http"。CLAUDE.md 另外记下了“每次改动都要在两种模式下跑 E2E”。插件单独安装(只放 DLL)的用户暂时用不了 headless:它需要按上面的方式放在
dnSpy.Console.exe旁边并匹配运行时,一体包已经配好。Testing
以下 E2E 数字是在合入 #25 之后的代码上跑的([36] 只测 HTTP 会话,headless 下跳过)。
@modelcontextprotocol/sdkstdio 客户端claude mcp add+claude mcp list✔ Connected两个自包含包是用 dnSpy 的
build.ps1在本地构建的;COREHOST_TRACE确认 headless exe 用的是包内自带的 hostfxr(self-contained 模式),不是本机安装的 .NET。部署脚本拒绝架构不匹配的情况也验证过。与 #25 的关系
#25 已先合并。本分支已合入 main:
CLAUDE.md的冲突已解决,测试步骤 [36] 测的是 Streamable HTTP 会话,在-Headless下跳过(与 [23] 相同)。🤖 Generated with Claude Code