vulnzap-mcp
Version:
Multi-ecosystem vulnerability scanning service with MCP interface for LLMs
988 lines (868 loc) • 35.2 kB
JavaScript
// Fix import paths for SDK
import { Server } from '@modelcontextprotocol/sdk/dist/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/dist/server/stdio.js';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import semver from 'semver';
import dotenv from 'dotenv';
// Load environment variables first thing, before any other code
dotenv.config();
// Verify environment variables loaded correctly
console.log(`Environment loaded - NVD API Key: ${process.env.NVD_API_KEY ? 'Yes (hidden)' : 'No'}`);
console.log(`Environment loaded - GitHub Token: ${process.env.GITHUB_TOKEN ? 'Yes (hidden)' : 'No'}`);
console.log(`Environment loaded - USE_NVD: ${process.env.USE_NVD}`);
import { initNvdClient, checkNvdVulnerability } from './nvd-client.js';
import { initGithubClient, checkGithubVulnerability, fetchAllAdvisories } from './github-client.js';
// Get __dirname equivalent in ESM
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Configuration
const CONFIG = {
PREMIUM_API_KEY: process.env.PREMIUM_API_KEY || 'test123',
SUPPORTED_ECOSYSTEMS: [
'npm', 'pip', 'gem', 'cargo', 'composer', 'go', 'maven', 'nuget',
'debian', 'ubuntu', 'alpine', 'centos', 'rhel', 'pypi'
],
DATA_PATH: process.env.DATA_PATH || path.join(__dirname, 'data', 'advisories.json'),
DATA_REFRESH_INTERVAL: parseInt(process.env.GITHUB_REFRESH_INTERVAL || '86400000'), // 24 hours in milliseconds
USE_NVD: process.env.USE_NVD === 'true' || process.env.USE_NVD === '"true"',
PREMIUM_FEATURES: {
batchScan: true,
detailedReport: true
}
};
// Vulnerability database
let vulnerabilityDatabase = new Map();
let nvdConfig = null;
let githubConfig = null;
/**
* Load vulnerability data from the GitHub Advisory Database file
* This function reads the JSON file and transforms it into an efficient in-memory structure
*/
function loadVulnerabilityData() {
try {
if (!fs.existsSync(CONFIG.DATA_PATH)) {
console.error(`Advisory data file not found at ${CONFIG.DATA_PATH}`);
return false;
}
const data = JSON.parse(fs.readFileSync(CONFIG.DATA_PATH, 'utf8'));
if (!data || !data.advisories || !Array.isArray(data.advisories)) {
console.error('Invalid advisory data format');
return false;
}
// Clear existing data
vulnerabilityDatabase.clear();
// Process each advisory
data.advisories.forEach(advisory => {
const { ecosystem, package: packageName, vulnerable_versions } = advisory;
// Skip if missing required fields
if (!ecosystem || !packageName || !vulnerable_versions) return;
const key = `${ecosystem}:${packageName}`;
// If this package already has vulnerabilities, append this one
if (vulnerabilityDatabase.has(key)) {
vulnerabilityDatabase.get(key).push({
...advisory,
vulnerable_versions,
source: 'local'
});
} else {
// Otherwise create a new entry
vulnerabilityDatabase.set(key, [{
...advisory,
vulnerable_versions,
source: 'local'
}]);
}
});
console.log(`Loaded ${vulnerabilityDatabase.size} package vulnerability records from GitHub Advisory Database`);
console.log(`Last updated: ${data.last_updated || 'unknown'}`);
return true;
} catch (error) {
console.error(`Error loading vulnerability data: ${error.message}`);
return false;
}
}
/**
* Check if a pip package version is vulnerable based on version ranges
*
* @param {string} version - The version to check
* @param {string} range - The pip version range string
* @returns {boolean} - True if vulnerable, false if not
*/
function isPipVersionVulnerable(version, range) {
// For pip, we'll implement a simplified version comparison
// This handles basic ranges like "<=2.25.0", ">=2.25.1", "<1.1.3,>=1.0"
if (!version || !range) return false;
// Split the range into parts (handles multiple constraints)
const rangeParts = range.split(',');
for (const part of rangeParts) {
const trimmed = part.trim();
if (!trimmed) continue;
// Extract operator and version
const operator = trimmed.substring(0, 2);
const rangeVersion = trimmed.substring(2);
// Split versions into components
const versionParts = version.split('.').map(Number);
const rangeParts = rangeVersion.split('.').map(Number);
// Pad arrays to equal length
while (versionParts.length < rangeParts.length) versionParts.push(0);
while (rangeParts.length < versionParts.length) rangeParts.push(0);
// Compare version components
let comparison = 0;
for (let i = 0; i < versionParts.length; i++) {
if (versionParts[i] > rangeParts[i]) {
comparison = 1;
break;
} else if (versionParts[i] < rangeParts[i]) {
comparison = -1;
break;
}
}
// Check if version satisfies this part of the range
if (operator === '<=') {
if (comparison > 0) return false;
} else if (operator === '>=') {
if (comparison < 0) return false;
} else if (operator === '==') {
if (comparison !== 0) return false;
} else if (operator === '!=') {
if (comparison === 0) return false;
} else if (operator.startsWith('<') && !operator.includes('=')) {
if (comparison >= 0) return false;
} else if (operator.startsWith('>') && !operator.includes('=')) {
if (comparison <= 0) return false;
}
}
// If all range constraints are satisfied, the version is vulnerable
return true;
}
/**
* Check if a package version is vulnerable using local database
*
* @param {string} ecosystem - The package ecosystem (npm, pip)
* @param {string} packageName - The name of the package
* @param {string} packageVersion - The version of the package
* @returns {object} - Object with isVulnerable flag and advisory details if found
*/
function checkLocalVulnerability(ecosystem, packageName, packageVersion) {
// Validate ecosystem
if (!CONFIG.SUPPORTED_ECOSYSTEMS.includes(ecosystem)) {
return {
isVulnerable: false,
error: `Unsupported ecosystem: ${ecosystem}. Supported ecosystems are: ${CONFIG.SUPPORTED_ECOSYSTEMS.join(', ')}`
};
}
// Format the key for lookup
const key = `${ecosystem}:${packageName}`;
const advisories = vulnerabilityDatabase.get(key);
// If package not found in database
if (!advisories || advisories.length === 0) {
return {
isVulnerable: false,
isUnknown: true,
message: `Package ${packageName} (${ecosystem}) not found in local vulnerability database`
};
}
// Validate version format
if (ecosystem === 'npm' && !semver.valid(packageVersion)) {
return {
isVulnerable: false,
error: `Invalid npm version format: ${packageVersion}. Expected semver format (e.g., 1.2.3)`
};
}
// Find matching vulnerabilities
const matchingAdvisories = advisories.filter(advisory => {
if (ecosystem === 'npm') {
return semver.satisfies(packageVersion, advisory.vulnerable_versions);
} else if (ecosystem === 'pip') {
return isPipVersionVulnerable(packageVersion, advisory.vulnerable_versions);
}
return false;
});
if (matchingAdvisories.length > 0) {
// Package is vulnerable
return {
isVulnerable: true,
advisories: matchingAdvisories,
source: 'local',
message: `${packageName}@${packageVersion} (${ecosystem}) has ${matchingAdvisories.length} known vulnerabilities`
};
}
// Package is safe
return {
isVulnerable: false,
source: 'local',
message: `${packageName}@${packageVersion} (${ecosystem}) has no known vulnerabilities in local database`
};
}
/**
* Comprehensive vulnerability check using all available sources
*
* @param {string} ecosystem - The package ecosystem (npm, pip)
* @param {string} packageName - The name of the package
* @param {string} packageVersion - The version of the package
* @returns {Promise<Object>} - Comprehensive vulnerability check result
*/
async function checkVulnerability(ecosystem, packageName, packageVersion) {
// Validate ecosystem
if (!CONFIG.SUPPORTED_ECOSYSTEMS.includes(ecosystem)) {
return {
isVulnerable: false,
error: `Unsupported ecosystem: ${ecosystem}. Supported ecosystems are: ${CONFIG.SUPPORTED_ECOSYSTEMS.join(', ')}`
};
}
// First check local database
const localResult = checkLocalVulnerability(ecosystem, packageName, packageVersion);
// If we have an error, return it immediately
if (localResult.error) {
return localResult;
}
// Initialize results array
let allAdvisories = localResult.advisories || [];
const sources = localResult.source ? [localResult.source] : [];
// Check GitHub API if enabled
if (githubConfig) {
try {
const githubResult = await checkGithubVulnerability(ecosystem, packageName, packageVersion, githubConfig);
if (githubResult.vulnerabilities && githubResult.vulnerabilities.length > 0) {
allAdvisories = [...allAdvisories, ...githubResult.vulnerabilities];
sources.push(githubResult.source);
}
} catch (error) {
console.error(`Error checking GitHub vulnerability: ${error.message}`);
}
}
// Check NVD if enabled
if (CONFIG.USE_NVD && nvdConfig) {
try {
const nvdResult = await checkNvdVulnerability(ecosystem, packageName, packageVersion, nvdConfig);
if (nvdResult.vulnerabilities && nvdResult.vulnerabilities.length > 0) {
allAdvisories = [...allAdvisories, ...nvdResult.vulnerabilities];
sources.push(nvdResult.source);
}
} catch (error) {
console.error(`Error checking NVD vulnerability: ${error.message}`);
}
}
// Deduplicate advisories based on CVE ID
const advisoriesMap = new Map();
allAdvisories.forEach(adv => {
const key = adv.cve_id || adv.id;
if (!advisoriesMap.has(key)) {
advisoriesMap.set(key, adv);
}
});
const uniqueAdvisories = Array.from(advisoriesMap.values());
// Return comprehensive result
if (uniqueAdvisories.length > 0) {
return {
isVulnerable: true,
advisories: uniqueAdvisories,
sources: Array.from(new Set(sources)),
message: `${packageName}@${packageVersion} (${ecosystem}) has ${uniqueAdvisories.length} known vulnerabilities across ${sources.length} sources`
};
} else if (localResult.isUnknown && sources.length <= 1) {
return {
isVulnerable: false,
isUnknown: true,
sources: Array.from(new Set(sources)),
message: `Package ${packageName} (${ecosystem}) not found in any vulnerability database`
};
} else {
return {
isVulnerable: false,
sources: Array.from(new Set(sources)),
message: `${packageName}@${packageVersion} (${ecosystem}) has no known vulnerabilities`
};
}
}
/**
* Refresh the vulnerability database from GitHub API
*/
async function refreshVulnerabilityDatabase() {
if (!githubConfig || !githubConfig.githubToken) {
console.warn('GitHub token not provided. Skipping database refresh.');
return;
}
console.log('Refreshing vulnerability database from GitHub API...');
try {
const count = await fetchAllAdvisories(githubConfig, CONFIG.DATA_PATH);
console.log(`Updated vulnerability database with ${count} advisories`);
// Reload the data
loadVulnerabilityData();
} catch (error) {
console.error(`Error refreshing vulnerability database: ${error.message}`);
}
}
// Initialize the MCP server
async function main() {
// Initialize NVD client if API key is available
if (process.env.NVD_API_KEY && CONFIG.USE_NVD) {
try {
console.log(`Using NVD API key: ${process.env.NVD_API_KEY.substring(0, 5)}...`);
const initResult = await initNvdClient({
apiKey: process.env.NVD_API_KEY,
cacheDir: path.join(__dirname, 'cache')
});
nvdConfig = initResult.config;
console.log('NVD client initialized successfully');
} catch (error) {
console.error(`Failed to initialize NVD client: ${error.message}`);
console.warn('Continuing without NVD integration');
}
} else {
console.warn(`NVD integration disabled or API key not provided: USE_NVD=${CONFIG.USE_NVD}, API key exists: ${Boolean(process.env.NVD_API_KEY)}`);
}
// Initialize GitHub client if token is available
if (process.env.GITHUB_TOKEN) {
try {
const initResult = await initGithubClient({
githubToken: process.env.GITHUB_TOKEN,
cacheDir: path.join(__dirname, 'cache')
});
githubConfig = initResult.config;
console.log('GitHub client initialized successfully');
} catch (error) {
console.error(`Failed to initialize GitHub client: ${error.message}`);
console.warn('Continuing without GitHub API integration');
}
} else {
console.warn('GitHub token not provided. Continuing with limited functionality.');
}
// Load vulnerability data
if (!loadVulnerabilityData()) {
console.warn('Starting with empty vulnerability database. Check data file path.');
}
// Schedule periodic database refresh
setInterval(refreshVulnerabilityDatabase, CONFIG.DATA_REFRESH_INTERVAL);
// Create the MCP server
const server = new Server(
{
name: "Vulnzap",
version: "2.0.0",
protocolVersion: "2.0",
serverInfo: {
host: "127.0.0.1",
port: 8080
}
},
{
capabilities: {
resources: {},
tools: {
"batch-scan": {},
"detailed-report": {},
"scan-code": {},
"scan-repository": {}
}
}
}
);
// Define a resource for vulnerability scanning
// URI pattern: vuln://{ecosystem}/{packageName}/{packageVersion}
server.setRequestHandler({
method: "resources/read",
params: {
uri: "vuln://{ecosystem}/{packageName}/{packageVersion}"
}
}, async (request) => {
try {
// Extract parameters from the URI
const uri = new URL(request.params.uri);
const segments = uri.pathname.split('/').filter(Boolean);
if (segments.length !== 3 || uri.protocol !== 'vuln:') {
throw new Error("Invalid vulnerability URI format. Expected: vuln://{ecosystem}/{packageName}/{packageVersion}");
}
const [ecosystem, packageName, packageVersion] = segments;
// Check if package is vulnerable
const result = await checkVulnerability(ecosystem, packageName, packageVersion);
// Construct response
if (result.error) {
// Return error response
throw new Error(result.error);
} else {
// Determine status based on vulnerability and whether it's known
let status = result.isUnknown ? "Unknown" : (result.isVulnerable ? "Vulnerable" : "Safe");
let content = `${status}: ${result.message}`;
// Add sources if available
if (result.sources && result.sources.length > 0) {
content += `\nSources: ${result.sources.join(', ')}`;
}
// Add vulnerability details if available
if (result.isVulnerable && result.advisories) {
content += `\n\nDetails:\n`;
result.advisories.forEach(adv => {
content += `- ${adv.title} (${adv.severity}`;
if (adv.cvss_score) content += `, CVSS: ${adv.cvss_score}`;
content += `, ${adv.cve_id || 'No CVE'}`;
if (adv.source) content += `, Source: ${adv.source}`;
content += `)\n`;
content += ` ${adv.description}\n`;
});
}
// Return result in MCP-compatible format
return {
contents: [{
uri: request.params.uri,
text: content
}]
};
}
} catch (error) {
console.error(`Error processing vulnerability check: ${error.message}`);
throw error;
}
});
// Premium feature: Batch vulnerability scanning
server.setRequestHandler({
method: "tools/invoke",
params: {
name: "batch-scan",
arguments: {
packages: {},
apiKey: { type: "string" }
}
}
}, async (request) => {
try {
const { packages, apiKey } = request.params.arguments;
// Check API key for premium access
if (apiKey !== CONFIG.PREMIUM_API_KEY) {
throw new Error("Invalid API key. Premium features require authentication.");
}
// Validate packages format
if (!Array.isArray(packages)) {
throw new Error("'packages' must be an array of objects with ecosystem, packageName, and packageVersion properties.");
}
// Process each package
const results = await Promise.all(packages.map(async pkg => {
const { ecosystem, packageName, packageVersion } = pkg;
// Skip invalid entries
if (!ecosystem || !packageName || !packageVersion) {
return {
package: pkg,
status: "error",
message: "Invalid package entry. Required fields: ecosystem, packageName, packageVersion"
};
}
// Check vulnerability
const result = await checkVulnerability(ecosystem, packageName, packageVersion);
// Format the response
if (result.error) {
return {
package: pkg,
status: "error",
message: result.error
};
} else if (result.isUnknown) {
return {
package: pkg,
status: "unknown",
message: result.message,
sources: result.sources
};
} else {
return {
package: pkg,
status: result.isVulnerable ? "vulnerable" : "safe",
message: result.message,
sources: result.sources,
...(result.isVulnerable && {
advisories: result.advisories.map(adv => ({
id: adv.id,
title: adv.title,
severity: adv.severity,
cvss_score: adv.cvss_score,
cve_id: adv.cve_id,
description: adv.description,
source: adv.source
}))
})
};
}
}));
// Return batch results
return {
content: [{
type: "text",
text: JSON.stringify({ results }, null, 2)
}]
};
} catch (error) {
console.error(`Error processing batch scan: ${error.message}`);
throw error;
}
});
// Premium feature: Detailed vulnerability report
server.setRequestHandler({
method: "tools/invoke",
params: {
name: "detailed-report",
arguments: {
ecosystem: { type: "string" },
packageName: { type: "string" },
packageVersion: { type: "string" },
apiKey: { type: "string" }
}
}
}, async (request) => {
try {
const { ecosystem, packageName, packageVersion, apiKey } = request.params.arguments;
// Check API key for premium access
if (apiKey !== CONFIG.PREMIUM_API_KEY) {
throw new Error("Invalid API key. Premium features require authentication.");
}
// Validate required fields
if (!ecosystem || !packageName || !packageVersion) {
throw new Error("Required fields missing. Please provide ecosystem, packageName, and packageVersion.");
}
// Check vulnerability
const result = await checkVulnerability(ecosystem, packageName, packageVersion);
// Generate detailed report
let report = `# Vulnerability Report for ${packageName}@${packageVersion} (${ecosystem})\n\n`;
// Add summary
if (result.error) {
report += `## Error\n\n${result.error}\n\n`;
} else if (result.isUnknown) {
report += `## Status: Unknown\n\n${result.message}\n\n`;
} else if (result.isVulnerable) {
report += `## Status: Vulnerable\n\n${result.message}\n\n`;
// Add data sources
if (result.sources && result.sources.length > 0) {
report += `## Data Sources\n\n`;
report += result.sources.map(source => `- ${source}`).join('\n');
report += '\n\n';
}
// Add vulnerability details if available
if (result.advisories && result.advisories.length > 0) {
report += `## Vulnerabilities\n\n`;
result.advisories.forEach(adv => {
report += `### ${adv.title}\n\n`;
report += `- **ID**: ${adv.id}\n`;
report += `- **Severity**: ${adv.severity}\n`;
if (adv.cvss_score) report += `- **CVSS Score**: ${adv.cvss_score}\n`;
report += `- **CVE**: ${adv.cve_id || 'N/A'}\n`;
report += `- **Data Source**: ${adv.source || 'N/A'}\n`;
report += `- **Vulnerable Versions**: ${adv.vulnerable_versions}\n`;
report += `- **Patched Versions**: ${adv.patched_versions || 'N/A'}\n`;
report += `- **Description**: ${adv.description}\n\n`;
});
}
// Add remediation advice
report += `## Remediation\n\n`;
report += `The recommended action is to update ${packageName} to the latest patched version.\n\n`;
// For npm packages
if (ecosystem === 'npm') {
report += `Run the following command to update:\n\n`;
report += `\`\`\`bash\nnpm update ${packageName}\n\`\`\`\n\n`;
report += `Or specify a specific version:\n\n`;
report += `\`\`\`bash\nnpm install ${packageName}@latest\n\`\`\`\n\n`;
}
// For pip packages
if (ecosystem === 'pip') {
report += `Run the following command to update:\n\n`;
report += `\`\`\`bash\npip install --upgrade ${packageName}\n\`\`\`\n\n`;
}
} else {
report += `## Status: Safe\n\n${result.message}\n\n`;
// Add data sources
if (result.sources && result.sources.length > 0) {
report += `## Data Sources\n\n`;
report += result.sources.map(source => `- ${source}`).join('\n');
report += '\n\n';
}
}
// Add disclaimer
report += `## Disclaimer\n\n`;
report += `This vulnerability report is generated automatically and may not be comprehensive. `;
report += `Always review the security advisories from the package maintainers and consider additional security measures for critical applications.\n\n`;
report += `Report generated by Vulnzap on ${new Date().toISOString()}\n`;
// Return the report
return {
content: [{
type: "text",
text: report
}]
};
} catch (error) {
console.error(`Error generating detailed report: ${error.message}`);
throw error;
}
});
// Add a tool for code scanning to detect potential vulnerabilities in code snippets
server.setRequestHandler({
method: "tools/invoke",
params: {
name: "scan-code",
arguments: {
code: { type: "string" },
language: { type: "string" },
apiKey: { type: "string" }
}
}
}, async (request) => {
try {
const { code, language, apiKey } = request.params.arguments;
// Check API key for premium access
if (apiKey !== CONFIG.PREMIUM_API_KEY) {
throw new Error("Invalid API key. This feature requires authentication.");
}
// Validate required fields
if (!code || !language) {
throw new Error("Required fields missing. Please provide code and language.");
}
// Map language to ecosystem
let ecosystem = language.toLowerCase();
if (ecosystem === 'javascript' || ecosystem === 'typescript' || ecosystem === 'js' || ecosystem === 'ts') {
ecosystem = 'npm';
} else if (ecosystem === 'python' || ecosystem === 'py') {
ecosystem = 'pip';
} else if (ecosystem === 'ruby' || ecosystem === 'rb') {
ecosystem = 'gem';
} else if (ecosystem === 'rust' || ecosystem === 'rs') {
ecosystem = 'cargo';
} else if (ecosystem === 'php') {
ecosystem = 'composer';
} else if (ecosystem === 'golang') {
ecosystem = 'go';
} else if (ecosystem === 'java') {
ecosystem = 'maven';
} else if (ecosystem === 'csharp' || ecosystem === 'cs') {
ecosystem = 'nuget';
}
// Basic pattern matching to detect potential dependency usage
const packages = [];
// npm/JavaScript package detection
if (ecosystem === 'npm') {
// Look for require, import statements
const requireRegex = /(?:const|let|var)\s+\S+\s*=\s*require\(['"]([^'"]+)['"]\)/g;
const importRegex = /import\s+(?:\S+\s+from\s+)?['"]([^'"]+)['"]/g;
const packageJsonRegex = /"dependencies":\s*{([^}]*)}/g;
let match;
while ((match = requireRegex.exec(code)) !== null) {
if (!match[1].startsWith('.') && !match[1].startsWith('/')) {
const pkgName = match[1].split('/')[0]; // Get base package name
packages.push({ ecosystem: 'npm', packageName: pkgName, packageVersion: 'latest' });
}
}
while ((match = importRegex.exec(code)) !== null) {
if (!match[1].startsWith('.') && !match[1].startsWith('/')) {
const pkgName = match[1].split('/')[0]; // Get base package name
packages.push({ ecosystem: 'npm', packageName: pkgName, packageVersion: 'latest' });
}
}
// Try to extract from package.json format
while ((match = packageJsonRegex.exec(code)) !== null) {
const dependencies = match[1];
const dependencyRegex = /"([^"]+)":\s*"([^"]+)"/g;
let depMatch;
while ((depMatch = dependencyRegex.exec(dependencies)) !== null) {
packages.push({ ecosystem: 'npm', packageName: depMatch[1], packageVersion: depMatch[2] });
}
}
}
// Python package detection
else if (ecosystem === 'pip') {
// Look for import statements and pip requirements
const importRegex = /(?:import|from)\s+([a-zA-Z0-9_]+)(?:\s+import|\s*$)/g;
const requirementsRegex = /([a-zA-Z0-9_\-]+)(?:==|>=|<=|>|<|~=)([0-9]+(?:\.[0-9]+)*)/g;
let match;
while ((match = importRegex.exec(code)) !== null) {
// Skip standard library modules
const stdLibModules = ['os', 'sys', 'math', 'json', 'time', 'datetime', 're', 'random', 'collections', 'itertools'];
if (!stdLibModules.includes(match[1])) {
packages.push({ ecosystem: 'pip', packageName: match[1], packageVersion: 'latest' });
}
}
while ((match = requirementsRegex.exec(code)) !== null) {
packages.push({ ecosystem: 'pip', packageName: match[1], packageVersion: match[2] });
}
}
// If we found packages, scan them for vulnerabilities
if (packages.length > 0) {
// Remove duplicates
const uniquePackages = Array.from(
new Map(packages.map(pkg => [`${pkg.ecosystem}:${pkg.packageName}`, pkg])).values()
);
// Check for vulnerabilities
const results = await Promise.all(uniquePackages.map(async pkg => {
if (pkg.packageVersion === 'latest') {
// Skip detailed version check for 'latest'
return {
package: pkg,
status: "warning",
message: `${pkg.packageName} detected, but version unknown. Please specify exact version for vulnerability scanning.`
};
}
// Check vulnerability
const result = await checkVulnerability(pkg.ecosystem, pkg.packageName, pkg.packageVersion);
// Format the response
if (result.error) {
return {
package: pkg,
status: "error",
message: result.error
};
} else if (result.isUnknown) {
return {
package: pkg,
status: "unknown",
message: result.message,
sources: result.sources
};
} else {
return {
package: pkg,
status: result.isVulnerable ? "vulnerable" : "safe",
message: result.message,
sources: result.sources,
...(result.isVulnerable && {
advisories: result.advisories.map(adv => ({
id: adv.id,
title: adv.title,
severity: adv.severity,
cvss_score: adv.cvss_score,
cve_id: adv.cve_id,
description: adv.description,
source: adv.source
}))
})
};
}
}));
// Generate report
let report = `# Code Vulnerability Scan Results\n\n`;
report += `Scanned ${uniquePackages.length} detected packages in ${language} code.\n\n`;
// Count by status
const vulnerableCount = results.filter(r => r.status === "vulnerable").length;
const warningCount = results.filter(r => r.status === "warning").length;
const safeCount = results.filter(r => r.status === "safe").length;
const unknownCount = results.filter(r => r.status === "unknown" || r.status === "error").length;
report += `## Summary\n\n`;
report += `- 🚨 Vulnerable packages: ${vulnerableCount}\n`;
report += `- ⚠️ Warning (version unknown): ${warningCount}\n`;
report += `- ✅ Safe packages: ${safeCount}\n`;
report += `- ❓ Unknown/error: ${unknownCount}\n\n`;
if (vulnerableCount > 0) {
report += `## Vulnerabilities\n\n`;
results
.filter(r => r.status === "vulnerable")
.forEach(result => {
report += `### ${result.package.packageName}@${result.package.packageVersion}\n\n`;
report += `${result.message}\n\n`;
if (result.advisories && result.advisories.length > 0) {
result.advisories.forEach(adv => {
report += `- ${adv.title} (${adv.severity})`;
if (adv.cvss_score) report += `, CVSS: ${adv.cvss_score}`;
report += `\n ${adv.description}\n\n`;
});
}
});
}
if (warningCount > 0) {
report += `## Warnings\n\n`;
results
.filter(r => r.status === "warning")
.forEach(result => {
report += `- ${result.package.packageName}: ${result.message}\n`;
});
report += `\n`;
}
report += `## Recommendations\n\n`;
report += `1. Always pin dependency versions to ensure reproducible builds\n`;
report += `2. Regularly update dependencies to include security patches\n`;
report += `3. Consider setting up automated vulnerability scanning in your CI/CD pipeline\n\n`;
report += `Scan performed by VulnZap on ${new Date().toISOString()}\n`;
return {
content: [{
type: "text",
text: report
}]
};
} else {
return {
content: [{
type: "text",
text: `No package dependencies detected in the provided ${language} code. The scan only supports dependency detection for common package formats.`
}]
};
}
} catch (error) {
console.error(`Error scanning code: ${error.message}`);
throw error;
}
});
// Add a tool for repository scanning that combines multiple checks
server.setRequestHandler({
method: "tools/invoke",
params: {
name: "scan-repository",
arguments: {
repositoryUrl: { type: "string" },
apiKey: { type: "string" }
}
}
}, async (request) => {
try {
const { repositoryUrl, apiKey } = request.params.arguments;
// Check API key for premium access
if (apiKey !== CONFIG.PREMIUM_API_KEY) {
throw new Error("Invalid API key. This feature requires authentication.");
}
// Validate repository URL
if (!repositoryUrl) {
throw new Error("Repository URL is required.");
}
// This is a placeholder for actual repository scanning functionality
// In a real implementation, we would:
// 1. Clone or download the repository
// 2. Identify package.json, requirements.txt, Gemfile, etc.
// 3. Extract dependencies and their versions
// 4. Check for vulnerabilities
return {
content: [{
type: "text",
text: `Repository scanning for ${repositoryUrl} is not yet implemented.\n\nFor now, please run 'scan-code' with specific code snippets or check individual packages using 'vulnerability-check'.`
}]
};
} catch (error) {
console.error(`Error scanning repository: ${error.message}`);
return {
content: [{
type: "text",
text: `Error scanning repository: ${error.message}`
}]
};
}
});
// Set up the transport
const transport = new StdioServerTransport();
// Start the server
server.connect(transport)
.then(() => {
console.log("Vulnzap MCP server started successfully");
console.log("Ready to process MCP requests");
console.log("\nActive Data Sources:");
console.log("- Local database");
if (githubConfig) console.log("- GitHub Advisory Database API");
if (nvdConfig) console.log("- National Vulnerability Database (NVD)");
console.log("\nSupported Ecosystems:");
console.log(CONFIG.SUPPORTED_ECOSYSTEMS.join(', '));
console.log("\nAvailable Tools:");
console.log("- vulnerability-check: Check individual packages (vuln://npm/express/4.16.0)");
console.log("- batch-scan: Scan multiple packages at once");
console.log("- detailed-report: Generate comprehensive vulnerability reports");
console.log("- scan-code: Detect and scan dependencies in code snippets");
console.log("- scan-repository: Check entire repositories (placeholder)");
console.log("- refresh-database: Update vulnerability database");
})
.catch(error => {
console.error(`Failed to start MCP server: ${error.message}`);
});
}
// Run the server
main().catch(error => {
console.error(`Failed to start Vulnzap server: ${error.message}`);
process.exit(1);
});