sourcegraph-mcp-server
Version:
A Model Context Protocol (MCP) server that connects AI assistants to Sourcegraph code search with natural language capabilities
205 lines (178 loc) • 6.89 kB
text/typescript
/**
* LLM Integration Service
* Handles communication with LLM providers for query translation
*/
import axios from 'axios';
import dotenv from 'dotenv';
// Load environment variables
dotenv.config();
// LLM configuration from environment
const LLM_PROVIDER = process.env.LLM_PROVIDER || 'openai';
const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
const OPENAI_MODEL = process.env.OPENAI_MODEL || 'gpt-4o';
const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
const ANTHROPIC_MODEL = process.env.ANTHROPIC_MODEL || 'claude-2';
/**
* Convert a natural language query to Sourcegraph syntax
* using an LLM provider (OpenAI or Anthropic)
*/
export async function convertQueryToSourcegraphSyntax(naturalQuery: string): Promise<string> {
try {
// Select the appropriate LLM provider based on configuration
switch (LLM_PROVIDER.toLowerCase()) {
case 'openai':
return await convertWithOpenAI(naturalQuery);
case 'anthropic':
return await convertWithAnthropic(naturalQuery);
default:
throw new Error(`Unsupported LLM provider: ${LLM_PROVIDER}`);
}
} catch (error: any) {
console.error('Error converting query with LLM:', error.message);
// Fall back to using the query directly with minimal processing
return fallbackQueryConversion(naturalQuery);
}
}
/**
* Convert a query using OpenAI's API
*/
async function convertWithOpenAI(naturalQuery: string): Promise<string> {
if (!OPENAI_API_KEY) {
throw new Error('OpenAI API key not configured');
}
const prompt = createOpenAIPrompt(naturalQuery);
const response = await axios.post(
'https://api.openai.com/v1/chat/completions',
{
model: OPENAI_MODEL,
messages: [
{
role: 'system',
content: 'You are a specialized assistant that converts natural language queries into Sourcegraph search syntax.'
},
{ role: 'user', content: prompt }
],
temperature: 0.1, // Low temperature for consistent, deterministic results
max_tokens: 100 // Limit response length since we only need the syntax
},
{
headers: {
'Authorization': `Bearer ${OPENAI_API_KEY}`,
'Content-Type': 'application/json'
}
}
);
// Extract the translated query from the response
const translatedQuery = response.data.choices[0]?.message?.content?.trim();
if (!translatedQuery) {
throw new Error('Empty or invalid response from OpenAI');
}
return translatedQuery;
}
/**
* Convert a query using Anthropic's API
*/
async function convertWithAnthropic(naturalQuery: string): Promise<string> {
if (!ANTHROPIC_API_KEY) {
throw new Error('Anthropic API key not configured');
}
const prompt = createAnthropicPrompt(naturalQuery);
const response = await axios.post(
'https://api.anthropic.com/v1/messages',
{
model: ANTHROPIC_MODEL,
messages: [
{
role: 'user',
content: prompt
}
],
max_tokens: 100
},
{
headers: {
'x-api-key': ANTHROPIC_API_KEY,
'anthropic-version': '2023-06-01',
'Content-Type': 'application/json'
}
}
);
// Extract the translated query from the response
const translatedQuery = response.data.content[0]?.text?.trim();
if (!translatedQuery) {
throw new Error('Empty or invalid response from Anthropic');
}
return translatedQuery;
}
/**
* Create a detailed prompt for OpenAI
*/
function createOpenAIPrompt(naturalQuery: string): string {
return `
Convert the following natural language query into a Sourcegraph search query syntax.
Guidelines:
- Extract the key search terms
- Use appropriate Sourcegraph operators (repo:, type:, lang:, etc.)
- If specific repositories are mentioned, include them with repo: operator
- Don't include terms like "show me", "find", etc. in the translated query
- For code searches, use type:file or add specific language filters with lang:
- For commit searches, use type:commit and add author: or after: if specified
- For diff searches, use type:diff and add appropriate filters
- Never wrap the entire query in quotes
- Include count:20 for reasonable result limiting
- Add lang: filters when language context is provided or implied
- Remove trailing spaces from the query
Examples:
- "Find authentication code in the frontend" → "authentication frontend type:file count:20"
- "Show commits by Jane from last week" → "author:Jane after:"1 week ago" type:commit count:20"
- "Files using supabase for authentication" → "supabase authentication type:file count:20"
- "Show all Java files with SQL queries" → "lang:java SQL type:file count:20"
- "Find changes to the API in March" → "api after:2023-03-01 before:2023-04-01 type:diff count:20"
Natural language query: ${naturalQuery}
Sourcegraph query:
`;
}
/**
* Create a detailed prompt for Anthropic
*/
function createAnthropicPrompt(naturalQuery: string): string {
return `
Convert the following natural language query into a Sourcegraph search query syntax.
Guidelines:
- Extract the key search terms
- Use appropriate Sourcegraph operators (repo:, type:, lang:, etc.)
- If specific repositories are mentioned, include them with repo: operator
- Don't include terms like "show me", "find", etc. in the translated query
- For code searches, use type:file or add specific language filters with lang:
- For commit searches, use type:commit and add author: or after: if specified
- For diff searches, use type:diff and add appropriate filters
- Never wrap the entire query in quotes
- Include count:20 for reasonable result limiting
- Add lang: filters when language context is provided or implied
- Remove trailing spaces from the query
Examples:
- "Find authentication code in the frontend" → "authentication frontend type:file count:20"
- "Show commits by Jane from last week" → "author:Jane after:"1 week ago" type:commit count:20"
- "Files using supabase for authentication" → "supabase authentication type:file count:20"
- "Show all Java files with SQL queries" → "lang:java SQL type:file count:20"
- "Find changes to the API in March" → "api after:2023-03-01 before:2023-04-01 type:diff count:20"
Natural language query: ${naturalQuery}
Sourcegraph query:
`;
}
/**
* Simple fallback method if LLM translation fails
*/
function fallbackQueryConversion(naturalQuery: string): string {
// Remove common filler phrases
const cleanedQuery = naturalQuery
.replace(/^(show me|find|search for|look for|get|retrieve)\s+/i, '')
.replace(/\b(all|the|those|any)\s+/g, '')
.replace(/\s+(files?|code|results?)\s+/g, ' ')
.replace(/\s+in\s+(the\s+)?repos?(itory)?\s+/g, ' repo:');
// Default to file search if not specified
if (!cleanedQuery.includes('type:')) {
return `${cleanedQuery.trim()} type:file`;
}
return cleanedQuery.trim();
}