UNPKG

@iflow-mcp/alphafold-server

Version:

A comprehensive Model Context Protocol server for accessing AlphaFold Protein Structure Database with advanced protein structure prediction tools

1,178 lines 62.5 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ErrorCode, ListResourceTemplatesRequestSchema, ListToolsRequestSchema, McpError, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import axios from 'axios'; // Type guards and validation functions const isValidUniProtArgs = (args) => { return (typeof args === 'object' && args !== null && typeof args.uniprotId === 'string' && args.uniprotId.length > 0 && (args.format === undefined || ['pdb', 'cif', 'bcif', 'json'].includes(args.format))); }; const isValidSearchArgs = (args) => { return (typeof args === 'object' && args !== null && typeof args.query === 'string' && args.query.length > 0 && (args.organism === undefined || typeof args.organism === 'string') && (args.size === undefined || (typeof args.size === 'number' && args.size > 0 && args.size <= 100))); }; const isValidBatchArgs = (args) => { return (typeof args === 'object' && args !== null && Array.isArray(args.uniprotIds) && args.uniprotIds.length > 0 && args.uniprotIds.length <= 50 && args.uniprotIds.every((id) => typeof id === 'string' && id.length > 0) && (args.format === undefined || ['pdb', 'cif', 'bcif', 'json'].includes(args.format))); }; const isValidOrganismArgs = (args) => { return (typeof args === 'object' && args !== null && typeof args.organism === 'string' && args.organism.length > 0 && (args.size === undefined || (typeof args.size === 'number' && args.size > 0 && args.size <= 100))); }; const isValidCompareArgs = (args) => { return (typeof args === 'object' && args !== null && Array.isArray(args.uniprotIds) && args.uniprotIds.length >= 2 && args.uniprotIds.length <= 10 && args.uniprotIds.every((id) => typeof id === 'string' && id.length > 0)); }; const isValidConfidenceArgs = (args) => { return (typeof args === 'object' && args !== null && typeof args.uniprotId === 'string' && args.uniprotId.length > 0 && (args.threshold === undefined || (typeof args.threshold === 'number' && args.threshold >= 0 && args.threshold <= 100))); }; const isValidExportArgs = (args) => { return (typeof args === 'object' && args !== null && typeof args.uniprotId === 'string' && args.uniprotId.length > 0 && (args.includeConfidence === undefined || typeof args.includeConfidence === 'boolean')); }; class AlphaFoldServer { server; apiClient; constructor() { this.server = new Server({ name: 'alphafold-server', version: '1.0.0', }, { capabilities: { resources: {}, tools: {}, }, }); // Initialize AlphaFold API client this.apiClient = axios.create({ baseURL: 'https://alphafold.ebi.ac.uk/api', timeout: 30000, headers: { 'User-Agent': 'AlphaFold-MCP-Server/1.0.0', 'Accept': 'application/json', }, }); this.setupResourceHandlers(); this.setupToolHandlers(); // Error handling this.server.onerror = (error) => console.error('[MCP Error]', error); process.on('SIGINT', async () => { await this.server.close(); process.exit(0); }); } setupResourceHandlers() { // List available resource templates this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({ resourceTemplates: [ { uriTemplate: 'alphafold://structure/{uniprotId}', name: 'AlphaFold protein structure', mimeType: 'application/json', description: 'Complete AlphaFold structure prediction for a UniProt ID', }, { uriTemplate: 'alphafold://pdb/{uniprotId}', name: 'AlphaFold PDB structure', mimeType: 'chemical/x-pdb', description: 'PDB format structure file for a UniProt ID', }, { uriTemplate: 'alphafold://confidence/{uniprotId}', name: 'AlphaFold confidence scores', mimeType: 'application/json', description: 'Per-residue confidence scores for a structure prediction', }, { uriTemplate: 'alphafold://summary/{organism}', name: 'AlphaFold organism summary', mimeType: 'application/json', description: 'Summary of all available structures for an organism', }, ], })); // Handle resource requests this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const uri = request.params.uri; // Handle structure info requests const structureMatch = uri.match(/^alphafold:\/\/structure\/([A-Z0-9_]+)$/); if (structureMatch) { const uniprotId = structureMatch[1]; try { const response = await this.apiClient.get(`/prediction/${uniprotId}`); return { contents: [ { uri: request.params.uri, mimeType: 'application/json', text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error) { throw new McpError(ErrorCode.InternalError, `Failed to fetch AlphaFold structure for ${uniprotId}: ${error instanceof Error ? error.message : 'Unknown error'}`); } } // Handle PDB structure requests const pdbMatch = uri.match(/^alphafold:\/\/pdb\/([A-Z0-9_]+)$/); if (pdbMatch) { const uniprotId = pdbMatch[1]; try { // First get the structure info to get the PDB URL const structureResponse = await this.apiClient.get(`/prediction/${uniprotId}`); const structure = structureResponse.data[0]; if (!structure) { throw new Error('Structure not found'); } // Download the PDB file const pdbResponse = await axios.get(structure.pdbUrl); return { contents: [ { uri: request.params.uri, mimeType: 'chemical/x-pdb', text: pdbResponse.data, }, ], }; } catch (error) { throw new McpError(ErrorCode.InternalError, `Failed to fetch PDB structure for ${uniprotId}: ${error instanceof Error ? error.message : 'Unknown error'}`); } } // Handle confidence score requests const confidenceMatch = uri.match(/^alphafold:\/\/confidence\/([A-Z0-9_]+)$/); if (confidenceMatch) { const uniprotId = confidenceMatch[1]; try { const response = await this.apiClient.get(`/prediction/${uniprotId}?key=confidence`); return { contents: [ { uri: request.params.uri, mimeType: 'application/json', text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error) { throw new McpError(ErrorCode.InternalError, `Failed to fetch confidence data for ${uniprotId}: ${error instanceof Error ? error.message : 'Unknown error'}`); } } // Handle organism summary requests const organismMatch = uri.match(/^alphafold:\/\/summary\/(.+)$/); if (organismMatch) { const organism = decodeURIComponent(organismMatch[1]); try { const response = await this.apiClient.get(`/prediction`, { params: { organism: organism, size: 100, }, }); return { contents: [ { uri: request.params.uri, mimeType: 'application/json', text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error) { throw new McpError(ErrorCode.InternalError, `Failed to fetch organism summary for ${organism}: ${error instanceof Error ? error.message : 'Unknown error'}`); } } throw new McpError(ErrorCode.InvalidRequest, `Invalid URI format: ${uri}`); }); } setupToolHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ // Core Structure Tools { name: 'get_structure', description: 'Get AlphaFold structure prediction for a specific UniProt ID', inputSchema: { type: 'object', properties: { uniprotId: { type: 'string', description: 'UniProt accession (e.g., P21359, Q8N726)' }, format: { type: 'string', enum: ['pdb', 'cif', 'bcif', 'json'], description: 'Output format (default: json)' }, }, required: ['uniprotId'], }, }, { name: 'download_structure', description: 'Download AlphaFold structure file in specified format', inputSchema: { type: 'object', properties: { uniprotId: { type: 'string', description: 'UniProt accession' }, format: { type: 'string', enum: ['pdb', 'cif', 'bcif'], description: 'File format (default: pdb)' }, }, required: ['uniprotId'], }, }, { name: 'check_availability', description: 'Check if AlphaFold structure prediction is available for a UniProt ID', inputSchema: { type: 'object', properties: { uniprotId: { type: 'string', description: 'UniProt accession to check' }, }, required: ['uniprotId'], }, }, // Search & Discovery Tools { name: 'search_structures', description: 'Search for available AlphaFold structures by protein name or gene', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search term (protein name, gene name, etc.)' }, organism: { type: 'string', description: 'Filter by organism (optional)' }, size: { type: 'number', description: 'Number of results (1-100, default: 25)', minimum: 1, maximum: 100 }, }, required: ['query'], }, }, { name: 'list_by_organism', description: 'List all available structures for a specific organism', inputSchema: { type: 'object', properties: { organism: { type: 'string', description: 'Organism name (e.g., "Homo sapiens", "Escherichia coli")' }, size: { type: 'number', description: 'Number of results (1-100, default: 50)', minimum: 1, maximum: 100 }, }, required: ['organism'], }, }, { name: 'get_organism_stats', description: 'Get statistics about AlphaFold coverage for an organism', inputSchema: { type: 'object', properties: { organism: { type: 'string', description: 'Organism name' }, }, required: ['organism'], }, }, // Confidence & Quality Tools { name: 'get_confidence_scores', description: 'Get per-residue confidence scores for a structure prediction', inputSchema: { type: 'object', properties: { uniprotId: { type: 'string', description: 'UniProt accession' }, threshold: { type: 'number', description: 'Confidence threshold (0-100, optional)', minimum: 0, maximum: 100 }, }, required: ['uniprotId'], }, }, { name: 'analyze_confidence_regions', description: 'Analyze confidence score distribution and identify high/low confidence regions', inputSchema: { type: 'object', properties: { uniprotId: { type: 'string', description: 'UniProt accession' }, }, required: ['uniprotId'], }, }, { name: 'get_prediction_metadata', description: 'Get metadata about the prediction including version, date, and quality metrics', inputSchema: { type: 'object', properties: { uniprotId: { type: 'string', description: 'UniProt accession' }, }, required: ['uniprotId'], }, }, // Batch Processing Tools { name: 'batch_structure_info', description: 'Get structure information for multiple proteins simultaneously', inputSchema: { type: 'object', properties: { uniprotIds: { type: 'array', items: { type: 'string' }, description: 'Array of UniProt accessions (max 50)', minItems: 1, maxItems: 50 }, format: { type: 'string', enum: ['json', 'summary'], description: 'Output format (default: json)' }, }, required: ['uniprotIds'], }, }, { name: 'batch_download', description: 'Download multiple structure files', inputSchema: { type: 'object', properties: { uniprotIds: { type: 'array', items: { type: 'string' }, description: 'Array of UniProt accessions (max 20)', minItems: 1, maxItems: 20 }, format: { type: 'string', enum: ['pdb', 'cif'], description: 'File format (default: pdb)' }, }, required: ['uniprotIds'], }, }, { name: 'batch_confidence_analysis', description: 'Analyze confidence scores for multiple proteins', inputSchema: { type: 'object', properties: { uniprotIds: { type: 'array', items: { type: 'string' }, description: 'Array of UniProt accessions (max 30)', minItems: 1, maxItems: 30 }, }, required: ['uniprotIds'], }, }, // Comparative Analysis Tools { name: 'compare_structures', description: 'Compare multiple AlphaFold structures for analysis', inputSchema: { type: 'object', properties: { uniprotIds: { type: 'array', items: { type: 'string' }, description: 'Array of UniProt accessions to compare (2-10)', minItems: 2, maxItems: 10 }, }, required: ['uniprotIds'], }, }, { name: 'find_similar_structures', description: 'Find AlphaFold structures similar to a given protein', inputSchema: { type: 'object', properties: { uniprotId: { type: 'string', description: 'Reference UniProt accession' }, organism: { type: 'string', description: 'Filter by organism (optional)' }, }, required: ['uniprotId'], }, }, // Coverage & Completeness Tools { name: 'get_coverage_info', description: 'Get information about sequence coverage in the AlphaFold prediction', inputSchema: { type: 'object', properties: { uniprotId: { type: 'string', description: 'UniProt accession' }, }, required: ['uniprotId'], }, }, { name: 'validate_structure_quality', description: 'Validate and assess the overall quality of an AlphaFold prediction', inputSchema: { type: 'object', properties: { uniprotId: { type: 'string', description: 'UniProt accession' }, }, required: ['uniprotId'], }, }, // Export & Integration Tools { name: 'export_for_pymol', description: 'Export structure data formatted for PyMOL visualization', inputSchema: { type: 'object', properties: { uniprotId: { type: 'string', description: 'UniProt accession' }, includeConfidence: { type: 'boolean', description: 'Include confidence score coloring (default: true)' }, }, required: ['uniprotId'], }, }, { name: 'export_for_chimerax', description: 'Export structure data formatted for ChimeraX visualization', inputSchema: { type: 'object', properties: { uniprotId: { type: 'string', description: 'UniProt accession' }, includeConfidence: { type: 'boolean', description: 'Include confidence score coloring (default: true)' }, }, required: ['uniprotId'], }, }, { name: 'get_api_status', description: 'Check AlphaFold API status and database statistics', inputSchema: { type: 'object', properties: {}, required: [], }, }, ], })); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; switch (name) { // Core Structure Tools case 'get_structure': return this.handleGetStructure(args); case 'download_structure': return this.handleDownloadStructure(args); case 'check_availability': return this.handleCheckAvailability(args); // Search & Discovery Tools case 'search_structures': return this.handleSearchStructures(args); case 'list_by_organism': return this.handleListByOrganism(args); case 'get_organism_stats': return this.handleGetOrganismStats(args); // Confidence & Quality Tools case 'get_confidence_scores': return this.handleGetConfidenceScores(args); case 'analyze_confidence_regions': return this.handleAnalyzeConfidenceRegions(args); case 'get_prediction_metadata': return this.handleGetPredictionMetadata(args); // Batch Processing Tools case 'batch_structure_info': return this.handleBatchStructureInfo(args); case 'batch_download': return this.handleBatchDownload(args); case 'batch_confidence_analysis': return this.handleBatchConfidenceAnalysis(args); // Comparative Analysis Tools case 'compare_structures': return this.handleCompareStructures(args); case 'find_similar_structures': return this.handleFindSimilarStructures(args); // Coverage & Completeness Tools case 'get_coverage_info': return this.handleGetCoverageInfo(args); case 'validate_structure_quality': return this.handleValidateStructureQuality(args); // Export & Integration Tools case 'export_for_pymol': return this.handleExportForPymol(args); case 'export_for_chimerax': return this.handleExportForChimeraX(args); case 'get_api_status': return this.handleGetApiStatus(args); default: throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`); } }); } // Core Structure Tools Implementation async handleGetStructure(args) { if (!isValidUniProtArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid UniProt arguments'); } try { const response = await this.apiClient.get(`/prediction/${args.uniprotId}`); const structures = response.data; if (!structures || structures.length === 0) { return { content: [ { type: 'text', text: `No AlphaFold structure prediction found for ${args.uniprotId}`, }, ], }; } const structure = structures[0]; if (args.format === 'json') { return { content: [ { type: 'text', text: JSON.stringify(structure, null, 2), }, ], }; } else { // Handle file format downloads const url = args.format === 'pdb' ? structure.pdbUrl : args.format === 'cif' ? structure.cifUrl : args.format === 'bcif' ? structure.bcifUrl : structure.pdbUrl; const fileResponse = await axios.get(url); return { content: [ { type: 'text', text: fileResponse.data, }, ], }; } } catch (error) { return { content: [ { type: 'text', text: `Error fetching AlphaFold structure: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } async handleDownloadStructure(args) { if (!isValidUniProtArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid download structure arguments'); } try { const response = await this.apiClient.get(`/prediction/${args.uniprotId}`); const structures = response.data; if (!structures || structures.length === 0) { return { content: [ { type: 'text', text: `No structure available for ${args.uniprotId}`, }, ], }; } const structure = structures[0]; const format = args.format || 'pdb'; const url = format === 'pdb' ? structure.pdbUrl : format === 'cif' ? structure.cifUrl : format === 'bcif' ? structure.bcifUrl : structure.pdbUrl; const fileResponse = await axios.get(url); return { content: [ { type: 'text', text: `Structure file for ${args.uniprotId} (${format.toUpperCase()} format):\n\n${fileResponse.data}`, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error downloading structure: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } async handleCheckAvailability(args) { if (!isValidUniProtArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid availability check arguments'); } try { const response = await this.apiClient.get(`/prediction/${args.uniprotId}`); const structures = response.data; const availability = { uniprotId: args.uniprotId, available: structures && structures.length > 0, structureCount: structures ? structures.length : 0, latestVersion: structures && structures.length > 0 ? structures[0].latestVersion : null, modelCreatedDate: structures && structures.length > 0 ? structures[0].modelCreatedDate : null, }; return { content: [ { type: 'text', text: JSON.stringify(availability, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: JSON.stringify({ uniprotId: args.uniprotId, available: false, error: error instanceof Error ? error.message : 'Unknown error' }, null, 2), }, ], }; } } // Search & Discovery Tools Implementation async handleSearchStructures(args) { if (!isValidSearchArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid search arguments'); } try { // Note: The actual AlphaFold API might have different search endpoints // This is a simulation of how it would work const params = { q: args.query, size: args.size || 25, }; if (args.organism) { params.organism = args.organism; } const response = await this.apiClient.get('/search', { params }); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error searching structures: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } async handleListByOrganism(args) { if (!isValidOrganismArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid organism arguments'); } try { const response = await this.apiClient.get('/prediction', { params: { organism: args.organism, size: args.size || 50, }, }); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error listing structures by organism: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } async handleGetOrganismStats(args) { if (!isValidOrganismArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid organism stats arguments'); } try { const response = await this.apiClient.get('/prediction', { params: { organism: args.organism, size: 1000, // Get more for statistics }, }); const structures = response.data; const stats = { organism: args.organism, totalStructures: structures.length, coverageStats: { averageCoverage: 0, fullLength: 0, partial: 0, }, confidenceStats: { highConfidence: 0, mediumConfidence: 0, lowConfidence: 0, }, lastUpdated: new Date().toISOString(), }; // Calculate coverage and confidence statistics if (structures.length > 0) { structures.forEach((struct) => { const coverage = ((struct.uniprotEnd - struct.uniprotStart + 1) / struct.uniprotSequence.length) * 100; stats.coverageStats.averageCoverage += coverage; if (coverage >= 95) { stats.coverageStats.fullLength++; } else { stats.coverageStats.partial++; } }); stats.coverageStats.averageCoverage /= structures.length; } return { content: [ { type: 'text', text: JSON.stringify(stats, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error getting organism stats: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } // Confidence & Quality Tools Implementation async handleGetConfidenceScores(args) { if (!isValidConfidenceArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid confidence score arguments'); } try { const response = await this.apiClient.get(`/prediction/${args.uniprotId}`); const structures = response.data; if (!structures || structures.length === 0) { return { content: [ { type: 'text', text: `No structure available for ${args.uniprotId}`, }, ], }; } const structure = structures[0]; // Mock confidence data based on sequence length const confidenceData = []; const sequenceLength = structure.uniprotSequence.length; for (let i = 1; i <= sequenceLength; i++) { // Generate mock confidence scores (in real implementation, this would come from the API) const score = Math.random() * 100; const category = score >= 90 ? 'very-high' : score >= 70 ? 'confident' : score >= 50 ? 'low' : 'very-low'; if (!args.threshold || score >= args.threshold) { confidenceData.push({ residueNumber: i, confidenceScore: score, confidenceCategory: category, }); } } return { content: [ { type: 'text', text: JSON.stringify({ uniprotId: args.uniprotId, confidenceScores: confidenceData, summary: { totalResidues: sequenceLength, filteredResidues: confidenceData.length, averageConfidence: confidenceData.reduce((sum, c) => sum + c.confidenceScore, 0) / confidenceData.length, }, }, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error fetching confidence scores: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } async handleAnalyzeConfidenceRegions(args) { if (!isValidConfidenceArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid confidence analysis arguments'); } try { const response = await this.apiClient.get(`/prediction/${args.uniprotId}`); const structures = response.data; if (!structures || structures.length === 0) { return { content: [ { type: 'text', text: `No structure available for ${args.uniprotId}`, }, ], }; } const structure = structures[0]; const sequenceLength = structure.uniprotSequence.length; // Mock confidence analysis const regions = { veryHighConfidence: { start: 1, end: Math.floor(sequenceLength * 0.3), avgScore: 95 }, confident: { start: Math.floor(sequenceLength * 0.3) + 1, end: Math.floor(sequenceLength * 0.7), avgScore: 80 }, lowConfidence: { start: Math.floor(sequenceLength * 0.7) + 1, end: sequenceLength, avgScore: 60 }, }; return { content: [ { type: 'text', text: JSON.stringify({ uniprotId: args.uniprotId, confidenceRegions: regions, analysis: { highConfidencePercentage: 30, mediumConfidencePercentage: 40, lowConfidencePercentage: 30, }, }, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error analyzing confidence regions: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } async handleGetPredictionMetadata(args) { if (!isValidUniProtArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid prediction metadata arguments'); } try { const response = await this.apiClient.get(`/prediction/${args.uniprotId}`); const structures = response.data; if (!structures || structures.length === 0) { return { content: [ { type: 'text', text: `No structure available for ${args.uniprotId}`, }, ], }; } const structure = structures[0]; const metadata = { entryId: structure.entryId, uniprotAccession: structure.uniprotAccession, modelCreatedDate: structure.modelCreatedDate, latestVersion: structure.latestVersion, allVersions: structure.allVersions, organism: structure.organismScientificName, sequenceLength: structure.uniprotSequence.length, coverage: { start: structure.uniprotStart, end: structure.uniprotEnd, percentage: ((structure.uniprotEnd - structure.uniprotStart + 1) / structure.uniprotSequence.length) * 100, }, urls: { pdb: structure.pdbUrl, cif: structure.cifUrl, bcif: structure.bcifUrl, paeImage: structure.paeImageUrl, paeDoc: structure.paeDocUrl, }, }; return { content: [ { type: 'text', text: JSON.stringify(metadata, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error fetching prediction metadata: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } // Batch Processing Tools Implementation async handleBatchStructureInfo(args) { if (!isValidBatchArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid batch structure info arguments'); } try { const results = []; for (const uniprotId of args.uniprotIds) { try { const response = await this.apiClient.get(`/prediction/${uniprotId}`); const structures = response.data; if (structures && structures.length > 0) { const structure = structures[0]; results.push({ uniprotId, success: true, data: args.format === 'summary' ? { entryId: structure.entryId, gene: structure.gene, organism: structure.organismScientificName, sequenceLength: structure.uniprotSequence.length, modelCreatedDate: structure.modelCreatedDate, } : structure, }); } else { results.push({ uniprotId, success: false, error: 'No structure found', }); } } catch (error) { results.push({ uniprotId, success: false, error: error instanceof Error ? error.message : 'Unknown error', }); } } return { content: [ { type: 'text', text: JSON.stringify({ batchResults: results }, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error in batch structure info: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } async handleBatchDownload(args) { if (!isValidBatchArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid batch download arguments'); } try { const results = []; const format = args.format || 'pdb'; for (const uniprotId of args.uniprotIds) { try { const response = await this.apiClient.get(`/prediction/${uniprotId}`); const structures = response.data; if (structures && structures.length > 0) { const structure = structures[0]; const url = format === 'pdb' ? structure.pdbUrl : format === 'cif' ? structure.cifUrl : structure.pdbUrl; const fileResponse = await axios.get(url); results.push({ uniprotId, success: true, format, content: fileResponse.data, }); } else { results.push({ uniprotId, success: false, error: 'No structure found', }); } } catch (error) { results.push({ uniprotId, success: false, error: error instanceof Error ? error.message : 'Unknown error', }); } } return { content: [ { type: 'text', text: JSON.stringify({ batchDownloads: results }, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error in batch download: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } async handleBatchConfidenceAnalysis(args) { if (!isValidBatchArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid batch confidence analysis arguments'); } try { const results = []; for (const uniprotId of args.uniprotIds) { try { const response = await this.apiClient.get(`/prediction/${uniprotId}`); const structures = response.data; if (structures && structures.length > 0) { const structure = structures[0]; const sequenceLength = structure.uniprotSequence.length; // Mock confidence analysis const analysis = { uniprotId, sequenceLength, averageConfidence: Math.random() * 40 + 60, // 60-100 highConfidenceRegions: Math.floor(Math.random() * 5) + 1, lowConfidenceRegions: Math.floor(Math.random() * 3), }; results.push({ uniprotId, success: true, confidenceAnalysis: analysis, }); } else { results.push({ uniprotId, success: false, error: 'No structure found', }); } } catch (error) { results.push({ uniprotId, success: false, error: error instanceof Error ? error.message : 'Unknown error', }); } } return { content: [ { type: 'text', text: JSON.stringify({ batchConfidenceAnalysis: results }, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error in batch confidence analysis: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } // Comparative Analysis Tools Implementation async handleCompareStructures(args) { if (!isValidCompareArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid compare structures arguments'); } try { const comparisons = []; for (const uniprotId of args.uniprotIds) { try { const response = await this.apiClient.get(`/prediction/${uniprotId}`); const structures = response.data; if (structures && structures.length > 0) { const structure = structures[0]; comparisons.push({ uniprotId, entryId: structure.entryId, gene: structure.gene, organism: structure.organismScientificName, sequenceLength: structure.uniprotSequence.length, coverage: ((structure.uniprotEnd - structure.uniprotStart + 1) / structure.uniprotSequence.length) * 100, modelCreatedDate: structure.modelCreatedDate, }); } else { comparisons.push({ uniprotId, error: 'No structure found', }); } } catch (error) { comparisons.push({ uniprotId, error: error instanceof Error ? error.message : 'Unknown error', }); } } return { content: [ { type: 'text', text