UNPKG

sourcegraph-mcp-server

Version:

A Model Context Protocol (MCP) server that connects AI assistants to Sourcegraph code search with natural language capabilities

311 lines (310 loc) â€ĸ 10.9 kB
"use strict"; /** * Security Service * * Provides access to Sourcegraph's security APIs for features like: * - CVE lookups * - Package vulnerability scans * - Exploit code searches * - Vendor security advisory lookups */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.buildVendorAdvisorySearchQuery = exports.buildExploitSearchQuery = exports.formatPackageVulnerabilityResults = exports.formatCVELookupResults = exports.getPackageVulnerabilityQuery = exports.getCVELookupQuery = exports.executeSourcegraphSearch = exports.executeSourcegraphQuery = exports.getSourcegraphConfig = void 0; const axios_1 = __importDefault(require("axios")); const dotenv_1 = __importDefault(require("dotenv")); // Load environment variables dotenv_1.default.config(); /** * Get Sourcegraph configuration from environment or provided config */ const getSourcegraphConfig = (config) => { const url = config?.url || process.env.SOURCEGRAPH_URL; const token = config?.token || process.env.SOURCEGRAPH_TOKEN; if (!url || !token) { throw new Error('Sourcegraph URL or token not configured. Please set SOURCEGRAPH_URL and SOURCEGRAPH_TOKEN environment variables.'); } return { url, token }; }; exports.getSourcegraphConfig = getSourcegraphConfig; /** * Execute a GraphQL query against the Sourcegraph API */ async function executeSourcegraphQuery(graphqlQuery, variables, sourcegraphConfig) { // Get configuration const config = (0, exports.getSourcegraphConfig)(sourcegraphConfig); // Headers for Sourcegraph API const headers = { 'Authorization': `token ${config.token}`, 'Content-Type': 'application/json' }; try { // Make the request to Sourcegraph API const response = await axios_1.default.post(`${config.url}/.api/graphql`, { query: graphqlQuery, variables }, { headers }); return response.data; } catch (error) { // Format error for better debugging if (error.response) { throw new Error(`Sourcegraph API error (${error.response.status}): ${JSON.stringify(error.response.data)}`); } else if (error.request) { throw new Error(`No response from Sourcegraph API: ${error.message}`); } else { throw new Error(`Error setting up request: ${error.message}`); } } } exports.executeSourcegraphQuery = executeSourcegraphQuery; /** * Execute a regular Sourcegraph search and return the results */ async function executeSourcegraphSearch(query, graphqlQuery, sourcegraphConfig) { // Headers for Sourcegraph API const headers = { 'Authorization': `token ${sourcegraphConfig.token}`, 'Content-Type': 'application/json' }; // Make the request to Sourcegraph API const response = await axios_1.default.post(`${sourcegraphConfig.url}/.api/graphql`, { query: graphqlQuery, variables: { query } }, { headers }); return response.data; } exports.executeSourcegraphSearch = executeSourcegraphSearch; /** * Get GraphQL query for CVE lookup */ function getCVELookupQuery() { return ` query CVELookup($cveId: String, $package: String, $repository: String, $limit: Int!) { vulnerabilities(first: $limit, cve: $cveId, package: $package, repository: $repository) { nodes { type id severity package { name ecosystem } affectedVersions fixedVersions summary details published references { url type } } totalCount } } `; } exports.getCVELookupQuery = getCVELookupQuery; /** * Get GraphQL query for package vulnerability lookup */ function getPackageVulnerabilityQuery() { return ` query PackageVulnerability($package: String!, $version: String, $limit: Int!) { vulnerabilities(first: $limit, package: $package, version: $version) { nodes { type id severity package { name ecosystem } affectedVersions fixedVersions summary details published references { url type } } totalCount } } `; } exports.getPackageVulnerabilityQuery = getPackageVulnerabilityQuery; /** * Format CVE lookup results to readable output */ function formatCVELookupResults(data, params) { // Handle error cases if (!data?.vulnerabilities?.nodes) { return "No vulnerability data found."; } const nodes = data.vulnerabilities.nodes; const totalCount = data.vulnerabilities.totalCount; if (nodes.length === 0) { return "No vulnerabilities found matching the criteria."; } // Build query description let queryDesc = ""; if (params.cveId) { queryDesc += `CVE ID: ${params.cveId}`; } if (params.package) { queryDesc += queryDesc ? `, Package: ${params.package}` : `Package: ${params.package}`; } if (params.repository) { queryDesc += queryDesc ? `, Repository: ${params.repository}` : `Repository: ${params.repository}`; } // Format for display let result = `## Vulnerabilities Found (${totalCount})\n\n`; if (queryDesc) { result += `Search criteria: ${queryDesc}\n\n`; } nodes.forEach((vuln, index) => { const vulnId = vuln.id; const severity = formatSeverity(vuln.severity); const pkgName = vuln.package?.name || "Unknown"; const ecosystem = vuln.package?.ecosystem || "Unknown"; result += `### ${index + 1}. ${vulnId} - ${pkgName} (${ecosystem})\n\n`; result += `**Severity:** ${severity}\n\n`; if (vuln.summary) { result += `**Summary:** ${vuln.summary}\n\n`; } if (vuln.affectedVersions && vuln.affectedVersions.length > 0) { result += `**Affected Versions:** ${vuln.affectedVersions.join(', ')}\n\n`; } if (vuln.fixedVersions && vuln.fixedVersions.length > 0) { result += `**Fixed Versions:** ${vuln.fixedVersions.join(', ')}\n\n`; } if (vuln.published) { result += `**Published:** ${formatDate(vuln.published)}\n\n`; } if (vuln.references && vuln.references.length > 0) { result += `**References:**\n`; vuln.references.forEach((ref) => { result += `- [${ref.type || 'Link'}](${ref.url})\n`; }); result += "\n"; } if (vuln.details) { result += `**Details:**\n${vuln.details}\n\n`; } if (index < nodes.length - 1) { result += "---\n\n"; } }); return result; } exports.formatCVELookupResults = formatCVELookupResults; /** * Format package vulnerability results to readable output */ function formatPackageVulnerabilityResults(data, params) { // Handle error cases if (!data?.vulnerabilities?.nodes) { return "No vulnerability data found."; } const nodes = data.vulnerabilities.nodes; const totalCount = data.vulnerabilities.totalCount; if (nodes.length === 0) { return `No vulnerabilities found for package ${params.package}${params.version ? ` version ${params.version}` : ''}.`; } // Format for display let result = `## Security Vulnerabilities for ${params.package}${params.version ? ` v${params.version}` : ''}\n\n`; result += `Found ${totalCount} vulnerabilities\n\n`; // Add severity summary const severityCounts = {}; nodes.forEach((vuln) => { const severity = vuln.severity || "unknown"; severityCounts[severity] = (severityCounts[severity] || 0) + 1; }); result += "**Severity Summary:**\n"; const severityOrder = ["critical", "high", "moderate", "medium", "low", "unknown"]; severityOrder.forEach(severity => { if (severityCounts[severity]) { result += `- ${formatSeverity(severity)}: ${severityCounts[severity]}\n`; } }); result += "\n"; // Detail each vulnerability nodes.forEach((vuln, index) => { const vulnId = vuln.id; const severity = formatSeverity(vuln.severity); result += `### ${index + 1}. ${vulnId}\n\n`; result += `**Severity:** ${severity}\n\n`; if (vuln.summary) { result += `**Summary:** ${vuln.summary}\n\n`; } if (vuln.affectedVersions && vuln.affectedVersions.length > 0) { result += `**Affected Versions:** ${vuln.affectedVersions.join(', ')}\n\n`; } if (vuln.fixedVersions && vuln.fixedVersions.length > 0) { result += `**Fixed Versions:** ${vuln.fixedVersions.join(', ')}\n\n`; } if (vuln.references && vuln.references.length > 0) { result += `**References:**\n`; vuln.references.forEach((ref) => { result += `- [${ref.type || 'Link'}](${ref.url})\n`; }); result += "\n"; } if (index < nodes.length - 1) { result += "---\n\n"; } }); return result; } exports.formatPackageVulnerabilityResults = formatPackageVulnerabilityResults; /** * Format severity to readable and colored format */ function formatSeverity(severity) { if (!severity) return "Unknown"; severity = severity.toLowerCase(); switch (severity) { case "critical": return "âš ī¸ Critical"; case "high": return "🔴 High"; case "moderate": case "medium": return "🟠 Medium"; case "low": return "🟡 Low"; default: return "â„šī¸ " + severity.charAt(0).toUpperCase() + severity.slice(1); } } /** * Format date to readable format */ function formatDate(dateStr) { if (!dateStr) return "Unknown"; try { const date = new Date(dateStr); return date.toISOString().split('T')[0]; // YYYY-MM-DD format } catch (e) { return dateStr; } } /** * Build a search query for exploits based on CVE ID */ function buildExploitSearchQuery(cveId) { return `type:file (${cveId} OR "${cveId}") (poc OR exploit OR proof-of-concept) count:20`; } exports.buildExploitSearchQuery = buildExploitSearchQuery; /** * Build a search query for vendor security advisories */ function buildVendorAdvisorySearchQuery(vendor, product) { return `type:file "${vendor}" "${product}" (security OR advisory OR vulnerability OR CVE) count:20`; } exports.buildVendorAdvisorySearchQuery = buildVendorAdvisorySearchQuery;