UNPKG

remcode

Version:

Turn your AI assistant into a codebase expert. Intelligent code analysis, semantic search, and software engineering guidance through MCP integration.

179 lines (178 loc) 7.46 kB
"use strict"; /** * GitHub MCP Handler * * Handles GitHub-related MCP requests, allowing AI assistants * to interact with GitHub repositories for codebase analysis. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.GitHubMCPHandler = void 0; const axios_1 = __importDefault(require("axios")); const logger_1 = require("../../utils/logger"); const logger = (0, logger_1.getLogger)('GitHub-MCP'); class GitHubMCPHandler { constructor(options) { this.baseUrl = 'https://api.github.com'; this.options = options; } async handleRequest(req, res) { const action = req.params.action; try { if (!this.options.token) { res.status(401).json({ error: 'GitHub token not provided' }); return; } switch (action) { case 'get-repo': await this.handleGetRepo(req, res); break; case 'list-files': await this.handleListFiles(req, res); break; case 'get-file': await this.handleGetFile(req, res); break; case 'search-code': await this.handleSearchCode(req, res); break; default: res.status(400).json({ error: `Unknown action: ${action}` }); } } catch (error) { logger.error(`Error handling GitHub MCP request: ${error instanceof Error ? error.message : String(error)}`); res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' }); } } async handleToolRequest(req, res) { const { tool, parameters } = req.body; try { if (!this.options.token) { res.status(401).json({ error: 'GitHub token not provided' }); return; } switch (tool) { case 'github_get_repo': await this.handleGetRepo(req, res, parameters); break; case 'github_list_files': await this.handleListFiles(req, res, parameters); break; case 'github_get_file': await this.handleGetFile(req, res, parameters); break; case 'github_search_code': await this.handleSearchCode(req, res, parameters); break; default: res.status(400).json({ error: `Unknown tool: ${tool}` }); } } catch (error) { logger.error(`Error handling GitHub tool request: ${error instanceof Error ? error.message : String(error)}`); res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' }); } } async makeGitHubRequest(endpoint, params = {}) { try { const response = await axios_1.default.get(`${this.baseUrl}${endpoint}`, { headers: { 'Authorization': `token ${this.options.token}`, 'Accept': 'application/vnd.github.v3+json' }, params }); return response.data; } catch (error) { if (axios_1.default.isAxiosError(error) && error.response) { throw new Error(`GitHub API error: ${error.response.status} - ${JSON.stringify(error.response.data)}`); } throw error; } } async handleGetRepo(req, res, params) { const requestParams = params || req.body; const { owner, repo } = requestParams; if (!owner || !repo) { res.status(400).json({ error: 'Owner and repo are required' }); return; } try { const repoData = await this.makeGitHubRequest(`/repos/${owner}/${repo}`); res.status(200).json(repoData); } catch (error) { logger.error(`Error getting GitHub repo: ${error instanceof Error ? error.message : String(error)}`); res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' }); } } async handleListFiles(req, res, params) { const requestParams = params || req.body; const { owner, repo, path = '', ref = 'main' } = requestParams; if (!owner || !repo) { res.status(400).json({ error: 'Owner and repo are required' }); return; } try { const contents = await this.makeGitHubRequest(`/repos/${owner}/${repo}/contents/${path}`, { ref }); res.status(200).json(contents); } catch (error) { logger.error(`Error listing GitHub files: ${error instanceof Error ? error.message : String(error)}`); res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' }); } } async handleGetFile(req, res, params) { const requestParams = params || req.body; const { owner, repo, path, ref = 'main' } = requestParams; if (!owner || !repo || !path) { res.status(400).json({ error: 'Owner, repo, and path are required' }); return; } try { const fileData = await this.makeGitHubRequest(`/repos/${owner}/${repo}/contents/${path}`, { ref }); if (Array.isArray(fileData)) { res.status(400).json({ error: 'Path points to a directory, not a file' }); return; } // For binary files or larger files, GitHub returns a download_url instead of content if (fileData.content) { const content = Buffer.from(fileData.content, 'base64').toString('utf-8'); res.status(200).json({ ...fileData, decodedContent: content }); } else { res.status(200).json(fileData); } } catch (error) { logger.error(`Error getting GitHub file: ${error instanceof Error ? error.message : String(error)}`); res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' }); } } async handleSearchCode(req, res, params) { const requestParams = params || req.body; const { query, owner, repo } = requestParams; if (!query) { res.status(400).json({ error: 'Search query is required' }); return; } try { // Construct the search query to limit to a specific repo if provided let searchQuery = query; if (owner && repo) { searchQuery = `${query} repo:${owner}/${repo}`; } const searchResults = await this.makeGitHubRequest('/search/code', { q: searchQuery }); res.status(200).json(searchResults); } catch (error) { logger.error(`Error searching GitHub code: ${error instanceof Error ? error.message : String(error)}`); res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' }); } } } exports.GitHubMCPHandler = GitHubMCPHandler;