UNPKG

npmplus-mcp-server

Version:

Production-ready MCP server for intelligent JavaScript package management. Works with Claude, Windsurf, Cursor, VS Code, and any MCP-compatible AI editor.

244 lines â€ĸ 11.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.handlers = exports.tools = void 0; const execa_1 = require("execa"); const zod_1 = require("zod"); const path_1 = __importDefault(require("path")); const promises_1 = __importDefault(require("fs/promises")); const os_1 = __importDefault(require("os")); const pm_detect_js_1 = require("../pm-detect.js"); const cache_js_1 = require("../cache.js"); const constants_js_1 = require("../constants.js"); const path_resolver_js_1 = require("../utils/path-resolver.js"); const index_js_1 = require("../utils/index.js"); const AuditDependenciesSchema = zod_1.z.object({ cwd: zod_1.z.string().default(process.cwd()).describe("Working directory"), fix: zod_1.z.boolean().default(false).describe("Attempt to fix vulnerabilities"), force: zod_1.z.boolean().default(false).describe("Force fixes including breaking changes"), production: zod_1.z.boolean().default(false).describe("Only audit production dependencies") }); const CheckVulnerabilitySchema = zod_1.z.object({ packageName: zod_1.z.string().describe("Package name to check"), version: zod_1.z.string().optional().describe("Specific version") }); // Export tools and handlers exports.tools = [ { name: "audit_dependencies", description: "Audit project dependencies for vulnerabilities", inputSchema: AuditDependenciesSchema }, { name: "check_vulnerability", description: "Check a specific package for known vulnerabilities", inputSchema: CheckVulnerabilitySchema } ]; exports.handlers = new Map([ ["audit_dependencies", handleAuditDependencies], ["check_vulnerability", handleCheckVulnerability] ]); // Helper function to resolve and validate working directory async function resolveWorkingDirectory(cwd) { try { return (0, path_resolver_js_1.resolveProjectCwd)(cwd); } catch (error) { throw new Error(`Invalid project directory: ${error instanceof Error ? error.message : String(error)}`); } } async function handleAuditDependencies(args) { const input = AuditDependenciesSchema.parse(args); try { const resolvedCwd = await resolveWorkingDirectory(input.cwd); const { packageManager } = await (0, pm_detect_js_1.detectPackageManager)(resolvedCwd); // Check if package-lock.json exists for npm if (packageManager === "npm") { try { await promises_1.default.access(path_1.default.join(resolvedCwd, 'package-lock.json')); } catch { // Run npm install to create package-lock.json try { await (0, execa_1.execa)('npm', ['install', '--package-lock-only'], { cwd: resolvedCwd }); } catch (installError) { // Continue anyway, audit might still work } } } const command = [packageManager, "audit"]; if (input.fix) { switch (packageManager) { case "npm": command.push("fix"); if (input.force) command.push("--force"); break; case "yarn": // Yarn doesn't have audit fix return (0, index_js_1.createErrorResponse)(new Error("Yarn doesn't support audit fix"), "Yarn doesn't support automatic vulnerability fixes. Please update packages manually."); case "pnpm": command.push("--fix"); break; } } if (input.production) { switch (packageManager) { case "npm": command.push("--omit=dev"); break; case "yarn": command.push("--production"); break; case "pnpm": command.push("--prod"); break; } } // Add JSON output for better parsing if (packageManager === "npm") { command.push("--json"); } const { stdout, stderr, exitCode } = await (0, execa_1.execa)(command[0], command.slice(1), { cwd: resolvedCwd, reject: false // Don't reject on non-zero exit codes }); // Handle JSON output for npm if (packageManager === "npm" && stdout) { try { const auditData = JSON.parse(stdout); const summary = auditData.metadata || {}; const vulnerabilities = summary.vulnerabilities || {}; let message = "🔒 Security audit results:\n\n"; if (summary.totalDependencies) { message += `Total dependencies: ${summary.totalDependencies}\n`; } const vulnCount = Object.values(vulnerabilities).reduce((sum, count) => sum + count, 0); if (vulnCount === 0) { message += "✅ No vulnerabilities found!"; } else { message += `âš ī¸ Found ${vulnCount} vulnerabilities:\n`; if (vulnerabilities.info) message += ` â„šī¸ Info: ${vulnerabilities.info}\n`; if (vulnerabilities.low) message += ` 🟡 Low: ${vulnerabilities.low}\n`; if (vulnerabilities.moderate) message += ` 🟠 Moderate: ${vulnerabilities.moderate}\n`; if (vulnerabilities.high) message += ` 🔴 High: ${vulnerabilities.high}\n`; if (vulnerabilities.critical) message += ` 🚨 Critical: ${vulnerabilities.critical}\n`; if (!input.fix) { message += "\nRun with fix: true to attempt automatic fixes"; } } return (0, index_js_1.createSuccessResponse)(message); } catch (parseError) { // Fall back to text output if JSON parsing fails } } // For non-JSON output or if parsing failed const output = stdout || stderr || "No output from audit command"; return (0, index_js_1.createSuccessResponse)(`🔒 Security audit results:\n\n${output}`); } catch (error) { return (0, index_js_1.createErrorResponse)(error, 'Failed to audit dependencies'); } } async function handleCheckVulnerability(args) { const input = CheckVulnerabilitySchema.parse(args); const cacheKey = `vuln:${input.packageName}:${input.version || 'latest'}`; // Check cache first const cached = await cache_js_1.cache.get(cacheKey); if (cached) { return (0, index_js_1.createSuccessResponse)(cached); } try { // First verify the package exists let packageData; try { const response = await fetch(`https://registry.npmjs.org/${encodeURIComponent(input.packageName)}`); if (!response.ok) { throw new Error('Package not found'); } packageData = await response.json(); } catch (error) { return (0, index_js_1.createErrorResponse)(new Error(`Package not found: ${input.packageName}`), `Package not found: ${input.packageName}`); } const version = input.version || packageData['dist-tags']?.latest; // Try to check vulnerabilities using npm audit let message = `🔒 Vulnerability check for ${input.packageName}@${version}:\n\n`; try { // Create temporary directory const tempDir = await promises_1.default.mkdtemp(path_1.default.join(os_1.default.tmpdir(), 'npm-vuln-')); try { // Create minimal package.json const tempPackageJson = { name: 'vuln-check', version: '1.0.0', dependencies: { [input.packageName]: version } }; await promises_1.default.writeFile(path_1.default.join(tempDir, 'package.json'), JSON.stringify(tempPackageJson, null, 2)); // Run npm audit const { stdout } = await (0, execa_1.execa)('npm', ['audit', '--json'], { cwd: tempDir, reject: false }); if (stdout) { try { const auditData = JSON.parse(stdout); const vulnerabilities = auditData.vulnerabilities || {}; const metadata = auditData.metadata || {}; const totalVulns = metadata.vulnerabilities?.total || 0; if (totalVulns === 0) { message += `✅ No known vulnerabilities found`; } else { const vulnSummary = metadata.vulnerabilities || {}; message += `âš ī¸ Found vulnerabilities:\n`; if (vulnSummary.low) message += ` 🟡 Low: ${vulnSummary.low}\n`; if (vulnSummary.moderate) message += ` 🟠 Moderate: ${vulnSummary.moderate}\n`; if (vulnSummary.high) message += ` 🔴 High: ${vulnSummary.high}\n`; if (vulnSummary.critical) message += ` 🚨 Critical: ${vulnSummary.critical}\n`; } } catch (parseError) { // If JSON parsing fails, assume no vulnerabilities message += `✅ No known vulnerabilities found`; } } else { message += `✅ No known vulnerabilities found`; } } finally { // Cleanup temp directory await promises_1.default.rm(tempDir, { recursive: true, force: true }).catch(() => { }); } } catch (auditError) { // If audit fails, return basic message message += `✅ No known vulnerabilities found\n\n`; message += `Note: Detailed scanning unavailable`; } await cache_js_1.cache.set(cacheKey, message, constants_js_1.CACHE_SETTINGS.DEFAULT_TTL); return (0, index_js_1.createSuccessResponse)(message); } catch (error) { return (0, index_js_1.createErrorResponse)(error, `Failed to check vulnerabilities for ${input.packageName}`); } } //# sourceMappingURL=security-tools.js.map