About Dnspy MCP
Dnspy MCP is an MCP server published by tuna1999 in the Developer Tools category: mCP extension for dnSpy.
The repository has 72 stars and 17 forks, with the last commit 18 days ago. Recent commit activity is the strongest signal that reported issues will get attention.
Install
git clone https://github.com/tuna1999/dnspy-mcpThis server has no ConduID identity, so agent calls to it are not receipted. Pin the version you install and review the source before granting it credentials.
Ask AI
Ask AI about Dnspy MCP
Powered by Claude · Grounded in docs
Security checks
Static checks derived from the repository 7 days ago. Heuristics on the source, not an audit.
- ✓README presentThe repository ships documentation.
- !License declaredNo license file. Legally you have no right to use it in a product.
- ✓Tests presentA test suite exists in the repository.
- !Dependencies pinnedNo lockfile. Installs can pull different code over time.
- ✓No dynamic code executionSource scan found no eval, dynamic Function, or shell execution.
1 check has not been run yet.
Releases
README
dnSpy MCP Server
A Model Context Protocol (MCP) server for dnSpy — the .NET debugger and assembly editor — enabling AI agents to decompile, analyze, search, and patch .NET assemblies directly through dnSpy's engine.
Highlights
- 55 MCP tools — static analysis, IL patching, and integrated debugger control (36 tools in headless)
- Byte-identical decompiler output — powered by the same
ICSharpCode.Decompilerpipeline dnSpy.exe uses - Two hosts, one core — run as a dnSpy extension (HTTP) or a standalone headless exe (stdio); identical tools and output
- Zero-drag transport — the extension uses a minimal
TcpListener-based HTTP transport, avoiding version conflicts with dnSpy's runtime - Hardened by default — loopback bind, constant-time token auth, concurrency caps, request-size limits, per-tool timeouts, serialized mutations
- Batch-ready — JSON-RPC batch requests (send an array, get ordered results); requests run sequentially per connection, so use several connections for throughput
How It Works
Two hosts share one tool core (dnSpy.MCP.Core):
┌─ Extension (in dnSpy) ─────────────────┐ ┌─ Headless (standalone exe) ─────────┐
│ dnSpy loads extension → MCP Server │ │ dotnet dnspy-mcp-headless.dll │
│ menu → Start → TcpListener on :5150 │ │ --load foo.dll (repeatable, globs) │
│ ↕ │ │ ↕ │
│ AI agent — HTTP POST (JSON-RPC 2.0) │ │ AI agent — stdio (MCP SDK) │
└────────────┬───────────────────────────┘ └──────────────┬──────────────────────┘
└──────────► dnSpy.MCP.Core ◄──────────────────┘
(36 tools + decompiler bridge,
output identical to dnSpy.exe)
- Extension — runs inside dnSpy with full UI integration (tree view, tabs, Output pane). Started manually from the MCP Server menu so WPF is fully initialized before any tool call touches the UI thread.
- Headless — standalone stdio MCP server for batch analysis: same tools, same decompiled output, no UI, no dnSpy install required at analysis time (only the vendored decompiler DLLs in
deps/).
Tools (55 total · 36 in headless)
Decompiler
| Tool | Description |
|---|---|
decompile_method |
Decompile a method to C#. Accepts full name (Namespace.Class::Method), metadata token (0x06000001), or partial name |
decompile_type |
Decompile an entire type (all members) to C# |
decompile_assembly |
Decompile all types in the assembly (limited to 10 for brevity) |
Search
| Tool | Description |
|---|---|
search_types |
Search types by name pattern. Use regex: prefix for regex matching |
search_methods |
Search methods by name, optionally scoped to a specific type |
search_strings |
Search string literals in method bodies |
grep |
Multi-scope search across types, methods, and strings |
Analysis
| Tool | Description |
|---|---|
get_method_il |
Raw IL instructions with exception handlers |
get_method_signatures |
Method metadata: parameters, return type, flags, generic params |
get_type_hierarchy |
Inheritance chain, interfaces, member counts |
get_method_body |
IL bytes with MaxStack/InitLocals info |
get_il_opcodes_formatted |
Formatted IL opcodes with offsets and line indices |
update_method_body |
Patch a method body using C# statements (dry-run supported) |
Cross-References
| Tool | Description |
|---|---|
get_xrefs_to |
Find all references to a method or field |
get_callees |
Methods and fields called by a method |
Assembly
| Tool | Description |
|---|---|
load_assembly |
Load a .NET DLL/EXE into dnSpy by absolute path |
close_assembly |
Unload an assembly by simple name (case-insensitive) |
list_loaded_assemblies |
List all loaded binaries: name, MVID, type count, path |
assembly_overview |
Module info, version, entry point, type count, references |
assembly_list_namespaces |
All namespaces in the loaded assembly |
assembly_list_types |
Type listing with optional regex filter |
assembly_get_references |
Assembly references (DLLs, NuGet packages) |
Resources & Metadata
| Tool | Description |
|---|---|
get_resources |
List embedded resources |
get_resource_data |
Raw bytes of a specific resource |
get_metadata |
PE headers, MVID, runtime version, sections |
Type Inspection
| Tool | Description |
|---|---|
get_type_members |
List all members of a type with optional filter |
get_fields |
Detailed field info: type, access, static/const, values |
get_properties |
Property details: getter/setter, type, access |
Custom Attributes
| Tool | Description |
|---|---|
get_attributes |
Attributes on assembly/type/method/field with filter |
get_method_attributes |
Shortcut: attributes on a specific method |
Constants & Enums
| Tool | Description |
|---|---|
get_enum_values |
Enum members with name + value (hex + decimal) |
search_constants |
Search const/literal fields across assemblies |
get_global_namespaces |
List all types in the global namespace |
UI & Navigation (Extension only)
| Tool | Description |
|---|---|
get_selected_node |
Get the currently selected node in dnSpy tree view |
refresh_ui |
Refresh tree view UI after metadata changes |
Rename
| Tool | Description |
|---|---|
rename_namespace |
Rename a namespace across matching types (dry-run supported) |
rename_class |
Rename one class in an assembly+namespace (dry-run supported) |
rename_method |
Rename methods by exact or partial match (dry-run supported) |
Debugger (Extension only — needs the dnSpy host; not in headless)
| Tool | Description |
|---|---|
debug_get_state |
Session state: debugging/running, processes, current thread, last break |
debug_list_processes |
Attachable .NET processes (pid, name, runtime, arch, filename) |
debug_start |
Start an EXE or managed DLL (runtime: auto/netfx/dotnet); omitted/empty working_dir uses the target folder; break-at-entry by default |
debug_attach |
Attach to a running .NET process by pid |
debug_stop |
Stop the session — stop (default) / detach / terminate (terminate is dry-run by default) |
debug_continue |
Run all debuggee processes |
debug_break_all |
Request a break in all debuggees |
debug_step |
Step the current thread (into/over/out) |
debug_wait_paused |
Block until paused (timeout_ms), then return the state |
debug_set_breakpoint |
Breakpoint on Namespace.Type::Method — binds on module load, no PDB needed |
debug_delete_breakpoint |
Delete an MCP breakpoint by id |
debug_list_breakpoints |
List MCP breakpoints |
debug_get_callstack |
Actual runtime stack; optional pid/thread_id, no GUI selection change |
debug_list_threads |
Paused threads with process, OS/managed IDs and current-thread marker |
debug_get_locals |
Bounded runtime locals, arguments and this for an exact paused frame |
debug_get_value |
Safe instance-field/array page from a pause-scoped value ID |
debug_dismiss_dialog |
Dismiss dnSpy modal error dialogs after a failed launch |
For modern .NET, managed images run through dotnet.exe; native apphost EXEs
launch directly. For an apphost, load its companion managed DLL before setting
method breakpoints. debug_start checks the actual process state, not just
whether dnSpy accepted the asynchronous launch request.
Runtime inspection requires a fully paused managed frame. thread_id requires
pid; omitted selectors use the current thread without changing GUI selection.
frame_index=0 is the top frame. Pages use start_index >= 0, count=1..256
(default 64), with at most 4,096 retained values and 4,096 UTF-16 units per
formatted field. Strings retain the engine's preview limit.
A successful debug_get_locals replaces the single snapshot and invalidates
previous value_ids. Resume, step, stop and detach invalidate them even after
another pause. debug_get_value does not evaluate expressions, getters,
ToString, debugger displays/proxies or enumerators. Static/Results/Dynamic
groups are blocked; static reads can trigger type initialization in this engine.
Unavailable/optimized-out values retain provider errors rather than fake nulls.
Tips
- Multiple assemblies? Call
list_loaded_assembliesfirst; search tools accept an optionalassemblyparameter to scope results. - Method identifiers — all method-accepting tools resolve hex tokens, plain tokens, full names, then short names via one shared resolver. Prefer full names (
Namespace.Class::Method) to avoid ambiguity. - Mutating tools (
rename_*,update_method_body) are dry-run by default — passdryRun: falseto apply.
Getting Started
Prerequisites
- dnSpy (.NET 10 build, e.g. dnSpyEx v6.6.0+)
- .NET 10.0 SDK
deps/folder with these DLLs synced from a dnSpy install (runpwsh scripts/sync-deps.ps1 -DnSpyBin <dnSpy>\bin):dnSpy.Contracts.DnSpy.dll,dnSpy.Contracts.Logic.dllICSharpCode.Decompiler.dll,dnlib.dll- Headless-only:
dnSpy.Decompiler.dll,dnSpy.Decompiler.ILSpy.Core.dll,ICSharpCode.NRefactory.dll,ICSharpCode.NRefactory.CSharp.dll,ICSharpCode.NRefactory.VB.dll
Configure DnSpyBin path
The project resolves dnSpy contract DLLs via the <DnSpyBin> MSBuild property in dnSpy.MCP.csproj:
<DnSpyBin>..\..\deps</DnSpyBin>
Default path resolves to <repo>/deps/. To change it, edit this property or pass it at the command line:
dotnet build -p:DnSpyBin="D:\path\to\dnSpy\bin"
Build & Deploy
# 1) Build only (Release default)
pwsh scripts/build.ps1 -DnSpyPath "D:\tools\dnSpy"
# 2) Clean + build
pwsh scripts/build.ps1 -DnSpyPath "D:\tools\dnSpy" -Clean
# 3) Build + deploy into <DnSpyPath>\bin\Extensions
pwsh scripts/build.ps1 -DnSpyPath "D:\tools\dnSpy" -Deploy
# 4) Build + deploy to a custom folder
pwsh scripts/build.ps1 -DnSpyPath "D:\tools\dnSpy" -Deploy -DeployDir "D:\tools\dnSpy\bin\Extensions"
# 5) Debug build + deploy
pwsh scripts/build.ps1 -DnSpyPath "D:\tools\dnSpy" -Configuration Debug -Deploy
The script syncs deps/ from the dnSpy install, builds the solution, and deploys the extension. dnSpy must be closed before -Deploy.
| Option | Effect |
|---|---|
-DnSpyPath "<path>" |
Required: dnSpy folder (DLLs synced from its bin\) |
-Clean |
Clean before build |
-Deploy |
Deploy after build |
-DeployDir "<path>" |
Custom deploy target (used with -Deploy) |
-Configuration Debug |
Build Debug instead of Release |
-PublishHeadless |
Publish the headless exe bundle |
Build output paths
- Build output (Release default):
src/dnSpy.MCP/bin/Release/net10.0-windows/dnSpy.MCP.x.dll - Build output (Debug override):
src/dnSpy.MCP/bin/Debug/net10.0-windows/dnSpy.MCP.x.dll - Runtime deploy (default):
<DnSpyPath>/bin/Extensions/dnSpy.MCP.x.dll - Runtime deploy (custom
-DeployDir):<DeployDir>/dnSpy.MCP.x.dll
Only these files should be copied to dnSpy's Extensions folder:
dnSpy.MCP.x.dlldnSpy.MCP.Core.dll(required — hard dependency ofdnSpy.MCP.x.dll; without it dnSpy silently fails to load the extension)dnSpy.MCP.x.deps.jsondnSpy.MCP.x.pdb(optional for debugging)dnSpy.MCP.Core.pdb(optional for debugging)
Do not copy the whole build/Extensions folder recursively into dnSpy (avoid nested Extensions/Extensions/ and stale dependency files).
Troubleshooting: if the extension loaded before but breaks after swapping DLL versions, delete dnSpy's cached MEF composition at
%LOCALAPPDATA%\dnSpy\Startup64\net\dnSpy-mef-info.binand restart dnSpy. The cache is keyed by extension DLL identity (path + timestamp + size + MVID); replacing a dependency (e.g.dnSpy.MCP.Core.dll) does not invalidate it.
Usage (Extension)
- Start
dnSpy.exe - Open a .NET assembly (.exe/.dll)
- Menu → MCP Server → Start
- Open View → Output (Alt+2) → select MCP Server to see logs
- Connect from an AI agent via
http://127.0.0.1:5150/
| Menu Item | Action |
|---|---|
| Start | Start the MCP HTTP server |
| Stop | Stop the MCP HTTP server |
| Status | Show running/stopped state and port |
| Show Log | Display recent log entries |
| Clear Log | Clear log file and output window |
Project Structure
dnspy_mcp/
├── src/
│ ├── dnSpy.MCP.Core/ # Shared library: 36 tools, McpContext, decompiler bridge
│ │ ├── Abstractions/ # IAssemblyLoader, ISourceDecompiler, IUIThreadScheduler,
│ │ │ # ILogSink, ITreeRefreshNotifier
│ │ ├── Adapters/ # DnSpyDecompilerSourceProvider (shared by both hosts)
│ │ ├── Mcp/ # McpContext, ToolRegistry, McpServerHost (HTTP), JsonRpc
│ │ ├── Helpers/ # MethodResolver
│ │ └── Tools/ # 13 instance tool classes ([Description] methods)
│ ├── dnSpy.MCP/ # Extension (net10.0-windows, WPF, MEF) — HTTP transport
│ │ ├── Adapters/ # dnSpy-backed adapter implementations
│ │ ├── Tools/TreeViewTools.cs # 2 UI-only tools (get_selected_node, refresh_ui)
│ │ └── TheExtension.cs # MEF entry, composes McpContext
│ ├── dnSpy.MCP.Headless/ # Standalone exe (stdio MCP transport via MCP SDK)
│ │ ├── Program.cs # Host + fail-fast startup + stdio server
│ │ ├── CliOptions.cs # --load (globs) / --config / --help
│ │ └── Adapters/ # dnlib loader, reflection decompiler loader,
│ │ # stderr log sink, mutation-lock filter
│ └── dnSpy.MCP.Tests/ # Unit + headless E2E tests (spawn real server process)
│ └── TestData/SampleLibrary/ # E2E fixture assembly
├── deps/ # Vendored dnSpy DLLs (sync via scripts/sync-deps.ps1)
├── scripts/ # build.ps1, sync-deps.ps1, verify-tool-count.ps1, mcp-probe.js
└── skills/ # AI agent workflow guides
Headless Mode
For batch analysis without dnSpy running: same 36 tools, decompiler output byte-identical to dnSpy.exe, stdio transport (Claude Desktop / Cursor / VS Code can auto-spawn it).
# Build & publish
pwsh scripts/build.ps1 -DnSpyPath "D:\tools\dnSpy" -PublishHeadless
# Run directly from build output
dotnet src/dnSpy.MCP.Headless/bin/Release/net10.0-windows/dnspy-mcp-headless.dll --load path\to\target.dll
# Or from the published bundle
dotnet publish\headless\dnspy-mcp-headless.dll --load "path\to\*.dll"
--load, -l <path>— pre-load assemblies (repeatable, supports*/?globs)--config, -c <json>— reserved, currently unused- Logging goes to stderr only (stdout carries the MCP JSON-RPC frames)
- Parallel mutation calls (
rename_*,update_method_body) are serialized via a shared lock, mirroring the Extension transport
Client config (stdio):
{
"mcpServers": {
"dnspy-headless": {
"command": "dotnet",
"args": [
"D:\\path\\to\\publish\\headless\\dnspy-mcp-headless.dll",
"--load", "D:\\path\\to\\target.dll"
]
}
}
}
Configuration
Defaults live in src/dnSpy.MCP/Settings/McpSettings.cs and are editable in dnSpy via Options → MCP Server:
| Setting | Default | Description |
|---|---|---|
| Host | 127.0.0.1 |
Loopback only by default; 0.0.0.0 binds all interfaces |
| Port | 5150 |
TCP port for the HTTP transport |
| AutoStart | off | Start the server when dnSpy launches |
| RequireAuth / ApiToken | off | Require Authorization: Bearer <token> on every request |
| AllowedOrigins | empty | CORS origins allowed to call the server (empty = CORS disabled) |
| MaxConcurrency | 4 |
Max simultaneous in-flight requests |
| MaxRequestSizeMB | 1 |
Request body cap |
| ToolTimeoutSeconds | 30 |
Per-tool-call timeout (in-flight work is cancelled on expiry) |
Security
- Loopback by default — the server binds
127.0.0.1; binding0.0.0.0exposes it to the network, so pair it withRequireAuth. - Auth fail-closed — if
RequireAuthis on but the token is empty, the server refuses to start. Tokens are compared in constant time (CryptographicOperations.FixedTimeEquals). - Mutation serialization — destructive tools (
rename_*,update_method_body) run under an exclusive lock so parallel batch requests can't race on dnlib metadata. - Roslyn sandbox — IL patch compilation references only 5 core BCL assemblies plus the target assembly.
- Cancellation — per-tool timeouts cancel the actual decompilation work, not just the HTTP response.
Logging
Logs are written to three destinations:
- File:
mcp-server.login the dnSpybinfolder (rotates at 5 MB, keeps 3 backups) - In-memory: Viewable via MCP Server → Show Log
- Output Window: View → Output → MCP Server (in dnSpy)
Connecting AI Agents
This MCP server exposes dnSpy's decompilation and analysis tools via the standard MCP protocol over HTTP at http://127.0.0.1:5150/. Most modern AI agents support HTTP MCP servers natively — no bridge package needed.
Claude Code (recommended)
Use the claude mcp add command to add the server. Choose a scope:
# Local scope (default) — only this project, stored in ~/.claude.json
claude mcp add --transport http dnspy http://127.0.0.1:5150
# Project scope — shared with team via .mcp.json (check into git)
claude mcp add --transport http dnspy --scope project http://127.0.0.1:5150
# User scope — all your projects
claude mcp add --transport http dnspy --scope user http://127.0.0.1:5150
Project scope generates a .mcp.json at the project root:
{
"mcpServers": {
"dnspy": {
"type": "http",
"url": "http://127.0.0.1:5150"
}
}
}
Local/User scope writes to ~/.claude.json under the project path:
{
"projects": {
"/path/to/your/project": {
"mcpServers": {
"dnspy": {
"type": "http",
"url": "http://127.0.0.1:5150"
}
}
}
}
}
Other useful commands:
claude mcp list # list all configured servers
claude mcp get dnspy # show config for a server
claude mcp remove dnspy # remove a server
Other AI Editors
| Editor | Config file | Format |
|---|---|---|
| Cursor | ~/.cursor/mcp.json |
{ "mcpServers": { "dnspy": { "url": "http://127.0.0.1:5150/" } } } |
| VS Code (Cline/Roo) | .vscode/mcp.json |
Same as above |
Verification
- Start dnSpy and open an assembly
- Menu → MCP Server → Start
- Check
GET http://127.0.0.1:5150/healthreturns status and tool count, or list tools from your agent — you should see 38 MCP tools (decompile_method,search_types,grep,get_xrefs_to,assembly_overview, …)
If the agent does not auto-discover the tools, tell it: "Use the dnSpy MCP server at http://127.0.0.1:5150/ to access decompilation and analysis tools."
Adding New Tools
Tools are discovered at runtime via reflection. To add a new tool:
- Create a
public sealedclass insrc/dnSpy.MCP.Core/Tools/under thednSpy.MCP.Core.Toolsnamespace, with a constructor takingMcpContext - Add instance methods
public string MyTool(...)with a[Description("...")]attribute - Parameters use
[Description("...")]for documentation - Run
pwsh scripts/verify-tool-count.ps1to keep docs and code in sync
using System.ComponentModel;
using dnSpy.MCP.Core.Mcp;
namespace dnSpy.MCP.Core.Tools {
public sealed class MyTools(McpContext ctx) {
[Description("Describe what this tool does")]
public string MyTool(
[Description("Parameter description")] string param1) {
// Access loaded modules via the abstraction — works in both
// Extension (dnSpy) and Headless (dnlib) hosts
var docs = ctx.AssemblyLoader.GetDocuments();
return $"Result: {param1} ({docs.Count} assemblies loaded)";
}
}
}
Method names are automatically converted to snake_case for the MCP protocol (e.g., MyTool → my_tool).
Architecture Notes
Why a custom TcpListener transport instead of MCP SDK?
The official MCP SDK (ModelContextProtocol 1.4.0) pulls Microsoft.Extensions.* 10.x dependencies that conflict with the versions dnSpy ships on its .NET runtime. This is a hard version conflict that cannot be resolved with binding redirects. The solution is a minimal custom HTTP transport over System.Net.Sockets.TcpListener. The headless exe runs in its own process, so it uses the MCP SDK's stdio transport without conflict.
Standalone build
The project references pre-built DLLs from deps/, enabling fast iteration without cloning the full dnSpy source. For integrated builds as part of dnSpy.sln, clone dnSpyEx and copy src/dnSpy.MCP/ into Extensions/.
Thread safety
MCP tools run on background threads. All WPF TreeView/UI access marshals to the UI thread via the IUIThreadScheduler abstraction (Dispatcher.Invoke in the Extension). Metadata mutation tools auto-refresh the tree view after changes.
Skills
The skills/ directory contains reusable workflow guides for AI agents working with this MCP server. These skills teach the AI how to think about common analysis tasks — it dynamically picks tools based on what it discovers, rather than following rigid steps.
Available Skills
| Skill | Description |
|---|---|
deobfuscate-dotnet |
Deobfuscate .NET binaries: string decryption, symbol renaming, control flow analysis, proxy call resolution, anti-tamper removal |
Skill Structure
Each skill is a folder containing a SKILL.md file:
skills/
└── deobfuscate-dotnet/
└── SKILL.md # Skill definition (YAML frontmatter + instructions)
Installing Skills for Claude Code
Claude Code auto-discovers skills from .claude/skills/. To install:
# Install a specific skill
cp -r skills/deobfuscate-dotnet .claude/skills/
# Or install all skills
cp -r skills/* .claude/skills/
After installing, the skill activates automatically when you describe a matching task — for example:
# These will trigger the deobfuscation skill:
"Giúp tôi decrypt các string trong binary này"
"Rename lại các class/method bị obfuscate"
"Binary này bị protect bằng gì? Phân tích giúp tôi"
No restart needed — Claude Code picks up new skills on the next message.
For Other AI Editors
If your AI editor supports custom instructions or system prompts, paste the content of SKILL.md directly into your configuration. The skill content is self-contained and editor-agnostic.
License
This project is licensed under GPLv3, consistent with dnSpy's license.
README fetched from the repository 7 days ago. The original is authoritative.