sourcegraph-mcp-server
Version:
A Model Context Protocol (MCP) server that connects AI assistants to Sourcegraph code search with natural language capabilities
912 lines (907 loc) • 73.5 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createServer = void 0;
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
const zod_1 = require("zod");
const dotenv_1 = __importDefault(require("dotenv"));
// import { naturalLanguageSearch } from './services/natural-language';
const formatter_1 = require("./utils/formatter");
const sourcegraph_1 = require("./services/sourcegraph");
const code_intelligence_1 = require("./services/code-intelligence");
const repository_content_1 = require("./services/repository-content");
const security_1 = require("./services/security");
// Load environment variables
dotenv_1.default.config();
// Sourcegraph API configuration
const sgUrl = process.env.SOURCEGRAPH_URL;
const sgToken = process.env.SOURCEGRAPH_TOKEN;
const test_tools_1 = require("./test-tools");
/**
* Creates and configures the Sourcegraph MCP server
* with resources, prompts, and tools
*/
function createServer() {
const toolImplementations = {};
// Create an MCP server
const server = new mcp_js_1.McpServer({
name: "sourcegraph-mcp-server",
version: "1.0.0",
debug: false, // Disable debug mode in production to prevent console output breaking JSON
});
// Add a static resource
server.resource("hello", "hello://sourcegraph", async (uri) => ({
contents: [
{
uri: uri.href,
text: "Hello from Sourcegraph MCP Server! Ready to search code repositories.",
},
],
}));
// Add a dynamic resource with parameters
server.resource("greeting", new mcp_js_1.ResourceTemplate("greeting://{name}", { list: undefined }), async (uri, { name }) => ({
contents: [
{
uri: uri.href,
text: `Hello, ${name}! Welcome to the Sourcegraph MCP Server.`,
},
],
}));
// Add a prompt
server.prompt("sourcegraph-assistant", "A prompt that introduces Sourcegraph search capabilities", () => ({
messages: [
{
role: "assistant",
content: {
type: "text",
text: "I'm a Sourcegraph assistant that can help you search through code repositories. You can ask me to search for code, commits, or diffs.",
},
},
],
}));
// Add an echo tool
// Just use direct implementation in the tool
toolImplementations["echo"] = async (args) => {
return {
content: [
{
type: "text",
text: `Hello ${args.message}`,
},
],
};
};
server.tool("echo", "Simple echo tool for testing that returns your message with 'Hello' prefix.\n\n WHEN TO USE THIS TOOL:\n - When testing if the MCP server is responsive\n - When verifying tool invocation is working correctly\n - For basic connectivity tests\n - When learning how to use the MCP server\n\n PARAMETER USAGE:\n - message: Any text string you want echoed back\n\n EXAMPLES:\n - message = 'world' returns 'Hello world'\n - message = 'testing' returns 'Hello testing'\n \n This is primarily a diagnostic tool to verify the system is working properly.", { message: zod_1.z.string().describe("The message to echo") }, async ({ message }) => ({
content: [
{
type: "text",
text: `Hello ${message}`,
},
],
}));
// Add code search tool - now using direct Sourcegraph API access
server.tool("search-code", `Searches code across repositories using Sourcegraph's API.
Query parameters and syntax examples:
- File search (default):
- Basic search: functionName - Search for the term across repositories
- Repository filter: repo:^github\\.com/owner/repo$ - Limit search to specific repositories
- File filter: file:\\.js$ - Search only in files with specific patterns
- Language filter: lang:javascript - Search only in specific language files
- Combined filters: repo:^github\\.com/org/repo$ file:\\.js$ functionName
- Content search: content:"exact phrase" - Search for exact text match
- Boolean operators: term1 AND term2, term1 OR term2, term1 NOT term2
- Regular expressions: /pattern/ or use patternType:regexp in query
- Commit search:
- Basic: type:commit searchTerm - Search in commit metadata
- Message filter: type:commit message:"fix bug" - Search in commit messages
- Author filter: type:commit author:username - Filter by commit author
- Time filters: type:commit after:"2 weeks ago" or before:"2023-01-01"
- Repository branch: repo:owner/repo@branch type:commit term - Search in specific branch
- Example: type:commit message:"security fix" author:john after:"1 month ago"
- Diff search:
- Basic: type:diff searchTerm - Search in code changes
- Added/removed code: type:diff select:commit.diff.added term - Only in added code
- Repository filter: repo:owner/repo type:diff term - Limit to specific repositories
- Time range: type:diff after:"1 week ago" term - Recent changes only
- Example: type:diff select:commit.diff.removed securityCheck
Notes:
- The function automatically adds the appropriate type: parameter if not included in the query.
- Results are limited to 20 by default. Use count:N in your query to adjust this limit.
- For regex searches, use /pattern/ syntax or add patternType:regexp to your query.
- All searches use keyword pattern matching by default (case-insensitive).`, {
query: zod_1.z.string().describe("Search query text"),
type: zod_1.z.enum(['file', 'commit', 'diff']).default('file').describe("Type of search: file, commit, or diff")
}, async ({ query, type }) => {
// Validate Sourcegraph credentials
const effectiveUrl = sgUrl || process.env.SOURCEGRAPH_URL;
const effectiveToken = sgToken || process.env.SOURCEGRAPH_TOKEN;
if (!effectiveUrl || !effectiveToken) {
return {
content: [{
type: "text",
text: "Error: Sourcegraph URL or token not configured. Please set SOURCEGRAPH_URL and SOURCEGRAPH_TOKEN environment variables."
}],
isError: true
};
}
try {
// No natural language processing - use direct query syntax
// Just add the type and count parameters if not present
let finalQuery = query.includes('type:') ? query : `${query} type:${type}`;
finalQuery = finalQuery.includes('count:') ? finalQuery : `${finalQuery} count:20`;
// Select appropriate GraphQL query based on search type
let graphqlQuery;
switch (type) {
case 'commit':
graphqlQuery = (0, sourcegraph_1.getCommitSearchQuery)();
break;
case 'diff':
graphqlQuery = (0, sourcegraph_1.getDiffSearchQuery)();
break;
case 'file':
default:
graphqlQuery = (0, sourcegraph_1.getFileSearchQuery)();
break;
}
// Execute the search using the Sourcegraph service
const response = await (0, sourcegraph_1.executeSourcegraphSearch)(finalQuery, graphqlQuery, { url: effectiveUrl, token: effectiveToken });
if (response.errors) {
return {
content: [{
type: "text",
text: `Sourcegraph API Error: ${JSON.stringify(response.errors)}`
}],
isError: true
};
}
// Format the results
const results = response.data.search.results;
// Use the formatter for results
const formattedResults = (0, formatter_1.formatSearchResults)(results, { query: finalQuery, type });
return {
content: [{
type: "text",
text: formattedResults
}]
};
}
catch (error) {
return {
content: [{
type: "text",
text: `Error searching Sourcegraph: ${error.message || 'Unknown error'}`
}],
isError: true
};
}
});
// Add commit search tool - using direct Sourcegraph API
server.tool("search-commits", "Search for commits in Sourcegraph repositories with flexible filtering options.\n\n WHEN TO USE THIS TOOL:\n - When you need to find specific commits across repositories\n - When searching for code changes by a particular author\n - When looking for commits within a particular timeframe\n - When searching for specific commit messages or fixes\n\n PARAMETER USAGE:\n - author: The username of the commit author (e.g., 'jane', 'john.doe')\n - message: Text to search for in commit messages (e.g., 'fix authentication bug')\n - after: Date filter in YYYY-MM-DD format (e.g., '2023-01-15') or relative time ('2 weeks ago')\n\n SEARCH EXAMPLES:\n - Find security fixes: message = 'security fix'\n - Find recent commits by a specific author: author = 'username', after = '2023-10-01'\n - Find all commits mentioning a specific feature: message = 'user authentication'\n \n Notes:\n - The tool automatically adds 'type:commit' to your search\n - Results are limited to 20 by default\n - Date strings in 'after' can be exact dates or relative like '2 weeks ago'\n - Commit results include hash, message, author, and date", {
author: zod_1.z.string().optional().describe("Filter by commit author"),
message: zod_1.z.string().optional().describe("Filter by commit message"),
after: zod_1.z.string().optional().describe("Filter for commits after this date (YYYY-MM-DD)")
}, async ({ author, message, after }) => {
// Validate Sourcegraph credentials
const effectiveUrl = sgUrl || process.env.SOURCEGRAPH_URL;
const effectiveToken = sgToken || process.env.SOURCEGRAPH_TOKEN;
if (!effectiveUrl || !effectiveToken) {
return {
content: [{
type: "text",
text: "Error: Sourcegraph URL or token not configured. Please set SOURCEGRAPH_URL and SOURCEGRAPH_TOKEN environment variables."
}],
isError: true
};
}
try {
// Build the search query using provided parameters directly
let finalQuery = 'type:commit';
if (author)
finalQuery += ` author:${author}`;
if (message)
finalQuery += ` message:${message}`;
if (after)
finalQuery += ` after:${after}`;
finalQuery += ' count:20';
// Get the commit search GraphQL query
const graphqlQuery = (0, sourcegraph_1.getCommitSearchQuery)();
// Execute the search using the Sourcegraph service
const response = await (0, sourcegraph_1.executeSourcegraphSearch)(finalQuery, graphqlQuery, { url: effectiveUrl, token: effectiveToken });
if (response.errors) {
return {
content: [{
type: "text",
text: `Sourcegraph API Error: ${JSON.stringify(response.errors)}`
}],
isError: true
};
}
// Format the results
const results = response.data.search.results;
const formattedResults = (0, formatter_1.formatSearchResults)(results, { query: finalQuery, type: 'commit' });
return {
content: [{
type: "text",
text: formattedResults
}]
};
}
catch (error) {
return {
content: [{
type: "text",
text: `Error searching Sourcegraph commits: ${error.message || 'Unknown error'}`
}],
isError: true
};
}
});
// Add diff search tool - using direct Sourcegraph API
server.tool("search-diffs", "Search for code changes (diffs) in Sourcegraph repositories with detailed filtering.\n\n WHEN TO USE THIS TOOL:\n - When looking for specific code changes or modifications\n - When you need to find added or removed code\n - When tracking changes by specific authors\n - When investigating changes made during a particular time period\n\n PARAMETER USAGE:\n - query: Terms to search for in the changed code (e.g., 'fix memory leak')\n - author: Filter diffs by the commit author (e.g., 'jane.smith')\n - after: Filter for changes after a specific date (YYYY-MM-DD or relative time)\n\n ADVANCED SEARCH TECHNIQUES:\n - Find added code: query = 'select:commit.diff.added new_function'\n - Find removed code: query = 'select:commit.diff.removed old_function'\n - Limit to specific file types: query = 'path:\\.js$ authentication'\n - Combine author with timeframe: author = 'alex', after = '2 months ago'\n \n Notes:\n - Diffs show hunks of changed code for each modification\n - Changes include file path, line number ranges, and exact modifications\n - The tool automatically adds 'type:diff' to your search\n - Results include commit context (message, author, date) along with the changes", {
query: zod_1.z.string().optional().describe("Search query text"),
author: zod_1.z.string().optional().describe("Filter by commit author"),
after: zod_1.z.string().optional().describe("Filter for diffs after this date (YYYY-MM-DD)")
}, async ({ query, author, after }) => {
// Validate Sourcegraph credentials
const effectiveUrl = sgUrl || process.env.SOURCEGRAPH_URL;
const effectiveToken = sgToken || process.env.SOURCEGRAPH_TOKEN;
if (!effectiveUrl || !effectiveToken) {
return {
content: [{
type: "text",
text: "Error: Sourcegraph URL or token not configured. Please set SOURCEGRAPH_URL and SOURCEGRAPH_TOKEN environment variables."
}],
isError: true
};
}
try {
// Build the search query with provided parameters directly
let finalQuery = 'type:diff';
if (query)
finalQuery += ` ${query}`;
if (author)
finalQuery += ` author:${author}`;
if (after)
finalQuery += ` after:${after}`;
finalQuery += ' count:20';
// Get the diff search GraphQL query
const graphqlQuery = (0, sourcegraph_1.getDiffSearchQuery)();
// Execute the search using the Sourcegraph service
const response = await (0, sourcegraph_1.executeSourcegraphSearch)(finalQuery, graphqlQuery, { url: effectiveUrl, token: effectiveToken });
if (response.errors) {
return {
content: [{
type: "text",
text: `Sourcegraph API Error: ${JSON.stringify(response.errors)}`
}],
isError: true
};
}
// Format the results
const results = response.data.search.results;
const formattedResults = (0, formatter_1.formatSearchResults)(results, { query: finalQuery, type: 'diff' });
return {
content: [{
type: "text",
text: formattedResults
}]
};
}
catch (error) {
return {
content: [{
type: "text",
text: `Error searching Sourcegraph diffs: ${error.message || 'Unknown error'}`
}],
isError: true
};
}
});
// Add a tool to search specifically in GitHub repositories - using direct Sourcegraph API
server.tool("search-github-repos", "Search for code, commits, or diffs specifically in GitHub repositories.\n\n WHEN TO USE THIS TOOL:\n - When you need to search within specific known GitHub repositories\n - When searching across multiple GitHub repos simultaneously\n - When you need targeted searches in open source projects\n - When you want to limit searches to verified repositories\n\n PARAMETER USAGE:\n - query: What to search for (e.g., 'render function', 'authentication middleware')\n - repos: Comma-separated list of GitHub repositories in 'owner/repo' format\n - type: The type of search to perform ('file', 'commit', or 'diff')\n\n REPOSITORY SPECIFICATION:\n - Single repository: 'microsoft/typescript'\n - Multiple repositories: 'facebook/react,angular/angular,vuejs/vue'\n - Organization-wide: Use multiple specific repos instead of wildcards\n \n EXAMPLES:\n - Find authentication code in React: query='authentication', repos='facebook/react'\n - Find GraphQL usage across popular frameworks: query='graphql', repos='apollographql/apollo-client,graphql/graphql-js'\n - Find recent security fixes: query='security fix', repos='kubernetes/kubernetes', type='commit'\n \n Notes:\n - Format repositories exactly as they appear on GitHub (owner/repo)\n - Searches within specified repos only, not forks or related projects\n - Can be combined with any syntax from search-code, search-commits, and search-diffs", {
query: zod_1.z.string().describe("Search query text"),
repos: zod_1.z.string().describe("Comma-separated list of GitHub repositories to search in (e.g., 'owner/repo1,owner/repo2')"),
type: zod_1.z.enum(['file', 'commit', 'diff']).default('file').describe("Type of search: file, commit, or diff")
}, async ({ query, repos, type }) => {
// Validate Sourcegraph credentials
const effectiveUrl = sgUrl || process.env.SOURCEGRAPH_URL;
const effectiveToken = sgToken || process.env.SOURCEGRAPH_TOKEN;
if (!effectiveUrl || !effectiveToken) {
return {
content: [{
type: "text",
text: "Error: Sourcegraph URL or token not configured. Please set SOURCEGRAPH_URL and SOURCEGRAPH_TOKEN environment variables."
}],
isError: true
};
}
try {
// Parse the repo list
const repoList = repos.split(',').map(r => r.trim());
// Build the search query with repo filters
const repoFilters = repoList.map(repo => `repo:^github\\.com/${repo}$`).join(' ');
// Build the final search query directly - no NL processing
const finalQuery = `${query} ${repoFilters} type:${type} count:20`;
// Select appropriate GraphQL query based on search type
let graphqlQuery;
switch (type) {
case 'commit':
graphqlQuery = (0, sourcegraph_1.getCommitSearchQuery)();
break;
case 'diff':
graphqlQuery = (0, sourcegraph_1.getDiffSearchQuery)();
break;
case 'file':
default:
graphqlQuery = (0, sourcegraph_1.getFileSearchQuery)();
break;
}
// Execute the search using the Sourcegraph service
const response = await (0, sourcegraph_1.executeSourcegraphSearch)(finalQuery, graphqlQuery, { url: effectiveUrl, token: effectiveToken });
if (response.errors) {
return {
content: [{
type: "text",
text: `Sourcegraph API Error: ${JSON.stringify(response.errors)}`
}],
isError: true
};
}
// Format the results
const results = response.data.search.results;
const formattedResults = (0, formatter_1.formatSearchResults)(results, { query: finalQuery, type });
return {
content: [{
type: "text",
text: formattedResults
}]
};
}
catch (error) {
return {
content: [{
type: "text",
text: `Error searching GitHub repositories: ${error.message || 'Unknown error'}`
}],
isError: true
};
}
});
// Natural language search tool commented out
/*
server.tool(
"natural-search",
"Search code repositories using natural language queries instead of precise syntax.\n\n WHEN TO USE THIS TOOL:\n - When you want to search using plain English instead of specific query syntax\n - When you're unsure of the exact Sourcegraph search syntax\n - When you want to describe what you're looking for conceptually\n - When you want automatic detection of search type (code, commits, diffs)\n\n PARAMETER USAGE:\n - query: Your search request in natural language (e.g., 'Find authentication code in React components')\n - max_results: Optional limit on the number of results (default: 20)\n\n NATURAL LANGUAGE EXAMPLES:\n - 'Find all implementations of authentication in the frontend code'\n - 'Show me commits by Sarah from last month related to the login system'\n - 'Look for recent changes to the API error handling'\n - 'Find code that handles file uploads in Python repositories'\n \n SUPPORTED CONCEPTS (AUTOMATICALLY DETECTED):\n - Code patterns: 'Find code that validates user input'\n - Specific authors: 'Show commits by John'\n - Time periods: 'Find changes from last week'\n - Repositories: 'Search in the React codebase'\n - Languages: 'Find JavaScript code for authentication'\n \n Notes:\n - This tool uses AI to convert your query into Sourcegraph syntax\n - It automatically detects if you're looking for code, commits, or diffs\n - You can freely mix concepts in a single query\n - Results are formatted for readability with context",
{
query: z.string().describe("Natural language query describing what you want to search for"),
max_results: z.number().optional().describe("Maximum number of results to return (default: 20)")
},
async ({ query, max_results }) => {
// Implementation commented out
return {
content: [{
type: "text",
text: "Natural language search is disabled."
}]
};
}
);
*/
// Add get-hover-documentation tool
server.tool("get-hover-documentation", "Get hover documentation and type information for a symbol.\n\n" +
"WHEN TO USE THIS TOOL:\n" +
"- When you need to see the documentation for a specific function, variable, or class\n" +
"- When you want to check the type information for a variable or expression\n" +
"- When you need quick information about a symbol without navigating to its definition\n" +
"- When exploring unfamiliar code to understand how components work\n\n" +
"PARAMETER USAGE:\n" +
"- repository: The full repository name (e.g., 'github.com/owner/repo')\n" +
"- path: The file path where the symbol appears (e.g., 'src/main.js')\n" +
"- line: The zero-indexed line number where the symbol appears\n" +
"- character: The zero-indexed character position of the symbol on that line\n\n" +
"IMPORTANT NOTES:\n" +
"- Requires LSIF data to be available in Sourcegraph (precise code intelligence)\n" +
"- For accurate results, the repository must be properly indexed in Sourcegraph\n" +
"- Line and character positions are zero-indexed (unlike editors which often use 1-indexed)\n\n" +
"EXAMPLES:\n" +
"- Get information about a function: { repository: 'github.com/golang/go', path: 'src/net/http/server.go', line: 142, character: 15 }\n" +
"- Check a variable's type: { repository: 'github.com/microsoft/typescript', path: 'src/compiler/program.ts', line: 124, character: 30 }\n\n" +
"The results will show the documentation and type information for the symbol at the specified position.", {
repository: zod_1.z.string().describe("The repository name (e.g. github.com/owner/repo)"),
path: zod_1.z.string().describe("The file path within the repository"),
line: zod_1.z.number().describe("Zero-indexed line number of the symbol"),
character: zod_1.z.number().describe("Zero-indexed character position of the symbol")
}, async ({ repository, path, line, character }) => {
// Validate Sourcegraph credentials
const effectiveUrl = sgUrl || process.env.SOURCEGRAPH_URL;
const effectiveToken = sgToken || process.env.SOURCEGRAPH_TOKEN;
if (!effectiveUrl || !effectiveToken) {
return {
content: [{
type: "text",
text: "Error: Sourcegraph URL or token not configured. Please set SOURCEGRAPH_URL and SOURCEGRAPH_TOKEN environment variables."
}],
isError: true
};
}
try {
// Get the hover query
const graphqlQuery = (0, code_intelligence_1.getHoverQuery)();
// Execute the query
const response = await (0, code_intelligence_1.executeSourcegraphQuery)(graphqlQuery, { repository, path, line, character }, { url: effectiveUrl, token: effectiveToken });
if (response.errors) {
return {
content: [{
type: "text",
text: `Sourcegraph API Error: ${JSON.stringify(response.errors)}`
}],
isError: true
};
}
// Format the results
const hoverData = response.data?.repository?.commit?.blob?.lsif?.hover;
if (!hoverData) {
return {
content: [{
type: "text",
text: "No hover documentation found or LSIF data not available for this file."
}]
};
}
let result = "## Hover Documentation\n\n";
if (hoverData.markdown?.text) {
result += `${hoverData.markdown.text}\n\n`;
}
else if (hoverData.plainText) {
result += `\`\`\`\n${hoverData.plainText}\n\`\`\`\n\n`;
}
else {
result += "No documentation available for this symbol.\n\n";
}
if (hoverData.range) {
const startLine = hoverData.range.start.line + 1;
const startChar = hoverData.range.start.character + 1;
const endLine = hoverData.range.end.line + 1;
const endChar = hoverData.range.end.character + 1;
result += `**Symbol Range:** Line ${startLine}:${startChar} to ${endLine}:${endChar}\n`;
}
return {
content: [{
type: "text",
text: result
}]
};
}
catch (error) {
return {
content: [{
type: "text",
text: `Error getting hover documentation: ${error.message || 'Unknown error'}`
}],
isError: true
};
}
});
// Add get-document-symbols tool
server.tool("get-document-symbols", "Get all symbols (functions, classes, variables, etc.) in a file.\n\n" +
"WHEN TO USE THIS TOOL:\n" +
"- When you need to see a structural overview of a file\n" +
"- When looking for specific functions or classes in a large file\n" +
"- When analyzing the organization and hierarchy of code in a file\n" +
"- When you want to quickly understand the contents of a file without reading all the code\n\n" +
"PARAMETER USAGE:\n" +
"- repository: The full repository name (e.g., 'github.com/owner/repo')\n" +
"- path: The file path to analyze (e.g., 'src/main.js')\n\n" +
"IMPORTANT NOTES:\n" +
"- Requires LSIF data to be available in Sourcegraph (precise code intelligence)\n" +
"- For accurate results, the repository must be properly indexed in Sourcegraph\n" +
"- Symbol kinds vary by language (e.g., classes, methods, functions, variables)\n" +
"- Symbols will be organized hierarchically when possible (e.g., methods inside classes)\n\n" +
"EXAMPLES:\n" +
"- Get symbols in a JavaScript file: { repository: 'github.com/facebook/react', path: 'packages/react/src/React.js' }\n" +
"- Analyze a complex TypeScript file: { repository: 'github.com/microsoft/vscode', path: 'src/vs/editor/editor.api.ts' }\n\n" +
"The results will show all symbols in the file, their types, and their locations, organized hierarchically when possible.", {
repository: zod_1.z.string().describe("The repository name (e.g. github.com/owner/repo)"),
path: zod_1.z.string().describe("The file path within the repository")
}, async ({ repository, path }) => {
// Validate Sourcegraph credentials
const effectiveUrl = sgUrl || process.env.SOURCEGRAPH_URL;
const effectiveToken = sgToken || process.env.SOURCEGRAPH_TOKEN;
if (!effectiveUrl || !effectiveToken) {
return {
content: [{
type: "text",
text: "Error: Sourcegraph URL or token not configured. Please set SOURCEGRAPH_URL and SOURCEGRAPH_TOKEN environment variables."
}],
isError: true
};
}
try {
// Get the document symbols query
const graphqlQuery = (0, code_intelligence_1.getDocumentSymbolsQuery)();
// Execute the query
const response = await (0, code_intelligence_1.executeSourcegraphQuery)(graphqlQuery, { repository, path }, { url: effectiveUrl, token: effectiveToken });
if (response.errors) {
return {
content: [{
type: "text",
text: `Sourcegraph API Error: ${JSON.stringify(response.errors)}`
}],
isError: true
};
}
// Format the results
const symbolsData = response.data?.repository?.commit?.blob?.lsif?.documentSymbols?.symbols;
if (!symbolsData || symbolsData.length === 0) {
return {
content: [{
type: "text",
text: "No symbols found or LSIF data not available for this file."
}]
};
}
let result = `## Symbols in ${repository}:${path}\n\n`;
// Map for symbol kinds to more readable formats
const kindMap = {
'File': '📄',
'Module': '📦',
'Namespace': '🔠',
'Package': '📦',
'Class': '🔶',
'Method': '🔹',
'Property': '🔸',
'Field': '🔸',
'Constructor': '🏗️',
'Enum': '🔢',
'Interface': '🔷',
'Function': '⚙️',
'Variable': '📌',
'Constant': '🔒',
'String': '🔤',
'Number': '🔢',
'Boolean': '✓❌',
'Array': '📋',
'Object': '📦',
'Key': '🔑',
'Null': '⭕',
'EnumMember': '🔹',
'Struct': '🏛️',
'Event': '⚡',
'Operator': '➗',
'TypeParameter': '🆃'
};
// Recursively format symbols
const formatSymbols = (symbols, indent = 0) => {
let result = '';
symbols.forEach(symbol => {
const kind = symbol.kind || 'Unknown';
const icon = kindMap[kind] || '•';
const startLine = symbol.location?.range?.start?.line + 1 || '?';
const indentStr = ' '.repeat(indent);
result += `${indentStr}${icon} **${symbol.name}** (${kind}, line ${startLine})\n`;
if (symbol.children && symbol.children.length > 0) {
result += formatSymbols(symbol.children, indent + 1);
}
});
return result;
};
result += formatSymbols(symbolsData);
return {
content: [{
type: "text",
text: result
}]
};
}
catch (error) {
return {
content: [{
type: "text",
text: `Error getting document symbols: ${error.message || 'Unknown error'}`
}],
isError: true
};
}
});
// Add a debug tool to list available tools and methods
// Add get-file-content tool
server.tool("get-file-content", "Get the raw content of a file from any repository.\n\n" +
"WHEN TO USE THIS TOOL:\n" +
"- When you need to retrieve the contents of a specific file\n" +
"- When analyzing a file's implementation details\n" +
"- When you want to understand configuration files or scripts\n" +
"- When you need to see how a specific file is structured\n\n" +
"PARAMETER USAGE:\n" +
"- repository: The full repository name (e.g., 'github.com/owner/repo')\n" +
"- path: The file path within the repository (e.g., 'src/main.js')\n" +
"- revision: Optional git revision/branch/tag (e.g., 'main', 'v1.0.0', or a commit SHA)\n\n" +
"IMPORTANT NOTES:\n" +
"- Binary files will not display their content properly\n" +
"- Very large files may be truncated\n" +
"- For privacy and security, access is limited to repositories you have permissions for\n\n" +
"EXAMPLES:\n" +
"- Get a specific file: { repository: 'github.com/golang/go', path: 'src/net/http/server.go' }\n" +
"- Get a file from a specific branch: { repository: 'github.com/facebook/react', path: 'packages/react/src/React.js', revision: 'experimental' }\n\n" +
"The results will show the file's content with syntax highlighting for the appropriate language.", {
repository: zod_1.z.string().describe("The repository name (e.g. github.com/owner/repo)"),
path: zod_1.z.string().describe("The file path within the repository"),
revision: zod_1.z.string().optional().describe("Optional git revision/branch/tag")
}, async ({ repository, path, revision }) => {
// Validate Sourcegraph credentials
const effectiveUrl = sgUrl || process.env.SOURCEGRAPH_URL;
const effectiveToken = sgToken || process.env.SOURCEGRAPH_TOKEN;
if (!effectiveUrl || !effectiveToken) {
return {
content: [{
type: "text",
text: "Error: Sourcegraph URL or token not configured. Please set SOURCEGRAPH_URL and SOURCEGRAPH_TOKEN environment variables."
}],
isError: true
};
}
try {
// Get the file content query
const graphqlQuery = (0, repository_content_1.getFileContentQuery)();
// Execute the query
const response = await (0, code_intelligence_1.executeSourcegraphQuery)(graphqlQuery, { repository, path, revision }, { url: effectiveUrl, token: effectiveToken });
if (response.errors) {
return {
content: [{
type: "text",
text: `Sourcegraph API Error: ${JSON.stringify(response.errors)}`
}],
isError: true
};
}
// Format the results
const formattedResults = (0, repository_content_1.formatFileContentResults)(response.data, { repository, path, revision });
return {
content: [{
type: "text",
text: formattedResults
}]
};
}
catch (error) {
return {
content: [{
type: "text",
text: `Error getting file content: ${error.message || 'Unknown error'}`
}],
isError: true
};
}
});
// Add get-file-blame tool
server.tool("get-file-blame", "Get git blame information for a file.\n\n" +
"WHEN TO USE THIS TOOL:\n" +
"- When you need to see who last modified a specific section of a file\n" +
"- When investigating when and why a particular change was made\n" +
"- When tracking the history and ownership of code\n" +
"- When you want to understand how a file evolved over time\n\n" +
"PARAMETER USAGE:\n" +
"- repository: The full repository name (e.g., 'github.com/owner/repo')\n" +
"- path: The file path within the repository (e.g., 'src/main.js')\n" +
"- startLine: Optional zero-indexed start line for partial file blame (default: 0)\n" +
"- endLine: Optional zero-indexed end line for partial file blame (default: 100)\n\n" +
"IMPORTANT NOTES:\n" +
"- Line numbers are zero-indexed (unlike editors which often use 1-indexed)\n" +
"- For large files, consider specifying a line range to improve performance\n" +
"- The maximum recommended line range is 200 lines\n\n" +
"EXAMPLES:\n" +
"- Get blame for a specific file section: { repository: 'github.com/golang/go', path: 'src/net/http/server.go', startLine: 100, endLine: 150 }\n" +
"- Get blame for the beginning of a file: { repository: 'github.com/facebook/react', path: 'packages/react/src/React.js' }\n\n" +
"The results will show line ranges, authors, dates, and commit messages for each section of the file.", {
repository: zod_1.z.string().describe("The repository name (e.g. github.com/owner/repo)"),
path: zod_1.z.string().describe("The file path within the repository"),
startLine: zod_1.z.number().default(0).describe("Optional zero-indexed start line for partial file blame"),
endLine: zod_1.z.number().default(100).describe("Optional zero-indexed end line for partial file blame")
}, async ({ repository, path, startLine, endLine }) => {
// Validate Sourcegraph credentials
const effectiveUrl = sgUrl || process.env.SOURCEGRAPH_URL;
const effectiveToken = sgToken || process.env.SOURCEGRAPH_TOKEN;
if (!effectiveUrl || !effectiveToken) {
return {
content: [{
type: "text",
text: "Error: Sourcegraph URL or token not configured. Please set SOURCEGRAPH_URL and SOURCEGRAPH_TOKEN environment variables."
}],
isError: true
};
}
try {
// Get the file blame query
const graphqlQuery = (0, repository_content_1.getFileBlameQuery)();
// Execute the query
const response = await (0, code_intelligence_1.executeSourcegraphQuery)(graphqlQuery, { repository, path, startLine, endLine }, { url: effectiveUrl, token: effectiveToken });
if (response.errors) {
return {
content: [{
type: "text",
text: `Sourcegraph API Error: ${JSON.stringify(response.errors)}`
}],
isError: true
};
}
// Format the results
const formattedResults = (0, repository_content_1.formatFileBlameResults)(response.data, { repository, path, startLine, endLine });
return {
content: [{
type: "text",
text: formattedResults
}]
};
}
catch (error) {
return {
content: [{
type: "text",
text: `Error getting file blame: ${error.message || 'Unknown error'}`
}],
isError: true
};
}
});
// Add lookup-cve tool
server.tool("lookup-cve", "Search for CVEs affecting repositories or packages.\n\n" +
"WHEN TO USE THIS TOOL:\n" +
"- When you need to check if a specific CVE affects a repository\n" +
"- When you want to find vulnerabilities related to a specific package\n" +
"- When investigating security issues in a codebase\n" +
"- When performing security audits for packages or repositories\n\n" +
"PARAMETER USAGE:\n" +
"- cveId: Optional specific CVE ID to look up (e.g., 'CVE-2023-1234')\n" +
"- package: Optional package name to check for vulnerabilities (e.g., 'lodash')\n" +
"- repository: Optional repository name to check for vulnerabilities (e.g., 'github.com/owner/repo')\n\n" +
"IMPORTANT NOTES:\n" +
"- At least one parameter must be provided (cveId, package, or repository)\n" +
"- Results include severity levels, affected versions, and remediation guidance when available\n" +
"- Enterprise features may provide more detailed results\n\n" +
"EXAMPLES:\n" +
"- Look up a specific CVE: { cveId: 'CVE-2023-1234' }\n" +
"- Check vulnerabilities for a package: { package: 'lodash' }\n" +
"- Check vulnerabilities in a repository: { repository: 'github.com/facebook/react' }\n\n" +
"The results will show detailed information about matching vulnerabilities including severity, affected versions, and available fixes.", {
cveId: zod_1.z.string().optional().describe("Optional specific CVE ID to look up"),
package: zod_1.z.string().optional().describe("Optional package name to check"),
repository: zod_1.z.string().optional().describe("Optional repository name")
}, async ({ cveId, package: packageName, repository }) => {
// Validate Sourcegraph credentials
const effectiveUrl = sgUrl || process.env.SOURCEGRAPH_URL;
const effectiveToken = sgToken || process.env.SOURCEGRAPH_TOKEN;
if (!effectiveUrl || !effectiveToken) {
return {
content: [{
type: "text",
text: "Error: Sourcegraph URL or token not configured. Please set SOURCEGRAPH_URL and SOURCEGRAPH_TOKEN environment variables."
}],
isError: true
};
}
// Require at least one parameter
if (!cveId && !packageName && !repository) {
return {
content: [{
type: "text",
text: "Error: At least one parameter (cveId, package, or repository) must be provided."
}],
isError: true
};
}
try {
// Get the CVE lookup query
const graphqlQuery = (0, security_1.getCVELookupQuery)();
// Execute the query
const response = await (0, code_intelligence_1.executeSourcegraphQuery)(graphqlQuery, {
cveId: cveId || null,
package: packageName || null,
repository: repository || null,
limit: 50
}, { url: effectiveUrl, token: effectiveToken });
if (response.errors) {
return {
content: [{
type: "text",
text: `Sourcegraph API Error: ${JSON.stringify(response.errors)}`
}],
isError: true
};
}
// Format the results
const formattedResults = (0, security_1.formatCVELookupResults)(response.data, { cveId, package: packageName, repository });
return {
content: [{
type: "text",
text: formattedResults
}]
};
}
catch (error) {
return {
content: [{
type: "text",
text: `Error looking up CVE: ${error.message || 'Unknown error'}`
}],
isError: true
};
}
});
// Add lookup-package-vulnerability tool
server.tool("lookup-package-vulnerability", "Check if specific packages have known vulnerabilities.\n\n" +
"WHEN TO USE THIS TOOL:\n" +
"- When you need to check if a package has known security vulnerabilities\n" +
"- When you want to audit a specific package version for security issues\n" +
"- When investigating dependency security before adding to a project\n" +
"- When checking if a package needs updating for security reasons\n\n" +
"PARAMETER USAGE:\n" +
"- package: The package name to check (e.g., 'lodash', 'react', 'django')\n" +
"- version: Optional specific package version to check (e.g., '4.17.20')\n\n" +
"IMPORTANT NOTES:\n" +
"- Without a version, all vulnerabilities for any version will be returned\n" +
"- Results include severity levels, affected versions, and remediation guidance\n" +
"- Different ecosystems (npm, PyPI, etc.) are supported\n\n" +
"EXAMPLES:\n" +
"- Check all vulnerabilities for a package: { package: 'lodash' }\n" +
"- Check vulnerabilities for a specific version: { package: 'react', version: '16.8.0' }\n\n" +
"The results will show detailed information about matching vulnerabilities including severity, affected versions, and available fixes.", {
package: zod_1.z.string().describe("Package name (e.g., 'lodash')"),
version: zod_1.z.string().optional().describe("Optional package version")
}, async ({ package: packageName, version }) => {
// Validate Sourcegraph credentials
const effectiveUrl = sgUrl || process.env.SOURCEGRAPH_URL;
const effectiveToken = sgToken || process.env.SOURCEGRAPH_TOKEN;
if (!effectiveUrl || !effectiveToken) {
return {
content: [{
type: "text",
text: "Error: Sourcegraph URL or token not configured. Please set SOURCEGRAPH_URL and SOURCEGRAPH_TOKEN environment variables."
}],
isError: true
};
}
try {
// Get the package vulnerability query
const graphqlQuery = (0, security_1.getPackageVulnerabilityQuery)();
// Execute the query
const response = await (0, code_intelligence_1.executeSourcegraphQuery)(graphqlQuery, {
package: packageName,
version: version || null,
limit: 50
}, { url: effectiveUrl, token: effectiveToken });
if (response.errors) {
return {
content: [{
type: "text",
text: `Sourcegraph API Error: ${JSON.stringify(response.errors)}`
}],
isError: true
};
}
// Format the results
const formattedResults = (0, security_1.formatPackageVulnerabilityResults)(response.data, { package: packageName, version });
return {
content: [{
type: "text",
text: formattedResults
}]
};
}
catch (error) {
return {
content: [{
type: "text",
text: `Error looking up package vulnerabilities: ${error.message || 'Unknown error'}`
}],
isError: true
};
}
});
// Add search-exploits tool
server.tool("search-exploits", "Search for exploit code for known vulnerabilities.\n\n" +
"WHEN TO USE THIS TOOL:\n" +
"- When you need to assess the risk of a CVE by finding available exploits\n" +
"- When researching how a vulnerability works\n" +
"- When checking if a vulnerability has public proof-of-concept code\n" +
"- When investigating