Metadata-Version: 2.4
Name: package-upgrade-mcp
Version: 0.1.1
Summary: MCP server that scans package.json/pubspec.yaml/Cargo.toml/requirements.txt, flags risky upgrades via static semver/deprecation analysis, and surfaces pre-filtered breaking-change excerpts for LLM-driven migration planning
Keywords: mcp,model-context-protocol,dependencies,upgrade,npm,pypi,cargo,pub
Author: Hasil T
Author-email: Hasil T <contact.hasil@gmail.com>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Dist: httpx>=0.28.1
Requires-Dist: mcp[cli]>=1.28.1
Requires-Dist: packaging>=25.0
Requires-Dist: pydantic>=2.13.4
Requires-Dist: pydantic-settings>=2.14.2
Requires-Dist: pyyaml>=6.0.2
Requires-Python: >=3.11
Project-URL: Homepage, https://github.com/Hasilt/package-upgrade-mcp
Project-URL: Repository, https://github.com/Hasilt/package-upgrade-mcp
Project-URL: Issues, https://github.com/Hasilt/package-upgrade-mcp/issues
Description-Content-Type: text/markdown

# package-upgrade-mcp

<!-- mcp-name: io.github.Hasilt/package-upgrade-mcp -->

An MCP server that helps an LLM plan dependency upgrades without drowning in
changelog text. Point it at a manifest file and it tells you what's
outdated, how risky each upgrade looks, and — only for the packages worth
worrying about — pulls the actual breaking-change notes.

Supported manifests: `package.json` (npm), `requirements.txt` (PyPI),
`pubspec.yaml` (pub.dev), `Cargo.toml` (crates.io).

No API keys required — every registry it talks to (npm, PyPI, pub.dev,
crates.io, GitHub) is public.

## Why it's split into stages

Fetching full release notes for every dependency in a real project would
burn a huge amount of context for basically no benefit, since most upgrades
are patch/minor bumps with nothing breaking in them. So the server does the
boring, deterministic part itself and only reaches for text when it's
actually warranted:

1. **Cheap and always called** — `scan_manifest` / `check_upgrades` return
   current vs. latest version, the semver delta, and a risk level computed
   from version math plus registry metadata (deprecated/yanked flags). No
   changelog text, just one registry lookup per dependency.
2. **Targeted, only for what's flagged** — `get_breaking_change_excerpts`
   fetches changelog or release-note text and runs it through a heuristic
   filter that keeps the parts that look like breaking-change
   announcements (`## Breaking`, `BREAKING CHANGE:`, removed/renamed/
   deprecated notes, migration guides) and throws away routine feature/fix
   entries. `get_raw_changelog` is there too, for the rare case the
   filtered excerpt isn't enough.

The server never decides "this will take 3 days" or writes the upgrade plan
itself — that synthesis is left to whichever LLM is calling it. Its job
ends at handing over small, pre-digested findings instead of a wall of
release notes.

## Install

