Skip to content
Conduit

mcp server

legacy-modernizer-mcp

MCP server that scans a Python 2 / Django codebase and turns it into a prioritized, phased Python 3 migration plan. 52 rules, read-only static analysis.

ErenOzgu/legacy-modernizer-mcpcommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

Before you start

  • uv (`curl -LsSf https://astral.sh/uv/install.sh | sh`)
~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "legacy-modernizer-mcp": {
      "command": "uvx",
      "args": [
        "legacy-modernizer-mcp"
      ]
    }
  }
}

same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12

Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs

README

synced Oct 26, 2026

Quick start

With uv installed, one command adds the server to Claude Code:

claude mcp add legacy-modernizer -- \
  uvx --from git+https://github.com/ErenOzgu/legacy-modernizer-mcp \
  legacy-modernizer-mcp

Then ask:

Use legacy-modernizer to plan the Python 3 migration of ~/work/my-old-django-app.

Claude Desktop, pip and plain-CLI setups are in Install.

What you get

CapabilityDetails
A map of the damage52 rules (31 Python 2 → 3, 20 Django, 1 template) with file and line, a fix for each, and the Django release that removed each API.
A ranked list of where to startFiles scored by weighted findings, so you know which five files hold most of the work.
An ordered planSix phases from safety net to clean-up, with the Django APIs that block each LTS upgrade and a checklist for what static analysis cannot see.
A dependency checkDetects the pinned Django version, flags Python 2-only packages (MySQL-python, PIL, South, ...) and unpinned requirements.
Safe by constructionReads source files as text. Never imports, executes or modifies the code it scans; skips symlinks, virtualenvs, .git, node_modules and files over 1 MB.

Findings are heuristic (regular expressions over source with comments and string contents masked out), not a parse tree. Counts are a lower bound; confirm each finding in context. See Limitations.

Example

The plan it produces is organised in six phases. Python and Django move in separate steps, so you are never changing both at once:

Tools

ToolWhat it does
generate_migration_plan(path)Full plan: snapshot, hot-spot files, ordered phases, blocking Django APIs, dependency notes, manual checklist. Start here.
scan_python2_issues(path, max_findings=100)Python 2 constructs that break or change behaviour on Python 3, grouped by rule with file:line and fix.
scan_django_legacy(path, max_findings=100)Django APIs removed in newer releases, each tagged with the release that removed it.
analyze_requirements(path)Reads requirements*.txt, flags problem packages, detects the Django version.
list_rules(category="")The rule catalogue (python2, django, template).
explain_rule(rule_id)What a rule flags, why it matters, how to fix it.

All tools are annotated readOnlyHint=true, so clients can run them without a confirmation prompt.

Install

Requires Python 3.11+. The easiest way is uv, which runs the server straight from GitHub with no manual install:

uvx --from git+https://github.com/ErenOzgu/legacy-modernizer-mcp legacy-modernizer-mcp --version

or with pip:

pip install git+https://github.com/ErenOzgu/legacy-modernizer-mcp
claude mcp add legacy-modernizer -- \
  uvx --from git+https://github.com/ErenOzgu/legacy-modernizer-mcp \
  legacy-modernizer-mcp

Add this to claude_desktop_config.json and restart the app:

{
  "mcpServers": {
    "legacy-modernizer": {
      "command": "uvx",
      "args": [
        "--from",
        "git+https://github.com/ErenOzgu/legacy-modernizer-mcp",
        "legacy-modernizer-mcp"
      ]
    }
  }
}
legacy-modernizer-mcp plan path/to/project   # print a migration plan
legacy-modernizer-mcp rules                  # print the rule catalogue
legacy-modernizer-mcp                        # no arguments: run the MCP server on stdio

Rules

52 built-in rules. Run legacy-modernizer-mcp rules or the explain_rule tool for the explanation and fix of each one.

Python 2 → 3

RuleSeverityWhat it flagsRemoved in
PY2-PRINT-STMTerrorprint statement—
PY2-EXCEPT-COMMAerrorexcept X, e—
PY2-RAISE-COMMAerrorraise E, message—
PY2-HAS-KEYerrordict.has_key()—
PY2-DICT-ITERerroriteritems / iterkeys / itervalues / view*—
PY2-XRANGEerrorxrange()—
PY2-UNICODEerrorunicode type / builtin—
PY2-BASESTRINGerrorbasestring—
PY2-LONGerrorlong type / builtin—
PY2-LONG-LITERALerrorlong integer literal (10L)—
PY2-OCTALerrorold octal literal (0755)—
PY2-REMOVED-BUILTINerrorremoved builtin—
PY2-REDUCEwarningreduce() builtin—
PY2-NEXT-METHODwarningiterator.next()—
PY2-BACKTICKerrorbacktick repr—
PY2-NE-OPERATORerror<> operator—
PY2-EXEC-STMTerrorexec statement—
PY2-METACLASSwarningmetaclass attribute—
PY2-CMP-ARGerrorsort(cmp=...)—
PY2-MAXINTerrorsys.maxint—
PY2-STRING-MODULEerrorlegacy string module functions—
PY2-OLD-IMPORTerrorrenamed standard-library module—
PY2-ITERTOOLSerroritertools.izip / imap / ifilter—
PY2-TUPLE-PARAMSerrortuple parameter unpacking in def—
PY2-LAMBDA-TUPLEerrortuple parameter unpacking in lambda—
PY2-DICT-VIEW-INDEXerrorindexing or sorting a dict view—
PY2-LEN-ITERATORerrorlen(map/filter/zip(...))—
PY2-DUNDER-METHODSwarningPython 2-only special methods—
PY2-GETCWDUerroros.getcwdu()—
PY2-SETDEFAULTENCODINGwarningsys.setdefaultencoding hack—
PY2-UR-PREFIXerrorur'' string prefix—

