Skip to main content
Glama

ProteinAtlas MCP Server

index.ts45.9 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'; // Human Protein Atlas API interfaces interface ProteinSearchResult { gene: string; geneSynonym?: string; ensembl: string; geneDescription?: string; uniprot?: string; chromosome?: string; position?: string; proteinClass?: string; evidence?: string; } interface ProteinInfo { gene: string; ensembl: string; uniprot?: string; geneDescription?: string; tissueExpression?: any; subcellularLocation?: any; pathologyData?: any; antibodyInfo?: any; bloodExpression?: any; brainExpression?: any; } // Type guards and validation functions const isValidSearchArgs = ( args: any ): args is { query: string; format?: string; columns?: string[]; compress?: boolean; maxResults?: number; } => { return ( typeof args === 'object' && args !== null && typeof args.query === 'string' && args.query.length > 0 && (args.format === undefined || ['json', 'tsv', 'xml', 'trig'].includes(args.format)) && (args.columns === undefined || Array.isArray(args.columns)) && (args.compress === undefined || typeof args.compress === 'boolean') && (args.maxResults === undefined || (typeof args.maxResults === 'number' && args.maxResults > 0 && args.maxResults <= 10000)) ); }; const isValidGeneArgs = ( args: any ): args is { gene: string; format?: string } => { return ( typeof args === 'object' && args !== null && typeof args.gene === 'string' && args.gene.length > 0 && (args.format === undefined || ['json', 'tsv', 'xml', 'trig'].includes(args.format)) ); }; const isValidEnsemblArgs = ( args: any ): args is { ensemblId: string; format?: string } => { return ( typeof args === 'object' && args !== null && typeof args.ensemblId === 'string' && args.ensemblId.length > 0 && (args.format === undefined || ['json', 'tsv', 'xml', 'trig'].includes(args.format)) ); }; const isValidAdvancedSearchArgs = ( args: any ): args is { query?: string; tissueSpecific?: string; subcellularLocation?: string; cancerPrognostic?: string; proteinClass?: string; chromosome?: string; antibodyReliability?: string; format?: string; columns?: string[]; maxResults?: number; } => { return ( typeof args === 'object' && args !== null && (args.query === undefined || typeof args.query === 'string') && (args.tissueSpecific === undefined || typeof args.tissueSpecific === 'string') && (args.subcellularLocation === undefined || typeof args.subcellularLocation === 'string') && (args.cancerPrognostic === undefined || typeof args.cancerPrognostic === 'string') && (args.proteinClass === undefined || typeof args.proteinClass === 'string') && (args.chromosome === undefined || typeof args.chromosome === 'string') && (args.antibodyReliability === undefined || ['approved', 'enhanced', 'supported', 'uncertain'].includes(args.antibodyReliability)) && (args.format === undefined || ['json', 'tsv'].includes(args.format)) && (args.columns === undefined || Array.isArray(args.columns)) && (args.maxResults === undefined || (typeof args.maxResults === 'number' && args.maxResults > 0 && args.maxResults <= 10000)) ); }; const isValidBatchArgs = ( args: any ): args is { genes: string[]; format?: string; columns?: string[] } => { return ( typeof args === 'object' && args !== null && Array.isArray(args.genes) && args.genes.length > 0 && args.genes.length <= 100 && args.genes.every((gene: any) => typeof gene === 'string' && gene.length > 0) && (args.format === undefined || ['json', 'tsv'].includes(args.format)) && (args.columns === undefined || Array.isArray(args.columns)) ); }; const isValidTissueSearchArgs = ( args: any ): args is { tissue: string; expressionLevel?: string; format?: string; maxResults?: number } => { return ( typeof args === 'object' && args !== null && typeof args.tissue === 'string' && args.tissue.length > 0 && (args.expressionLevel === undefined || ['high', 'medium', 'low', 'not detected'].includes(args.expressionLevel)) && (args.format === undefined || ['json', 'tsv'].includes(args.format)) && (args.maxResults === undefined || (typeof args.maxResults === 'number' && args.maxResults > 0 && args.maxResults <= 10000)) ); }; const isValidSubcellularSearchArgs = ( args: any ): args is { location: string; reliability?: string; format?: string; maxResults?: number } => { return ( typeof args === 'object' && args !== null && typeof args.location === 'string' && args.location.length > 0 && (args.reliability === undefined || ['approved', 'enhanced', 'supported', 'uncertain'].includes(args.reliability)) && (args.format === undefined || ['json', 'tsv'].includes(args.format)) && (args.maxResults === undefined || (typeof args.maxResults === 'number' && args.maxResults > 0 && args.maxResults <= 10000)) ); }; const isValidPathologySearchArgs = ( args: any ): args is { cancer?: string; prognostic?: string; format?: string; maxResults?: number } => { return ( typeof args === 'object' && args !== null && (args.cancer === undefined || typeof args.cancer === 'string') && (args.prognostic === undefined || ['favorable', 'unfavorable'].includes(args.prognostic)) && (args.format === undefined || ['json', 'tsv'].includes(args.format)) && (args.maxResults === undefined || (typeof args.maxResults === 'number' && args.maxResults > 0 && args.maxResults <= 10000)) ); }; class ProteinAtlasServer { private server: Server; private apiClient: AxiosInstance; constructor() { this.server = new Server( { name: 'proteinatlas-server', version: '0.1.0', }, { capabilities: { resources: {}, tools: {}, }, } ); // Initialize Human Protein Atlas API client this.apiClient = axios.create({ baseURL: 'https://www.proteinatlas.org', timeout: 30000, headers: { 'User-Agent': 'ProteinAtlas-MCP-Server/0.1.0', }, }); 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: 'hpa://protein/{gene}', name: 'Human Protein Atlas protein entry', mimeType: 'application/json', description: 'Complete protein atlas data for a gene symbol', }, { uriTemplate: 'hpa://ensembl/{ensemblId}', name: 'Human Protein Atlas Ensembl entry', mimeType: 'application/json', description: 'Complete protein atlas data for an Ensembl gene ID', }, { uriTemplate: 'hpa://tissue/{gene}', name: 'Tissue expression data', mimeType: 'application/json', description: 'Tissue-specific expression data for a gene', }, { uriTemplate: 'hpa://subcellular/{gene}', name: 'Subcellular localization data', mimeType: 'application/json', description: 'Subcellular localization information for a gene', }, { uriTemplate: 'hpa://pathology/{gene}', name: 'Pathology data', mimeType: 'application/json', description: 'Cancer and pathology data for a gene', }, { uriTemplate: 'hpa://blood/{gene}', name: 'Blood expression data', mimeType: 'application/json', description: 'Blood cell expression data for a gene', }, { uriTemplate: 'hpa://brain/{gene}', name: 'Brain expression data', mimeType: 'application/json', description: 'Brain region expression data for a gene', }, { uriTemplate: 'hpa://antibody/{gene}', name: 'Antibody information', mimeType: 'application/json', description: 'Antibody validation and staining information for a gene', }, { uriTemplate: 'hpa://search/{query}', name: 'Search results', mimeType: 'application/json', description: 'Search results for proteins matching the query', }, ], }) ); // Handle resource requests this.server.setRequestHandler( ReadResourceRequestSchema, async (request) => { const uri = request.params.uri; try { // Handle protein by gene symbol requests const proteinMatch = uri.match(/^hpa:\/\/protein\/([^/]+)$/); if (proteinMatch) { const gene = decodeURIComponent(proteinMatch[1]); const data = await this.fetchProteinData(gene); return { contents: [ { uri: request.params.uri, mimeType: 'application/json', text: JSON.stringify(data, null, 2), }, ], }; } // Handle protein by Ensembl ID requests const ensemblMatch = uri.match(/^hpa:\/\/ensembl\/([^/]+)$/); if (ensemblMatch) { const ensemblId = decodeURIComponent(ensemblMatch[1]); const data = await this.fetchProteinDataByEnsembl(ensemblId); return { contents: [ { uri: request.params.uri, mimeType: 'application/json', text: JSON.stringify(data, null, 2), }, ], }; } // Handle tissue expression requests const tissueMatch = uri.match(/^hpa:\/\/tissue\/([^/]+)$/); if (tissueMatch) { const gene = decodeURIComponent(tissueMatch[1]); const data = await this.fetchTissueExpression(gene); return { contents: [ { uri: request.params.uri, mimeType: 'application/json', text: JSON.stringify(data, null, 2), }, ], }; } // Handle subcellular localization requests const subcellularMatch = uri.match(/^hpa:\/\/subcellular\/([^/]+)$/); if (subcellularMatch) { const gene = decodeURIComponent(subcellularMatch[1]); const data = await this.fetchSubcellularLocalization(gene); return { contents: [ { uri: request.params.uri, mimeType: 'application/json', text: JSON.stringify(data, null, 2), }, ], }; } // Handle pathology requests const pathologyMatch = uri.match(/^hpa:\/\/pathology\/([^/]+)$/); if (pathologyMatch) { const gene = decodeURIComponent(pathologyMatch[1]); const data = await this.fetchPathologyData(gene); return { contents: [ { uri: request.params.uri, mimeType: 'application/json', text: JSON.stringify(data, null, 2), }, ], }; } // Handle blood expression requests const bloodMatch = uri.match(/^hpa:\/\/blood\/([^/]+)$/); if (bloodMatch) { const gene = decodeURIComponent(bloodMatch[1]); const data = await this.fetchBloodExpression(gene); return { contents: [ { uri: request.params.uri, mimeType: 'application/json', text: JSON.stringify(data, null, 2), }, ], }; } // Handle brain expression requests const brainMatch = uri.match(/^hpa:\/\/brain\/([^/]+)$/); if (brainMatch) { const gene = decodeURIComponent(brainMatch[1]); const data = await this.fetchBrainExpression(gene); return { contents: [ { uri: request.params.uri, mimeType: 'application/json', text: JSON.stringify(data, null, 2), }, ], }; } // Handle antibody information requests const antibodyMatch = uri.match(/^hpa:\/\/antibody\/([^/]+)$/); if (antibodyMatch) { const gene = decodeURIComponent(antibodyMatch[1]); const data = await this.fetchAntibodyInfo(gene); return { contents: [ { uri: request.params.uri, mimeType: 'application/json', text: JSON.stringify(data, null, 2), }, ], }; } // Handle search requests const searchMatch = uri.match(/^hpa:\/\/search\/(.+)$/); if (searchMatch) { const query = decodeURIComponent(searchMatch[1]); const data = await this.searchProteins(query); return { contents: [ { uri: request.params.uri, mimeType: 'application/json', text: JSON.stringify(data, null, 2), }, ], }; } throw new McpError( ErrorCode.InvalidRequest, `Invalid URI format: ${uri}` ); } catch (error) { throw new McpError( ErrorCode.InternalError, `Failed to fetch resource: ${error instanceof Error ? error.message : 'Unknown error'}` ); } } ); } private setupToolHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ // Basic search and retrieval tools { name: 'search_proteins', description: 'Search Human Protein Atlas for proteins by name, gene symbol, or description', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query (gene name, protein name, or keyword)' }, format: { type: 'string', enum: ['json', 'tsv'], description: 'Output format (default: json)' }, columns: { type: 'array', items: { type: 'string' }, description: 'Specific columns to include in results' }, maxResults: { type: 'number', description: 'Maximum number of results (1-10000, default: 100)', minimum: 1, maximum: 10000 }, compress: { type: 'boolean', description: 'Whether to compress the response (default: false)' }, }, required: ['query'], }, }, { name: 'get_protein_info', description: 'Get detailed information for a specific protein by gene symbol', inputSchema: { type: 'object', properties: { gene: { type: 'string', description: 'Gene symbol (e.g., BRCA1, TP53)' }, format: { type: 'string', enum: ['json', 'tsv', 'xml', 'trig'], description: 'Output format (default: json)' }, }, required: ['gene'], }, }, { name: 'get_protein_by_ensembl', description: 'Get protein information using Ensembl gene ID', inputSchema: { type: 'object', properties: { ensemblId: { type: 'string', description: 'Ensembl gene ID (e.g., ENSG00000139618)' }, format: { type: 'string', enum: ['json', 'tsv', 'xml', 'trig'], description: 'Output format (default: json)' }, }, required: ['ensemblId'], }, }, // Tissue and expression analysis tools { name: 'get_tissue_expression', description: 'Get tissue-specific expression data for a protein', inputSchema: { type: 'object', properties: { gene: { type: 'string', description: 'Gene symbol' }, format: { type: 'string', enum: ['json', 'tsv'], description: 'Output format (default: json)' }, }, required: ['gene'], }, }, { name: 'search_by_tissue', description: 'Find proteins highly expressed in specific tissues', inputSchema: { type: 'object', properties: { tissue: { type: 'string', description: 'Tissue name (e.g., liver, brain, heart)' }, expressionLevel: { type: 'string', enum: ['high', 'medium', 'low', 'not detected'], description: 'Expression level filter' }, format: { type: 'string', enum: ['json', 'tsv'], description: 'Output format (default: json)' }, maxResults: { type: 'number', description: 'Maximum number of results (1-10000, default: 100)', minimum: 1, maximum: 10000 }, }, required: ['tissue'], }, }, { name: 'get_blood_expression', description: 'Get blood cell expression data for a protein', inputSchema: { type: 'object', properties: { gene: { type: 'string', description: 'Gene symbol' }, format: { type: 'string', enum: ['json', 'tsv'], description: 'Output format (default: json)' }, }, required: ['gene'], }, }, { name: 'get_brain_expression', description: 'Get brain region expression data for a protein', inputSchema: { type: 'object', properties: { gene: { type: 'string', description: 'Gene symbol' }, format: { type: 'string', enum: ['json', 'tsv'], description: 'Output format (default: json)' }, }, required: ['gene'], }, }, // Subcellular localization tools { name: 'get_subcellular_location', description: 'Get subcellular localization data for a protein', inputSchema: { type: 'object', properties: { gene: { type: 'string', description: 'Gene symbol' }, format: { type: 'string', enum: ['json', 'tsv'], description: 'Output format (default: json)' }, }, required: ['gene'], }, }, { name: 'search_by_subcellular_location', description: 'Find proteins localized to specific subcellular compartments', inputSchema: { type: 'object', properties: { location: { type: 'string', description: 'Subcellular location (e.g., nucleus, mitochondria, cytosol)' }, reliability: { type: 'string', enum: ['approved', 'enhanced', 'supported', 'uncertain'], description: 'Reliability filter' }, format: { type: 'string', enum: ['json', 'tsv'], description: 'Output format (default: json)' }, maxResults: { type: 'number', description: 'Maximum number of results (1-10000, default: 100)', minimum: 1, maximum: 10000 }, }, required: ['location'], }, }, // Pathology and cancer tools { name: 'get_pathology_data', description: 'Get cancer and pathology data for a protein', inputSchema: { type: 'object', properties: { gene: { type: 'string', description: 'Gene symbol' }, format: { type: 'string', enum: ['json', 'tsv'], description: 'Output format (default: json)' }, }, required: ['gene'], }, }, { name: 'search_cancer_markers', description: 'Find proteins associated with specific cancers or with prognostic value', inputSchema: { type: 'object', properties: { cancer: { type: 'string', description: 'Cancer type (e.g., breast cancer, lung cancer)' }, prognostic: { type: 'string', enum: ['favorable', 'unfavorable'], description: 'Prognostic filter' }, format: { type: 'string', enum: ['json', 'tsv'], description: 'Output format (default: json)' }, maxResults: { type: 'number', description: 'Maximum number of results (1-10000, default: 100)', minimum: 1, maximum: 10000 }, }, required: [], }, }, // Antibody and validation tools { name: 'get_antibody_info', description: 'Get antibody validation and staining information for a protein', inputSchema: { type: 'object', properties: { gene: { type: 'string', description: 'Gene symbol' }, format: { type: 'string', enum: ['json', 'tsv'], description: 'Output format (default: json)' }, }, required: ['gene'], }, }, // Advanced search and batch processing { name: 'advanced_search', description: 'Perform advanced search with multiple filters and criteria', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Base search query' }, tissueSpecific: { type: 'string', description: 'Tissue-specific expression filter' }, subcellularLocation: { type: 'string', description: 'Subcellular localization filter' }, cancerPrognostic: { type: 'string', description: 'Cancer prognostic filter' }, proteinClass: { type: 'string', description: 'Protein class filter' }, chromosome: { type: 'string', description: 'Chromosome filter' }, antibodyReliability: { type: 'string', enum: ['approved', 'enhanced', 'supported', 'uncertain'], description: 'Antibody reliability filter' }, format: { type: 'string', enum: ['json', 'tsv'], description: 'Output format (default: json)' }, columns: { type: 'array', items: { type: 'string' }, description: 'Specific columns to include in results' }, maxResults: { type: 'number', description: 'Maximum number of results (1-10000, default: 100)', minimum: 1, maximum: 10000 }, }, required: [], }, }, { name: 'batch_protein_lookup', description: 'Look up multiple proteins simultaneously', inputSchema: { type: 'object', properties: { genes: { type: 'array', items: { type: 'string' }, description: 'Array of gene symbols (max 100)', minItems: 1, maxItems: 100 }, format: { type: 'string', enum: ['json', 'tsv'], description: 'Output format (default: json)' }, columns: { type: 'array', items: { type: 'string' }, description: 'Specific columns to include in results' }, }, required: ['genes'], }, }, // Analysis and comparison tools { name: 'compare_expression_profiles', description: 'Compare expression profiles between multiple proteins', inputSchema: { type: 'object', properties: { genes: { type: 'array', items: { type: 'string' }, description: 'Array of gene symbols to compare (2-10)', minItems: 2, maxItems: 10 }, expressionType: { type: 'string', enum: ['tissue', 'brain', 'blood', 'single_cell'], description: 'Type of expression data to compare (default: tissue)' }, format: { type: 'string', enum: ['json', 'tsv'], description: 'Output format (default: json)' }, }, required: ['genes'], }, }, { name: 'get_protein_classes', description: 'Get protein classification and functional annotation data', inputSchema: { type: 'object', properties: { gene: { type: 'string', description: 'Gene symbol' }, format: { type: 'string', enum: ['json', 'tsv'], description: 'Output format (default: json)' }, }, required: ['gene'], }, }, ], })); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; switch (name) { // Basic search and retrieval tools case 'search_proteins': return this.handleSearchProteins(args); case 'get_protein_info': return this.handleGetProteinInfo(args); case 'get_protein_by_ensembl': return this.handleGetProteinByEnsembl(args); // Tissue and expression analysis tools case 'get_tissue_expression': return this.handleGetTissueExpression(args); case 'search_by_tissue': return this.handleSearchByTissue(args); case 'get_blood_expression': return this.handleGetBloodExpression(args); case 'get_brain_expression': return this.handleGetBrainExpression(args); // Subcellular localization tools case 'get_subcellular_location': return this.handleGetSubcellularLocation(args); case 'search_by_subcellular_location': return this.handleSearchBySubcellularLocation(args); // Pathology and cancer tools case 'get_pathology_data': return this.handleGetPathologyData(args); case 'search_cancer_markers': return this.handleSearchCancerMarkers(args); // Antibody and validation tools case 'get_antibody_info': return this.handleGetAntibodyInfo(args); // Advanced search and batch processing case 'advanced_search': return this.handleAdvancedSearch(args); case 'batch_protein_lookup': return this.handleBatchProteinLookup(args); // Analysis and comparison tools case 'compare_expression_profiles': return this.handleCompareExpressionProfiles(args); case 'get_protein_classes': return this.handleGetProteinClasses(args); default: throw new McpError( ErrorCode.MethodNotFound, `Unknown tool: ${name}` ); } }); } // Helper methods for API interactions private async searchProteins(query: string, format: string = 'json', columns?: string[], maxResults?: number): Promise<any> { // Default columns if none provided - basic protein information const defaultColumns = ['g', 'gs', 'eg', 'gd', 'up', 'chr', 'pc', 'pe']; const searchColumns = columns && columns.length > 0 ? columns : defaultColumns; const params: any = { search: query, format: format, columns: searchColumns.join(','), compress: 'no', }; const response = await this.apiClient.get('/api/search_download.php', { params }); if (format === 'json') { return this.parseResponse(response.data, format); } return response.data; } private async fetchProteinData(gene: string, format: string = 'json'): Promise<any> { // Use searchProteins method which properly handles columns parameter return this.searchProteins(gene, format, undefined, 1); } private async fetchProteinDataByEnsembl(ensemblId: string, format: string = 'json'): Promise<any> { const response = await this.apiClient.get(`/${ensemblId}.${format}`); return this.parseResponse(response.data, format); } private async fetchTissueExpression(gene: string): Promise<any> { const columns = ['g', 'eg', 'rnats', 'rnatd', 'rnatss', 't_RNA_adipose_tissue', 't_RNA_adrenal_gland', 't_RNA_brain', 't_RNA_breast', 't_RNA_colon', 't_RNA_heart_muscle', 't_RNA_kidney', 't_RNA_liver', 't_RNA_lung', 't_RNA_ovary', 't_RNA_pancreas', 't_RNA_prostate', 't_RNA_skeletal_muscle', 't_RNA_skin_1', 't_RNA_spleen', 't_RNA_stomach_1', 't_RNA_testis', 't_RNA_thyroid_gland']; return this.searchProteins(gene, 'json', columns, 1); } private async fetchSubcellularLocalization(gene: string): Promise<any> { const columns = ['g', 'eg', 'scl', 'scml', 'scal', 'relce']; return this.searchProteins(gene, 'json', columns, 1); } private async fetchPathologyData(gene: string): Promise<any> { const columns = ['g', 'eg', 'prognostic_Breast_Invasive_Carcinoma_(TCGA)', 'prognostic_Colon_Adenocarcinoma_(TCGA)', 'prognostic_Lung_Adenocarcinoma_(TCGA)', 'prognostic_Prostate_Adenocarcinoma_(TCGA)']; return this.searchProteins(gene, 'json', columns, 1); } private async fetchBloodExpression(gene: string): Promise<any> { const columns = ['g', 'eg', 'rnabcs', 'rnabcd', 'rnabcss', 'blood_RNA_basophil', 'blood_RNA_classical_monocyte', 'blood_RNA_eosinophil', 'blood_RNA_neutrophil', 'blood_RNA_NK-cell']; return this.searchProteins(gene, 'json', columns, 1); } private async fetchBrainExpression(gene: string): Promise<any> { const columns = ['g', 'eg', 'rnabrs', 'rnabrd', 'rnabrss', 'brain_RNA_amygdala', 'brain_RNA_cerebellum', 'brain_RNA_cerebral_cortex', 'brain_RNA_hippocampal_formation', 'brain_RNA_hypothalamus']; return this.searchProteins(gene, 'json', columns, 1); } private async fetchAntibodyInfo(gene: string): Promise<any> { const columns = ['g', 'eg', 'ab', 'abrr', 'relih', 'relmb', 'relce']; return this.searchProteins(gene, 'json', columns, 1); } private parseResponse(data: any, format: string): any { if (format === 'json') { if (typeof data === 'string') { try { return JSON.parse(data); } catch { // If it's TSV data, convert to JSON-like structure const lines = data.split('\n'); if (lines.length > 1) { const headers = lines[0].split('\t'); const results = []; for (let i = 1; i < lines.length; i++) { if (lines[i].trim()) { const values = lines[i].split('\t'); const row: any = {}; headers.forEach((header, index) => { row[header] = values[index] || ''; }); results.push(row); } } return results; } } } return data; } return data; } // Tool handler methods private async handleSearchProteins(args: any) { if (!isValidSearchArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid search arguments'); } try { const result = await this.searchProteins(args.query, args.format || 'json', args.columns, args.maxResults); return { content: [ { type: 'text', text: typeof result === 'object' ? JSON.stringify(result, null, 2) : String(result), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error searching proteins: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } private async handleGetProteinInfo(args: any) { if (!isValidGeneArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid gene arguments'); } try { const result = await this.fetchProteinData(args.gene, args.format || 'json'); return { content: [ { type: 'text', text: typeof result === 'object' ? JSON.stringify(result, null, 2) : String(result), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error fetching protein info: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } private async handleGetProteinByEnsembl(args: any) { if (!isValidEnsemblArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid Ensembl arguments'); } try { const result = await this.fetchProteinDataByEnsembl(args.ensemblId, args.format || 'json'); return { content: [ { type: 'text', text: typeof result === 'object' ? JSON.stringify(result, null, 2) : String(result), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error fetching protein by Ensembl ID: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } private async handleGetTissueExpression(args: any) { if (!isValidGeneArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid gene arguments'); } try { const result = await this.fetchTissueExpression(args.gene); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error fetching tissue expression: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } private async handleSearchByTissue(args: any) { if (!isValidTissueSearchArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid tissue search arguments'); } try { let searchQuery = `tissue:"${args.tissue}"`; if (args.expressionLevel) { searchQuery += ` AND expression:"${args.expressionLevel}"`; } const result = await this.searchProteins(searchQuery, args.format || 'json', undefined, args.maxResults); return { content: [ { type: 'text', text: typeof result === 'object' ? JSON.stringify(result, null, 2) : String(result), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error searching by tissue: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } private async handleGetBloodExpression(args: any) { if (!isValidGeneArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid gene arguments'); } try { const result = await this.fetchBloodExpression(args.gene); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error fetching blood expression: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } private async handleGetBrainExpression(args: any) { if (!isValidGeneArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid gene arguments'); } try { const result = await this.fetchBrainExpression(args.gene); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error fetching brain expression: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } private async handleGetSubcellularLocation(args: any) { if (!isValidGeneArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid gene arguments'); } try { const result = await this.fetchSubcellularLocalization(args.gene); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error fetching subcellular location: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } private async handleSearchBySubcellularLocation(args: any) { if (!isValidSubcellularSearchArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid subcellular search arguments'); } try { let searchQuery = `location:"${args.location}"`; if (args.reliability) { searchQuery += ` AND reliability:"${args.reliability}"`; } const result = await this.searchProteins(searchQuery, args.format || 'json', undefined, args.maxResults); return { content: [ { type: 'text', text: typeof result === 'object' ? JSON.stringify(result, null, 2) : String(result), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error searching by subcellular location: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } private async handleGetPathologyData(args: any) { if (!isValidGeneArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid gene arguments'); } try { const result = await this.fetchPathologyData(args.gene); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error fetching pathology data: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } private async handleSearchCancerMarkers(args: any) { if (!isValidPathologySearchArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid pathology search arguments'); } try { let searchQuery = ''; if (args.cancer) { searchQuery = `cancer:"${args.cancer}"`; } if (args.prognostic) { searchQuery += searchQuery ? ` AND prognostic:"${args.prognostic}"` : `prognostic:"${args.prognostic}"`; } if (!searchQuery) { searchQuery = 'prognostic:*'; // Search for any prognostic markers } const result = await this.searchProteins(searchQuery, args.format || 'json', undefined, args.maxResults); return { content: [ { type: 'text', text: typeof result === 'object' ? JSON.stringify(result, null, 2) : String(result), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error searching cancer markers: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } private async handleGetAntibodyInfo(args: any) { if (!isValidGeneArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid gene arguments'); } try { const result = await this.fetchAntibodyInfo(args.gene); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error fetching antibody info: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } private async handleAdvancedSearch(args: any) { if (!isValidAdvancedSearchArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid advanced search arguments'); } try { let searchQuery = args.query || ''; if (args.tissueSpecific) { searchQuery += (searchQuery ? ' AND ' : '') + `tissue:"${args.tissueSpecific}"`; } if (args.subcellularLocation) { searchQuery += (searchQuery ? ' AND ' : '') + `location:"${args.subcellularLocation}"`; } if (args.cancerPrognostic) { searchQuery += (searchQuery ? ' AND ' : '') + `prognostic:"${args.cancerPrognostic}"`; } if (args.proteinClass) { searchQuery += (searchQuery ? ' AND ' : '') + `class:"${args.proteinClass}"`; } if (args.chromosome) { searchQuery += (searchQuery ? ' AND ' : '') + `chromosome:"${args.chromosome}"`; } if (args.antibodyReliability) { searchQuery += (searchQuery ? ' AND ' : '') + `reliability:"${args.antibodyReliability}"`; } if (!searchQuery) { searchQuery = '*'; // Search for everything if no criteria specified } const result = await this.searchProteins(searchQuery, args.format || 'json', args.columns, args.maxResults); return { content: [ { type: 'text', text: typeof result === 'object' ? JSON.stringify(result, null, 2) : String(result), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error in advanced search: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } private async handleBatchProteinLookup(args: any) { if (!isValidBatchArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid batch lookup arguments'); } try { const results = await Promise.all( args.genes.map(async (gene: string) => { try { const data = await this.fetchProteinData(gene, args.format || 'json'); return { gene, data, success: true }; } catch (error) { return { gene, error: error instanceof Error ? error.message : 'Unknown error', success: false }; } }) ); return { content: [ { type: 'text', text: JSON.stringify({ batchResults: results }, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error in batch lookup: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } private async handleCompareExpressionProfiles(args: any) { if (!isValidBatchArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid comparison arguments'); } try { const expressionType = (args as any).expressionType || 'tissue'; const comparisons = []; for (const gene of args.genes) { let expressionData; switch (expressionType) { case 'tissue': expressionData = await this.fetchTissueExpression(gene); break; case 'brain': expressionData = await this.fetchBrainExpression(gene); break; case 'blood': expressionData = await this.fetchBloodExpression(gene); break; default: expressionData = await this.fetchTissueExpression(gene); } comparisons.push({ gene, expressionData }); } return { content: [ { type: 'text', text: JSON.stringify({ expressionComparison: comparisons }, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error comparing expression profiles: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } private async handleGetProteinClasses(args: any) { if (!isValidGeneArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid gene arguments'); } try { const columns = ['g', 'eg', 'pc', 'upbp', 'up_mf', 'pe']; const result = await this.searchProteins(args.gene, args.format || 'json', columns, 1); return { content: [ { type: 'text', text: typeof result === 'object' ? JSON.stringify(result, null, 2) : String(result), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error fetching protein classes: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Human Protein Atlas MCP server running on stdio'); } } const server = new ProteinAtlasServer(); server.run().catch(console.error);

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Augmented-Nature/ProteinAtlas-MCP-Server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server