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