UNPKG

mcp-omnisearch

Version:

MCP server for integrating Omnisearch with LLMs

156 lines (155 loc) 7.16 kB
import { Octokit } from 'octokit'; import { ErrorType, ProviderError, } from '../../../common/types.js'; import { retry_with_backoff, validate_api_key, } from '../../../common/utils.js'; import { config } from '../../../config/env.js'; export class GitHubSearchProvider { constructor() { this.name = 'github'; this.description = 'Search for code on GitHub. This is ideal for finding code examples, tracking down function definitions, or locating files with specific names or paths. Supports advanced query syntax with qualifiers like `filename:`, `path:`, `repo:`, `user:`, `language:`, and `in:file`. For example, to find a file named `settings.json` in a `.claude` directory, you could use the query: `filename:settings.json path:.claude`'; } // Main search method for code search (default behavior) async search(params) { return this.search_code(params); } // Dedicated code search method with enhanced snippets async search_code(params) { const api_key = validate_api_key(config.search.github.api_key, this.name); const octokit = new Octokit({ auth: api_key }); const search_request = async () => { try { // Enable text matches to get better snippets const response = await octokit.rest.search.code({ q: params.query, per_page: params.limit ?? 10, // Request text matches for better snippets headers: { accept: 'application/vnd.github.v3.text-match+json', }, }); return response.data.items.map((item) => { // Extract better snippet from text matches let snippet = `No snippet available for ${item.path}`; if (item.text_matches && item.text_matches.length > 0) { // Combine multiple fragments for better context const fragments = item.text_matches .map((match) => match.fragment) .filter(Boolean); if (fragments.length > 0) { snippet = fragments.slice(0, 2).join(' ... '); } } return { title: `${item.repository.full_name}/${item.path}`, url: item.html_url, snippet, score: item.score, source_provider: this.name, // Add metadata for better context metadata: { repository: item.repository.full_name, file_path: item.path, file_name: item.name, search_type: 'code', }, }; }); } catch (error) { return this.handle_search_error(error); } }; return retry_with_backoff(search_request); } // Dedicated repository search method with enhanced metadata async search_repositories(params) { const api_key = validate_api_key(config.search.github.api_key, this.name); const octokit = new Octokit({ auth: api_key }); const search_request = async () => { try { const response = await octokit.rest.search.repos({ q: params.query, per_page: params.limit ?? 10, sort: params.sort, }); return response.data.items.map((item) => { // Create richer description let snippet = item.description ?? 'No description available.'; if (item.language) { snippet += ` • Language: ${item.language}`; } snippet += ` • ⭐ ${item.stargazers_count} • 🍴 ${item.forks_count}`; return { title: item.full_name, url: item.html_url, snippet, score: item.score, source_provider: this.name, metadata: { repository: item.full_name, language: item.language, stars: item.stargazers_count, forks: item.forks_count, last_push: item.pushed_at, search_type: 'repository', }, }; }); } catch (error) { return this.handle_search_error(error); } }; return retry_with_backoff(search_request); } // Alias for backward compatibility async repository_search(params) { return this.search_repositories(params); } // User search method async search_users(params) { const api_key = validate_api_key(config.search.github.api_key, this.name); const octokit = new Octokit({ auth: api_key }); const search_request = async () => { try { const response = await octokit.rest.search.users({ q: params.query, per_page: params.limit ?? 10, }); return response.data.items.map((user) => ({ title: user.login, url: user.html_url, snippet: user.bio ?? `GitHub user: ${user.login}${user.type}`, score: user.score, source_provider: this.name, metadata: { username: user.login, user_type: user.type, search_type: 'user', }, })); } catch (error) { return this.handle_search_error(error); } }; return retry_with_backoff(search_request); } // Centralized error handling handle_search_error(error) { const status = error.status || 500; const message = error.message || 'An unexpected error occurred.'; switch (status) { case 401: case 403: throw new ProviderError(ErrorType.API_ERROR, `Invalid or unauthorized GitHub API key: ${message}`, this.name); case 422: throw new ProviderError(ErrorType.INVALID_INPUT, `Invalid GitHub search query: ${message}`, this.name); case 429: throw new ProviderError(ErrorType.RATE_LIMIT, `GitHub API rate limit exceeded: ${message}`, this.name); default: throw new ProviderError(ErrorType.PROVIDER_ERROR, `GitHub API error: ${message}`, this.name, { status }); } } } // Export the provider instance export const github_search_provider = new GitHubSearchProvider();