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
JavaScript
;
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