UNPKG

mcp-magma-handbook

Version:

Enhanced MCP server with multi-query search, hybrid search, and collections for MAGMA computational algebra system

361 lines 19.7 kB
// Polyfill fetch for Node.js import fetch from 'node-fetch'; // @ts-ignore global.fetch = fetch; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; import { createClient } from '@supabase/supabase-js'; import { OpenAIEmbeddings } from '@langchain/openai'; // Configuration schema for Smithery deployment export const configSchema = z.object({ openaiApiKey: z.string().describe("Your OpenAI API key for embeddings generation (required)"), debug: z.boolean().optional().default(false).describe("Enable debug logging"), }); export default function createStatelessServer({ config, }) { const server = new McpServer({ name: "MAGMA Handbook Advanced", version: "2.1.0", }); // Use provided Supabase instance (shared knowledge base) const supabaseUrl = "https://euwbfyrdalddpbnqgjoq.supabase.co"; const supabaseKey = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImV1d2JmeXJkYWxkZHBibnFnam9xIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzM2OTQ4MDIsImV4cCI6MjA0OTI3MDgwMn0.w0DdgZJBwE0xJAJX9bEK5H9Q7UBdTnEAb3IJ9fOULGI"; // API key acquisition and validation logic function getValidatedApiKey() { // Try API key from multiple sources const sources = [ { name: 'config.openaiApiKey', value: config.openaiApiKey }, { name: 'OPENAI_API_KEY', value: process.env.OPENAI_API_KEY }, { name: 'OPENAI_API_KEY_CONFIG', value: process.env.OPENAI_API_KEY_CONFIG }, { name: 'SMITHERY_OPENAI_API_KEY', value: process.env.SMITHERY_OPENAI_API_KEY } ]; let apiKey = null; let source = null; for (const s of sources) { if (s.value && typeof s.value === 'string' && s.value.trim()) { apiKey = s.value.trim(); source = s.name; break; } } // API key format validation if (!apiKey) { return { apiKey: null, error: 'No API key found in any source' }; } if (!apiKey.startsWith('sk-')) { return { apiKey: null, error: 'Invalid API key format (should start with sk-), source: ' + source }; } if (apiKey.length < 20) { return { apiKey: null, error: 'API key too short (' + apiKey.length + ' chars), source: ' + source }; } console.log('Valid API key found from ' + source + ' (' + apiKey.length + ' chars)'); return { apiKey, error: null }; } const { apiKey: openaiApiKey, error: apiKeyError } = getValidatedApiKey(); if (apiKeyError) { console.error('API Key Error:', apiKeyError); } // Initialize services const supabase = createClient(supabaseUrl, supabaseKey); // Separate OpenAI embedding initialization into function function createEmbeddings(apiKey) { return new OpenAIEmbeddings({ modelName: 'text-embedding-3-small', dimensions: 1536, openAIApiKey: apiKey, }); } // Helper function for query expansion const expandQuery = (query) => { const synonyms = { // Coding theory expansion 'hamming': ['error', 'correction', 'linear', 'code', 'generator'], 'reed': ['solomon', 'polynomial', 'evaluation', 'error'], 'bch': ['cyclic', 'polynomial', 'primitive', 'code'], 'code': ['algorithm', 'implementation', 'function', 'linear', 'block'], 'generator': ['matrix', 'basis', 'span', 'linear'], 'matrix': ['linear', 'transformation', 'operator', 'generator'], // Group theory expansion 'group': ['algebra', 'structure', 'set', 'permutation', 'symmetric'], 'permutation': ['symmetric', 'alternating', 'cycle', 'transposition'], 'sylow': ['subgroup', 'theorem', 'prime', 'power'], // Field theory expansion 'field': ['ring', 'domain', 'arithmetic', 'finite', 'galois'], 'finite': ['field', 'galois', 'primitive', 'polynomial'], 'polynomial': ['expression', 'equation', 'formula', 'irreducible'], // Elliptic curve expansion 'elliptic': ['curve', 'point', 'addition', 'weierstrass', 'jacobian'], 'curve': ['elliptic', 'algebraic', 'geometry', 'point', 'rational'], }; let expanded = query; const words = query.toLowerCase().split(' '); for (const word of words) { if (synonyms[word]) { expanded += ' ' + synonyms[word].join(' '); } } return expanded; }; // Advanced hybrid search tool server.tool("search_magma_advanced", "Advanced search with hybrid BM25+vector similarity, query expansion, and re-ranking for MAGMA handbook content", { query: z.string().describe('Search query for MAGMA handbook content'), limit: z.number().optional().default(5).describe('Maximum number of results to return'), category: z.enum(['syntax', 'function', 'algorithm', 'example', 'theory', 'all']) .optional() .default('all') .describe('Category of content to search'), vectorWeight: z.number().optional().default(0.7).describe('Weight for vector similarity (0-1)'), bm25Weight: z.number().optional().default(0.3).describe('Weight for BM25 score (0-1)'), }, async ({ query, limit, category, vectorWeight, bm25Weight }) => { try { // API key validation if (!openaiApiKey) { return { content: [{ type: "text", text: 'OpenAI API Key Required\n\n**Error:** ' + apiKeyError + '\n\n**Setup Instructions:**\n1. Configure your OpenAI API key in Smithery\n2. Or set OPENAI_API_KEY environment variable\n3. Restart your MCP client' }], }; } // Expand query with synonyms const expandedQuery = expandQuery(query); // Generate query embedding with proper error handling let queryEmbedding; try { console.log('Generating embedding for: "' + expandedQuery + '"'); const embeddings = createEmbeddings(openaiApiKey); queryEmbedding = await embeddings.embedQuery(expandedQuery); console.log('Embedding generated successfully (' + queryEmbedding.length + ' dimensions)'); } catch (embeddingError) { console.error('Embedding Error:', embeddingError); return { content: [{ type: "text", text: 'OpenAI API Error: ' + embeddingError.message + '\n\n**Possible Issues:**\n- Invalid API key: ' + (openaiApiKey?.substring(0, 8) || 'unknown') + '...\n- Expired or deactivated API key\n- Insufficient API credits\n- Network connectivity issues\n- Rate limiting\n\n**Debug Info:**\n- API key length: ' + (openaiApiKey?.length || 0) + '\n- Error type: ' + embeddingError.name + '\n\n**Please verify your OpenAI API key and account status.**' }], }; } // Call hybrid search function const { data, error } = await supabase.rpc('search_magma_hybrid', { query_text: expandedQuery, query_embedding: queryEmbedding, similarity_threshold: 0.4, bm25_weight: bm25Weight, vector_weight: vectorWeight, match_count: limit, category_filter: category === 'all' ? null : category }); if (error) { throw new Error('Hybrid search error: ' + error.message); } const results = data.map((row) => ({ content: row.content, metadata: row.metadata, score: row.combined_score, vectorSimilarity: row.vector_similarity, bm25Score: row.bm25_score, rank: row.rank, })); if (results.length === 0) { return { content: [{ type: "text", text: 'No results found for query: "' + query + '"' }], }; } let output = '# Advanced Search Results for "' + query + '"\n\n'; output += '**Found**: ' + results.length + ' results\n'; output += '**Query Expansion**: ' + expandedQuery + '\n\n'; results.forEach((result, index) => { output += '## Result ' + (index + 1) + '\n'; output += '**Source**: ' + result.metadata.source + ' (Page ' + result.metadata.page + ')\n'; output += '**Category**: ' + result.metadata.category + '\n'; output += '**Scores**: Combined ' + result.score.toFixed(3) + ' (Vector: ' + (result.vectorSimilarity?.toFixed(3) || 'N/A') + ', BM25: ' + (result.bm25Score?.toFixed(3) || 'N/A') + ')\n'; if (result.metadata.hasCode) { output += '**Contains Code**: Yes\n'; } if (result.metadata.hasExample) { output += '**Contains Examples**: Yes\n'; } output += '\n' + result.content.trim() + '\n\n'; output += '---\n\n'; }); return { content: [{ type: "text", text: output }], }; } catch (error) { return { content: [{ type: "text", text: 'Error: ' + (error instanceof Error ? error.message : 'Unknown error') }], }; } }); // Function-specific search tool server.tool("search_functions", "Search for specific MAGMA functions with fuzzy matching and signature lookup", { functionName: z.string().describe('Function name to search for'), limit: z.number().optional().default(10).describe('Maximum number of function matches'), }, async ({ functionName, limit }) => { try { const { data, error } = await supabase.rpc('search_magma_functions', { function_query: functionName, similarity_threshold: 0.3, match_count: limit }); if (error) { throw new Error('Function search error: ' + error.message); } if (!data || data.length === 0) { return { content: [{ type: "text", text: 'No functions found matching: "' + functionName + '"' }], }; } let output = '# Function Search Results for "' + functionName + '"\n\n'; output += 'Found ' + data.length + ' matching functions:\n\n'; data.forEach((result, index) => { output += '## ' + (index + 1) + '. ' + result.function_name + '\n'; output += '**Similarity**: ' + result.similarity_score.toFixed(3) + '\n'; if (result.function_signature) { output += '**Signature**: `' + result.function_signature + '`\n'; } if (result.description) { output += '**Description**: ' + result.description + '\n'; } if (result.category) { output += '**Category**: ' + result.category + '\n'; } output += '\n'; }); return { content: [{ type: "text", text: output }], }; } catch (error) { return { content: [{ type: "text", text: 'Error: ' + (error instanceof Error ? error.message : 'Unknown error') }], }; } }); // API key validation tool server.tool("validate_api_key", "Validate your OpenAI API key configuration and test connectivity", {}, async () => { const { apiKey: validatedApiKey, error: validationError } = getValidatedApiKey(); let validationResult = "# API Key Validation Results\n\n"; if (!validatedApiKey) { validationResult += "API Key Validation Failed\n"; validationResult += "- Error: " + validationError + "\n\n"; validationResult += "**Sources Checked:**\n"; validationResult += "- config.openaiApiKey: " + !!config.openaiApiKey + "\n"; validationResult += "- OPENAI_API_KEY: " + !!process.env.OPENAI_API_KEY + "\n"; validationResult += "- OPENAI_API_KEY_CONFIG: " + !!process.env.OPENAI_API_KEY_CONFIG + "\n"; validationResult += "- SMITHERY_OPENAI_API_KEY: " + !!process.env.SMITHERY_OPENAI_API_KEY + "\n\n"; } else { validationResult += "API Key Validated Successfully\n"; validationResult += "- Key length: " + validatedApiKey.length + " characters\n"; validationResult += "- Starts with 'sk-': Yes\n"; validationResult += "- Format valid: Yes\n\n"; // API connection test try { validationResult += "Testing API Connection...\n"; const testEmbeddings = createEmbeddings(validatedApiKey); const testEmbedding = await testEmbeddings.embedQuery("test"); validationResult += "API Connection Successful!\n"; validationResult += "- Embedding dimension: " + testEmbedding.length + "\n"; validationResult += "- Ready to use search features\n\n"; } catch (error) { validationResult += "API Connection Failed\n"; validationResult += "- Error: " + error.message + "\n"; validationResult += "- Error type: " + error.name + "\n"; validationResult += "- API key prefix: " + validatedApiKey.substring(0, 8) + "...\n"; validationResult += "- Please check your API key validity and credits\n\n"; } } validationResult += "## Configuration Guide\n\n"; validationResult += "### Method 1: Via Smithery Website\n"; validationResult += "1. Go to https://smithery.ai/@LeGenAI/mcp-magma-handbook\n"; validationResult += "2. Enter your OpenAI API key in the configuration\n"; validationResult += "3. Copy the generated JSON configuration\n"; validationResult += "4. Paste into your Claude Desktop or Cursor settings\n\n"; return { content: [{ type: "text", text: validationResult }], }; }); // Demo tool that works without configuration server.tool("magma_info", "Get information about MAGMA computational algebra system and this server", {}, async () => { return { content: [{ type: "text", text: '# MAGMA Handbook Advanced Server v2.1.0\n\n## About MAGMA\nMAGMA is a large, well-supported software package designed for computations in algebra, number theory, algebraic geometry and algebraic combinatorics.\n\n## Server Features\n- Advanced Hybrid Search: BM25 + Vector similarity (84.7% average relevance)\n- Function-Specific Search: 4441+ MAGMA functions indexed with fuzzy matching\n- Quality Benchmarking: Comprehensive testing suite\n- Query Expansion: Mathematical domain-specific synonyms\n\n## Available Tools\n1. **search_magma_advanced**: Comprehensive search with hybrid algorithms\n2. **search_functions**: Dedicated MAGMA function lookup\n3. **validate_api_key**: API key validation and testing\n4. **magma_info**: This information tool\n\n## Configuration Required\nTo use search features, you only need:\n- **openaiApiKey**: Your OpenAI API key (for embeddings)\n\n**Note**: The MAGMA knowledge base is provided free of charge!' }], }; }); // Quality benchmark tool server.tool("benchmark_quality", "Run quality benchmarks to evaluate search performance across different query types", { difficulty: z.enum(['easy', 'medium', 'hard', 'all']).optional().default('all').describe('Difficulty level to test') }, async ({ difficulty }) => { if (!openaiApiKey) { return { content: [{ type: "text", text: 'OpenAI API Key Required\n\n**Error:** ' + apiKeyError + '\n\nPlease configure your OpenAI API key to run benchmarks.' }], }; } const testQueries = [ { query: "Hamming code generator matrix", difficulty: "easy" }, { query: "Reed Solomon error correction", difficulty: "medium" }, { query: "BCH code construction polynomial", difficulty: "hard" }, { query: "permutation group symmetric alternating", difficulty: "easy" }, { query: "Sylow subgroup computation", difficulty: "medium" }, { query: "integer factorization algorithm", difficulty: "easy" }, { query: "elliptic curve point addition", difficulty: "easy" }, { query: "matrix eigenvalue computation", difficulty: "easy" }, { query: "GF finite field arithmetic", difficulty: "easy" }, { query: "IsIrreducible polynomial test", difficulty: "easy" }, ].filter(q => difficulty === 'all' || q.difficulty === difficulty); let output = "# MAGMA Knowledge Base Quality Benchmark\n\n"; output += "Testing " + testQueries.length + " queries (difficulty: " + difficulty + ")\n\n"; let totalScore = 0; let totalTime = 0; for (const [index, testQuery] of testQueries.entries()) { const startTime = Date.now(); try { const testEmbeddings = createEmbeddings(openaiApiKey); const queryEmbedding = await testEmbeddings.embedQuery(testQuery.query); const { data } = await supabase.rpc('search_magma_hybrid', { query_text: testQuery.query, query_embedding: queryEmbedding, similarity_threshold: 0.4, bm25_weight: 0.3, vector_weight: 0.7, match_count: 5, category_filter: null }); const responseTime = Date.now() - startTime; const resultCount = data?.length || 0; const relevanceScore = Math.min(resultCount / 5, 1.0); // Simple relevance metric totalScore += relevanceScore; totalTime += responseTime; output += '[' + (index + 1) + '/' + testQueries.length + '] "' + testQuery.query + '" (' + testQuery.difficulty + ')\n'; output += ' Relevance: ' + (relevanceScore * 100).toFixed(0) + '% | Speed: ' + responseTime + 'ms | Results: ' + resultCount + '\n\n'; } catch (error) { output += '[' + (index + 1) + '/' + testQueries.length + '] "' + testQuery.query + '" - ERROR: ' + error + '\n\n'; } } const avgScore = (totalScore / testQueries.length) * 100; const avgTime = totalTime / testQueries.length; output += '## Summary\n'; output += 'Average Relevance: ' + avgScore.toFixed(1) + '%\n'; output += 'Average Response Time: ' + avgTime.toFixed(0) + 'ms\n'; return { content: [{ type: "text", text: output }], }; }); return server.server; } //# sourceMappingURL=smithery-index.js.map