Repository navigation
v0.2.0
Headless mode: the tools without the dnSpy window
The all-in-one zips now include dnSpy.Extension.MCP.Headless.exe, next to dnSpy.exe. It serves the same 32 tools and 6 resources over the MCP stdio transport, so your MCP client starts it when it's needed and stops it afterwards: no dnSpy window to keep open, no server to enable, no port. It takes no arguments — it starts empty, and the AI opens what it needs with open_files.
Claude Code:
claude mcp add --scope user dnspy -- "C:\Tools\dnSpy\dnSpy.Extension.MCP.Headless.exe"Claude Desktop (Settings → Developer → Edit Config), Cursor, Chatbox and other clients that launch stdio servers:
{
"mcpServers": {
"dnspy": {
"command": "C:\\Tools\\dnSpy\\dnSpy.Extension.MCP.Headless.exe",
"args": []
}
}
}It loads assemblies and decompiles with dnSpy's own code, the way dnSpy.Console.exe does, so every tool works as it does inside dnSpy — IL patches, renames and save_assembly included. The differences: each client gets its own process with its own loaded assemblies; target files are read into memory, so they aren't locked while it runs; the decompiler uses dnSpy's default settings; the log goes to stderr. In each zip it runs on that bundle's runtime — self-contained in the win-x64 / win-x86 zips — so there is nothing extra to install. The plugin-only DLLs don't include it.
The server inside dnSpy is unchanged, and still the one to use when you want to look at the same assemblies yourself. The README now starts with a comparison of the two ways, followed by step-by-step client setup for Claude Code, Claude Desktop, codex and others.
Fixes
- Clients stay connected across a dnSpy restart (#24, #25). Streamable HTTP sessions live in dnSpy's memory, so after a restart a client still sending its old
Mcp-Session-Idgot404 Unknown Mcp-Session-Idon every call — and clients built on the official TypeScript SDK, Chatbox among them, never re-initialize, so they stayed broken until reconnected by hand. The server now adopts a session ID it didn't issue instead of refusing it; only a session the client ended withDELETEstill gets404. - Long searches no longer freeze dnSpy. Read-only tools — cross-reference sweeps, string and constant search, decompilation — now run off the UI thread; only tools that change metadata or the tree run on it. Calls are still handled one at a time.
- An ambiguous assembly name is refused instead of guessed. With two copies or versions of the same DLL loaded, a name silently picked the first one, so a patch, rename or save could land in the wrong file. Now the error lists each candidate's path,
assembly_nameaccepts a path, andlist_assembliesreports every assembly'sPath.patch_method_ilalso resolvesmethod:/field:/type:operands in the patched module first. - IL snapshots no longer mix up assemblies.
revert_method_ilkeyed its snapshots by metadata token, so same-token methods in two loaded assemblies shared one: reverting one of them could write the other's instructions into it. - Protocol: unknown methods get
-32601Method not found (was-32603);resources/templates/list, which clients probe on every connect, answers an empty list instead of an error;serverInfo.versionreports the release version (was always1.0.0).
Testing
The end-to-end suite now runs in both modes: 245 assertions against the server inside dnSpy over HTTP and 239 against the headless host over stdio (everything but the HTTP-only steps), each on .NET 10 and on .NET Framework 4.8, with 0 failures. Before tagging, the three zips were also built with the release workflow's own scripts, unpacked, and checked the way you'd use them: a full stdio session with the headless host, and the extension serving MCP from each bundle's own dnSpy. Claude Code connects to the headless host with the command above.
Thanks to @blackleaft-web for reporting #24.