sourcegraph-mcp-server
Version:
A Model Context Protocol (MCP) server that connects AI assistants to Sourcegraph code search with natural language capabilities
218 lines (217 loc) • 7.39 kB
JavaScript
;
/**
* Repository Content Service
*
* Provides access to Sourcegraph's API for retrieving repository content such as:
* - File content
* - File blame information
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.formatFileBlameResults = exports.formatFileContentResults = exports.getFileBlameQuery = exports.getFileContentQuery = 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;
/**
* Get GraphQL query for file content
*/
function getFileContentQuery() {
return `
query FileContent($repository: String!, $path: String!, $revision: String) {
repository(name: $repository) {
commit(rev: $revision, default: "HEAD") {
blob(path: $path) {
content
byteSize
binary
}
}
}
}
`;
}
exports.getFileContentQuery = getFileContentQuery;
/**
* Get GraphQL query for file blame information
*/
function getFileBlameQuery() {
return `
query FileBlame($repository: String!, $path: String!, $startLine: Int!, $endLine: Int!) {
repository(name: $repository) {
commit(rev: "HEAD") {
blob(path: $path) {
blame(startLine: $startLine, endLine: $endLine) {
startLine
endLine
author
email
date
message
commit {
oid
abbrevOid
message
author {
person {
name
email
}
date
}
}
}
}
}
}
}
`;
}
exports.getFileBlameQuery = getFileBlameQuery;
/**
* Format file content results to readable output
*/
function formatFileContentResults(data, params) {
// Handle error cases
if (!data?.repository?.commit?.blob) {
return "File not found or repository access error.";
}
const blob = data.repository.commit.blob;
if (blob.binary) {
return `Binary file detected (${blob.byteSize} bytes). Cannot display binary file content.`;
}
// Format for display
let result = `## File Content: ${params.repository}:${params.path}`;
if (params.revision) {
result += ` @ ${params.revision}`;
}
result += "\n\n";
// Add file size info
result += `File size: ${formatFileSize(blob.byteSize)}\n\n`;
// Get file extension for syntax highlighting
const fileExtension = params.path.split('.').pop() || '';
// Add the content with proper syntax highlighting
result += "```" + fileExtension + "\n";
result += blob.content;
result += "\n```";
return result;
}
exports.formatFileContentResults = formatFileContentResults;
/**
* Format file blame results to readable output
*/
function formatFileBlameResults(data, params) {
// Handle error cases
if (!data?.repository?.commit?.blob?.blame) {
return "Blame information not available or repository access error.";
}
const blameEntries = data.repository.commit.blob.blame;
if (blameEntries.length === 0) {
return "No blame information available for the specified lines.";
}
// Format for display
let result = `## Blame Information: ${params.repository}:${params.path} (Lines ${params.startLine + 1}-${params.endLine + 1})\n\n`;
// Format blame entries with table
result += "| Lines | Author | Date | Commit | Message |\n";
result += "|-------|--------|------|--------|---------|\n";
blameEntries.forEach((entry) => {
const startLine = entry.startLine + 1; // Convert to 1-indexed for display
const endLine = entry.endLine + 1; // Convert to 1-indexed for display
const lineRange = startLine === endLine ? `${startLine}` : `${startLine}-${endLine}`;
const author = entry.commit?.author?.person?.name || entry.author || "Unknown";
const date = formatDate(entry.commit?.author?.date || entry.date);
const commitId = entry.commit?.abbrevOid || "Unknown";
const message = formatCommitMessage(entry.commit?.message || entry.message || "No message");
result += `| ${lineRange} | ${author} | ${date} | ${commitId} | ${message} |\n`;
});
return result;
}
exports.formatFileBlameResults = formatFileBlameResults;
/**
* Format file size to human-readable format
*/
function formatFileSize(bytes) {
if (bytes < 1024) {
return `${bytes} bytes`;
}
else if (bytes < 1024 * 1024) {
return `${(bytes / 1024).toFixed(2)} KB`;
}
else if (bytes < 1024 * 1024 * 1024) {
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
}
else {
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
}
}
/**
* 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;
}
}
/**
* Format commit message (truncate if too long)
*/
function formatCommitMessage(message) {
if (!message)
return "No message";
// Extract first line and truncate if needed
const firstLine = message.split('\n')[0];
if (firstLine.length > 50) {
return firstLine.substring(0, 47) + "...";
}
return firstLine;
}