Skip to main content
Glama
Kommisaar

dotnet-decompiler-mcp

by Kommisaar
README.md
# dotnet-decompiler-mcp

An [MCP](https://modelcontextprotocol.io/) server that decompiles and inspects
.NET assemblies to C# source, wrapping the [ICSharpCode.Decompiler](https://github.com/icsharpcode/ILSpy)
engine (ILSpy, v9.0.0.7889) behind a set of callable tools.

Point an MCP client at any .NET DLL and ask an LLM to "decompile `Foo.Bar`,
then list its public methods" — it returns structured results (C# source, type
summaries, symbol matches) the model can reason over.

## What it does

- **Decompile** a whole type or a single member to readable C# source (with
  comments — the ILSpy "gold standard" output).
- **Inspect** an assembly: list types, list namespaces with counts, get a
  detailed member summary of one type (methods, fields, properties, events,
  attributes, base type).
- **Search** for symbols by name across one or more assemblies
  (case-insensitive substring, filterable by kind).
- A **`check_env`** diagnostic tool reports the active Python, .NET runtime,
  and engine version.

All .NET access is via reflection through [pythonnet](https://pythonnet.github.io/)
on CoreCLR; no .NET SDK install is required on the host beyond the runtime.

## Requirements

- **Python ≥ 3.14**
- **.NET 8 runtime** on the host (the decompiler targets `net8.0`; the bundled
  CoreCLR runtime pack is `8.0.6`, the minimum host runtime is `8.0.17`).
- **[uv](https://docs.astral.sh/uv/)** to run the project.

## Install

```bash
git clone <repo-url> dotnet-decompiler-mcp
cd dotnet-decompiler-mcp
uv sync
```

The ILSpy engine DLL ships in `lib/ICSharpCode.Decompiler.dll` — nothing else
to download.

## Environment variables

These are set **automatically** at runtime by the bootstrap
(`runtime/clr.py`); you normally do not need to configure them. They are listed
for completeness / troubleshooting.

| Variable | Value | Purpose |
|---|---|---|
| `PYTHONNET_RUNTIME` | `coreclr` | Selects CoreCLR over Mono. |
| `PYTHONNET_CORECLR_RUNTIME_CONFIG` | path to a temp `*.runtimeconfig.json` | Tells CoreCLR which framework (`Microsoft.NETCore.App` 8.0.6) to load. The file is generated into the system temp dir on first boot. |

If the runtime fails to start, run `check_env` (below) — it reports the
resolved versions.

## MCP client registration

The server speaks MCP over **stdio**. Register it with any MCP client.

### ZCode / OpenCode (`.zcode/config.json`)

```json
{
  "mcp": {
    "servers": {
      "dotnet-decompiler": {
        "type": "stdio",
        "command": "uv",
        "args": [
          "run",
          "--project",
          "/absolute/path/to/dotnet-decompiler-mcp",
          "python",
          "-m",
          "dotnet_decompiler_mcp"
        ],
        "env": {}
      }
    }
  }
}
```

Write this to the workspace `.zcode/config.json` or the user-level
`~/.zcode/v2/config.json`, then restart the client.

### Generic `mcpServers` (Claude Desktop and others)

```json
{
  "mcpServers": {
    "dotnet-decompiler": {
      "command": "uv",
      "args": [
        "run",
        "--project",
        "/absolute/path/to/dotnet-decompiler-mcp",
        "python",
        "-m",
        "dotnet_decompiler_mcp"
      ]
    }
  }
}
```

You can also run it directly to smoke-test the stdio server:

```bash
uv run python -m dotnet_decompiler_mcp
```

It should start without errors and wait on stdin. Ctrl+C to exit.

## Tools

Seven tools are registered. `asm_paths` (where present) is an optional list of
extra assembly search directories used to resolve cross-DLL references; pass it
when the target assembly depends on types in other DLLs.

| Tool | Arguments | Returns | Purpose |
|---|---|---|---|
| `check_env` | *(none)* | `EnvInfo` | Startup diagnostics: Python, .NET runtime, engine version, lib dir. |
| `decompile_type` | `dll`, `type_name`, `asm_paths?` | `DecompileResult` | Decompile a whole type to C# source. |
| `decompile_member` | `dll`, `type_name`, `member_name`, `asm_paths?` | `MemberDecompileResult` | Decompile a single method/field/property/event. |
| `list_types` | `dll`, `namespace?`, `asm_paths?` | `TypeListResult` | List types, optionally filtered by namespace prefix. |
| `list_namespaces` | `dll`, `asm_paths?` | `NamespaceListResult` | Distinct namespaces with type counts. |
| `get_type_summary` | `dll`, `type_name`, `asm_paths?` | `TypeSummary` | Detailed public-member breakdown of one type. |
| `search_symbol` | `dlls`, `query`, `kind?`, `asm_paths?` | `SearchResult` | Case-insensitive symbol search across one or more DLLs. |

Notes:
- `type_name` is a fully-qualified, case-sensitive name (e.g.
  `ICSharpCode.Decompiler.DecompilerSettings`).
- `namespace` is a **prefix** filter — `ICSharpCode` matches
  `ICSharpCode.Decompiler` but not `ICSharpCodeX`; empty string = all types.
- `search_symbol` `kind` is one of `type` / `method` / `field` / `property` /
  `event` / `any` (default `any`).

## Usage examples

These target the engine DLL bundled in `lib/` — the same cases the test suite
pins, so they are known to work. Replace the path with any .NET assembly you
want to explore.

```text
check_env
  → EnvInfo(decompiler_version="9.0.0.7889", net_tfm_target="net8.0", ...)

decompile_type("lib/ICSharpCode.Decompiler.dll",
               "ICSharpCode.Decompiler.DecompilerSettings")
  → DecompileResult(source="public class DecompilerSettings : ...", ...)

decompile_member("lib/ICSharpCode.Decompiler.dll",
                 "ICSharpCode.Decompiler.DecompilerSettings",
                 "GetMinimumRequiredVersion")
  → MemberDecompileResult(source="public static Version ...", ...)

get_type_summary("lib/ICSharpCode.Decompiler.dll",
                 "ICSharpCode.Decompiler.DecompilerSettings")
  → TypeSummary(base_type="System.Object",
                events=[EventInfo(name="PropertyChanged", ...)], ...)

search_symbol(["lib/ICSharpCode.Decompiler.dll"], "GetMinimum", kind="method")
  → SearchResult(matches=[SymbolMatch(kind="method",
          name="GetMinimumRequiredVersion", type="...DecompilerSettings")])
```

## Errors

The engine raises typed exceptions; the tool layer does **not** catch them, so
they surface to the MCP client as `is_error` responses with the exception
message. All engine errors inherit from `DecompilerError`.

| Error | Meaning |
|---|---|
| `DllNotFoundError` | The `dll` path does not exist or is not readable. |
| `TypeNotFoundError` | `type_name` was not found in the assembly. |
| `MemberNotFoundError` | `member_name` was not found in the type. |
| `DecompilationFailedError` | A reflected .NET call raised; the original exception text is appended as `(inner: ...)`. |
| `InvalidArgumentError` | An argument was rejected (e.g. an unknown `kind`). |

## Development

```bash
uv run pytest          # 65 tests (incl. real-.NET integration tests)
uv run ruff check      # lint (D/E/F/W, Google docstring convention)
```

### Architecture

Four one-way layers, no cycles:

```
config.py     constants (single source of truth)
runtime/      CoreCLR bootstrap via pythonnet (init_runtime, ensure_initialised)
engine/       CSharpDecompiler reflection wrappers (cache, decompile, metadata, version, errors)
tools/        thin async wrappers (list[str]|None -> tuple|None), errors bubble up
server.py     FastMCP assembly + tool registration
models/       Pydantic response DTOs (anemic, serialization only)
```

Dependency direction: `server → tools → engine → runtime/models/config`.

## Status

Milestones M0–M4 complete: runtime bootstrap, decompile (type/member),
inspect (list/summary) + search, error handling, and documentation.

TDQS

A4.6/5.0

Scored across 7 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: environment checking, decompiling types, decompiling members, getting type summaries, listing namespaces, listing types, and searching symbols. No overlap or ambiguity.

Naming Consistency5/5

All tool names follow a consistent verb_noun snake_case pattern: check_env, decompile_member, decompile_type, get_type_summary, list_namespaces, list_types, search_symbol. The pattern is uniform and predictable.

Tool Count5/5

Seven tools is an ideal size for a decompiler MCP server, covering diagnostics, exploration, and decompilation without being overwhelming or insufficient.

Completeness4/5

The tools cover the core exploration and decompilation workflow (list namespaces, types, summary, decompile type/member, search). However, missing are options for decompilation settings (e.g., with/without comments, debug info), which would make it fully complete.

Maintenance

ActivityInactive
ResponsivenessNo issues