check_outdated
Identify outdated npm packages in package.json by analyzing project dependencies. Specifies newer versions available and supports detailed, summary, or raw output formats.
Instructions
Check for outdated npm packages in package.json using 'npm outdated'. Analyzes the current project's dependencies and shows which packages have newer versions available. Requires npm to be installed and accessible from the command line.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| includeDevDependencies | No | Whether to include dev dependencies in the check | |
| outputFormat | No | Format of the output: detailed (full info), summary (counts only), or raw (npm command output) | detailed |
| projectPath | No | Path to the directory containing package.json. Defaults to the first allowed directory if not specified. |
Implementation Reference
- src/check-outdated.ts:54-281 (handler)The primary handler function that implements the core logic of the 'check_outdated' tool. It validates input, checks permissions, executes 'npm outdated --json', parses the output, categorizes packages, and formats a detailed Markdown response with options for different output formats.export async function handleCheckOutdated(args: any, allowedDirectories: string[]) { const { projectPath = allowedDirectories[0], includeDevDependencies = true, outputFormat = "detailed" } = args; if (!projectPath) { throw new McpError(ErrorCode.InvalidParams, "No project path specified and no allowed directories available"); } // Resolve to absolute path const resolvedPath = path.resolve(projectPath); // Check if path is within allowed directories const isPathAllowed = allowedDirectories.some(dir => { const normalizedDir = path.resolve(dir).replace(/\\/g, '/'); const normalizedPath = resolvedPath.replace(/\\/g, '/'); return normalizedPath === normalizedDir || normalizedPath.startsWith(normalizedDir + '/'); }); if (!isPathAllowed) { throw new McpError(ErrorCode.InvalidParams, `Path "${resolvedPath}" is not within allowed directories`); } // Check if package.json exists const packageJsonPath = path.join(resolvedPath, 'package.json'); if (!fs.existsSync(packageJsonPath)) { throw new McpError(ErrorCode.InvalidParams, `package.json not found in "${resolvedPath}"`); } try { // Build npm outdated command let command = 'npm outdated --json'; if (!includeDevDependencies) { command += ' --prod'; } // Execute npm outdated command const { stdout, stderr } = await execAsync(command, { cwd: resolvedPath, timeout: 10000 // 10 second timeout }); let outdatedData: any = {}; let rawOutput = stdout || stderr; // npm outdated returns exit code 1 when packages are outdated, so we need to handle both stdout and stderr if (stdout && stdout.trim()) { try { outdatedData = JSON.parse(stdout); } catch (parseError) { // If JSON parsing fails, treat as no outdated packages outdatedData = {}; } } else if (stderr && stderr.includes('{')) { try { // Sometimes npm outputs to stderr outdatedData = JSON.parse(stderr); } catch (parseError) { outdatedData = {}; } } // Parse the outdated packages const outdatedPackages: OutdatedPackage[] = []; for (const [packageName, info] of Object.entries(outdatedData)) { if (typeof info === 'object' && info !== null) { const packageInfo = info as any; outdatedPackages.push({ package: packageName, current: packageInfo.current || 'unknown', wanted: packageInfo.wanted || 'unknown', latest: packageInfo.latest || 'unknown', location: packageInfo.location || resolvedPath, type: packageInfo.type || 'dependencies' }); } } const totalOutdated = outdatedPackages.length; const hasOutdated = totalOutdated > 0; let message: string; if (!hasOutdated) { message = "All packages are up to date! 🎉"; } else { message = `Found ${totalOutdated} outdated package${totalOutdated === 1 ? '' : 's'}`; if (outputFormat === "detailed") { message += ":\n\n"; outdatedPackages.forEach(pkg => { message += `📦 ${pkg.package}\n`; message += ` Current: ${pkg.current}\n`; message += ` Wanted: ${pkg.wanted}\n`; message += ` Latest: ${pkg.latest}\n`; message += ` Type: ${pkg.type}\n\n`; }); message += "Run 'npm update' to update to wanted versions or 'npm install <package>@latest' for latest versions."; } else if (outputFormat === "summary") { const depTypes = outdatedPackages.reduce((acc, pkg) => { acc[pkg.type] = (acc[pkg.type] || 0) + 1; return acc; }, {} as Record<string, number>); message += "\n\nBreakdown by type:\n"; Object.entries(depTypes).forEach(([type, count]) => { message += `- ${type}: ${count}\n`; }); } } let result = `# NPM Outdated Check Results\n\n**Project:** ${packageJsonPath}\n**Total Packages Checked:** ${Object.keys(outdatedData).length}\n\n`; if (outputFormat === "raw") { result += `**Raw Output:**\n\`\`\`\n${rawOutput}\n\`\`\`\n\n`; } result += message; return { content: [ { type: "text", text: result } ] }; } catch (error: any) { // npm outdated exits with code 1 when packages are outdated, this is normal if (error.code === 1) { // This is the normal case - packages are outdated let outdatedData: any = {}; let rawOutput = error.stdout || error.stderr || ''; // Try to parse JSON from stdout or stderr const outputToParse = error.stdout || error.stderr || ''; if (outputToParse && outputToParse.trim()) { try { outdatedData = JSON.parse(outputToParse); } catch (parseError) { // If JSON parsing fails, treat as no outdated packages outdatedData = {}; } } // Parse the outdated packages const outdatedPackages: OutdatedPackage[] = []; for (const [packageName, info] of Object.entries(outdatedData)) { if (typeof info === 'object' && info !== null) { const packageInfo = info as any; outdatedPackages.push({ package: packageName, current: packageInfo.current || 'unknown', wanted: packageInfo.wanted || 'unknown', latest: packageInfo.latest || 'unknown', location: packageInfo.location || resolvedPath, type: packageInfo.type || 'dependencies' }); } } const totalOutdated = outdatedPackages.length; const hasOutdated = totalOutdated > 0; let message: string; if (!hasOutdated) { message = "All packages are up to date! 🎉"; } else { message = `Found ${totalOutdated} outdated package${totalOutdated === 1 ? '' : 's'}`; if (outputFormat === "detailed") { message += ":\n\n"; outdatedPackages.forEach(pkg => { message += `📦 ${pkg.package}\n`; message += ` Current: ${pkg.current}\n`; message += ` Wanted: ${pkg.wanted}\n`; message += ` Latest: ${pkg.latest}\n`; message += ` Type: ${pkg.type}\n\n`; }); message += "Run 'npm update' to update to wanted versions or 'npm install <package>@latest' for latest versions."; } else if (outputFormat === "summary") { const depTypes = outdatedPackages.reduce((acc, pkg) => { acc[pkg.type] = (acc[pkg.type] || 0) + 1; return acc; }, {} as Record<string, number>); message += "\n\nBreakdown by type:\n"; Object.entries(depTypes).forEach(([type, count]) => { message += `- ${type}: ${count}\n`; }); } } let result = `# NPM Outdated Check Results\n\n**Project:** ${packageJsonPath}\n**Total Packages Checked:** ${Object.keys(outdatedData).length}\n\n`; if (outputFormat === "raw") { result += `**Raw Output:**\n\`\`\`\n${rawOutput}\n\`\`\`\n\n`; } result += message; return { content: [ { type: "text", text: result } ] }; } let errorMessage = `Failed to check outdated packages: ${error.message}`; if (error.code === 'ENOENT') { errorMessage = "npm command not found. Please ensure npm is installed and available in your PATH."; } else if (error.message.includes('timeout')) { errorMessage = "npm outdated command timed out. The operation took too long to complete."; } else if (error.message.includes('ENOTDIR') || error.message.includes('ENOENT')) { errorMessage = `Invalid project directory: "${resolvedPath}"`; } throw new McpError(ErrorCode.InternalError, errorMessage); } }
- src/check-outdated.ts:28-52 (schema)The tool definition object providing the name, description, and input schema for validation in the MCP protocol.export const checkOutdatedTool = { name: "check_outdated", description: "Check for outdated npm packages in package.json using 'npm outdated'. Analyzes the current project's dependencies and shows which packages have newer versions available. Requires npm to be installed and accessible from the command line.", inputSchema: { type: "object", properties: { projectPath: { type: "string", description: "Path to the directory containing package.json. Defaults to the first allowed directory if not specified." }, includeDevDependencies: { type: "boolean", description: "Whether to include dev dependencies in the check", default: true }, outputFormat: { type: "string", enum: ["detailed", "summary", "raw"], description: "Format of the output: detailed (full info), summary (counts only), or raw (npm command output)", default: "detailed" } }, additionalProperties: false } };
- src/index.ts:33-44 (registration)Registration of the checkOutdatedTool in the server's ListToolsRequestHandler, making it discoverable to MCP clients.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ exploreProjectTool, listAllowedTool, searchTool, renameFileTool, deleteFileTool, checkOutdatedTool ] }; });
- src/index.ts:47-77 (registration)Dispatch logic in the CallToolRequestHandler that routes 'check_outdated' calls to the handleCheckOutdated function.server.setRequestHandler(CallToolRequestSchema, async (request) => { // Safely access arguments with null checking const args = request.params.arguments || {}; // Route to appropriate handler based on tool name switch (request.params.name) { case "list_allowed_directories": return await handleListAllowed(args, ALLOWED_DIRECTORIES); case "explore_project": return await handleExploreProject(args, ALLOWED_DIRECTORIES); case "search_files": return await handleSearch(args, ALLOWED_DIRECTORIES); case "rename_file": return await handleRenameFile(args, ALLOWED_DIRECTORIES); case "delete_file": return await handleDeleteFile(args, ALLOWED_DIRECTORIES); case "check_outdated": return await handleCheckOutdated(args, ALLOWED_DIRECTORIES); default: throw new McpError( ErrorCode.InvalidRequest, `Unknown tool: ${request.params.name}` ); } });