An AI agent inspecting a .dll typically falls back to ildasm (IL dump) or ilspycmd (full C# decompile) shelled out via Bash. Each invocation dumps an entire file as text, and that output stays in the agent's context as input tokens on every subsequent turn — quickly thousands of tokens spent inspecting a single class.
For a reference assembly of roughly 2 MB containing about 500 public types, the per-task cost looks like this:
| Task | ildasm | ilspycmd | ILens | ILens tokens |
|---|---|---|---|---|
| List every public type | ~150,000 | ~120,000 | list_types | ~500 |
| API surface of one mid-sized class | ~150,000 | ~120,000 | summarize_type | ~300 |
| Find which type exposes a method | ~150,000 | ~120,000 | search_types | ~200 |
Figures are illustrative, not measured.
ILens turns assembly inspection into bounded, targeted lookups instead of full-file dumps — which is what makes it cheap enough to leave registered as an always-available tool.
ILens is an MCP server that speaks stdio and exposes nine tools across three categories: discovery (locating types and methods), inspection (reading API surface and cross-references), and decompilation (full C# source for a type or method). Trust posture: read-only, scoped to --allow-root directories, no network access, single self-contained binary.
Extract the release ZIP anywhere on disk. The archive contains:
ILens.exe — the self-contained server binary (~70 MB)LICENSE, README.md, guide.html (this file)third-party-licenses/ — license texts for bundled dependencies plus a manifest.jsonNo .NET runtime install is required — the binary bundles its own. Windows x64 only; the publish RID is win-x64.
Two equivalent ways to register the server. Pick one.
claude mcp add CLI commandclaude mcp add ilens "C:\path\to\ILens.exe" --allow-root "C:\path\to\dlls"
.mcp.json snippet in the project root{
"mcpServers": {
"ilens": {
"command": "C:\\path\\to\\ILens.exe",
"args": [
"--allow-root", "C:\\path\\to\\dlls"
]
}
}
}
.mcp.json inside a running session will not register the new server in that session. Start a new session (menu: "New session"). Restarting just the IDE or CLI window is not sufficient — the same conversation persists across the restart.
Each --allow-root flag adds a directory tree from which assemblies may be loaded. Multiple flags are accepted. Without any roots, the server cannot load any assemblies.
Claude Desktop reads its MCP configuration from %APPDATA%\Claude\claude_desktop_config.json. Add a mcpServers entry equivalent to the Claude Code one:
{
"mcpServers": {
"ilens": {
"command": "C:\\path\\to\\ILens.exe",
"args": [
"--allow-root", "C:\\path\\to\\dlls"
]
}
}
}
Restart Claude Desktop after editing this file for the new server to register.
Registering ILens makes the tools available. In practice, models will still reach for ildasm via Bash, read raw .dll files, or web-search for source unless the consuming project nudges them toward ILens. A short snippet in the project's CLAUDE.md is what closes that gap.
Drop the following block into your project's CLAUDE.md and replace <allow-root> with the directory configured in .mcp.json:
## Inspecting .NET assemblies
Assemblies under `<allow-root>` are reachable through the `ilens` MCP server.
Prefer ILens tools over running `ildasm` / `ilspycmd` via Bash, reading `.dll`
files directly, or web-searching for source.
- Discovery: `search_types` (substring match), `list_types` (whole namespace),
`find_methods` (signature search).
- Reading: `summarize_type` (public surface, no bodies), `list_members`
(filtered surface), `decompile_type` (full C#), `decompile_method` (single
method body).
- Cross-references: `analyze` with `kind` set to one of `UsedBy`,
`InstantiatedBy`, `ExposedBy`, `ExtensionMethods`, `AppliedTo`,
`OverriddenBy`, `ImplementedBy`, `Uses`, `Implements`, `ReadBy`,
`AssignedBy`. Valid kinds depend on the symbol category.
Bash.search_types, a known namespace goes to list_types, a method shape goes to find_methods.summarize_type first, list_members when only part of the surface is needed, decompile_method for one method body, decompile_type only when full source is required.analyze bullet enumerates the kind enum so the model picks values the schema accepts — using one that does not apply to the symbol category produces an error like Analysis kind 'ReadBy' is not valid for Method.CLAUDE.md. The equivalent guidance goes into the Project's instructions or the first message of a conversation.
--allow-root flags are mandatory at startup; with no roots, every load attempt is rejected with "No allowed roots configured."..) are rejected before normalization as defense in depth.Nine tools, grouped by purpose. Every tool is read-only; every tool that takes an assembly parameter validates it against the configured allow-roots.
find_methods read-onlySearch the assembly for methods matching any combination of name, return type, parameter shape, declaring location, and accessibility. Type patterns match by short name or full name; generics are erased at the top level (List matches List<T>); Nullable<T> is unwrapped (int matches int?); arrays use a [] suffix.
assembly required stringnamePattern optional stringreturns optional stringbool, IEnumerable, a fully qualified name).parameterTypes optional string[]parameterCount optional int?parameterTypes is also set, the two must agree.declaringNamespace optional stringdeclaringTypePattern optional stringaccessibility optional AccessibilityFilterPublic, PublicProtected (default), All.limit optional int?A header line with the total match count followed by one line per method, each formatted as declaringType.methodName(parameters) → returnType. Sorted by declaring type, then method name. Constructors and property/event accessors are excluded; operator methods (op_*) are included. If the cap is exceeded, a trailing ... (N more matches; raise limit to see all) line is appended. With no matches, returns (no methods match).
parameterCount contradicts parameterTypes.Length.search_types for type-name search, list_members for one type's members, analyze for callers/uses of a specific method.
list_allowed_roots read-onlyList the directories from which assemblies can be loaded. Useful for an agent to discover what's reachable before issuing a path-bound call.
None.
One configured root per line. With no roots configured, returns the literal string No allowed roots configured. The server cannot load any assemblies.
None reachable from this tool.
Every other tool's assembly parameter must point inside one of these roots.
list_types read-onlyList all types in a namespace, by exact namespace match. Returns fully qualified type names sorted alphabetically.
assembly required stringnamespaceName required stringA count header followed by one fully qualified type name per line. With no matches, returns No types found in namespace '<name>'.
search_types for substring search across namespaces, summarize_type for one type's API surface.
search_types read-onlySearch for types by case-insensitive substring on the short name (the namespace is not searched). Returns up to 50 matches.
assembly required stringpattern required stringA count header followed by up to 50 fully qualified type names, sorted alphabetically. If more than 50 types match, a trailing ... (truncated, more than 50 matches) line is appended. With no matches, returns No types matching '<pattern>'.
list_types when the namespace is known, find_methods for method-shape search.
analyze read-onlyRun cross-reference analysis on a type or member. The set of valid kind values depends on the symbol category that the type or member resolves to.
UsedBy, InstantiatedBy, ExposedBy, ExtensionMethods, AppliedToUsedBy, OverriddenBy, ImplementedBy, Uses, ImplementsOverriddenBy, ImplementedByReadBy, AssignedByOverriddenBy, ImplementedByAppliedTo is meaningful only on attribute-derived types — a non-attribute type returns an empty result, not an error.
assembly required stringtypeName required stringOuter+Inner or Outer.Inner.kind required AnalysisKindmemberName optional stringparameterCount optional int?memberName resolves to a method).limit optional int?A header line of the form <Header> — <symbol> [origin]: followed by formatted result symbols, one per line. The origin tag indicates whether the resolved member was declared, inherited, or an extension method.
Analysis kind 'X' is not valid for <Category>. Valid kinds for <Category>: ...Type not found: <name>Member '<name>' not found on <type>, its base types, or as an extension method'<method>' on <context> has N overloads. Specify parameterCount to disambiguate: ...No overload of '<method>' on <context> with N parameters. Available: ...decompile_method to read the body of a method analyze reports as a caller; list_members to find candidate memberName values.
list_members read-onlyList members of a type grouped by kind (Methods, Properties, Fields, Events). One signature per line, no method bodies, no XML doc. Filter by kind, accessibility, and name; optionally walk base types up to System.Object. Cheaper than summarize_type when only part of the surface is needed.
assembly required stringtypeName required stringkinds optional MemberKind[]Method, Property, Field, Event. Omit to include all four.accessibility optional AccessibilityFilterPublic, PublicProtected (default), All.namePattern optional stringincludeInherited optional boolSystem.Object. Default false. Inherited members carry an [from <BaseType>] tag; overrides hide their bases.limit optional int?The type's full name, then one section per kind that has matches (Methods (N):, etc.) with one signature per line. If the cap truncates the output, a trailing ... (truncated, N more members; raise limit to see all) line is appended. With no matches, returns <type>: no members match the filter.
Type not found: <name>summarize_type for the full public surface in C# syntax; find_methods for cross-type method search.
summarize_type read-onlyPublic and protected API surface of a type, rendered as C#-shaped declarations with method bodies stripped. Decompiles the type, then walks the syntax tree removing non-public members and replacing bodies with semicolons. Interface members are kept regardless of modifiers.
assembly required stringtypeName required stringA multi-line C# block: the type declaration with its surviving members, each method or constructor terminated by ; instead of a body. Property and indexer accessors are present but empty. Destructors are removed.
Type not found: <name>list_members for filtered or partial surface; decompile_type for the full implementation; decompile_method for one method body.
decompile_method read-onlyDecompile a single method to C# source. Faster and more focused than decompile_type when only one method is needed.
assembly required stringtypeName required stringmethodName required stringparameterCount optional int?A header comment of the form // <Type>.<Method> [origin] followed by the decompiled method source. The origin tag is present only when the method was found through inheritance or as an extension method.
Type not found: <name>Method '<name>' not found on <type>, its base types, or as an extension method'<method>' on <context> has N overloads. Specify parameterCount to disambiguate: ...No overload of '<method>' on <context> with N parameters. Available: ...analyze with kind=UsedBy for callers; summarize_type for the surrounding type's API.
decompile_type read-onlyDecompile a whole type to C# source, including all member implementations. Use this only when the full body is needed; the response can be large.
assembly required stringtypeName required stringThe full C# source of the type as one string.
Type not found: <name>summarize_type for signature-only output; decompile_method when only one method body is needed.
Errors surface as Error: <ExceptionType>: <Message>. Common cases:
No allowed roots configured. The server cannot load any assemblies.--allow-root flags. Add at least one in .mcp.json (or via claude mcp add) and start a new session.--allow-root path does not exist: <path>Path is not under any allowed root: <path>. Allowed roots: ...Path contains '..' which is not allowed: <path>.. segment. Pass an absolute or already-normalized path.Assembly path is empty.assembly argument was missing or whitespace.Assembly file does not exist: <path>Assembly exceeds 200 MB size cap (actual: N MB): <path>Type not found: <name>search_types with a substring; for nested types, use Outer+Inner (. also works).Method '<name>' not found on <type>, its base types, or as an extension methodlist_members shows what is on the type.'<method>' on <context> has N overloads. Specify parameterCount to disambiguate: ...parameterCount; same-arity overloads still match the first found, by parameter count only.No overload of '<method>' on <context> with N parameters. Available: ...parameterCount matches none of the overloads. The error lists the available signatures.Analysis kind 'X' is not valid for <Category>. Valid kinds for <Category>: ...kind argument does not apply to the resolved symbol category. The error lists the kinds that do.parameterCount (N) contradicts parameterTypes.Length (M)find_methods: both are set but disagree. Drop one or align them.analyze result on an attribute targetAppliedTo is the only kind that runs on a non-method symbol but only fires when the type derives from System.Attribute. A non-attribute target returns an empty list, not an error.LICENSE file.third-party-licenses/manifest.json in the release ZIP for the full dependency list with license names and URLs; the third-party-licenses/ directory contains the downloaded license texts.