Skip to main content
Glama
impact-mcp
by 0xmortuex
README.md
# impact-mcp

**Ask "what depends on this file, and what breaks if I change it?" and get an answer that tells you how much to trust it.**

<!-- mcp-name: io.github.0xmortuex/impact-mcp -->

impact-mcp is an [MCP](https://modelcontextprotocol.io) server that builds the import graph of a
Python and/or JavaScript/TypeScript project and answers blast-radius questions: who imports this
file, who uses this one symbol, which tests reach it, which entry points are downstream, where the
cycles are, what looks dead.

**What is different from the other code-graph MCP servers** (codegraph-mcp, code-impact-mcp,
ts-review-graph, dep-graph-guard and friends):

- **Zero extra dependencies and fully offline.** Only `mcp`. Python is read with the stdlib `ast`; JS/TS
  with a small hand-written tokenizer. No Node, no TypeScript compiler, no ts-morph, no SQLite, no
  network, nothing executed, nothing written inside your repo.
- **It tells you what it could not see.** Every answer ends with a `Graph confidence` line: resolved
  edges, external packages, **unresolved project-looking imports, dynamic non-literal imports,
  syntax-error files**. Dynamic `importlib` / `import(x)` / `require(x)` is reported as UNRESOLVED, never
  guessed. `impact` also warns when unresolved imports end in the name of the file you are about to change.
- **Python and JS/TS resolution that matches how projects are really laid out:** `src/` layouts,
  namespace packages, `__init__.py` re-exports and `__all__`, `TYPE_CHECKING`, tsconfig `extends` /
  `baseUrl` / `paths`, monorepo workspace packages, `.js` -> `.ts` ESM style imports, `index.ts`
  directories, `export * from`, `import type`.
- **Symbol-level answers that follow re-exports:** `symbol_users("pkg/core.py", "Engine")` also finds the
  file that imports `Engine` from the package `__init__.py` that re-exports it.

## What it looks like

```
> impact path=mort/lexer.py
Impact of changing mort/lexer.py
Direct dependents: 2; transitive: 10; max depth: 4
  mort/lsp.py  -- line 10: from-import {Lexer}
  mortc.py [entry]  -- line 36: from-import {Lexer}
Tests that reach it through imports (4) - a superset to run, not proof of coverage:
  kernel/test_exec.py
  tests/test_mort.py
  ...
Entry points affected (4):
  mortc.py  (console script in pyproject.toml)
  ...
Risk: HIGH
  - 10 transitive dependent file(s) = 31% of 32 files
  - 4 entry point(s) are downstream (a break can surface at startup)
Graph confidence: 32 files (32 py, 0 js/ts) | 52 resolved internal edges | external packages not tracked: 4 (+37 stdlib/builtin) | UNRESOLVED project-looking imports: 0 | dynamic non-literal imports: 0 | syntax-error files: 0
```

## Install

You need [uv](https://docs.astral.sh/uv/). Point `--dir` at the project you want analyzed.

**Claude Code**

```
claude mcp add impact -- uvx impact-mcp --dir /path/to/your/project
```

**Claude Desktop** (`claude_desktop_config.json`)

```json
{
  "mcpServers": {
    "impact": {
      "command": "uvx",
      "args": ["impact-mcp", "--dir", "C:\\path\\to\\your\\project"]
    }
  }
}
```

`--dir` defaults to the current directory (or `IMPACT_MCP_DIR`). One server instance analyzes one project.

## Tools

| Tool | What it answers |
|---|---|
| `project_map` | Languages, module count, packages, guessed entry points, cycle count, unresolved/dynamic imports. Start here. |
| `dependents(path_or_module, depth=3, limit=100)` | Who imports this file, transitively, grouped by depth, with the import line, names and flags. |
| `dependencies(path_or_module, depth=1, limit=100)` | What this file imports from the project; lists its unresolved and dynamic imports. |
| `impact(path, symbol=None, limit=40)` | Blast radius: dependents, tests that reach it, entry points affected, LOW/MEDIUM/HIGH risk with reasons. With `symbol`, the direct users are narrowed to that name. |
| `find_cycles(limit=10)` | Strongly connected components (Tarjan) with the shortest cycle written out edge by edge; notes lazy/type-only edges that soften a cycle. |
| `orphans_and_hubs(limit=15)` | Dead-code candidates (with caveats) and the most depended-on files. |
| `symbol_users(module, symbol, limit=100)` | Who imports or accesses one name, including through re-exports. Best effort, labeled as such. |

`path_or_module` accepts a project-relative path (`src/app/util.py`, `packages/ui/src/Button.tsx`), a
dotted Python module (`app.util`), an absolute path inside the project, or an extensionless JS path.

## Try asking

- "I'm about to change `src/billing/invoice.py`. What could break and which tests should I run?"
- "Who uses `parse_config` from `app.config`?"
- "Are there import cycles in this repo? Show me the shortest one."
- "What are the most depended-on files here? Which files look dead?"
- "Which entry points are affected if I rename `Button` in `packages/ui/src/Button.tsx`?"
- "Give me an overview of this project and how much I can trust the import graph."

## What it cannot resolve (read this)

The graph is static. These are counted in the confidence line where they are detectable, and the
`impact` and `dependents` add hints when they could be hiding a dependent of the file you asked about.

Python
- `importlib.import_module(name)` / `__import__(name)` with a non-literal argument, `exec`, plugin
  discovery by entry points or string names, `getattr` on modules. A literal `import_module("pkg.mod")` is resolved and flagged `dynamic`.
- Implicit parent-package execution is not modeled: `import a.b.c` is an edge to `a/b/c.py` only, not also to `a/__init__.py` and `a/b/__init__.py`.
- Which directories act as import roots is inferred (project root, `src/`, dirs with `pyproject.toml`/`setup.py`, and the dir above each top-level package); a custom `PYTHONPATH` or `sys.path` edit is invisible.
- `from x import *` is an edge; which names it brings in is taken from `__all__` / top-level definitions.
- Re-exports renamed with `as` inside `__init__.py` are not followed by symbol tracking.

JavaScript / TypeScript
- A tokenizer, not a parser. Known weak spots: JSX text containing an apostrophe can desync the rest of that line; regex-vs-division is heuristic.
- `require(x)` / `import(x)` with a non-literal argument, `require.context`, bundler magic (`import.meta.glob`), Next.js/Nuxt file-system routing.
- tsconfig: `extends` to an npm package is not followed (warned); `references`/`include` are ignored; the nearest tsconfig.json wins.
- package.json `exports` is read only for the `.` entry and simple subpaths (conditions: import, module, default, require, node, types); `imports` (`#alias`) maps are reported as unresolved.
- Workspace packages whose entry is a build output (`dist/`) resolve to `src/` equivalents if the source file exists; otherwise the import counts as unresolved.
- Namespace-import and `require` attribute tracking (`ns.foo`) is by name only; a namespace passed around as a value marks the edge as "may use anything".
- Only `.js .jsx .mjs .cjs .ts .tsx .mts .cts` files are in the graph; `.vue`, `.svelte`, `.astro` are not.
- Files over 2 MB (bundles) are skipped and reported.

Both
- Symlinks and junctions are not followed (loop safe); skipped counts are reported.
- On Windows and macOS (case-insensitive file systems) an import that only matches by case resolves and is counted.
- Dead-code candidates are candidates: frameworks, external consumers of a library and non-code tooling can use files the graph cannot see.

## Performance

Parsed results are cached by file mtime and size in a cache directory outside your repo
(`%LOCALAPPDATA%\impact-mcp` on Windows, `$XDG_CACHE_HOME/impact-mcp` or `~/.cache/impact-mcp` elsewhere;
override with `IMPACT_MCP_CACHE_DIR`). A cold scan of a 2,700-file JS repo takes a few seconds; a warm scan is
dominated by the directory walk. File reading is threaded; `.gitignore` files (nested too), `.git`,
`node_modules`, `venv`, `.venv`, `dist`, `build`, `__pycache__` and similar directories are skipped.

## Security

- **Read-only.** Source files are opened for reading and parsed; nothing is imported, executed, installed or written inside the project. The only write is the parse cache, in your user cache directory.
- **Sandboxed.** All paths are resolved against `--dir`; absolute paths or `..` that leave it are rejected. Symlinks are not followed.
- **Offline.** No network access of any kind.
- Output contains file paths, line numbers, and import names from your code, which go to whatever AI client you connected.

## Develop

```
uv sync --extra test
uv run pytest
uv run --with ruff==0.16.0 ruff check src tests
uv run --with mypy==2.3.0 mypy --strict src
```

Set `IMPACT_TEST_REPOS` (paths separated by `;` on Windows, `:` elsewhere) to also run read-only checks against your own repositories.

There is a from-scratch tutorial that builds a minimal version of this server:
[impact-mcp-the-hard-way](https://github.com/0xmortuex/impact-mcp-the-hard-way).

MIT licensed.

TDQS

A3.6/5.0

Scored across 7 tools

Disambiguation4/5

Each tool targets a distinct analysis: overview, cycles, forward imports, reverse imports, blast radius, hub/orphan stats, and symbol-level usage. The main risk is `dependents` vs `impact` (impact largely subsumes dependents' output plus tests and risk) and the near-homophone pair `dependents`/`dependencies`, which an agent could swap despite direction being stated in the text.

Naming Consistency3/5

Names are all lowercase snake_case, but grammatical form is mixed: verb phrases (`find_cycles`), bare nouns (`dependents`, `dependencies`, `impact`), and a conjoined noun (`orphans_and_hubs`). The pair `dependents`/`dependencies` is only one character-apart in semantics and spelling, which is a real readability hazard even though no convention is violated outright.

Tool Count5/5

Seven tools is well-scoped for an import-graph/impact-analysis server, with one tool per analysis question rather than redundant variants. Nothing feels padded or missing at the count level.

Completeness4/5

The surface covers the full lifecycle of impact analysis: orient (project_map), forward/reverse graph queries, cycles, dead code/hubs, symbol usage, and a risk-rated blast radius including affected tests and entry points. Minor gaps: no direct way to locate a file by fuzzy name or browse the test-to-source mapping outside `impact`, and no handling of dynamic/reflective edges beyond flagging them.

Maintenance

ActivityMaintained
ResponsivenessNo recent community issues