Dnspy.extension.mcp
MCP extension for dnSpy.
Installation
npx dnspy-extension-mcpAsk AI about Dnspy.extension.mcp
Powered by Claude Β· Grounded in docs
I know everything about Dnspy.extension.mcp. Ask me about installation, configuration, usage, or troubleshooting.
0/500
Reviews
Documentation
dnSpy MCP Extension
A Model Context Protocol (MCP) extension for dnSpyEx that exposes .NET assembly analysis and IL-editing tools to AI assistants like Claude.
Chinese / δΈζθ―΄ζ: see README.zh-CN.md.
Features
MCP Tools (15 total)
Analysis & navigation
- list_assemblies β list all loaded assemblies with metadata
- get_assembly_info β detailed info about a specific assembly (paginated namespaces)
- list_types β all types in an assembly or namespace (paginated)
- get_type_info β fields, properties, and paginated methods for a type (methods include
token/MDTokenfor unambiguous identification) - list_methods β methods of a type with
token+parameter_typesper entry, paginated - get_type_fields β filter fields by wildcard pattern (e.g.
*Bonus*) - get_type_property β detailed info about a property including getter/setter
- search_types β wildcard / substring type search across all assemblies
- find_path_to_type β BFS over fields/properties to connect two types
- decompile_method β decompile a method to C# (accepts
parameter_types/method_tokento disambiguate overloads)
IL viewing & editing (new in 0.1.3)
- get_method_il β instructions (index, offset, opcode, operand) + locals + exception handlers + body flags
- patch_method_il β ordered
replace/insert/delete/set_init_localsedits; snapshot-on-first-patch - revert_method_il β restore the pre-patch body shape
- save_assembly β write the module to disk (timestamped backup on overwrite,
NativeWritepreserves native stubs / Win32 resources / delay-loaded imports, GAC refused)
Codegen
- generate_bepinex_plugin β BepInEx plugin template with Harmony hooks
MCP Resources (6 total)
Embedded BepInEx documentation served over resources/list / resources/read:
- plugin-structure
- harmony-patching (Prefix / Postfix / Transpiler)
- configuration
- common-scenarios
- il2cpp-guide
- mono-vs-il2cpp
All docs ship inside the DLL β no network required.
IL viewing and editing
See, patch, and save bytecode from an AI client. Mirrors the dnSpy Edit Method Body dialog.
Operand grammar
Each instruction's operand is a single tagged string; the same grammar is used by get_method_il (read) and patch_method_il (write), so operands round-trip unchanged.
| Tag | Example | Opcodes |
|---|---|---|
int: / int8: / uint8: / long: | int:42 | ldc.i4, ldc.i4.s, ldc.i8 |
float: / double: | double:3.14 | ldc.r4, ldc.r8 |
str: (JSON-quoted) | str:"hello\n" | ldstr |
method: (dnlib FullName) | method:System.Void Ns.T::M(System.Int32) | call, callvirt, newobj, ldftn, ldvirtftn, jmp |
field: | field:System.Int32 Ns.T::F | ldfld, stfld, ldsfld, stsfld, ldflda, ldsflda |
type: | type:System.String | castclass, isinst, box, unbox, newarr, initobj, ldelem*, stelem*, β¦ |
token:method:β¦ / token:field:β¦ / token:type:β¦ | token:type:System.String | ldtoken |
label:<idx> | label:7 | br, brtrue.s, blt, β¦ |
switch:[<i>,<i>,β¦] | switch:[3,7,12] | switch |
local:<idx> | local:0 | ldloc*, stloc* |
arg:<idx> | arg:1 | ldarg*, starg* |
| (empty) | "" | no operand (ldarg.0, add, ret, β¦) |
calli / InlineSig is not supported in 0.1.3.
End-to-end: patch a constant and persist
Assume TestIL.dll contains public static int AddOne(int x) => x + 1;.
# 1. Find the method (parameter_types disambiguates overloads).
curl -s -X POST http://localhost:3000/ -H "Content-Type: application/json" -d '{
"jsonrpc":"2.0","id":1,"method":"tools/call","params":{
"name":"list_methods",
"arguments":{"assembly_name":"TestIL","type_full_name":"TestIL.Simple"}}}'
# 2. Read the IL.
curl -s -X POST http://localhost:3000/ -H "Content-Type: application/json" -d '{
"jsonrpc":"2.0","id":1,"method":"tools/call","params":{
"name":"get_method_il",
"arguments":{"assembly_name":"TestIL","type_full_name":"TestIL.Simple","method_name":"AddOne"}}}'
# Instructions include: {"index":1,"opcode":"ldc.i4.1","operand":""}
# 3. Replace the +1 with +41.
curl -s -X POST http://localhost:3000/ -H "Content-Type: application/json" -d '{
"jsonrpc":"2.0","id":1,"method":"tools/call","params":{
"name":"patch_method_il",
"arguments":{"assembly_name":"TestIL","type_full_name":"TestIL.Simple","method_name":"AddOne",
"edits":[{"op":"replace","index":1,"opcode":"ldc.i4","operand":"int:41"}]}}}'
# 4. Save. Original file is backed up to <path>.<yyyyMMdd-HHmmss>.bak first.
curl -s -X POST http://localhost:3000/ -H "Content-Type: application/json" -d '{
"jsonrpc":"2.0","id":1,"method":"tools/call","params":{
"name":"save_assembly",
"arguments":{"assembly_name":"TestIL"}}}'
Reload the saved DLL in a fresh process and AddOne(10) returns 51 instead of 11.
Caveats
- No Ctrl+Z.
patch_method_ildoes not route through dnSpy's undo stack. Userevert_method_ilβ the snapshot is taken the first time a given method is patched, and dropped after revert or after a successful save. - dnSpy's in-memory view is not refreshed after save. Reopen the assembly in dnSpy to see the saved state in the running instance.
- GAC paths are refused. Saving
mscorlibetc. returns a-32602error. - Instruction-level only. Adding / removing locals or exception handlers is out of scope for 0.1.3;
get_method_ilexposes them read-only.
Installation
Recommended: all-in-one zip
Head to Releases and download the bundle that matches your system β the extension is already placed inside, no paths to figure out:
| File | Contents | Runtime requirement |
|---|---|---|
dnSpy-MCP-win-x64.zip | dnSpy .NET 10 self-contained x64 + MCP extension | None β runtime is bundled |
dnSpy-MCP-win-x86.zip | dnSpy .NET 10 self-contained x86 + MCP extension | None β runtime is bundled |
dnSpy-MCP-net48.zip | dnSpy .NET Framework 4.8 build + MCP extension | .NET Framework 4.8 (default on Windows 10+) |
- Download and unzip anywhere.
- Double-click
dnSpy.exe. - Open Edit β Settings β MCP Server, tick Enable Server, click OK.
That's it. If you already use dnSpy and just want the plugin, see "Plugin-only" below.
Plugin-only (for users who already have dnSpy installed)
- Download the DLL matching your dnSpy runtime:
dnSpy.Extension.MCP-net48.dllβ .NET Framework 4.8 dnSpydnSpy.Extension.MCP-net10.0-windows.dllβ .NET 10 dnSpy
- Rename to
dnSpy.Extension.MCP.x.dll(the.xsuffix is required by dnSpy's extension loader). - Create the folder
dnSpy.Extension.MCPunder<dnSpy-Install>\bin\Extensions\and put the DLL inside. - Restart dnSpy.
The final path must look exactly like this β same folder name as the DLL stem, .x.dll suffix present, one level deep under Extensions\:
<dnSpy-Install>\
βββ bin\
βββ Extensions\
βββ dnSpy.Extension.MCP\ β folder (create if missing)
βββ dnSpy.Extension.MCP.x.dll β DLL with the .x suffix
Concrete example if dnSpy is installed at C:\Tools\dnSpy:
C:\Tools\dnSpy\bin\Extensions\dnSpy.Extension.MCP\dnSpy.Extension.MCP.x.dll
If the DLL ends up directly under bin\Extensions\ (no subfolder), or without the .x suffix, dnSpy silently skips it and the MCP Server settings page will not appear.
From source
# Clone dnSpyEx (submodules are required)
git clone --recursive https://github.com/dnSpyEx/dnSpy.git
cd dnSpy
# Clone this extension into the Extensions directory
git clone https://github.com/KernelErr/dnSpy.Extension.MCP.git Extensions/dnSpy.Extension.MCP
# Build (both TFMs)
cd Extensions/dnSpy.Extension.MCP
dotnet build -c Release
# Deploy
cp bin/Release/net10.0-windows/dnSpy.Extension.MCP.x.dll \
<dnSpy-Install>/bin/Extensions/dnSpy.Extension.MCP/
Configuration
Settings live under Edit β Settings β MCP Server:
- Enable Server β starts/stops the HTTP server immediately when toggled and applied.
- Port β preferred TCP port (default
3000). If the port is already in use, the server automatically triesport + 1, up to 20 attempts, and logs which port it actually bound to. Check the Server Log pane for the resolved port. - Host β bind address (default
localhost).
Transports
All three transports run on the same HttpListener on the same port. The server picks the right one by inspecting the path, HTTP method, and Accept header of each request.
Streamable HTTP (MCP 2025-03-26)
Single-endpoint transport used by codex and other modern MCP clients. The client POSTs JSON-RPC requests with Accept: application/json, text/event-stream; the server returns the JSON-RPC response inline as application/json and allocates a session on initialize via the Mcp-Session-Id response header. Subsequent POSTs must echo that header. The server also honours GET on the same endpoint for server-initiated SSE and DELETE for teardown.
Both / and /mcp are accepted as the endpoint path.
# 1. Initialize β server returns the session ID in the Mcp-Session-Id header.
curl -i -X POST http://localhost:3000/ \
-H "Accept: application/json, text/event-stream" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize"}'
# HTTP/1.1 200 OK
# Mcp-Session-Id: <sid>
# Content-Type: application/json
# {"jsonrpc":"2.0","id":1,"result":{...}}
# 2. Subsequent calls echo the session header.
curl -X POST http://localhost:3000/ \
-H "Accept: application/json, text/event-stream" \
-H "Content-Type: application/json" \
-H "Mcp-Session-Id: <sid>" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
# 3. Tear down explicitly (optional β the server also drops the session on shutdown).
curl -X DELETE http://localhost:3000/ -H "Mcp-Session-Id: <sid>"
Codex ~/.codex/config.toml:
[mcp_servers.dnspy-mcp]
type = "streamable-http"
url = "http://localhost:3000"
Plain HTTP JSON-RPC
One-shot request/response β POST JSON-RPC to / without text/event-stream in Accept and read the response from the same HTTP response body. Useful for quick curl testing and for MCP clients that only speak plain HTTP.
curl -s http://localhost:3000/health
# {"status":"ok","service":"dnSpy MCP Server"}
curl -s -X POST http://localhost:3000/ \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize"}'
Server-Sent Events (MCP 2024-11-05)
Legacy two-endpoint transport kept for backwards compatibility with MCP Inspector and older clients: a long-lived SSE stream, plus a POST endpoint for client messages.
GET /sseβ openstext/event-stream. The first event (event: endpoint) carries the URL the client should POST to (/message?sessionId=<id>).POST /message?sessionId=<id>β accepts a JSON-RPC request, returns202 Accepted, and writes the real JSON-RPC response onto the corresponding SSE stream as anevent: message.
# Terminal A: open the stream and keep it open
curl -N http://localhost:3000/sse
# event: endpoint
# data: /message?sessionId=<sessionId>
# ... (later, once POST arrives) ...
# event: message
# data: {"jsonrpc":"2.0","id":1,"result":...}
# Terminal B: send a request on that session
curl -X POST "http://localhost:3000/message?sessionId=<sessionId>" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize"}'
# HTTP 202 Accepted β the response appears on Terminal A's SSE stream
Client configuration
Claude Code
Use the CLI to register the server once β it picks up the Streamable HTTP transport at /:
claude mcp add --transport http dnspy http://localhost:3000
# verify:
claude mcp list
Or add it to a checked-in .mcp.json at your project root (scoped to the project):
{
"mcpServers": {
"dnspy": {
"type": "http",
"url": "http://localhost:3000"
}
}
}
Run /mcp inside Claude Code to confirm dnspy is connected and list its tools.
Claude Desktop
{
"mcpServers": {
"dnspy": {
"command": "http",
"args": ["http://localhost:3000"]
}
}
}
codex
See the Streamable HTTP section above for the ~/.codex/config.toml snippet.
Development
# Single-TFM builds for fast iteration
dotnet build -c Debug -f net48
dotnet build -c Debug -f net10.0-windows
Project layout
dnSpy.Extension.MCP/
βββ .github/workflows/ GitHub Actions (build, release)
βββ McpServer.cs HttpListener HTTP + SSE + Streamable HTTP + port fallback
βββ McpProtocol.cs JSON-RPC 2.0 / MCP DTOs
βββ McpTools.cs Analysis tools + MEF export + dispatch (sealed partial)
βββ McpTools.IL.cs IL view/patch/revert/save + operand renderer & parser
βββ McpSettings.cs Settings view-model + persistence + log (disk log in Debug only)
βββ McpSettingsPage.cs IAppSettingsPageProvider for dnSpy settings dialog
βββ BepInExResources.cs Embedded BepInEx docs (6 resources)
βββ TheExtension.cs IExtension entry point; starts server on Loaded
βββ tests/fixtures/ TestIL.cs + build-fixture.ps1 + run-tests.ps1 (E2E harness)
βββ dnSpy.Extension.MCP.csproj
Architecture notes
- Targets:
net48andnet10.0-windows(inherited fromDnSpyCommon.props). - Transport: a single
HttpListenerserves the plain HTTP JSON-RPC, 2024-11-05 SSE, and 2025-03-26 Streamable HTTP paths on one port. Kestrel is intentionally not used β dnSpy's self-contained .NET bundle does not ship ASP.NET Core, so anyMicrosoft.AspNetCore.*reference would cause a silentTypeLoadExceptionduring MEF composition and the extension'sIExtensionpart would never instantiate. - MEF: services use
[Export(typeof(T))]+[ImportingConstructor]. Don'tnewupMcpServer/McpSettings/McpTools. - UI-thread marshalling: every tool handler in
ExecuteToolruns on the WPF dispatcher.IDocumentTreeViewnodes areDispatcherObjects and throw "calling thread cannot access this object" if read from an HTTP worker, so marshalling is mandatory; handlers that already take the dispatcher path (patch, revert, save) double-wrap harmlessly. - Error codes:
ArgumentExceptioninside a tool handler β JSON-RPC-32602(invalid params); any other exception β-32603(internal error). - Logging:
McpSettings.Log(...)writes to the in-UI log pane always, and toD:\dnspy-mcp.logonly in Debug builds. Release builds keep everything in-memory; no writableD:drive is required on end-user machines.
Protocol
Implements MCP version 2024-11-05 over JSON-RPC 2.0.
Supported methods: initialize, ping, tools/list, tools/call, resources/list, resources/read, and notifications/*.
CI / Release
.github/workflows/build.ymlβ builds both TFMs on every push/PR..github/workflows/release.ymlβ builds release DLLs and attaches them to the GitHub release on tag push (v*.*.*).
git tag v1.0.0
git push origin v1.0.0
Technical details
- Dependencies:
dnSpy.Contracts.DnSpy,dnSpy.Contracts.Logic,dnlib,System.Text.Json(package onnet48, in-box onnet10.0-windows). - BFS path finding:
find_path_to_typedoes breadth-first search over each type's fields and properties. - Decompilation: uses dnSpy's default decompiler (usually C#) via
IDecompilerService. - IL writing:
save_assemblycalls((ModuleDefMD)module).NativeWrite(path, NativeModuleWriterOptions)for modules loaded from disk (preserves native stubs, Win32 resources, delay-loaded imports, mixed-mode code) andmodule.Write(path, ModuleWriterOptions)for freshly constructed modules. Memory-mapped I/O is disabled viapeImage as dnlib.PE.IInternalPEImagebefore the write β the internalIMmapDisablerindnSpy.AsmEditoris inlined to avoid depending on AsmEditor. - Cross-method references in
patch_method_iloperands (method:,field:,type:) are resolved by walking every loaded module for aFullNamematch and then imported into the destination module vianew Importer(module, ImporterOptions.TryToUseDefs).
Troubleshooting
Settings page shows but the server never starts
Most commonly a MEF composition failure for the IExtension part while IAppSettingsPageProvider (the settings page) composes fine. Symptoms: the MCP Server page exists and lets you toggle Enable Server, but nothing happens on click and no log ever appears. Root cause is usually a missing runtime dependency β check the on-disk fallback log first, and make sure you deployed the DLL matching your dnSpy TFM.
Port already in use
The server automatically falls back to port + 1 (up to 20 tries). Look for Port N is in use; falling back to M in the log β clients should connect to the fallback port.
Build errors
- Ensure you cloned dnSpyEx with
--recursive(submodules must be initialized). - Run
dotnet restorein the dnSpyEx repo root. - Requires .NET 10 SDK (previous dnSpy versions used .NET 8;
DnSpyCommon.propsis the source of truth).
License
Same as dnSpyEx β see the dnSpyEx repository.
Acknowledgments
- dnSpyEx β .NET debugger and assembly editor
- Model Context Protocol β Anthropic's MCP specification
- BepInEx β Unity game modding framework
