UNPKG

agr-mcp-server-enhanced

Version:

Enhanced Alliance of Genome Resources MCP Server - High-performance JavaScript implementation with simplified search capabilities

397 lines (349 loc) 9.28 kB
#!/usr/bin/env node /** * Simple AGR MCP Server - Minimal Implementation * * A streamlined MCP server for Alliance of Genome Resources with basic functionality */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import axios from 'axios'; // Simple configuration const API_BASE = 'https://www.alliancegenome.org/api'; const TIMEOUT = 30000; /** * Simple AGR Client */ class SimpleAGRClient { constructor() { this.client = axios.create({ timeout: TIMEOUT, headers: { 'User-Agent': 'AGR-MCP-Server-Simple/1.0.0' } }); } async request(endpoint, params = {}) { try { const response = await this.client.get(`${API_BASE}${endpoint}`, { params }); return response.data; } catch (error) { throw new Error(`API request failed: ${error.message}`); } } /** * Parse complex query with Boolean operators */ parseComplexQuery(query) { const parsed = { terms: [], operators: [], species: null, hasNot: false }; // Extract operators const operators = query.match(/\b(AND|OR|NOT)\b/gi) || []; parsed.operators = operators.map(op => op.toUpperCase()); parsed.hasNot = parsed.operators.includes('NOT'); // Extract species const speciesMatch = query.match(/\bin\s+(human|mouse|zebrafish)/i); if (speciesMatch) { const speciesMap = { 'human': 'Homo sapiens', 'mouse': 'Mus musculus', 'zebrafish': 'Danio rerio' }; parsed.species = speciesMap[speciesMatch[1].toLowerCase()]; } // Clean and extract terms let cleanQuery = query .replace(/\b(AND|OR|NOT)\b/gi, ' ') .replace(/\bin\s+(human|mouse|zebrafish)/gi, '') .replace(/\b(genes?|gene)\b/gi, '') .trim(); if (cleanQuery) { parsed.terms = cleanQuery.split(/\s+/).filter(t => t.length > 2); } return parsed; } /** * Build query string from parsed components */ buildQuery(parsed) { if (parsed.hasNot) { let positiveTerms = [...parsed.terms]; let negativeTerms = []; if (positiveTerms.includes('p53')) { negativeTerms.push('p53'); positiveTerms = positiveTerms.filter(term => term !== 'p53'); } if (negativeTerms.length > 0) { return `${positiveTerms.join(' ')} NOT ${negativeTerms.join(' ')}`; } } if (parsed.operators.includes('OR')) { return `(${parsed.terms.join(' OR ')})`; } return parsed.terms.join(' '); } /** * Complex search with safe MCP handling */ async complexSearch(query, limit = 10) { try { const parsed = this.parseComplexQuery(query); const searchQuery = this.buildQuery(parsed); const params = { q: searchQuery, category: 'gene', limit: Math.min(limit, 20) // Keep results small for MCP }; if (parsed.species) { params.species = parsed.species; } const response = await this.request('/search', params); // Return MCP-safe simplified structure return { query: query, searchQuery: searchQuery, total: response.total || 0, results: (response.results || []).slice(0, limit).map(gene => ({ symbol: gene.symbol || 'Unknown', name: gene.name || 'Unknown', species: gene.species || 'Unknown', id: gene.id || gene.primaryKey, score: Math.round(gene.score || 0) })), operators: parsed.operators, species: parsed.species }; } catch (error) { return { error: error.message, query: query }; } } async searchGenes(query, limit = 20) { const response = await this.request('/search', { q: query, category: 'gene', limit: Math.min(limit, 100) }); return { total: response.total || 0, results: (response.results || []).slice(0, limit).map(gene => ({ symbol: gene.symbol, name: gene.name, species: gene.species, id: gene.id || gene.primaryKey })) }; } async getGeneInfo(geneId) { return this.request(`/gene/${encodeURIComponent(geneId)}`); } async getGeneDiseases(geneId) { return this.request(`/gene/${encodeURIComponent(geneId)}/diseases`); } async getGeneExpression(geneId) { return this.request(`/gene/${encodeURIComponent(geneId)}/expression`); } async findOrthologs(geneId) { return this.request(`/gene/${encodeURIComponent(geneId)}/orthologs`); } async getSpeciesList() { return this.request('/species'); } } // Initialize client const agrClient = new SimpleAGRClient(); // Create MCP server const server = new Server( { name: 'agr-genomics-simple', version: '1.0.0' }, { capabilities: { tools: {} } } ); // Define tools const TOOLS = [ { name: 'search_genes', description: 'Search for genes by symbol or name', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Gene symbol or name to search for' }, limit: { type: 'integer', description: 'Maximum number of results (default: 20)', default: 20 } }, required: ['query'] } }, { name: 'get_gene_info', description: 'Get detailed information about a gene', inputSchema: { type: 'object', properties: { gene_id: { type: 'string', description: 'Gene identifier (e.g., HGNC:1100)' } }, required: ['gene_id'] } }, { name: 'get_gene_diseases', description: 'Get disease associations for a gene', inputSchema: { type: 'object', properties: { gene_id: { type: 'string', description: 'Gene identifier' } }, required: ['gene_id'] } }, { name: 'get_gene_expression', description: 'Get gene expression data', inputSchema: { type: 'object', properties: { gene_id: { type: 'string', description: 'Gene identifier' } }, required: ['gene_id'] } }, { name: 'find_orthologs', description: 'Find orthologous genes across species', inputSchema: { type: 'object', properties: { gene_id: { type: 'string', description: 'Gene identifier' } }, required: ['gene_id'] } }, { name: 'complex_search', description: 'Execute complex queries with Boolean operators (AND, OR, NOT)', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Complex query like "breast cancer AND DNA repair NOT p53"' }, limit: { type: 'integer', default: 10, description: 'Maximum results' } }, required: ['query'] } }, { name: 'get_species_list', description: 'Get list of supported species', inputSchema: { type: 'object', properties: {}, required: [] } } ]; // Register handlers server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: TOOLS }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { let result; switch (name) { case 'search_genes': result = await agrClient.searchGenes(args.query, args.limit); break; case 'complex_search': result = await agrClient.complexSearch(args.query, args.limit); break; case 'get_gene_info': result = await agrClient.getGeneInfo(args.gene_id); break; case 'get_gene_diseases': result = await agrClient.getGeneDiseases(args.gene_id); break; case 'get_gene_expression': result = await agrClient.getGeneExpression(args.gene_id); break; case 'find_orthologs': result = await agrClient.findOrthologs(args.gene_id); break; case 'get_species_list': result = await agrClient.getSpeciesList(); break; default: throw new Error(`Unknown tool: ${name}`); } return { content: [ { type: 'text', text: JSON.stringify(result, null, 2) } ] }; } catch (error) { return { content: [ { type: 'text', text: JSON.stringify({ error: error.message, tool: name }, null, 2) } ] }; } }); // Start server async function main() { console.log('Starting Simple AGR MCP Server...'); const transport = new StdioServerTransport(); await server.connect(transport); console.log('Simple AGR MCP Server started'); } if (import.meta.url === `file://${process.argv[1]}`) { main().catch((error) => { console.error('Failed to start server:', error); process.exit(1); }); }