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
Project description
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:
- Cheap and always called —
scan_manifest/check_upgradesreturn 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. - Targeted, only for what's flagged —
get_breaking_change_excerptsfetches 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_changelogis 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:
claude mcp add package-upgrade-mcp -- uvx package-upgrade-mcp
With OpenAI Codex CLI:
codex mcp add package-upgrade-mcp -- uvx package-upgrade-mcp
With Gemini CLI:
gemini mcp add package-upgrade-mcp -- uvx package-upgrade-mcp
Or add it to your MCP config by hand with uvx
(no clone or install step — uvx fetches the package from PyPI on first run):
{
"mcpServers": {
"package-upgrade-mcp": {
"command": "uvx",
"args": ["package-upgrade-mcp"]
}
}
}
This package hasn't been published to PyPI yet, so
uvx package-upgrade-mcpwon'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_upgradesnever 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 toget_breaking_change_excerptsfor all of them, which is slower and uses more context than necessary.- Dependencies pinned via
file:,path:,git:, orworkspace:specs show up inscan_manifest/check_upgradesbut 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
uv sync
cp .env.example .env # optional
uv run package-upgrade-mcp
Point your MCP config at the local checkout instead of uvx:
{
"mcpServers": {
"package-upgrade-mcp": {
"command": "uv",
"args": [
"--directory",
"/absolute/path/to/package-upgrade-mcp",
"run",
"package-upgrade-mcp"
]
}
}
}
Development
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:
npx @modelcontextprotocol/inspector uv run package-upgrade-mcp
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file package_upgrade_mcp-0.1.1.tar.gz.
File metadata
- Download URL: package_upgrade_mcp-0.1.1.tar.gz
- Upload date:
- Size: 15.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8a58169ba92a2b02390008d5ad90e2a8a23f198b68cce8daf6540154f72d7668
|
|
| MD5 |
e7f7e230cd15cea79990262fb651aeb5
|
|
| BLAKE2b-256 |
1b0b02a6069c10cf5fcfecfd5444e222730b718288bd485c15f80f0dbebda02f
|
Provenance
The following attestation bundles were made for package_upgrade_mcp-0.1.1.tar.gz:
Publisher:
publish.yml on Hasilt/package-upgrade-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
package_upgrade_mcp-0.1.1.tar.gz -
Subject digest:
8a58169ba92a2b02390008d5ad90e2a8a23f198b68cce8daf6540154f72d7668 - Sigstore transparency entry: 2251272757
- Sigstore integration time:
-
Permalink:
Hasilt/package-upgrade-mcp@26b1a855163f0eedbdca813ca89a216f672ce774 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/Hasilt
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@26b1a855163f0eedbdca813ca89a216f672ce774 -
Trigger Event:
push
-
Statement type:
File details
Details for the file package_upgrade_mcp-0.1.1-py3-none-any.whl.
File metadata
- Download URL: package_upgrade_mcp-0.1.1-py3-none-any.whl
- Upload date:
- Size: 24.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
11b51dddca1a6514d1ee07b79813166b5f4f5475b6f340cc47735938f670be77
|
|
| MD5 |
a80b6c74f0de4044ffaab1fce7df0909
|
|
| BLAKE2b-256 |
3602eceafa0cea732e46490e477e9785f2095c5c88872108a01b95b053a897ba
|
Provenance
The following attestation bundles were made for package_upgrade_mcp-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on Hasilt/package-upgrade-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
package_upgrade_mcp-0.1.1-py3-none-any.whl -
Subject digest:
11b51dddca1a6514d1ee07b79813166b5f4f5475b6f340cc47735938f670be77 - Sigstore transparency entry: 2251273161
- Sigstore integration time:
-
Permalink:
Hasilt/package-upgrade-mcp@26b1a855163f0eedbdca813ca89a216f672ce774 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/Hasilt
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@26b1a855163f0eedbdca813ca89a216f672ce774 -
Trigger Event:
push
-
Statement type: