index.ts•52 kB
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ErrorCode,
  ListResourcesRequestSchema,
  ListResourceTemplatesRequestSchema,
  ListToolsRequestSchema,
  McpError,
  ReadResourceRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import axios, { AxiosInstance } from 'axios';
// AlphaFold API interfaces
interface AlphaFoldStructure {
  entryId: string;
  gene?: string;
  uniprotAccession: string;
  uniprotId: string;
  uniprotDescription: string;
  taxId: number;
  organismScientificName: string;
  uniprotStart: number;
  uniprotEnd: number;
  uniprotSequence: string;
  modelCreatedDate: string;
  latestVersion: number;
  allVersions: number[];
  cifUrl: string;
  bcifUrl: string;
  pdbUrl: string;
  paeImageUrl: string;
  paeDocUrl: string;
}
interface ConfidenceData {
  residueNumber: number;
  confidenceScore: number;
  confidenceCategory: 'very-high' | 'confident' | 'low' | 'very-low';
}
interface StructureSummary {
  entryId: string;
  uniprotAccession: string;
  gene?: string;
  organismScientificName: string;
  structureUrl: string;
  confidenceUrl: string;
  coverage: {
    start: number;
    end: number;
    percentage: number;
  };
}
// Type guards and validation functions
const isValidUniProtArgs = (
  args: any
): args is { uniprotId: string; format?: 'pdb' | 'cif' | 'bcif' | 'json' } => {
  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: any
): args is { query: string; organism?: string; size?: number } => {
  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: any
): args is { uniprotIds: string[]; format?: string } => {
  return (
    typeof args === 'object' &&
    args !== null &&
    Array.isArray(args.uniprotIds) &&
    args.uniprotIds.length > 0 &&
    args.uniprotIds.length <= 50 &&
    args.uniprotIds.every((id: any) => typeof id === 'string' && id.length > 0) &&
    (args.format === undefined || ['pdb', 'cif', 'bcif', 'json'].includes(args.format))
  );
};
const isValidOrganismArgs = (
  args: any
): args is { organism: string; size?: number } => {
  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: any
): args is { uniprotIds: string[] } => {
  return (
    typeof args === 'object' &&
    args !== null &&
    Array.isArray(args.uniprotIds) &&
    args.uniprotIds.length >= 2 &&
    args.uniprotIds.length <= 10 &&
    args.uniprotIds.every((id: any) => typeof id === 'string' && id.length > 0)
  );
};
const isValidConfidenceArgs = (
  args: any
): args is { uniprotId: string; threshold?: number } => {
  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: any
): args is { uniprotId: string; includeConfidence?: boolean } => {
  return (
    typeof args === 'object' &&
    args !== null &&
    typeof args.uniprotId === 'string' &&
    args.uniprotId.length > 0 &&
    (args.includeConfidence === undefined || typeof args.includeConfidence === 'boolean')
  );
};
class AlphaFoldServer {
  private server: Server;
  private apiClient: AxiosInstance;
  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);
    });
  }
  private 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}`
        );
      }
    );
  }
  private 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
  private async handleGetStructure(args: any) {
    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,
      };
    }
  }
  private async handleDownloadStructure(args: any) {
    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,
      };
    }
  }
  private async handleCheckAvailability(args: any) {
    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
  private async handleSearchStructures(args: any) {
    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: any = {
        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,
      };
    }
  }
  private async handleListByOrganism(args: any) {
    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,
      };
    }
  }
  private async handleGetOrganismStats(args: any) {
    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: any) => {
          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
  private async handleGetConfidenceScores(args: any) {
    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: 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 as any,
          });
        }
      }
      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,
      };
    }
  }
  private async handleAnalyzeConfidenceRegions(args: any) {
    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,
      };
    }
  }
  private async handleGetPredictionMetadata(args: any) {
    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
  private async handleBatchStructureInfo(args: any) {
    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,
      };
    }
  }
  private async handleBatchDownload(args: any) {
    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,
      };
    }
  }
  private async handleBatchConfidenceAnalysis(args: any) {
    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
  private async handleCompareStructures(args: any) {
    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: JSON.stringify({ structureComparison: comparisons }, null, 2),
          },
        ],
      };
    } catch (error) {
      return {
        content: [
          {
            type: 'text',
            text: `Error comparing structures: ${error instanceof Error ? error.message : 'Unknown error'}`,
          },
        ],
        isError: true,
      };
    }
  }
  private async handleFindSimilarStructures(args: any) {
    if (!isValidUniProtArgs(args)) {
      throw new McpError(ErrorCode.InvalidParams, 'Invalid find similar structures 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 similar structure search
      const similarStructures = [
        {
          uniprotId: 'P21359',
          similarity: 0.85,
          organism: 'Homo sapiens',
          gene: 'NF1',
        },
        {
          uniprotId: 'Q8N726',
          similarity: 0.72,
          organism: 'Homo sapiens',
          gene: 'CD109',
        },
      ];
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              queryProtein: args.uniprotId,
              similarStructures,
            }, null, 2),
          },
        ],
      };
    } catch (error) {
      return {
        content: [
          {
            type: 'text',
            text: `Error finding similar structures: ${error instanceof Error ? error.message : 'Unknown error'}`,
          },
        ],
        isError: true,
      };
    }
  }
  // Coverage & Completeness Tools Implementation
  private async handleGetCoverageInfo(args: any) {
    if (!isValidUniProtArgs(args)) {
      throw new McpError(ErrorCode.InvalidParams, 'Invalid coverage info 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 coverageInfo = {
        uniprotId: args.uniprotId,
        fullSequenceLength: structure.uniprotSequence.length,
        predictedRegion: {
          start: structure.uniprotStart,
          end: structure.uniprotEnd,
          length: structure.uniprotEnd - structure.uniprotStart + 1,
        },
        coverage: {
          percentage: ((structure.uniprotEnd - structure.uniprotStart + 1) / structure.uniprotSequence.length) * 100,
          isComplete: structure.uniprotStart === 1 && structure.uniprotEnd === structure.uniprotSequence.length,
        },
        gaps: {
          nTerminal: structure.uniprotStart > 1 ? structure.uniprotStart - 1 : 0,
          cTerminal: structure.uniprotEnd < structure.uniprotSequence.length ? structure.uniprotSequence.length - structure.uniprotEnd : 0,
        },
      };
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(coverageInfo, null, 2),
          },
        ],
      };
    } catch (error) {
      return {
        content: [
          {
            type: 'text',
            text: `Error getting coverage info: ${error instanceof Error ? error.message : 'Unknown error'}`,
          },
        ],
        isError: true,
      };
    }
  }
  private async handleValidateStructureQuality(args: any) {
    if (!isValidUniProtArgs(args)) {
      throw new McpError(ErrorCode.InvalidParams, 'Invalid structure quality validation 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 quality validation
      const quality = {
        uniprotId: args.uniprotId,
        overallQuality: 'HIGH',
        qualityScore: 0.85,
        coverage: ((structure.uniprotEnd - structure.uniprotStart + 1) / structure.uniprotSequence.length) * 100,
        recommendations: [
          'Structure has high confidence in core domains',
          'Terminal regions may have lower reliability',
          'Suitable for most structural analyses',
        ],
        warnings: structure.uniprotStart > 10 || structure.uniprotEnd < structure.uniprotSequence.length - 10 ?
          ['Incomplete sequence coverage detected'] : [],
      };
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(quality, null, 2),
          },
        ],
      };
    } catch (error) {
      return {
        content: [
          {
            type: 'text',
            text: `Error validating structure quality: ${error instanceof Error ? error.message : 'Unknown error'}`,
          },
        ],
        isError: true,
      };
    }
  }
  // Export & Integration Tools Implementation
  private async handleExportForPymol(args: any) {
    if (!isValidExportArgs(args)) {
      throw new McpError(ErrorCode.InvalidParams, 'Invalid PyMOL export 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 includeConfidence = args.includeConfidence !== false;
      const pymolScript = `
