Analyze a single dependency version change
analyze_package_changeAnalyze upgrading a single npm or PyPI package from one version to another, returning semver class, breaking changes, security fixes, migration guides, and a clear upgrade recommendation.
Instructions
Given one package and two versions (from -> to), returns a structured upgrade analysis: semver classification, GitHub release notes summary, detected breaking changes, security advisories fixed in the range, migration guide links, and a clear recommendation. Use when the user asks about a specific package upgrade ('what changed between react 18 and 19', 'is it safe to bump axios from 0.27 to 1.0', 'what does upgrading lodash 4.17.20 to 4.17.21 fix'). Supports npm and pypi. For analyzing many packages at once or a Dependabot batch, use analyze_packages_bulk instead.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| ecosystem | Yes | Package ecosystem | |
| name | Yes | Package name (e.g. 'react', 'requests') | |
| fromVersion | Yes | Current version (e.g. '18.2.0') | |
| toVersion | Yes | Target version (e.g. '19.0.0') |
Implementation Reference
- src/analyzer.ts:317-369 (handler)The main orchestrator function that executes the analyze_package_change tool logic. Fetches metadata, discovers GitHub repo, fetches releases between versions, checks CVEs via OSV.dev, extracts breaking changes, migration links, and generates a recommendation.
export async function analyzePackageChange( ecosystem: Ecosystem, name: string, fromVersion: string, toVersion: string, githubToken?: string ): Promise<PackageAnalysis> { const semverClass = classifyBump(fromVersion, toVersion); const meta = ecosystem === "npm" ? await fetchNpmMeta(name) : await fetchPyPIMeta(name); const repo = extractGitHubRepo(meta, ecosystem); const [releases, cvesAtFrom, cvesAtTo] = await Promise.all([ repo ? fetchReleasesBetween(repo.owner, repo.repo, fromVersion, toVersion, githubToken).catch(() => []) : Promise.resolve([]), fetchCvesAtVersion(ecosystem, name, fromVersion).catch(() => []), fetchCvesAtVersion(ecosystem, name, toVersion).catch(() => []), ]); const toIds = new Set(cvesAtTo.map((c: any) => c.id)); const fixedCves = cvesAtFrom.filter((c: any) => !toIds.has(c.id)); const breakingChanges = extractBreakingChanges(releases); const migrationLinks = extractMigrationLinks(releases); const rec = generateRecommendation(semverClass, breakingChanges, fixedCves); const needsFallback = breakingChanges.length === 0 && (semverClass === "major" || semverClass === "minor"); const releaseExcerpts = needsFallback ? extractReleaseExcerpts(releases) : undefined; const result: PackageAnalysis = { package: name, ecosystem, fromVersion, toVersion, semverClass, repoUrl: repo ? `https://github.com/${repo.owner}/${repo.repo}` : null, releaseCount: releases.length, breakingChanges, securityFixes: fixedCves.map((c: any) => ({ id: c.id, summary: c.summary ?? c.details?.slice(0, 200) ?? "", severity: c.database_specific?.severity ?? "unknown", })), migrationLinks, recommendation: rec.text, recommendationLevel: rec.level, }; if (releaseExcerpts && releaseExcerpts.length > 0) { result.releaseExcerpts = releaseExcerpts; } return result; } - src/analyzer.ts:19-33 (schema)The PackageAnalysis interface defines the output schema returned by analyze_package_change.
export interface PackageAnalysis { package: string; ecosystem: Ecosystem; fromVersion: string; toVersion: string; semverClass: "major" | "minor" | "patch" | "downgrade" | "unknown"; repoUrl: string | null; releaseCount: number; breakingChanges: string[]; releaseExcerpts?: { tag: string; excerpt: string }[]; securityFixes: { id: string; summary: string; severity: string }[]; migrationLinks: string[]; recommendation: string; recommendationLevel: "safe" | "likely-safe" | "review" | "caution" | "security"; } - src/index.ts:24-63 (registration)Registers the 'analyze_package_change' tool with the MCP server, defining its inputSchema (ecosystem, name, fromVersion, toVersion) and the handler that calls analyzePackageChange.
server.registerTool( "analyze_package_change", { title: "Analyze a single dependency version change", description: "Given one package and two versions (from -> to), returns a structured upgrade analysis: " + "semver classification, GitHub release notes summary, detected breaking changes, security " + "advisories fixed in the range, migration guide links, and a clear recommendation. " + "Use when the user asks about a specific package upgrade ('what changed between react 18 and 19', " + "'is it safe to bump axios from 0.27 to 1.0', 'what does upgrading lodash 4.17.20 to 4.17.21 fix'). " + "Supports npm and pypi. For analyzing many packages at once or a Dependabot batch, " + "use analyze_packages_bulk instead.", annotations: { title: "Analyze a single dependency version change", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true, }, inputSchema: { ecosystem: ecosystemSchema, name: z.string().min(1).describe("Package name (e.g. 'react', 'requests')"), fromVersion: z.string().min(1).describe("Current version (e.g. '18.2.0')"), toVersion: z.string().min(1).describe("Target version (e.g. '19.0.0')"), }, }, async ({ ecosystem, name, fromVersion, toVersion }) => { try { const result = await analyzePackageChange( ecosystem as Ecosystem, name, fromVersion, toVersion, githubToken ); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; } catch (err: any) { return { content: [{ type: "text", text: `Failed to analyze ${name}: ${err.message}` }], isError: true, }; } } ); - src/analyzer.ts:39-49 (helper)Helper that classifies a version bump as major/minor/patch/downgrade/unknown using semver.
export function classifyBump(from: string, to: string): PackageAnalysis["semverClass"] { const cleanFrom = semver.coerce(from)?.version; const cleanTo = semver.coerce(to)?.version; if (!cleanFrom || !cleanTo) return "unknown"; if (semver.eq(cleanFrom, cleanTo)) return "patch"; if (semver.lt(cleanTo, cleanFrom)) return "downgrade"; const diff = semver.diff(cleanFrom, cleanTo); if (diff === "major" || diff === "premajor") return "major"; if (diff === "minor" || diff === "preminor") return "minor"; return "patch"; } - src/analyzer.ts:283-313 (helper)Helper that generates a recommendation text and level based on security fixes, semver class, and breaking changes.
function generateRecommendation( semverClass: PackageAnalysis["semverClass"], breaking: string[], fixedCves: any[] ): { text: string; level: PackageAnalysis["recommendationLevel"] } { if (fixedCves.length > 0) { const sev = fixedCves.some((c: any) => /critical|high/i.test(c.database_specific?.severity ?? "")); return { text: `RECOMMENDED: ${fixedCves.length} security fix(es)${sev ? " (incl. high/critical)" : ""}.`, level: "security", }; } if (semverClass === "downgrade") { return { text: `WARNING: This is a downgrade. Verify intentional.`, level: "caution" }; } if (semverClass === "major") { return { text: breaking.length > 0 ? `CAUTION: Major version with ${breaking.length} breaking change(s) noted in release notes.` : `REVIEW: Major version bump. Breaking changes possible even if not explicitly listed.`, level: "caution", }; } if (breaking.length > 0) { return { text: `REVIEW: ${breaking.length} breaking change(s) noted despite non-major bump.`, level: "review" }; } if (semverClass === "minor") { return { text: `LIKELY SAFE: Minor version, additive changes per semver.`, level: "likely-safe" }; } return { text: `SAFE: Patch-level change.`, level: "safe" }; }