Second Opinion MCP Server

import { exec } from 'child_process'; import { promisify } from 'util'; import * as fs from 'fs/promises'; import * as path from 'path'; import { FileContext } from './types.js'; import { LANGUAGE_MAP, FileExtension } from './config.js'; const execAsync = promisify(exec); export async function detectFileLanguage(filePath: string): Promise<string> { const ext = path.extname(filePath).toLowerCase(); return Object.prototype.hasOwnProperty.call(LANGUAGE_MAP, ext) ? LANGUAGE_MAP[ext as FileExtension] : 'Unknown'; } export async function findRelevantFiles( currentFile: string, errorMessage?: string ): Promise<FileContext[]> { const fileContexts: FileContext[] = []; const baseDir = path.dirname(currentFile); try { // Get the content and language of the current file const content = await fs.readFile(currentFile, 'utf-8'); const language = await detectFileLanguage(currentFile); fileContexts.push({ path: currentFile, content, language, }); // If there's an error message, use git grep to find related files if (errorMessage) { const searchTerms = errorMessage .split(/[\s,.:]+/) .filter((term) => term.length > 3) .join('|'); try { const { stdout } = await execAsync( `cd "${baseDir}" && git grep -l -E "${searchTerms}"`, { maxBuffer: 10 * 1024 * 1024 } // 10MB buffer ); const relatedFiles = stdout.split('\n').filter(Boolean); for (const file of relatedFiles.slice(0, 5)) { // Limit to 5 related files if (file !== currentFile) { const content = await fs.readFile(path.join(baseDir, file), 'utf-8'); const language = await detectFileLanguage(file); fileContexts.push({ path: file, content, language, }); } } } catch (error) { // Git grep failed, fallback to searching common related files const currentExt = path.extname(currentFile); const files = await fs.readdir(baseDir); for (const file of files) { if (path.extname(file) === currentExt && file !== path.basename(currentFile)) { const filePath = path.join(baseDir, file); const content = await fs.readFile(filePath, 'utf-8'); const language = await detectFileLanguage(filePath); fileContexts.push({ path: file, content, language, }); } } } } } catch (error) { console.error('Error finding relevant files:', error); } return fileContexts; } export async function saveResponseToMarkdown( args: { goal: string; error?: string; code?: string; solutionsTried?: string; filePath?: string; }, response: string, language: string ): Promise<string> { try { // Create responses directory if it doesn't exist const responsesDir = path.join(process.cwd(), 'responses'); await fs.mkdir(responsesDir, { recursive: true }); // Generate a filename based on the goal const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const sanitizedGoal = args.goal .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .slice(0, 50); const filename = `${timestamp}-${sanitizedGoal}.md`; const filepath = path.join(responsesDir, filename); // Create the markdown content const markdown = `# ${args.goal} ## Context ${args.error ? `\n### Error\n\`\`\`\n${args.error}\n\`\`\`` : ''} ${args.code ? `\n### Code\n\`\`\`${language}\n${args.code}\n\`\`\`` : ''} ${args.solutionsTried ? `\n### Solutions Tried\n${args.solutionsTried}` : ''} ${args.filePath ? `\n### File Path\n\`${args.filePath}\`` : ''} ## Solution ${response} --- Generated by Second Opinion MCP Server on ${new Date().toLocaleString()} `; // Write the markdown file await fs.writeFile(filepath, markdown, 'utf-8'); return filepath; } catch (error) { console.error('Error saving response to markdown:', error); return ''; } }