Django

RuleSeverityWhat it flagsRemoved in
DJ-PATTERNSerrorurlpatterns built with patterns()1.10
DJ-URL-STRING-VIEWerrorview referenced by dotted string in url()1.10
DJ-URLS-DEFAULTSerrordjango.conf.urls.defaults1.6
DJ-ADMIN-URLS-INCLUDEerrorinclude(admin.site.urls)2.0
DJ-RENDER-TO-RESPONSEerrorrender_to_response3.0
DJ-CONTEXT-INSTANCEerrorcontext_instance=RequestContext(...)1.10
DJ-URLRESOLVERSerrordjango.core.urlresolvers2.0
DJ-CONTEXT-PROCESSORSerrordjango.core.context_processors1.10
DJ-MIDDLEWARE-CLASSESerrorMIDDLEWARE_CLASSES setting2.0
DJ-TEMPLATE-SETTINGSerrorTEMPLATE_* settings1.10
DJ-SOUTHerrorSouth migrations—
DJ-FK-ON-DELETEerrorForeignKey / OneToOneField without on_delete2.0
DJ-PERMALINKerrormodels.permalink2.1
DJ-IS-AUTH-CALLerroruser.is_authenticated()2.0
DJ-SIXerrordjango.utils.six3.0
DJ-PY2-TEXT-HELPERSwarningpython_2_unicode_compatible / force_unicode / smart_unicode—
DJ-SORTEDDICTerrordjango.utils.datastructures.SortedDict1.9
DJ-SIMPLEJSONerrordjango.utils.simplejson—
DJ-ASSIGNMENT-TAGerrorregister.assignment_tag2.0
DJ-GET-MODELerrordjango.db.models.get_model / get_models / get_app1.9

Django templates

RuleSeverityWhat it flagsRemoved in
TPL-LOAD-FROM-FUTUREerror{% load ... from future %}1.9

How it works

flowchart LR
    A["Your project<br/>(read-only)"] --> B["Mask comments<br/>and strings"]
    B --> C["52 rules<br/>regex + small checks"]
    C --> D["Findings<br/>file:line, fix"]
    D --> E["Report and plan<br/>(Markdown)"]
    E --> F["6 MCP tools"]
    F --> G["Claude or any<br/>MCP client"]
  1. scanner.py walks the project and, for each file, builds a masked copy of the source in which comments are blanked and string contents are replaced by filler (same length, same newlines), so print "x" inside a docstring is not a finding but the real thing is. Rules that need string text (for example 'south' in INSTALLED_APPS) run on a variant that keeps strings.
  2. rules.py is a declarative table of Rule objects (id, severity, explanation, fix, pattern). One rule, DJ-FK-ON-DELETE, uses a small custom check that reads the call's arguments so ForeignKey(User, models.CASCADE) is not reported.
  3. requirements.py, plan.py and report.py turn findings into the requirements analysis, the plan and the Markdown that the tools return.

Limitations

  • Heuristic, not a parser. No data-flow, no type information. Constructs split in unusual ways across lines or built dynamically can be missed; some patterns (.keys()[0], len(map(...))) can in rare cases be false positives. Treat counts as a lower bound.
  • Text/bytes bugs are invisible. The hardest part of a Python 3 migration (implicit str/bytes conversions, I/O encodings, pickles) cannot be found statically; the plan ends with a checklist for it.
  • Dependency notes are a short built-in list, not a compatibility database.
  • Not a rewriter. It tells you what and in which order. For mechanical rewrites use tools such as python-modernize or futurize, and review the diffs.
  • Only requirements*.txt files are read (no Pipfile, pyproject.toml or setup.py yet).

Development

git clone https://github.com/ErenOzgu/legacy-modernizer-mcp
cd legacy-modernizer-mcp
pip install -e ".[dev]"
pytest

Adding a rule: append a Rule(...) to the right list in src/legacy_modernizer_mcp/rules.py, add a positive and a negative example in tests/test_rules.py (a test fails if a rule has no positive example).

Roadmap

  • 52 rules, requirements analysis, migration plan, CI
  • pyproject.toml / Pipfile / setup.py dependency parsing
  • Per-file explain_finding with surrounding context
  • Optional diff-style fix suggestions for the mechanical rules
  • SARIF output for CI use

Author

Eren Özgü (@ErenOzgu), built with Claude Code.

License

MIT

↑↓ navigate openesc close