mcp-magma-handbook
Version:
Enhanced MCP server with multi-query search, hybrid search, and collections for MAGMA computational algebra system
251 lines • 9.87 kB
JavaScript
import { ChromaClient } from 'chromadb';
import { OpenAIEmbeddings } from '@langchain/openai';
import { PDFLoader } from '@langchain/community/document_loaders/fs/pdf';
import { RecursiveCharacterTextSplitter } from '@langchain/textsplitters';
export class MagmaKnowledgeBase {
client;
collection;
embeddings;
textSplitter;
isInitialized = false;
constructor() {
this.client = new ChromaClient();
this.embeddings = new OpenAIEmbeddings({
modelName: 'text-embedding-3-small',
dimensions: 1536, // Advanced와 동일한 차원
});
// MAGMA 코드에 최적화된 텍스트 분할기
this.textSplitter = new RecursiveCharacterTextSplitter({
chunkSize: 1500,
chunkOverlap: 200,
separators: [
'\n\n\n', // 섹션 구분
'\n\n', // 단락 구분
'\n', // 줄 구분
';', // MAGMA 문장 종료
'.', // 일반 문장
' ', // 공백
''
],
});
}
async initialize() {
if (this.isInitialized)
return;
try {
// Create or get collection
this.collection = await this.client.getOrCreateCollection({
name: 'magma_handbook',
embeddingFunction: {
generate: async (texts) => {
const embeddings = await this.embeddings.embedDocuments(texts);
return embeddings;
}
}
});
// Check if indexing is needed
const count = await this.collection.count();
if (count === 0) {
console.error('No documents in collection. Please run indexing first.');
console.error('Place MAGMA_HANDBOOK.pdf in data/pdfs/ and run: npm run index');
}
else {
console.error(`Loaded collection with ${count} documents`);
}
this.isInitialized = true;
}
catch (error) {
console.error('Failed to initialize knowledge base:', error);
throw error;
}
}
async indexPDF(pdfPath) {
console.log(`Indexing PDF: ${pdfPath}`);
// Load PDF
const loader = new PDFLoader(pdfPath);
const docs = await loader.load();
// Split documents
const splitDocs = await this.textSplitter.splitDocuments(docs);
// Process and categorize documents
const processedDocs = splitDocs.map((doc, index) => ({
id: `magma_${index}`,
content: doc.pageContent,
metadata: {
...doc.metadata,
category: this.categorizeContent(doc.pageContent),
indexed_at: new Date().toISOString(),
}
}));
// Add to collection
if (this.collection) {
await this.collection.add({
ids: processedDocs.map(d => d.id),
documents: processedDocs.map(d => d.content),
metadatas: processedDocs.map(d => d.metadata),
});
}
console.log(`Indexed ${processedDocs.length} documents from ${pdfPath}`);
}
categorizeContent(content) {
const lowerContent = content.toLowerCase();
// MAGMA 특화 카테고리 분류
if (lowerContent.includes('syntax') || lowerContent.includes('::=')) {
return 'syntax';
}
else if (lowerContent.includes('function') || lowerContent.includes('intrinsic')) {
return 'function';
}
else if (lowerContent.includes('algorithm') || lowerContent.includes('procedure')) {
return 'algorithm';
}
else if (lowerContent.includes('example') || content.includes('>')) {
return 'example';
}
else if (lowerContent.includes('theorem') || lowerContent.includes('lemma')) {
return 'theory';
}
return 'general';
}
async search(query, limit = 5, category = 'all') {
if (!this.collection) {
throw new Error('Knowledge base not initialized');
}
// Build filter
const where = category !== 'all' ? { category: category } : undefined;
// Search
const results = await this.collection.query({
queryTexts: [query],
nResults: limit,
where: where,
});
// Format results
return results.ids[0].map((id, index) => ({
content: results.documents[0][index] || '',
metadata: results.metadatas[0][index],
score: results.distances ? 1 - results.distances[0][index] : 0.5,
}));
}
async getExamples(topic, complexity = 'basic') {
// Search for examples related to the topic
const searchResults = await this.search(`${topic} example code`, 10, 'example');
// Extract and format MAGMA code examples
const examples = searchResults
.map(result => {
const codeMatches = result.content.match(/```magma([\s\S]*?)```|>(.*?)$/gm);
if (codeMatches) {
return {
title: this.extractTitle(result.content),
code: this.cleanMagmaCode(codeMatches[0]),
explanation: this.extractExplanation(result.content),
complexity: this.assessComplexity(codeMatches[0]),
source: result.metadata.source,
page: result.metadata.page,
};
}
return null;
})
.filter(Boolean)
.filter(ex => complexity === 'all' || ex?.complexity === complexity);
return examples;
}
async explainCode(code, context) {
// Search for relevant documentation about the functions used in the code
const functions = this.extractFunctions(code);
const searchPromises = functions.map(func => this.search(`${func} function syntax usage`, 3, 'function'));
const searchResults = await Promise.all(searchPromises);
const relevantDocs = searchResults.flat();
// Build explanation
let explanation = '# MAGMA Code Explanation\n\n';
explanation += '## Code:\n```magma\n' + code + '\n```\n\n';
if (context) {
explanation += `## Context:\n${context}\n\n`;
}
explanation += '## Analysis:\n';
// Parse and explain the code structure
const lines = code.split('\n').filter(line => line.trim());
for (const line of lines) {
if (line.trim().startsWith('//'))
continue;
const analysis = this.analyzeLine(line, relevantDocs);
if (analysis) {
explanation += `- ${analysis}\n`;
}
}
// Add relevant function documentation
if (functions.length > 0) {
explanation += '\n## Related Functions:\n';
const uniqueFunctions = [...new Set(functions)];
for (const func of uniqueFunctions) {
const funcDocs = relevantDocs.filter(doc => doc.content.toLowerCase().includes(func.toLowerCase()));
if (funcDocs.length > 0) {
explanation += `\n### ${func}\n`;
explanation += funcDocs[0].content.substring(0, 300) + '...\n';
}
}
}
return explanation;
}
extractFunctions(code) {
// MAGMA 함수 패턴 매칭
const functionPattern = /([A-Z][a-zA-Z0-9]*)\s*\(/g;
const matches = code.match(functionPattern) || [];
return matches.map(m => m.replace('(', '').trim());
}
analyzeLine(line, docs) {
const trimmed = line.trim();
// 변수 할당
if (trimmed.includes(':=')) {
const [varName, value] = trimmed.split(':=').map(s => s.trim());
return `\`${varName}\` is assigned the value/result of \`${value}\``;
}
// 함수 호출
const funcMatch = trimmed.match(/([A-Z][a-zA-Z0-9]*)\s*\(/);
if (funcMatch) {
return `Calls function \`${funcMatch[1]}\``;
}
// 제어 구조
if (trimmed.startsWith('if') || trimmed.startsWith('while') || trimmed.startsWith('for')) {
return `Control structure: ${trimmed.split(' ')[0]}`;
}
return null;
}
extractTitle(content) {
const lines = content.split('\n');
for (const line of lines) {
if (line.trim() && !line.startsWith('>') && line.length < 100) {
return line.trim();
}
}
return 'MAGMA Example';
}
cleanMagmaCode(code) {
return code
.replace(/```magma/g, '')
.replace(/```/g, '')
.replace(/^>\s*/gm, '')
.trim();
}
extractExplanation(content) {
// Extract explanation text between code blocks
const parts = content.split(/```magma[\s\S]*?```|>.*$/gm);
return parts
.map(p => p.trim())
.filter(p => p.length > 20)
.join(' ')
.substring(0, 200);
}
assessComplexity(code) {
const lines = code.split('\n').length;
const hasLoops = /for|while/.test(code);
const hasFunctions = /function|procedure/.test(code);
const complexFunctions = /Factorization|IsIrreducible|GaloisGroup/.test(code);
if (complexFunctions || (hasLoops && hasFunctions) || lines > 20) {
return 'advanced';
}
else if (hasLoops || hasFunctions || lines > 10) {
return 'intermediate';
}
return 'basic';
}
}
//# sourceMappingURL=knowledge-base.js.map