Second Opinion MCP Server
by PoliTwit1984
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 '';
}
}