UNPKG

sourcegraph-mcp-server

Version:

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

415 lines (414 loc) 13.7 kB
"use strict"; /** * Code Intelligence Service * * Provides access to Sourcegraph's code intelligence APIs for features like: * - Definitions * - References * - Implementations * - Hover documentation * - Document symbols */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.formatImplementationsResults = exports.formatReferencesResults = exports.formatDefinitionResults = exports.getDocumentSymbolsQuery = exports.getHoverQuery = exports.getImplementationsQuery = exports.getReferencesQuery = exports.getDefinitionQuery = 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 definitions */ function getDefinitionQuery() { return ` query Definitions($repository: String!, $path: String!, $line: Int!, $character: Int!) { repository(name: $repository) { commit(rev: "HEAD") { blob(path: $path) { lsif { definitions(line: $line, character: $character) { nodes { resource { path repository { name } } range { start { line character } end { line character } } hover { markdown { text } plainText } } } } } } } } `; } exports.getDefinitionQuery = getDefinitionQuery; /** * Get GraphQL query for references */ function getReferencesQuery() { return ` query References($repository: String!, $path: String!, $line: Int!, $character: Int!, $limit: Int!) { repository(name: $repository) { commit(rev: "HEAD") { blob(path: $path) { lsif { references(line: $line, character: $character, first: $limit) { nodes { resource { path repository { name } } range { start { line character } end { line character } } preview } pageInfo { hasNextPage } totalCount } } } } } } `; } exports.getReferencesQuery = getReferencesQuery; /** * Get GraphQL query for implementations */ function getImplementationsQuery() { return ` query Implementations($repository: String!, $path: String!, $line: Int!, $character: Int!, $limit: Int!) { repository(name: $repository) { commit(rev: "HEAD") { blob(path: $path) { lsif { implementations(line: $line, character: $character, first: $limit) { nodes { resource { path repository { name } } range { start { line character } end { line character } } preview } pageInfo { hasNextPage } totalCount } } } } } } `; } exports.getImplementationsQuery = getImplementationsQuery; /** * Get GraphQL query for hover documentation */ function getHoverQuery() { return ` query Hover($repository: String!, $path: String!, $line: Int!, $character: Int!) { repository(name: $repository) { commit(rev: "HEAD") { blob(path: $path) { lsif { hover(line: $line, character: $character) { markdown { text } plainText range { start { line character } end { line character } } } } } } } } `; } exports.getHoverQuery = getHoverQuery; /** * Get GraphQL query for document symbols */ function getDocumentSymbolsQuery() { return ` query DocumentSymbols($repository: String!, $path: String!) { repository(name: $repository) { commit(rev: "HEAD") { blob(path: $path) { lsif { documentSymbols { symbols { name kind location { range { start { line character } end { line character } } } children { name kind location { range { start { line character } end { line character } } } } } } } } } } } `; } exports.getDocumentSymbolsQuery = getDocumentSymbolsQuery; /** * Format definitions results to readable output */ function formatDefinitionResults(data) { // Handle error cases if (!data?.repository?.commit?.blob?.lsif?.definitions?.nodes) { return "No definitions found or LSIF data not available for this file. LSIF data requires prior indexing."; } const nodes = data.repository.commit.blob.lsif.definitions.nodes; if (nodes.length === 0) { return "No definitions found for this symbol."; } let result = `## Definitions Found (${nodes.length})\n\n`; nodes.forEach((node, index) => { const repo = node.resource.repository.name; const path = node.resource.path; const startLine = node.range.start.line; const startChar = node.range.start.character; const endLine = node.range.end.line; const endChar = node.range.end.character; result += `### Definition ${index + 1}\n`; result += `**Location:** ${repo} - ${path}:${startLine + 1}:${startChar + 1}\n\n`; if (node.hover?.markdown?.text || node.hover?.plainText) { const docText = node.hover.markdown?.text || node.hover.plainText; result += `**Documentation:**\n${docText}\n\n`; } }); return result; } exports.formatDefinitionResults = formatDefinitionResults; /** * Format references results to readable output */ function formatReferencesResults(data, params) { // Handle error cases if (!data?.repository?.commit?.blob?.lsif?.references?.nodes) { return "No references found or LSIF data not available for this file. LSIF data requires prior indexing."; } const references = data.repository.commit.blob.lsif.references; const nodes = references.nodes; const totalCount = references.totalCount; const hasMore = references.pageInfo.hasNextPage; if (nodes.length === 0) { return "No references found for this symbol."; } let result = `## References Found (${totalCount})\n\n`; result += `References to symbol at ${params.repository}:${params.path}:${params.line + 1}:${params.character + 1}\n\n`; // Group references by repository and file const groupedRefs = {}; nodes.forEach((node) => { const repo = node.resource.repository.name; const path = node.resource.path; if (!groupedRefs[repo]) { groupedRefs[repo] = {}; } if (!groupedRefs[repo][path]) { groupedRefs[repo][path] = []; } groupedRefs[repo][path].push(node); }); // Format grouped references Object.keys(groupedRefs).forEach(repo => { result += `### Repository: ${repo}\n\n`; Object.keys(groupedRefs[repo]).forEach(path => { result += `#### File: ${path}\n\n`; groupedRefs[repo][path].forEach(ref => { const startLine = ref.range.start.line; const preview = ref.preview || "(no preview available)"; result += `Line ${startLine + 1}: \`${escapeMarkdown(preview.trim())}\`\n\n`; }); }); }); if (hasMore) { result += `\n> Note: There are more references available. This result is limited to showing ${nodes.length} references.\n`; } return result; } exports.formatReferencesResults = formatReferencesResults; /** * Format implementations results to readable output */ function formatImplementationsResults(data, params) { // Handle error cases if (!data?.repository?.commit?.blob?.lsif?.implementations?.nodes) { return "No implementations found or LSIF data not available for this file. LSIF data requires prior indexing."; } const implementations = data.repository.commit.blob.lsif.implementations; const nodes = implementations.nodes; const totalCount = implementations.totalCount; const hasMore = implementations.pageInfo.hasNextPage; if (nodes.length === 0) { return "No implementations found for this interface/abstract class."; } let result = `## Implementations Found (${totalCount})\n\n`; result += `Implementations of interface/class at ${params.repository}:${params.path}:${params.line + 1}:${params.character + 1}\n\n`; // Group implementations by repository and file const groupedImpls = {}; nodes.forEach((node) => { const repo = node.resource.repository.name; const path = node.resource.path; if (!groupedImpls[repo]) { groupedImpls[repo] = {}; } if (!groupedImpls[repo][path]) { groupedImpls[repo][path] = []; } groupedImpls[repo][path].push(node); }); // Format grouped implementations Object.keys(groupedImpls).forEach(repo => { result += `### Repository: ${repo}\n\n`; Object.keys(groupedImpls[repo]).forEach(path => { result += `#### File: ${path}\n\n`; groupedImpls[repo][path].forEach(impl => { const startLine = impl.range.start.line; const preview = impl.preview || "(no preview available)"; result += `Line ${startLine + 1}: \`${escapeMarkdown(preview.trim())}\`\n\n`; }); }); }); if (hasMore) { result += `\n> Note: There are more implementations available. This result is limited to showing ${nodes.length} implementations.\n`; } return result; } exports.formatImplementationsResults = formatImplementationsResults; /** * Escape markdown special characters in text */ function escapeMarkdown(text) { return text .replace(/\*/g, '\\*') .replace(/\_/g, '\\_') .replace(/\~/g, '\\~') .replace(/\`/g, '\\`'); }