# PyMOL script for AlphaFold structure ${args.uniprotId}
fetch ${structure.pdbUrl}
as cartoon
color spectrum
${includeConfidence ? `
# Color by confidence
# Very high confidence (pLDDT > 90): blue
# Confident (pLDDT 70-90): cyan
# Low confidence (pLDDT 50-70): yellow
# Very low confidence (pLDDT < 50): orange
` : ''}
center
zoom
`;
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              uniprotId: args.uniprotId,
              pymolScript,
              structureUrl: structure.pdbUrl,
              instructions: 'Copy the PyMOL script above and paste it into PyMOL command line',
            }, null, 2),
          },
        ],
      };
    } catch (error) {
      return {
        content: [
          {
            type: 'text',
            text: `Error exporting for PyMOL: ${error instanceof Error ? error.message : 'Unknown error'}`,
          },
        ],
        isError: true,
      };
    }
  }
  private async handleExportForChimeraX(args: any) {
    if (!isValidExportArgs(args)) {
      throw new McpError(ErrorCode.InvalidParams, 'Invalid ChimeraX export 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 includeConfidence = args.includeConfidence !== false;
      const chimeraScript = `
# ChimeraX script for AlphaFold structure ${args.uniprotId}
open ${structure.pdbUrl}
cartoon
color bychain
${includeConfidence ? `
# Color by confidence scores
# This would require the confidence data to be loaded
` : ''}
view
`;
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              uniprotId: args.uniprotId,
              chimeraScript,
              structureUrl: structure.pdbUrl,
              instructions: 'Open ChimeraX and run the commands above',
            }, null, 2),
          },
        ],
      };
    } catch (error) {
      return {
        content: [
          {
            type: 'text',
            text: `Error exporting for ChimeraX: ${error instanceof Error ? error.message : 'Unknown error'}`,
          },
        ],
        isError: true,
      };
    }
  }
  private async handleGetApiStatus(args: any) {
    try {
      // Mock API status check
      const status = {
        apiStatus: 'ONLINE',
        version: '2.0',
        lastUpdated: new Date().toISOString(),
        databaseStats: {
          totalStructures: 200000000,
          totalOrganisms: 1000000,
          lastStructureUpdate: '2024-01-15',
        },
        endpoints: {
          prediction: 'https://alphafold.ebi.ac.uk/api/prediction',
          search: 'https://alphafold.ebi.ac.uk/api/search',
        },
      };
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(status, null, 2),
          },
        ],
      };
    } catch (error) {
      return {
        content: [
          {
            type: 'text',
            text: `Error checking API status: ${error instanceof Error ? error.message : 'Unknown error'}`,
          },
        ],
        isError: true,
      };
    }
  }
  async run() {
    const transport = new StdioServerTransport();
    await this.server.connect(transport);
    console.error('AlphaFold MCP server running on stdio');
  }
}
const server = new AlphaFoldServer();
server.run().catch(console.error);