Skip to main content
Glama

Analyze multiple dependency changes in parallel

analyze_packages_bulk
Read-onlyIdempotent

Analyzes multiple package upgrades simultaneously to generate a risk-ranked report identifying security fixes, breaking changes, and recommendations from caution to safe.

Instructions

Analyzes a list of package upgrades in parallel and returns a unified risk report with packages ranked by recommendation level (security > caution > review > likely-safe > safe). Use when the user provides many dependency changes from a Dependabot PR, npm outdated output, lockfile diff, or batch upgrade. Returns: total count, breakdown by semver class, total security fixes found, packages with breaking changes, and per-package details. Limit 50 packages per call (chunk larger lists).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
changesYesList of package changes to analyze

Implementation Reference

  • The handler function for the analyze_packages_bulk tool. It iterates over an array of package changes, calls analyzePackageChange (from analyzer.ts) for each in parallel (concurrency-limited via p-limit), sorts results by recommendation level, computes summary statistics (total, semver breakdown, security fixes count, breaking changes count), and returns a JSON report.
    async ({ changes }) => {
    	try {
    		const results = await Promise.allSettled(
    			changes.map((c) =>
    				limit(() =>
    					analyzePackageChange(c.ecosystem as Ecosystem, c.name, c.fromVersion, c.toVersion, githubToken)
    				)
    			)
    		);
    
    		const analyzed = results.map((r, i) =>
    			r.status === "fulfilled"
    				? r.value
    				: {
    					package: changes[i]?.name ?? "unknown",
    					error: (r.reason as Error).message,
    					recommendationLevel: "review" as const,
    				}
    		);
    
    		const ranks: Record<string, number> = { security: 0, caution: 1, review: 2, "likely-safe": 3, safe: 4 };
    		const sorted = [...analyzed].sort(
    			(a: any, b: any) => (ranks[a.recommendationLevel] ?? 5) - (ranks[b.recommendationLevel] ?? 5)
    		);
    
    		const summary = {
    			totalPackages: changes.length,
    			bySemverClass: {
    				major: analyzed.filter((a: any) => a.semverClass === "major").length,
    				minor: analyzed.filter((a: any) => a.semverClass === "minor").length,
    				patch: analyzed.filter((a: any) => a.semverClass === "patch").length,
    			},
    			securityFixesTotal: analyzed.reduce(
    				(n, a: any) => n + (a.securityFixes?.length ?? 0), 0
    			),
    			packagesWithBreakingChanges: analyzed.filter(
    				(a: any) => (a.breakingChanges?.length ?? 0) > 0
    			).length,
    			packages: sorted,
    		};
    
    		return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
    	} catch (err: any) {
    		return {
    			content: [{ type: "text", text: `Bulk analysis failed: ${err?.message ?? String(err)}` }],
    			isError: true,
    		};
    	}
    }
  • The input schema for analyze_packages_bulk — a Zod schema defining an array of 'changes' objects (ecosystem, name, fromVersion, toVersion), limited to 1-50 items.
    inputSchema: {
    	changes: z
    		.array(
    			z.object({
    				ecosystem: ecosystemSchema,
    				name: z.string().min(1),
    				fromVersion: z.string().min(1),
    				toVersion: z.string().min(1),
    			})
    		)
    		.min(1)
    		.max(50)
    		.describe("List of package changes to analyze"),
    },
  • src/index.ts:65-147 (registration)
    The registration of the tool using server.registerTool('analyze_packages_bulk', ...) in the createMcpServer function.
    server.registerTool(
    	"analyze_packages_bulk",
    	{
    		title: "Analyze multiple dependency changes in parallel",
    		description:
    			"Analyzes a list of package upgrades in parallel and returns a unified risk report with " +
    			"packages ranked by recommendation level (security > caution > review > likely-safe > safe). " +
    			"Use when the user provides many dependency changes from a Dependabot PR, npm outdated output, " +
    			"lockfile diff, or batch upgrade. Returns: total count, breakdown by semver class, total " +
    			"security fixes found, packages with breaking changes, and per-package details. " +
    			"Limit 50 packages per call (chunk larger lists).",
    		annotations: {
    			title: "Analyze multiple dependency changes in parallel",
    			readOnlyHint: true,
    			destructiveHint: false,
    			idempotentHint: true,
    			openWorldHint: true,
    		},
    		inputSchema: {
    			changes: z
    				.array(
    					z.object({
    						ecosystem: ecosystemSchema,
    						name: z.string().min(1),
    						fromVersion: z.string().min(1),
    						toVersion: z.string().min(1),
    					})
    				)
    				.min(1)
    				.max(50)
    				.describe("List of package changes to analyze"),
    		},
    	},
    	async ({ changes }) => {
    		try {
    			const results = await Promise.allSettled(
    				changes.map((c) =>
    					limit(() =>
    						analyzePackageChange(c.ecosystem as Ecosystem, c.name, c.fromVersion, c.toVersion, githubToken)
    					)
    				)
    			);
    
    			const analyzed = results.map((r, i) =>
    				r.status === "fulfilled"
    					? r.value
    					: {
    						package: changes[i]?.name ?? "unknown",
    						error: (r.reason as Error).message,
    						recommendationLevel: "review" as const,
    					}
    			);
    
    			const ranks: Record<string, number> = { security: 0, caution: 1, review: 2, "likely-safe": 3, safe: 4 };
    			const sorted = [...analyzed].sort(
    				(a: any, b: any) => (ranks[a.recommendationLevel] ?? 5) - (ranks[b.recommendationLevel] ?? 5)
    			);
    
    			const summary = {
    				totalPackages: changes.length,
    				bySemverClass: {
    					major: analyzed.filter((a: any) => a.semverClass === "major").length,
    					minor: analyzed.filter((a: any) => a.semverClass === "minor").length,
    					patch: analyzed.filter((a: any) => a.semverClass === "patch").length,
    				},
    				securityFixesTotal: analyzed.reduce(
    					(n, a: any) => n + (a.securityFixes?.length ?? 0), 0
    				),
    				packagesWithBreakingChanges: analyzed.filter(
    					(a: any) => (a.breakingChanges?.length ?? 0) > 0
    				).length,
    				packages: sorted,
    			};
    
    			return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
    		} catch (err: any) {
    			return {
    				content: [{ type: "text", text: `Bulk analysis failed: ${err?.message ?? String(err)}` }],
    				isError: true,
    			};
    		}
    	}
    );
  • src/worker.ts:56-89 (registration)
    The server card registration (declarative metadata) for analyze_packages_bulk, used in the .well-known MCP server card.
    {
    	name: "analyze_packages_bulk",
    	description:
    		"Analyzes a list of package upgrades in parallel and returns a unified risk report with packages ranked by recommendation level (security > caution > review > likely-safe > safe). Use when the user provides many dependency changes from a Dependabot PR, npm outdated output, lockfile diff, or batch upgrade. Returns: total count, breakdown by semver class, total security fixes found, packages with breaking changes, and per-package details. Limit 50 packages per call.",
    	annotations: {
    		title: "Analyze multiple dependency changes in parallel",
    		readOnlyHint: true,
    		destructiveHint: false,
    		idempotentHint: true,
    		openWorldHint: true,
    	},
    	inputSchema: {
    		type: "object",
    		properties: {
    			changes: {
    				type: "array",
    				minItems: 1,
    				maxItems: 50,
    				description: "List of package changes to analyze",
    				items: {
    					type: "object",
    					properties: {
    						ecosystem: { type: "string", enum: ["npm", "pypi"] },
    						name: { type: "string", minLength: 1 },
    						fromVersion: { type: "string", minLength: 1 },
    						toVersion: { type: "string", minLength: 1 },
    					},
    					required: ["ecosystem", "name", "fromVersion", "toVersion"],
    				},
    			},
    		},
    		required: ["changes"],
    	},
    },
  • The analyzePackageChange helper function which is called by the bulk handler for each individual package change. It fetches package metadata, classifies semver bumps, checks CVEs via OSV.dev, extracts breaking changes/release notes, and generates recommendations.
    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;
    }
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, destructiveHint, idempotentHint, and openWorldHint. The description adds behavioral details such as parallelism, the ranking of recommendations, and output breakdown (semver class, security fixes, breaking changes), enhancing transparency beyond annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is concise with three sentences, no fluff. It front-loads the primary action and output, then provides usage context and constraints.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the single parameter with full schema coverage, clear annotations, and no output schema, the description covers the tool's purpose, usage context, limit, and output contents sufficiently for correct selection and invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3. The description mentions input sources (Dependabot PR, etc.) but does not add new parameter semantics beyond what the schema already provides for each field. Thus no significant addition.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool analyzes package upgrades in parallel and returns a unified risk report. It distinguishes from the sibling tool 'analyze_package_change' by focusing on bulk analysis and mentioning parallelism and chunking.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly says 'Use when the user provides many dependency changes from a Dependabot PR, npm outdated output, lockfile diff, or batch upgrade.' It also provides a limit of 50 packages per call, guiding chunking for larger lists.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/DigiCatalyst-Systems/dep-diff-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server