Skip to content

Contributing

  • Python 3.10+ — mcilspy uses str | None union syntax and other 3.10+ features
  • uv — for dependency management and running the project (install uv)
  • ilspycmd (optional) — only needed if you are working on decompilation tools. Metadata tools and tests that mock the subprocess work without it.
  1. Clone the repository

    Terminal window
    git clone https://git.supported.systems/MCP/mcilspy.git
    cd mcilspy
  2. Install in development mode

    Terminal window
    uv pip install -e ".[dev]"

    This installs mcilspy in editable mode along with dev dependencies: pytest, pytest-asyncio, and ruff.

  3. Verify the install

    Terminal window
    mcilspy --help

    Or run directly:

    Terminal window
    uv run mcilspy

The test suite uses pytest with async support via pytest-asyncio.

Terminal window
# Run all tests
pytest
# Run with verbose output
pytest -v
# Run a specific test file
pytest tests/test_models.py
# Run tests matching a pattern
pytest -k "test_decompile"

Tests are configured in pyproject.toml:

[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"

The asyncio_mode = "auto" setting means you do not need to decorate async test functions with @pytest.mark.asyncio — pytest-asyncio detects them automatically.

mcilspy uses ruff for both linting and formatting.

Terminal window
# Check for lint issues
ruff check src/
# Auto-fix what can be fixed
ruff check --fix src/
# Format code
ruff format src/

The ruff configuration in pyproject.toml:

[tool.ruff]
target-version = "py310"
line-length = 100
src = ["src"]
[tool.ruff.lint]
select = ["E", "F", "W", "I", "UP", "B", "SIM"]
ignore = ["E501"]
[tool.ruff.format]
quote-style = "double"
mcilspy/
src/mcilspy/
__init__.py # Package version
__main__.py # python -m mcilspy entry point
server.py # FastMCP tool definitions (16 tools, 2 prompts)
ilspy_wrapper.py # Async subprocess wrapper for ilspycmd
metadata_reader.py # dnfile-based PE metadata parsing
il_parser.py # Method extraction from IL/C# output
models.py # Pydantic request/response models
constants.py # Timeouts, limits, configuration
utils.py # PATH discovery helpers
tests/
test_models.py
test_il_parser.py
test_ilspy_wrapper.py
test_server.py
...
pyproject.toml
README.md
LICENSE

Async by default. All tool functions in server.py are async. The ilspy_wrapper uses asyncio.create_subprocess_exec for non-blocking subprocess management.

Pydantic for validation. Request parameters are validated through Pydantic models in models.py. Add new fields with sensible defaults to maintain backward compatibility.

No stdout prints. MCP uses stdin/stdout for the JSON-RPC transport. Any print() to stdout corrupts the protocol stream. Use logger (which writes to stderr) for diagnostics. The main() function should only call mcp.run().

Error consistency. Use the _format_error() helper in server.py for all error responses. The format is **Error** (context): description.

Constants centralized. Timeouts, output limits, and search limits live in constants.py. Do not hardcode magic numbers in tool functions.

You can test your local changes with Claude Code without publishing:

Terminal window
claude mcp add mcilspy-local -- uv run --directory /path/to/mcilspy mcilspy

This starts the MCP server from your local source tree. Changes to the source are picked up on the next server restart.