ILens

Version 9 — user guide

MCP server for inspecting .NET assemblies

Why ILens

An AI agent that needs to look inside a compiled .NET assembly usually shells out to ildasm (an IL dump) or ilspycmd (a full C# decompile) through a generic Bash tool. Both dump an entire file as text. That output does not evaporate once it has been read: it stays in the agent's context as input tokens, re-sent on every subsequent turn. Inspecting a single class can cost thousands of tokens, and the cost recurs for the rest of the session.

The target measured below is ICSharpCode.Decompiler.dll 9.1.0.7988 — a 2.4 MB assembly with 741 public types. A full decompile of it costs the same regardless of how narrow the actual question is, which is why the baseline column repeats.

Taskilspycmd costILens toolILens cost
List the public types in a namespace1,041,476 tokenslist_types1,196 tokens
See the API surface of one mid-sized class1,041,476 tokenssummarize_type895 tokens
Find which types expose a given method1,041,476 tokensfind_methods618 tokens

Measured against ICSharpCode.Decompiler.dll 9.1.0.7988 on 2026-08-03; token figures are character counts divided by 4, not a real tokenizer.

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.

Overview and scope

ILens is an MCP server that speaks JSON-RPC over stdio and exposes tools in four categories: discovery (finding types and methods by name or signature), inspection (API surface and cross-reference analysis), decompilation (C# source for a type, method, property, or event), and comparison (what differs between two builds). It is read-only, restricted to the directories named by --allow-root, makes no network calls, and ships as a single self-contained Windows binary.

Scope. ILens is built for inspecting C# assemblies, and its output is C#. The metadata-driven tools — list_types, search_types, list_members, find_methods, analyze — read language-agnostic metadata and work on an assembly produced by any .NET language. The decompiling tools — decompile_type, decompile_method, decompile_property, decompile_event, summarize_type — always emit C#. An assembly compiled from F#, VB.NET, or C++/CLI still decompiles, but constructs the source language has and C# does not may render unfaithfully.

Built on ILSpy

ILens would be nothing without ILSpy. The decompiler, the analyzer infrastructure, and the type system that lets ILens talk about a .NET assembly in C# terms at all are not ILens's work — they belong to the ILSpy team. ILens links ICSharpCode.Decompiler for the decompiler proper and ICSharpCode.ILSpyX for the analyzer infrastructure, and is, candidly, a thin MCP shim on top of both.

That upstream work is MIT-licensed, which is the only reason ILens can exist at all. If ILens is useful to you, please consider giving ILSpy a star. They earned it.

To be clear: bug reports and feature requests about ILens belong on the ILens issue tracker, not ILSpy's. The upstream maintainers should not have to inherit downstream problems on top of their own.

Installation

System requirements: Windows 10/11, x64. The binary is self-contained — no separate .NET runtime install is required.

Recommended: winget

winget install Tadis.ILens

A standard winget portable install. It downloads the release ZIP, extracts it under %LOCALAPPDATA%\Microsoft\WinGet\Packages\Tadis.ILens_*\, and registers ilens as a command alias in %LOCALAPPDATA%\Microsoft\WinGet\Links\ — a directory already on your user PATH if you have ever installed another winget portable package. No admin elevation is needed.

For alternative installers see Appendix A; for updating and uninstalling see Appendix B; for installation troubleshooting see Appendix C.

Integrating with Claude Code

Add an entry to .mcp.json in your project root. The command field is the literal string "ilens" — the installer puts the binary on your PATH, so no full path is needed.

{
  "mcpServers": {
    "ilens": {
      "command": "ilens",
      "args": [
        "--allow-root", "C:\\path\\to\\dlls"
      ]
    }
  }
}

A first install needs a full restart, not just a new session. Two independent gates have to clear before a freshly installed ilens actually launches as an MCP server in a Claude app session.

The first is PATH propagation. Windows captures a process's PATH from the registry when that process starts, and later registry changes do not retroactively reach already-running processes. If ilens is a brand-new entry on your user PATH — true for a first-ever winget portable install, which adds %LOCALAPPDATA%\Microsoft\WinGet\Links\, and for any install.ps1 run, which adds %LOCALAPPDATA%\Programs\ILens\ — an already-running Claude app holds a stale environment, and every Claude Code session inside it inherits that stale environment from the parent app. Subsequent winget updates of an already-installed package do not touch PATH and do not trigger this gate.

The second is MCP-server registration. Claude Code reads .mcp.json only at session start, so editing it inside a running session does not register the new server in that session.

Recovering takes both: fully exit and relaunch Claude app, so the new app process gets the current registry PATH, and then start a new Claude Code session in the relaunched app. Existing Claude Code sessions survive a Claude app restart — the relaunched app reattaches to surviving session processes with their original environments intact — so a resumed session picks up neither change. Only a freshly spawned session inside the freshly launched app clears both gates.

The recipe for a first install via the Claude app: close Claude app entirely → install via winget (or install.ps1) → relaunch Claude app → start a new Claude Code session, not a resumed one.

Each --allow-root flag adds a directory tree from which assemblies may be loaded. Without any --allow-root flags the server cannot load anything at all. Pass the flag more than once to configure several roots.

For the equivalent claude mcp add CLI command see Appendix D; for Claude Desktop see Appendix E.

Project-level guidance

Registering the server makes the tools available. It does not make Claude prefer them. Left alone, a model will still reach for ildasm through Bash, try to read the assembly file directly, or web-search for source that may not match your build. Closing that gap takes one paste-ready block in the consuming project's CLAUDE.md.

Drop the following into your project's CLAUDE.md, replacing <allow-root> with the directory you configured above:

## Inspecting .NET assemblies

Assemblies under `<allow-root>` are reachable through the `ilens` MCP server.
Prefer ILens tools over running `ildasm` / `ilspycmd` via Bash, reading assembly
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), `decompile_property` / `decompile_event` (full property or
  event declaration with accessor bodies, by unprefixed name).
- 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.
- Comparing two builds: `list_changed_types` (what differs between two
  assemblies), `compare_type` (per-type member and body diff), `compare_method`
  (one method's body as C# or IL).

Per-line walkthrough

Security model

Tool reference

Fifteen tools, all read-only. Every tool except list_allowed_roots takes an assembly path that must resolve inside a configured allow-root; the comparison tools take two. Errors listed per tool are in addition to the path and budget errors shared by every assembly-loading tool, which are collected under Troubleshooting.

Any parameter typed as an array also accepts a lone value: kinds: "Field" means ["Field"], and parameterTypes: "string" means ["string"]. This is schema-driven, so it holds for every array parameter on every tool.

Discovery

find_methods read-only

Search a whole assembly for methods matching a signature, combining any of: name substring, return type, parameter types, parameter count, declaring namespace, declaring type, and accessibility.

Parameters

assembly required string
Path to the .NET assembly to inspect (must be under an allowed root).
namePattern optional string
Case-insensitive substring filter on method name.
returns optional string
Return-type pattern, e.g. bool, IEnumerable, String.
parameterTypes optional string[]
Ordered parameter-type patterns. The method must have exactly this many parameters, each matching the pattern at its position.
parameterCount optional int
Exact parameter count. If parameterTypes is also set, the two must agree.
declaringNamespace optional string
Exact declaring namespace, e.g. System.IO.
declaringType optional string
Exact fully qualified declaring type, e.g. System.IO.File. Nested types take + or .. Mutually exclusive with declaringTypePattern.
declaringTypePattern optional string
Case-insensitive substring filter on the declaring type's short name.
accessibility optional Public | PublicProtected | All
Accessibility filter. Default PublicProtected.
limit optional int
Cap on result lines. Default 50.

Returns

A count line followed by one signature per line, sorted by declaring type then method name, or No methods match .... Type patterns match by short or full name, generics are erased at the top level, Nullable<T> is unwrapped, and arrays take a [] suffix. Constructors and property/event accessors are excluded; operators (op_*) are included.

Errors

See also

search_types for finding the type first; decompile_method to read a match's body.

list_allowed_roots read-only

List the directories from which assemblies can be loaded. Every assembly argument passed to another tool must point inside one of these.

Parameters

None.

Returns

One configured root per line, or No allowed roots configured. The server cannot load any assemblies.

Errors

None — this tool never fails.

See also

Any tool taking an assembly parameter.

list_types read-only

List every type declared in one namespace.

Parameters

assembly required string
Path to the .NET assembly to inspect (must be under an allowed root).
namespaceName required string
Namespace to list types from, e.g. System.IO.
excludeCompilerGenerated optional bool
Drop types the compiler emitted — closures, anonymous types, source-generator output. Default true.

Returns

A count line followed by fully qualified type names sorted by short name, or No types in namespace '...'.

Errors

Only the shared path and budget errors.

See also

search_types when the namespace is unknown; summarize_type to read one result's surface.

search_types read-only

Find types whose short name contains a substring, case-insensitively. The namespace is not part of the match, but results come back fully qualified so they can be fed straight into another tool.

Parameters

assembly required string
Path to the .NET assembly to inspect (must be under an allowed root).
pattern required string
Substring matched against each type's short name. Stream finds MemoryStream, FileStream, BufferedStream, and so on.
excludeCompilerGenerated optional bool
Drop compiler-emitted types. Default true.

Returns

A count line followed by fully qualified names, capped at 50 with a truncation marker, or No types match '...'.

Errors

Only the shared path and budget errors.

See also

list_types once the namespace is known; find_methods to search by signature instead of by name.

Inspection

analyze read-only

Run cross-reference analysis on a type or one of its members — who calls it, who overrides it, who implements it, what it calls. Omit memberName to analyze the type itself.

Parameters

assembly required string
Path to the .NET assembly to inspect (must be under an allowed root).
typeName required string
Fully qualified type name, e.g. System.String.
kind required enum
Which analysis to run. Valid values depend on what the symbol turns out to be:
  • TypeUsedBy, InstantiatedBy, ExposedBy, ExtensionMethods, AppliedTo, ImplementedBy
  • MethodUsedBy, OverriddenBy, ImplementedBy, Uses, Implements
  • PropertyUsedBy, ReadBy, AssignedBy, Uses, OverriddenBy, ImplementedBy
  • FieldReadBy, AssignedBy
  • EventUsedBy, OverriddenBy, ImplementedBy
memberName optional string
Member name. Omit to analyze the type itself.
parameterCount optional int
Method overload disambiguator, relevant when memberName resolves to a method.
parameterTypes optional string[]
Ordered parameter-type patterns to tell same-arity overloads apart. Same loose matching as find_methods.
limit optional int
Cap on result lines. Default 50.

Returns

A header naming the analysis and the resolved symbol, then one fully qualified reference per line, or (no results).

On a property or event the usage questions are answered through the accessors, because that is what call sites actually reference: ReadBy consults the getter, AssignedBy the setter, UsedBy both, and Uses both for the outgoing direction. On an event, UsedBy covers the add, remove, and invoke accessors and — for an ordinary field-like event — the field holding the subscriber list, so the answer includes the code that raises the event and not just the code that subscribes. The header names what was consulted, e.g. (via get_X, set_X).

Errors

See also

decompile_method to read a caller's body; find_methods for name-shaped rather than reference-shaped search.

find_harmony_dependencies read-only

Extract the full reflective surface of a Harmony-patching assembly as JSON: every patch target and every reflective field access. Built to answer "do my patches still bind on game version X?" — pair it with compare_method against two versions of the host assembly.

Parameters

assembly required string
Path to the Harmony-patching assembly to scan (must be under an allowed root).

Returns

Indented JSON of the shape { patches: [...], fieldAccesses: [...] }. Each patch carries targetType, targetMember, optional paramTypes and methodType, patchType, resolutionKind, patchClass, and patchSite. resolutionKind is one of TypedAttribute, StringTargeted, Attribute, TargetMethod, TargetMethods, or DynamicTargetMethod; each field access carries an accessor of AccessToolsField, FieldRefAccess, or TraverseField.

Telling TypedAttribute from StringTargeted requires probing the target type for member visibility, so it only fires when the target lives in the same assembly being scanned. Cross-assembly targets — the typical case for a mod patching a host game — fall back to Attribute regardless.

Errors

Only the shared path and budget errors.

See also

compare_method and list_changed_types for checking the host assembly across versions.

list_members read-only

List a type's members grouped by kind — methods, properties, fields, events — one signature per line, no bodies. Cheaper than summarize_type when only part of the surface is needed.

Parameters

assembly required string
Path to the .NET assembly to inspect (must be under an allowed root).
typeName required string
Fully qualified type name.
kinds optional ("Method" | "Property" | "Field" | "Event")[]
Member kinds to include. Omit for all four.
accessibility optional Public | PublicProtected | All
Accessibility filter. Default PublicProtected.
namePattern optional string
Case-insensitive substring filter on member name.
includeInherited optional bool
Include members declared on base types, stopping at System.Object. Default false. Inherited entries are tagged with the type they came from.
limit optional int
Cap on total result lines across all kinds. Default 100.

Returns

The type name, then a section per non-empty kind with its own count, then a truncation marker if the limit cut the listing short. When nothing matches: No members on ... match the filter.

Errors

See also

summarize_type for the whole public surface in one call; decompile_type when bodies are needed.

summarize_type read-only

Summarize the public and protected API surface of a type — signatures only, method bodies stripped. The cheapest way to see what a type offers.

Parameters

assembly required string
Path to the .NET assembly to inspect (must be under an allowed root).
typeName required string
Fully qualified type name, e.g. System.String.

Returns

A C# type declaration with its public and protected members, bodies removed.

Errors

See also

list_members for a filtered subset; decompile_type for full source.

Decompilation

decompile_event read-only

Decompile one event by its plain name and get back the full C# event declaration with its add and remove accessor bodies.

Parameters

assembly required string
Path to the .NET assembly to inspect (must be under an allowed root).
typeName required string
Fully qualified type name, e.g. System.IO.FileSystemWatcher.
eventName required string
Event name without an IL prefix, e.g. Changed.

Returns

A comment header naming the event (and the base type it was inherited from, when it was not declared on the requested type), then the decompiled declaration.

Errors

See also

decompile_method with add_X / remove_X for a single accessor; analyze with UsedBy for subscribers and raise sites.

decompile_method read-only

Decompile a single method to C#. Faster and far cheaper than decompile_type when only one body is needed.

Parameters

assembly required string
Path to the .NET assembly to inspect (must be under an allowed root).
typeName required string
Fully qualified type name, e.g. System.IO.File.
methodName required string
Method name, e.g. ReadAllText. Property and event accessors are reachable by their IL name — get_X, set_X, add_X, remove_X — even though find_methods hides them from generic browsing.
parameterCount optional int
Number of parameters, to disambiguate overloads. If parameterTypes is also given, the two must agree.
parameterTypes optional string[]
Ordered parameter-type patterns for same-arity overloads, e.g. ['int','bool']. Same loose matching as find_methods.

Returns

A comment header carrying the resolved signature — parameter types and return type, plus an inherited or extension-method note where relevant — followed by the decompiled body. The header reports what resolution actually picked, so a mismatch against what was asked for is visible without reading IL.

Errors

See also

decompile_property / decompile_event for whole declarations; analyze with UsedBy for callers.

decompile_property read-only

Decompile one property by its plain name and get back the full C# property declaration with its accessor bodies.

Parameters

assembly required string
Path to the .NET assembly to inspect (must be under an allowed root).
typeName required string
Fully qualified type name, e.g. System.IO.FileInfo.
propertyName required string
Property name without an IL prefix, e.g. Length.

Returns

A comment header naming the property (and the base type it was inherited from, when applicable), then the decompiled declaration.

Errors

See also

decompile_method with get_X / set_X for one accessor; analyze with ReadBy or AssignedBy for call sites.

decompile_type read-only

Decompile a whole type to C# source, including every method body. The most expensive read in the tool set — reach for it only when the narrower tools will not do.

Parameters

assembly required string
Path to the .NET assembly to inspect (must be under an allowed root).
typeName required string
Fully qualified type name, e.g. System.IO.File.

Returns

Full C# source for the type.

Errors

See also

summarize_type for signatures only; decompile_method for one body.

Comparison

compare_method read-only

Compare one method across two assemblies and emit each body labeled by source. When the two bodies are identical it emits a single copy with a note rather than duplicating the text.

Parameters

assemblyA required string
Path to the first assembly (must be under an allowed root).
assemblyB required string
Path to the second assembly (must be under an allowed root).
typeName required string
Fully qualified type name, e.g. System.IO.File.
methodName required string
Method name, e.g. ReadAllText.
format optional string
csharp (default) for decompiled C#, or il for the normalized IL disassembly — the same form list_changed_types and compare_type use to decide body equality.
parameterCount optional int
Number of parameters, to disambiguate overloads.
parameterTypes optional string[]
Ordered parameter-type patterns for same-arity overloads, loose-matched like find_methods.

Returns

Both bodies, each labeled with the assembly it came from — or one copy plus a note when they are identical.

Errors

See also

compare_type for the whole type; list_changed_types to find which types moved at all.

compare_type read-only

Compare one type across two assemblies: added and removed members, plus a body-changed flag per method. No C# decompilation is invoked — body equality uses normalized IL.

Parameters

assemblyA required string
Path to the first assembly (must be under an allowed root).
assemblyB required string
Path to the second assembly (must be under an allowed root).
typeName required string
Fully qualified type name, e.g. System.String.

Returns

A header stating whether the type is present in both, only in A, or only in B, then one line per change: + for an added member, - for a removed one, and ~ for a method body that changed while keeping its signature. A type with no differences reports ... is identical in both assemblies.

Errors

Only the shared path and budget errors — a type absent from both assemblies is reported, not thrown.

See also

compare_method for a changed body; list_changed_types for the assembly-wide sweep.

list_changed_types read-only

Enumerate the types that differ between two assemblies — added, removed, or changed in metadata, members, or method bodies. A pure metadata and IL walk; the C# decompiler is never invoked.

Parameters

assemblyA required string
Path to the first assembly (must be under an allowed root).
assemblyB required string
Path to the second assembly (must be under an allowed root).
namespaceFilter optional string
Restrict the sweep to one namespace, e.g. System.IO. Omit to scan the whole module.
excludeCompilerGenerated optional bool
Drop compiler-emitted types. Default true.

Returns

A count line followed by one line per type, prefixed Added:, Removed:, or Changed [...]: with the change kinds named. When nothing differs: No type changes detected.

Members are matched by name plus parameter and return types, so any signature change surfaces as a remove plus an add. Body equality is decided from IL disassembly with operand tokens resolved to symbolic names, so a body whose source did not change compares equal across rebuilds even when the metadata tokens around it shift.

Errors

Only the shared path and budget errors.

See also

compare_type to drill into one result; find_harmony_dependencies when the question is whether patches still bind.

Troubleshooting

Every tool error reaches the caller as Error: <ExceptionType>: <message>. The messages below are shared by every assembly-loading tool.

No allowed roots configured. The server cannot load any assemblies.

The server was launched without --allow-root. Add one or more --allow-root <directory> arguments to the args array in .mcp.json and start a new session. list_allowed_roots reports the same condition without throwing.

Path is not under any allowed root: ...

The requested assembly lives outside every configured root. The message lists the roots that are configured. Either point the call at a file inside one, or add the containing directory as another --allow-root.

Path resolves (via a symbolic link or junction) to a location outside any allowed root: ...

The path passed the lexical containment check but resolves to a real target outside the roots. This is the reparse-point check doing its job — a link planted inside a root cannot be used to reach outside it.

Path contains '..' which is not allowed: ...

Parent-directory segments are rejected before normalization. Pass an absolute path with no .. components.

Assembly file does not exist: ...

The path is inside a root but names no file. Check the spelling and that the build actually produced the assembly.

Assembly is N MB, which exceeds the M MB total memory budget on its own: ...

A single assembly larger than the whole budget can never fit, even with an empty cache. Raise the budget with --max-total-size <MB>.

Type not found: ...

The fully qualified name did not resolve. Nested types use + as the separator, though a dotted form is tried as a fallback. Use search_types to find the exact name.

Method '...' not found on ..., its base types, or as an extension method.

The name does not exist anywhere on the inheritance chain. find_methods with declaringType lists what the type actually has.

No overload of '...' matches ...

A parameterCount or parameterTypes hint was given and nothing satisfies it. The message lists every same-named overload found across the chain, so the right hint is usually visible in the error itself.

Member '...' is ambiguous — it exists as more than one member kind ...

The name is used by more than one kind of member on the type — obfuscated or non-C# metadata. Use a kind-specific tool (decompile_property, decompile_event) or pass an overload hint if the method is meant. An ordinary C# event does not trigger this: its compiler-generated backing field is recognized as part of the event.

Unknown argument '...' for tool '...'. Valid arguments: ...

An argument name the tool does not declare. Most ILens filters are optional, so a silently dropped filter would return an unfiltered result that looks like an answer — the call is rejected instead. The message lists the valid names.

Analysis kind '...' is not valid for ...

The kind does not apply to what analyze resolved the symbol to. The message lists the kinds that do apply to that category.

The tools do not appear at all

Almost always the two gates described under Integrating with Claude Code: an already-running Claude app holding a stale PATH, or a session that started before .mcp.json existed. Close Claude app entirely, relaunch it, and start a new session.

Appendix A: Alternative installers

For machines where winget is unavailable or locked down by policy, or when you would rather inspect the binary before granting it execution.

PowerShell one-liner

irm https://raw.githubusercontent.com/tadis174/ILens/main/install.ps1 | iex

Installs to %LOCALAPPDATA%\Programs\ILens\ and adds that directory to your user PATH. No admin elevation is needed.

Manual ZIP

Download ILens-windows-x64.zip from the Releases page and extract it anywhere. Then either add the directory to PATH yourself, or give the binary's full path in .mcp.json's command field.

Appendix B: Updating and uninstalling

winget. winget upgrade Tadis.ILens to update; winget uninstall Tadis.ILens to remove.

PowerShell one-liner. Re-run irm https://raw.githubusercontent.com/tadis174/ILens/main/install.ps1 | iex to update — the script overwrites the install directory in place and stops any running ILens processes first. Uninstall with irm https://raw.githubusercontent.com/tadis174/ILens/main/uninstall.ps1 | iex.

Manual ZIP. No automatic update mechanism — re-download ILens-windows-x64.zip and re-extract on each new release. To uninstall, delete the extracted folder and remove the PATH entry if you added one.

Appendix C: Installation troubleshooting

Antivirus or Windows Defender quarantines ILens.exe

Self-contained .NET binaries are sometimes false-positive-flagged on first sight. Check the quarantine and restore the file, or add an exception for the install directory — %LOCALAPPDATA%\Microsoft\WinGet\Packages\Tadis.ILens_*\ for winget, %LOCALAPPDATA%\Programs\ILens\ for the PowerShell script — before re-running the installer.

SmartScreen shows "Windows protected your PC"

This affects browser-downloaded ZIP installs, which carry Mark-of-the-Web. The winget and PowerShell paths do not. For a manual browser download, click "More info → Run anyway".

winget upgrade Tadis.ILens fails with remove: Access is denied: "...\ILens.exe"

A running ILens MCP server — a Claude Code session, Claude Desktop, or another AI client that registered ILens — holds an exclusive lock on the binary winget is trying to replace. Close every AI client with ILens registered and re-run the upgrade. The PowerShell install.ps1 path handles this automatically by stopping running ILens processes before extraction, so the same upgrade via irm ... | iex needs no manual cleanup. winget's portable installer type has no pre-install hook where an equivalent step could run.

Appendix D: claude mcp add CLI

A programmatic alternative for anyone who would rather not edit .mcp.json by hand:

claude mcp add ilens ilens -- --allow-root "C:\path\to\dlls"

The doubled ilens is intentional: the first is the MCP server alias — the same string that becomes the JSON key in .mcp.json — and the second is the binary on PATH after install. The -- separator before --allow-root is required so claude mcp add does not consume it as one of its own options.

Appendix E: Claude Desktop

Claude Desktop reads %APPDATA%\Claude\claude_desktop_config.json. The entry is equivalent to the Claude Code one:

{
  "mcpServers": {
    "ilens": {
      "command": "ilens",
      "args": [
        "--allow-root", "C:\\path\\to\\dlls"
      ]
    }
  }
}

Claude Desktop has no per-project CLAUDE.md, so paste the snippet from Project-level guidance into your Claude Desktop Project's custom instructions instead.

Version, source, license

This guide documents ILens 9. Source and issue tracker: github.com/tadis174/ILens.

ILens is released under the MIT License; the full text ships as LICENSE beside this guide in the release ZIP. Every third-party dependency and bundled runtime component is mapped to its upstream license in third-party-licenses/INDEX.md, included in the same ZIP alongside the per-source license texts.