Skip to main content
Glama
index.ts25.4 kB
#!/usr/bin/env node /** * GTEx Portal MCP Server * * Provides comprehensive access to GTEx (Genotype-Tissue Expression) genomics data * through 25 specialized tools across three categories: * - Expression Analysis: Gene expression patterns and tissue specificity * - Association Analysis: eQTL/sQTL analysis and genetic associations * - Reference/Dataset: Gene/variant lookups and metadata */ import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; // Import our handler classes import { ExpressionHandlers } from "./handlers/expression-handlers.js"; import { AssociationHandlers } from "./handlers/association-handlers.js"; import { ReferenceHandlers } from "./handlers/reference-handlers.js"; import { GTExApiClient } from "./utils/api-client.js"; /** * Create an MCP server for GTEx Portal API access */ const server = new Server( { name: "gtex-server", version: "1.0.0", }, { capabilities: { tools: {}, }, } ); // Initialize handlers (they create their own API clients internally) const expressionHandlers = new ExpressionHandlers(); const associationHandlers = new AssociationHandlers(); const referenceHandlers = new ReferenceHandlers(); /** * Handler that lists all available GTEx tools */ server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ // Expression Analysis Tools (7 tools) { name: "get_gene_expression", description: "Get gene expression data across tissues for a specific gene", inputSchema: { type: "object", properties: { gencodeId: { type: "string", description: "GENCODE gene ID (e.g., ENSG00000223972.5)" }, datasetId: { type: "string", description: "GTEx dataset ID (default: gtex_v8)", default: "gtex_v8" } }, required: ["gencodeId"] } }, { name: "get_median_gene_expression", description: "Get median gene expression levels across tissues", inputSchema: { type: "object", properties: { gencodeId: { type: "string", description: "GENCODE gene ID (e.g., ENSG00000223972.5)" }, datasetId: { type: "string", description: "GTEx dataset ID (default: gtex_v8)", default: "gtex_v8" } }, required: ["gencodeId"] } }, { name: "get_top_expressed_genes", description: "Get top expressed genes in a specific tissue", inputSchema: { type: "object", properties: { tissueSiteDetailId: { type: "string", description: "Tissue site detail ID (e.g., Muscle_Skeletal, Brain_Cortex)" }, filterMtGenes: { type: "boolean", description: "Filter out mitochondrial genes (default: true)", default: true }, sortBy: { type: "string", description: "Sort criteria (default: median)", enum: ["median", "mean"], default: "median" }, sortDirection: { type: "string", description: "Sort direction (default: desc)", enum: ["asc", "desc"], default: "desc" }, datasetId: { type: "string", description: "GTEx dataset ID (default: gtex_v8)", default: "gtex_v8" } }, required: ["tissueSiteDetailId"] } }, { name: "get_tissue_specific_genes", description: "Get genes with tissue-specific expression patterns", inputSchema: { type: "object", properties: { tissueSiteDetailId: { type: "string", description: "Tissue site detail ID (e.g., Muscle_Skeletal, Brain_Cortex)" }, selectionCriteria: { type: "string", description: "Selection criteria for tissue specificity (default: highestInGroup)", enum: ["highestInGroup", "aboveThreshold"], default: "highestInGroup" }, datasetId: { type: "string", description: "GTEx dataset ID (default: gtex_v8)", default: "gtex_v8" } }, required: ["tissueSiteDetailId"] } }, { name: "get_clustered_expression", description: "Get clustered gene expression data for visualization", inputSchema: { type: "object", properties: { gencodeIds: { type: "array", items: { type: "string" }, description: "Array of GENCODE gene IDs" }, datasetId: { type: "string", description: "GTEx dataset ID (default: gtex_v8)", default: "gtex_v8" } }, required: ["gencodeIds"] } }, { name: "calculate_expression_correlation", description: "Calculate expression correlation between genes across tissues", inputSchema: { type: "object", properties: { gencodeIds: { type: "array", items: { type: "string" }, description: "Array of GENCODE gene IDs to compare" }, datasetId: { type: "string", description: "GTEx dataset ID (default: gtex_v8)", default: "gtex_v8" } }, required: ["gencodeIds"] } }, { name: "get_differential_expression", description: "Get differential gene expression between tissue groups", inputSchema: { type: "object", properties: { gencodeId: { type: "string", description: "GENCODE gene ID (e.g., ENSG00000223972.5)" }, comparisonGroups: { type: "array", items: { type: "string" }, description: "Array of tissue groups to compare" }, datasetId: { type: "string", description: "GTEx dataset ID (default: gtex_v8)", default: "gtex_v8" } }, required: ["gencodeId", "comparisonGroups"] } }, // Association Analysis Tools (6 tools) { name: "get_eqtl_genes", description: "Get genes with eQTL associations for a genomic region", inputSchema: { type: "object", properties: { chr: { type: "string", description: "Chromosome (e.g., chr1, chr2, chrX)" }, start: { type: "integer", description: "Start position (1-based)" }, end: { type: "integer", description: "End position (1-based)" }, tissueSiteDetailId: { type: "string", description: "Tissue site detail ID (optional, for tissue-specific results)" }, datasetId: { type: "string", description: "GTEx dataset ID (default: gtex_v8)", default: "gtex_v8" } }, required: ["chr", "start", "end"] } }, { name: "get_single_tissue_eqtls", description: "Get single-tissue eQTL results for a gene", inputSchema: { type: "object", properties: { gencodeId: { type: "string", description: "GENCODE gene ID (e.g., ENSG00000223972.5)" }, tissueSiteDetailId: { type: "string", description: "Tissue site detail ID (e.g., Muscle_Skeletal, Brain_Cortex)" }, datasetId: { type: "string", description: "GTEx dataset ID (default: gtex_v8)", default: "gtex_v8" } }, required: ["gencodeId", "tissueSiteDetailId"] } }, { name: "calculate_dynamic_eqtl", description: "Calculate dynamic eQTL effects across tissues", inputSchema: { type: "object", properties: { gencodeId: { type: "string", description: "GENCODE gene ID (e.g., ENSG00000223972.5)" }, snpId: { type: "string", description: "SNP ID (rs number or variant ID)" }, tissueSiteDetailIds: { type: "array", items: { type: "string" }, description: "Array of tissue site detail IDs to compare" }, datasetId: { type: "string", description: "GTEx dataset ID (default: gtex_v8)", default: "gtex_v8" } }, required: ["gencodeId", "snpId", "tissueSiteDetailIds"] } }, { name: "get_multi_tissue_eqtls", description: "Get multi-tissue eQTL meta-analysis results", inputSchema: { type: "object", properties: { gencodeId: { type: "string", description: "GENCODE gene ID (e.g., ENSG00000223972.5)" }, datasetId: { type: "string", description: "GTEx dataset ID (default: gtex_v8)", default: "gtex_v8" } }, required: ["gencodeId"] } }, { name: "get_sqtl_results", description: "Get splicing QTL (sQTL) results for a gene", inputSchema: { type: "object", properties: { gencodeId: { type: "string", description: "GENCODE gene ID (e.g., ENSG00000223972.5)" }, tissueSiteDetailId: { type: "string", description: "Tissue site detail ID (optional, for tissue-specific results)" }, datasetId: { type: "string", description: "GTEx dataset ID (default: gtex_v8)", default: "gtex_v8" } }, required: ["gencodeId"] } }, { name: "analyze_ld_structure", description: "Analyze linkage disequilibrium structure around eQTL variants", inputSchema: { type: "object", properties: { chr: { type: "string", description: "Chromosome (e.g., chr1, chr2, chrX)" }, position: { type: "integer", description: "Genomic position (1-based)" }, windowSize: { type: "integer", description: "Window size around position (default: 100000)", default: 100000 }, population: { type: "string", description: "Population for LD analysis (default: EUR)", enum: ["EUR", "AFR", "AMR", "EAS", "SAS"], default: "EUR" } }, required: ["chr", "position"] } }, // Reference/Dataset Tools (12 tools) { name: "search_genes", description: "Search for genes by symbol, name, or description", inputSchema: { type: "object", properties: { query: { type: "string", description: "Search query (gene symbol, name, or description)" }, species: { type: "string", description: "Species (default: human)", enum: ["human", "mouse"], default: "human" }, page: { type: "integer", description: "Page number for pagination (default: 0)", default: 0 }, pageSize: { type: "integer", description: "Number of results per page (default: 250)", default: 250 } }, required: ["query"] } }, { name: "get_gene_info", description: "Get detailed information about a specific gene", inputSchema: { type: "object", properties: { gencodeId: { type: "string", description: "GENCODE gene ID (e.g., ENSG00000223972.5)" }, geneSymbol: { type: "string", description: "Gene symbol (alternative to gencodeId)" } } } }, { name: "get_variants", description: "Get genetic variants in a genomic region", inputSchema: { type: "object", properties: { chr: { type: "string", description: "Chromosome (e.g., chr1, chr2, chrX)" }, start: { type: "integer", description: "Start position (1-based)" }, end: { type: "integer", description: "End position (1-based)" }, datasetId: { type: "string", description: "GTEx dataset ID (default: gtex_v8)", default: "gtex_v8" } }, required: ["chr", "start", "end"] } }, { name: "get_tissue_info", description: "Get information about GTEx tissues and sample counts", inputSchema: { type: "object", properties: { datasetId: { type: "string", description: "GTEx dataset ID (default: gtex_v8)", default: "gtex_v8" } } } }, { name: "get_sample_info", description: "Get GTEx sample metadata and demographics", inputSchema: { type: "object", properties: { tissueSiteDetailId: { type: "string", description: "Tissue site detail ID (optional, for tissue-specific samples)" }, datasetId: { type: "string", description: "GTEx dataset ID (default: gtex_v8)", default: "gtex_v8" } } } }, { name: "get_subject_phenotypes", description: "Get subject phenotype data and demographics", inputSchema: { type: "object", properties: { subjectId: { type: "string", description: "GTEx subject ID (optional, for specific subject)" }, datasetId: { type: "string", description: "GTEx dataset ID (default: gtex_v8)", default: "gtex_v8" } } } }, { name: "validate_gene_id", description: "Validate and normalize gene identifiers", inputSchema: { type: "object", properties: { geneId: { type: "string", description: "Gene ID to validate (GENCODE ID or gene symbol)" } }, required: ["geneId"] } }, { name: "validate_variant_id", description: "Validate variant identifiers and genomic coordinates", inputSchema: { type: "object", properties: { variantId: { type: "string", description: "Variant ID to validate (rs number or variant ID)" }, chr: { type: "string", description: "Chromosome (alternative validation method)" }, position: { type: "integer", description: "Genomic position (alternative validation method)" } } } }, { name: "get_dataset_info", description: "Get information about available GTEx datasets", inputSchema: { type: "object", properties: { datasetId: { type: "string", description: "Specific dataset ID (optional, returns all if not provided)" } } } }, { name: "search_transcripts", description: "Search for gene transcripts and isoforms", inputSchema: { type: "object", properties: { gencodeId: { type: "string", description: "GENCODE gene ID (e.g., ENSG00000223972.5)" }, transcriptType: { type: "string", description: "Transcript type filter (optional)", enum: ["protein_coding", "lncRNA", "pseudogene", "miRNA"] } }, required: ["gencodeId"] } }, { name: "get_gene_ontology", description: "Get Gene Ontology annotations for a gene", inputSchema: { type: "object", properties: { gencodeId: { type: "string", description: "GENCODE gene ID (e.g., ENSG00000223972.5)" }, ontologyType: { type: "string", description: "GO ontology type (optional)", enum: ["biological_process", "cellular_component", "molecular_function"] } }, required: ["gencodeId"] } }, { name: "convert_coordinates", description: "Convert between different genomic coordinate systems", inputSchema: { type: "object", properties: { chr: { type: "string", description: "Chromosome (e.g., chr1, chr2, chrX)" }, position: { type: "integer", description: "Genomic position to convert" }, fromBuild: { type: "string", description: "Source genome build (default: hg38)", enum: ["hg19", "hg38"], default: "hg38" }, toBuild: { type: "string", description: "Target genome build (default: hg19)", enum: ["hg19", "hg38"], default: "hg19" } }, required: ["chr", "position"] } } ] }; }); /** * Handler for tool calls - routes to appropriate handler based on tool name */ server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { // Expression Analysis Tools if (name === "get_gene_expression") { return await expressionHandlers.getGeneExpression({ geneIds: args?.gencodeId ? [args.gencodeId] : [], datasetId: args?.datasetId }); } if (name === "get_median_gene_expression") { return await expressionHandlers.getMedianGeneExpression({ geneIds: args?.gencodeId ? [args.gencodeId] : [], datasetId: args?.datasetId }); } if (name === "get_top_expressed_genes") { return await expressionHandlers.getTopExpressedGenes({ tissueId: args?.tissueSiteDetailId, filterMtGene: args?.filterMtGenes, datasetId: args?.datasetId }); } if (name === "get_tissue_specific_genes") { return await expressionHandlers.getTissueSpecificGenes({ tissueId: args?.tissueSiteDetailId, selectionCriteria: args?.selectionCriteria, datasetId: args?.datasetId }); } if (name === "get_clustered_expression") { return await expressionHandlers.getClusteredExpression({ gencodeIds: args?.gencodeIds || [], datasetId: args?.datasetId }); } if (name === "calculate_expression_correlation") { return await expressionHandlers.calculateExpressionCorrelation({ geneIds: args?.gencodeIds || [], datasetId: args?.datasetId }); } if (name === "get_differential_expression") { return await expressionHandlers.getDifferentialExpression({ gencodeId: args?.gencodeId, comparisonGroups: args?.comparisonGroups || [], datasetId: args?.datasetId }); } // Association Analysis Tools if (name === "get_eqtl_genes") { return await associationHandlers.getEQTLGenes({ tissueIds: args?.tissueSiteDetailId ? [args.tissueSiteDetailId] : undefined, datasetId: args?.datasetId }); } if (name === "get_single_tissue_eqtls") { return await associationHandlers.getSingleTissueEQTLs({ geneIds: args?.gencodeId ? [args.gencodeId] : undefined, tissueIds: args?.tissueSiteDetailId ? [args.tissueSiteDetailId] : undefined, datasetId: args?.datasetId }); } if (name === "calculate_dynamic_eqtl") { return await associationHandlers.calculateDynamicEQTL({ geneId: args?.gencodeId, variantId: args?.snpId, tissueId: Array.isArray(args?.tissueSiteDetailIds) && args.tissueSiteDetailIds.length > 0 ? args.tissueSiteDetailIds[0] : undefined, datasetId: args?.datasetId }); } if (name === "get_multi_tissue_eqtls") { return await associationHandlers.getMultiTissueEQTLs({ geneId: args?.gencodeId, datasetId: args?.datasetId }); } if (name === "get_sqtl_results") { return await associationHandlers.getSQTLGenes({ tissueIds: args?.tissueSiteDetailId ? [args.tissueSiteDetailId] : undefined, datasetId: args?.datasetId }); } if (name === "analyze_ld_structure") { return await associationHandlers.analyzeLDStructure({ chr: args?.chr, position: args?.position, windowSize: args?.windowSize, population: args?.population }); } // Reference/Dataset Tools if (name === "search_genes") { return await referenceHandlers.searchGenes({ query: args?.query, page: args?.page, itemsPerPage: args?.pageSize }); } if (name === "get_gene_info") { return await referenceHandlers.getGeneInfo({ geneIds: args?.gencodeId ? [args.gencodeId] : (args?.geneSymbol ? [args.geneSymbol] : []) }); } if (name === "get_variants") { return await referenceHandlers.getVariants({ chromosome: args?.chr, positions: args?.start && args?.end ? [args.start, args.end] : undefined, datasetId: args?.datasetId }); } if (name === "get_tissue_info") { return await referenceHandlers.getTissueInfo({ datasetId: args?.datasetId }); } if (name === "get_sample_info") { return await referenceHandlers.getSamples({ tissueIds: args?.tissueSiteDetailId ? [args.tissueSiteDetailId] : undefined, datasetId: args?.datasetId }); } if (name === "get_subject_phenotypes") { return await referenceHandlers.getSubjects({ subjectIds: args?.subjectId ? [args.subjectId] : undefined, datasetId: args?.datasetId }); } if (name === "validate_gene_id") { return await referenceHandlers.validateIds({ ids: [args?.geneId], type: 'gene' }); } if (name === "validate_variant_id") { return await referenceHandlers.validateIds({ ids: [args?.variantId], type: 'variant' }); } if (name === "get_dataset_info") { return await referenceHandlers.getDatasetInfo({ datasetId: args?.datasetId }); } if (name === "search_transcripts") { return await referenceHandlers.getTranscripts({ geneId: args?.gencodeId }); } if (name === "get_gene_ontology") { return await referenceHandlers.getGeneOntology({ gencodeId: args?.gencodeId, ontologyType: args?.ontologyType }); } if (name === "convert_coordinates") { return await referenceHandlers.convertCoordinates({ chr: args?.chr, position: args?.position, fromBuild: args?.fromBuild, toBuild: args?.toBuild }); } throw new Error(`Unknown tool: ${name}`); } catch (error) { console.error(`Error executing tool ${name}:`, error); return { content: [{ type: "text", text: `Error executing ${name}: ${error instanceof Error ? error.message : 'Unknown error'}` }], isError: true }; } }); /** * Start the server using stdio transport */ async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("GTEx Portal MCP Server running on stdio"); } main().catch((error) => { console.error("Server error:", error); process.exit(1); });

Implementation Reference

Latest Blog Posts

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/GTEx-MCP-Server'

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