With [Claude Code](https://docs.claude.com/en/docs/claude-code):

```bash
claude mcp add package-upgrade-mcp -- uvx package-upgrade-mcp
```

With [OpenAI Codex CLI](https://github.com/openai/codex):

```bash
codex mcp add package-upgrade-mcp -- uvx package-upgrade-mcp
```

With [Gemini CLI](https://github.com/google-gemini/gemini-cli):

```bash
gemini mcp add package-upgrade-mcp -- uvx package-upgrade-mcp
```

Or add it to your MCP config by hand with [`uvx`](https://docs.astral.sh/uv/guides/tools/)
(no clone or install step — `uvx` fetches the package from PyPI on first run):

```json
{
  "mcpServers": {
    "package-upgrade-mcp": {
      "command": "uvx",
      "args": ["package-upgrade-mcp"]
    }
  }
}
```

> This package hasn't been published to PyPI yet, so `uvx package-upgrade-mcp`
> won't resolve until it is. Until then, run it from a clone — see
> **Developing locally** below, it takes about a minute.

Everything above is optional to configure — the server works with zero
setup. If you want to raise GitHub's unauthenticated rate limit (60
requests/hour, which you can hit if you check a lot of packages in one
session) or tune HTTP timeouts and cache TTL, copy `.env.example` to `.env`
and fill in what you need; see the table in **Configuration** below.

## Tools

| Tool | Stage | What it does |
|---|---|---|
| `scan_manifest(path)` | 1 | Parse a manifest into a flat dependency list. Pure parsing, no network. |
| `check_upgrades(path)` | 1 | Scan + resolve latest versions + static risk analysis (semver delta, deprecated/yanked). |
| `get_versions_between(ecosystem, package, from_version, to_version)` | 2 | List every published version in a range. |
| `get_breaking_change_excerpts(ecosystem, package, from_version, to_version)` | 2 | Fetch changelogs and return only the excerpts that look breaking. |
| `get_raw_changelog(ecosystem, package, from_version, to_version)` | 2 | Full, unfiltered changelog text (opt-in fallback). |

`ecosystem` is one of `npm`, `pypi`, `pub`, `cargo`.

## Usage

You don't call these tools directly — you talk to your agent and it reaches
for them based on what you ask. A few examples of what that looks like in
practice:

**"What's outdated in this project, and how risky does it look?"** → triggers
`scan_manifest` + `check_upgrades` against your `package.json` (or whichever
manifest you point it at). A real run against a small project looked like
this:

```
check_upgrades("package.json") →
  left-pad     1.3.0 → 1.3.0   delta=none   risk=medium  (deprecated)
  lodash       4.17.21 → 4.18.1 delta=minor  risk=medium
  jest         29.0.0 → 30.4.2  delta=major  risk=high
  my-local-pkg (file: dependency, skipped — not resolvable against a registry)
```

**"What actually broke between jest 29 and 30 that could affect my tests?"**
→ once a package is flagged risky, this triggers
`get_breaking_change_excerpts("npm", "jest", "29.0.0", "30.4.2")`, which
pulls GitHub release notes and hands back just the migration-relevant bits
instead of every release's full changelog.

**"Show me the raw changelog for X, the summary you gave me wasn't enough"**
→ falls back to `get_raw_changelog` for the same version range.

A couple of things worth knowing:

- `check_upgrades` never fetches changelog text — it's the cheap first pass.
  Point the agent at specific packages afterward if you want detail; asking
  it to explain every dependency up front will make it fan out to
  `get_breaking_change_excerpts` for all of them, which is slower and uses
  more context than necessary.
- Dependencies pinned via `file:`, `path:`, `git:`, or `workspace:` specs
  show up in `scan_manifest`/`check_upgrades` but are marked unresolvable —
  there's no registry to check them against.
- Results are cached in memory for the life of the server process, so
  asking about the same package twice in one session doesn't refetch it.

## Configuration

All optional, all in `.env.example`:

| Variable | Default | What it's for |
|---|---|---|
| `REQUEST_TIMEOUT_SECONDS` | `15.0` | HTTP timeout for registry/changelog requests |
| `CACHE_TTL_SECONDS` | `3600` | How long registry/changelog lookups stay cached |
| `HTTP_USER_AGENT` | `package-upgrade-mcp (contact.hasil@gmail.com)` | Sent on every request — crates.io in particular rejects requests without one |
| `GITHUB_TOKEN` | unset | Optional; raises GitHub's release-notes rate limit above the unauthenticated 60/hr |

## Developing locally

```bash
uv sync
cp .env.example .env   # optional
uv run package-upgrade-mcp
```

Point your MCP config at the local checkout instead of `uvx`:

```json
{
  "mcpServers": {
    "package-upgrade-mcp": {
      "command": "uv",
      "args": [
        "--directory",
        "/absolute/path/to/package-upgrade-mcp",
        "run",
        "package-upgrade-mcp"
      ]
    }
  }
}
```

## Development

```bash
uv run pytest -v            # test suite (all HTTP mocked, no network needed)
uv run ruff check .          # lint
uv run ruff format .         # format
uv run mypy src              # type check
```

To poke at it against real registries instead of through an agent
conversation, use the [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector):

```bash
npx @modelcontextprotocol/inspector uv run package-upgrade-mcp
```
