# Directory Structure ``` ├── .claude │ └── settings.local.json ├── .github │ └── workflows │ ├── claude-code-review.yml │ └── claude.yml ├── .gitignore ├── CLAUDE.md ├── example-claude-config.json ├── package.json ├── README.md ├── src │ └── index.ts └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | # Dependencies 2 | node_modules/ 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Build output 8 | build/ 9 | *.js 10 | *.js.map 11 | *.d.ts 12 | *.d.ts.map 13 | 14 | # Keep TypeScript source files 15 | !src/**/*.ts 16 | 17 | # IDE files 18 | .vscode/ 19 | .idea/ 20 | *.swp 21 | *.swo 22 | *~ 23 | .DS_Store 24 | 25 | # Environment files 26 | .env 27 | .env.local 28 | .env.development.local 29 | .env.test.local 30 | .env.production.local 31 | 32 | # Logs 33 | logs/ 34 | *.log 35 | 36 | # Testing 37 | coverage/ 38 | .nyc_output/ 39 | 40 | # Temporary files 41 | tmp/ 42 | temp/ 43 | .cache/ 44 | temp_config.json 45 | 46 | # Package manager files 47 | package-lock.json 48 | yarn.lock 49 | pnpm-lock.yaml 50 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | # gnomAD MCP Server 2 | 3 | A Model Context Protocol (MCP) server that provides access to the gnomAD (Genome Aggregation Database) GraphQL API. This server enables AI assistants to query genetic variant data, gene constraints, and population genetics information from gnomAD. 4 | 5 | ## Features 6 | 7 | - 🧬 **Gene Information**: Search and retrieve detailed gene data including constraint scores 8 | - 🔬 **Variant Analysis**: Query specific variants and their population frequencies 9 | - 📊 **Population Genetics**: Access allele frequencies across different populations 10 | - 🧮 **Constraint Scores**: Get pLI, LOEUF, and other constraint metrics 11 | - 🔍 **Region Queries**: Find variants within specific genomic regions 12 | - 🧪 **Transcript Data**: Access transcript-specific information and constraints 13 | - 📈 **Coverage Data**: Retrieve sequencing coverage statistics 14 | - 🔄 **Structural Variants**: Query structural variant data 15 | - 🧲 **Mitochondrial Variants**: Access mitochondrial genome variants 16 | 17 | ## Installation 18 | 19 | ### Prerequisites 20 | 21 | - Node.js 18 or higher 22 | - npm or yarn 23 | 24 | ### Install from source 25 | 26 | ```bash 27 | git clone https://github.com/yourusername/gnomad-mcp-server.git 28 | cd gnomad-mcp-server 29 | npm install 30 | npm run build 31 | ``` 32 | 33 | ## Configuration 34 | 35 | ### Claude Desktop 36 | 37 | Add to your Claude Desktop configuration file: 38 | 39 | **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` 40 | **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` 41 | 42 | ```json 43 | { 44 | "mcpServers": { 45 | "gnomad": { 46 | "command": "node", 47 | "args": ["/path/to/gnomad-mcp-server/dist/index.js"] 48 | } 49 | } 50 | } 51 | ``` 52 | 53 | ### With MCP CLI 54 | 55 | ```bash 56 | npx @modelcontextprotocol/cli gnomad-mcp-server 57 | ``` 58 | 59 | ## Available Tools 60 | 61 | ### 1. `search` 62 | Search for genes, variants, or regions in gnomAD. 63 | 64 | **Parameters:** 65 | - `query` (required): Search query (gene symbol, gene ID, variant ID, rsID) 66 | - `reference_genome`: Reference genome (GRCh37 or GRCh38, default: GRCh38) 67 | - `dataset`: Dataset ID (gnomad_r4, gnomad_r3, gnomad_r2_1, etc., default: gnomad_r4) 68 | 69 | **Example:** 70 | ```json 71 | { 72 | "query": "TP53", 73 | "reference_genome": "GRCh38" 74 | } 75 | ``` 76 | 77 | ### 2. `get_gene` 78 | Get detailed information about a gene including constraint scores. 79 | 80 | **Parameters:** 81 | - `gene_id`: Ensembl gene ID (e.g., ENSG00000141510) 82 | - `gene_symbol`: Gene symbol (e.g., TP53) 83 | - `reference_genome`: Reference genome (default: GRCh38) 84 | 85 | **Example:** 86 | ```json 87 | { 88 | "gene_symbol": "BRCA1", 89 | "reference_genome": "GRCh38" 90 | } 91 | ``` 92 | 93 | ### 3. `get_variant` 94 | Get detailed information about a specific variant. 95 | 96 | **Parameters:** 97 | - `variant_id` (required): Variant ID in format: chr-pos-ref-alt (e ``` -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- ```markdown 1 | # CLAUDE.md 2 | 3 | This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 4 | 5 | ## Project Overview 6 | 7 | This is a gnomAD MCP (Model Context Protocol) server that provides access to the Genome Aggregation Database through the MCP protocol. The server allows LLMs and other tools to query genomic variant data from gnomAD. 8 | 9 | ## Development Commands 10 | 11 | Note: This appears to be a new repository. Common commands will be added as the project develops. 12 | 13 | ## Architecture 14 | 15 | This MCP server will likely follow the standard MCP server pattern: 16 | - Server implementation handling MCP protocol messages 17 | - Resource providers for gnomAD data access 18 | - Tool handlers for genomic queries 19 | - Configuration for database connections and API endpoints 20 | 21 | ## Key Considerations 22 | 23 | - Handle genomic coordinates and variant identifiers carefully 24 | - Ensure proper error handling for invalid genomic queries 25 | - Consider rate limiting and caching for gnomAD API calls 26 | - Follow genomic data standards (VCF, HGVS notation, etc.) ``` -------------------------------------------------------------------------------- /.claude/settings.local.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "permissions": { 3 | "allow": [ 4 | "WebFetch(domain:broadinstitute.github.io)", 5 | "WebFetch(domain:gnomad.broadinstitute.org)" 6 | ], 7 | "deny": [], 8 | "ask": [] 9 | } 10 | } ``` -------------------------------------------------------------------------------- /example-claude-config.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "mcpServers": { 3 | "gnomad": { 4 | "command": "node", 5 | "args": ["/absolute/path/to/gnomad-mcp-server/dist/index.js"], 6 | "env": {} 7 | } 8 | }, 9 | "_comment": "Replace /absolute/path/to/ with the actual path to your gnomad-mcp-server installation" 10 | } ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "lib": ["ES2022"], 7 | "outDir": "./dist", 8 | "rootDir": "./src", 9 | "strict": true, 10 | "esModuleInterop": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "declaration": true, 14 | "declarationMap": true, 15 | "sourceMap": true, 16 | "resolveJsonModule": true, 17 | "allowSyntheticDefaultImports": true 18 | }, 19 | "include": ["src/**/*"], 20 | "exclude": ["node_modules", "dist"] 21 | } ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "gnomad-mcp-server", 3 | "version": "1.0.0", 4 | "description": "Model Context Protocol server for gnomAD (Genome Aggregation Database) GraphQL API", 5 | "main": "dist/index.js", 6 | "type": "module", 7 | "bin": { 8 | "gnomad-mcp-server": "./dist/index.js" 9 | }, 10 | "scripts": { 11 | "build": "tsc", 12 | "start": "node dist/index.js", 13 | "dev": "tsx src/index.ts", 14 | "prepare": "npm run build" 15 | }, 16 | "keywords": [ 17 | "mcp", 18 | "gnomad", 19 | "genomics", 20 | "variants", 21 | "graphql", 22 | "bioinformatics" 23 | ], 24 | "author": "", 25 | "license": "MIT", 26 | "dependencies": { 27 | "@modelcontextprotocol/sdk": "^0.5.0", 28 | "node-fetch": "^3.3.2" 29 | }, 30 | "devDependencies": { 31 | "@types/node": "^20.10.0", 32 | "tsx": "^4.7.0", 33 | "typescript": "^5.3.0" 34 | }, 35 | "engines": { 36 | "node": ">=18.0.0" 37 | } 38 | } ``` -------------------------------------------------------------------------------- /.github/workflows/claude.yml: -------------------------------------------------------------------------------- ```yaml 1 | name: Claude Code 2 | 3 | on: 4 | issue_comment: 5 | types: [created] 6 | pull_request_review_comment: 7 | types: [created] 8 | issues: 9 | types: [opened, assigned] 10 | pull_request_review: 11 | types: [submitted] 12 | 13 | jobs: 14 | claude: 15 | if: | 16 | (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || 17 | (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || 18 | (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || 19 | (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) 20 | runs-on: ubuntu-latest 21 | permissions: 22 | contents: read 23 | pull-requests: read 24 | issues: read 25 | id-token: write 26 | actions: read # Required for Claude to read CI results on PRs 27 | steps: 28 | - name: Checkout repository 29 | uses: actions/checkout@v4 30 | with: 31 | fetch-depth: 1 32 | 33 | - name: Run Claude Code 34 | id: claude 35 | uses: anthropics/claude-code-action@beta 36 | with: 37 | claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} 38 | 39 | # This is an optional setting that allows Claude to read CI results on PRs 40 | additional_permissions: | 41 | actions: read 42 | 43 | # Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4.1) 44 | # model: "claude-opus-4-1-20250805" 45 | 46 | # Optional: Customize the trigger phrase (default: @claude) 47 | # trigger_phrase: "/claude" 48 | 49 | # Optional: Trigger when specific user is assigned to an issue 50 | # assignee_trigger: "claude-bot" 51 | 52 | # Optional: Allow Claude to run specific commands 53 | # allowed_tools: "Bash(npm install),Bash(npm run build),Bash(npm run test:*),Bash(npm run lint:*)" 54 | 55 | # Optional: Add custom instructions for Claude to customize its behavior for your project 56 | # custom_instructions: | 57 | # Follow our coding standards 58 | # Ensure all new code has tests 59 | # Use TypeScript for new files 60 | 61 | # Optional: Custom environment variables for Claude 62 | # claude_env: | 63 | # NODE_ENV: test 64 | 65 | ``` -------------------------------------------------------------------------------- /.github/workflows/claude-code-review.yml: -------------------------------------------------------------------------------- ```yaml 1 | name: Claude Code Review 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize] 6 | # Optional: Only run on specific file changes 7 | # paths: 8 | # - "src/**/*.ts" 9 | # - "src/**/*.tsx" 10 | # - "src/**/*.js" 11 | # - "src/**/*.jsx" 12 | 13 | jobs: 14 | claude-review: 15 | # Optional: Filter by PR author 16 | # if: | 17 | # github.event.pull_request.user.login == 'external-contributor' || 18 | # github.event.pull_request.user.login == 'new-developer' || 19 | # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' 20 | 21 | runs-on: ubuntu-latest 22 | permissions: 23 | contents: read 24 | pull-requests: read 25 | issues: read 26 | id-token: write 27 | 28 | steps: 29 | - name: Checkout repository 30 | uses: actions/checkout@v4 31 | with: 32 | fetch-depth: 1 33 | 34 | - name: Run Claude Code Review 35 | id: claude-review 36 | uses: anthropics/claude-code-action@beta 37 | with: 38 | claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} 39 | 40 | # Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4.1) 41 | # model: "claude-opus-4-1-20250805" 42 | 43 | # Direct prompt for automated review (no @claude mention needed) 44 | direct_prompt: | 45 | Please review this pull request and provide feedback on: 46 | - Code quality and best practices 47 | - Potential bugs or issues 48 | - Performance considerations 49 | - Security concerns 50 | - Test coverage 51 | 52 | Be constructive and helpful in your feedback. 53 | 54 | # Optional: Use sticky comments to make Claude reuse the same comment on subsequent pushes to the same PR 55 | # use_sticky_comment: true 56 | 57 | # Optional: Customize review based on file types 58 | # direct_prompt: | 59 | # Review this PR focusing on: 60 | # - For TypeScript files: Type safety and proper interface usage 61 | # - For API endpoints: Security, input validation, and error handling 62 | # - For React components: Performance, accessibility, and best practices 63 | # - For tests: Coverage, edge cases, and test quality 64 | 65 | # Optional: Different prompts for different authors 66 | # direct_prompt: | 67 | # ${{ github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' && 68 | # 'Welcome! Please review this PR from a first-time contributor. Be encouraging and provide detailed explanations for any suggestions.' || 69 | # 'Please provide a thorough code review focusing on our coding standards and best practices.' }} 70 | 71 | # Optional: Add specific tools for running tests or linting 72 | # allowed_tools: "Bash(npm run test),Bash(npm run lint),Bash(npm run typecheck)" 73 | 74 | # Optional: Skip review for certain conditions 75 | # if: | 76 | # !contains(github.event.pull_request.title, '[skip-review]') && 77 | # !contains(github.event.pull_request.title, '[WIP]') 78 | 79 | ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { Server } from "@modelcontextprotocol/sdk/server/index.js"; 2 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 3 | import { 4 | CallToolRequestSchema, 5 | ListToolsRequestSchema, 6 | } from "@modelcontextprotocol/sdk/types.js"; 7 | import fetch, { Response } from "node-fetch"; 8 | 9 | // gnomAD GraphQL API endpoint 10 | const GNOMAD_API_URL = "https://gnomad.broadinstitute.org/api"; 11 | 12 | // Type definitions for gnomAD responses 13 | interface GnomadResponse { 14 | data?: any; 15 | errors?: Array<{ 16 | message: string; 17 | extensions?: any; 18 | }>; 19 | } 20 | 21 | interface GeneConstraint { 22 | exp_lof: number; 23 | exp_mis: number; 24 | exp_syn: number; 25 | obs_lof: number; 26 | obs_mis: number; 27 | obs_syn: number; 28 | oe_lof: number; 29 | oe_lof_lower: number; 30 | oe_lof_upper: number; 31 | oe_mis: number; 32 | oe_mis_lower: number; 33 | oe_mis_upper: number; 34 | oe_syn: number; 35 | oe_syn_lower: number; 36 | oe_syn_upper: number; 37 | lof_z: number; 38 | mis_z: number; 39 | syn_z: number; 40 | pLI: number; 41 | } 42 | 43 | // Helper function to make GraphQL requests 44 | async function makeGraphQLRequest(query: string, variables: Record<string, any> = {}): Promise<GnomadResponse> { 45 | const response: Response = await fetch(GNOMAD_API_URL, { 46 | method: "POST", 47 | headers: { 48 | "Content-Type": "application/json", 49 | }, 50 | body: JSON.stringify({ 51 | query, 52 | variables, 53 | }), 54 | }); 55 | 56 | if (!response.ok) { 57 | throw new Error(`HTTP error! status: ${response.status}`); 58 | } 59 | 60 | return await response.json() as GnomadResponse; 61 | } 62 | 63 | // GraphQL query templates 64 | const QUERIES = { 65 | searchGene: ` 66 | query SearchGene($query: String!, $referenceGenome: ReferenceGenomeId!) { 67 | searchResults(query: $query, referenceGenome: $referenceGenome) { 68 | label 69 | value: url 70 | } 71 | } 72 | `, 73 | 74 | getGene: ` 75 | query GetGene($geneId: String, $geneSymbol: String, $referenceGenome: ReferenceGenomeId!) { 76 | gene(gene_id: $geneId, gene_symbol: $geneSymbol, reference_genome: $referenceGenome) { 77 | gene_id 78 | symbol 79 | name 80 | canonical_transcript_id 81 | hgnc_id 82 | omim_id 83 | chrom 84 | start 85 | stop 86 | strand 87 | gnomad_constraint { 88 | exp_lof 89 | exp_mis 90 | exp_syn 91 | obs_lof 92 | obs_mis 93 | obs_syn 94 | oe_lof 95 | oe_lof_lower 96 | oe_lof_upper 97 | oe_mis 98 | oe_mis_lower 99 | oe_mis_upper 100 | oe_syn 101 | oe_syn_lower 102 | oe_syn_upper 103 | lof_z 104 | mis_z 105 | syn_z 106 | pLI 107 | } 108 | transcripts { 109 | transcript_id 110 | transcript_version 111 | reference_genome 112 | } 113 | } 114 | } 115 | `, 116 | 117 | getVariant: ` 118 | query GetVariant($variantId: String!, $datasetId: DatasetId!) { 119 | variant(variantId: $variantId, dataset: $datasetId) { 120 | variant_id 121 | reference_genome 122 | chrom 123 | pos 124 | ref 125 | alt 126 | rsids 127 | caid 128 | colocated_variants 129 | multi_nucleotide_variants { 130 | combined_variant_id 131 | changes_amino_acids 132 | n_individuals 133 | other_constituent_snvs 134 | } 135 | exome { 136 | ac 137 | an 138 | ac_hemi 139 | ac_hom 140 | faf95 { 141 | popmax 142 | popmax_population 143 | } 144 | filters 145 | populations { 146 | id 147 | ac 148 | an 149 | ac_hemi 150 | ac_hom 151 | } 152 | } 153 | genome { 154 | ac 155 | an 156 | ac_hemi 157 | ac_hom 158 | faf95 { 159 | popmax 160 | popmax_population 161 | } 162 | filters 163 | populations { 164 | id 165 | ac 166 | an 167 | ac_hemi 168 | ac_hom 169 | } 170 | } 171 | transcript_consequences { 172 | gene_id 173 | gene_symbol 174 | transcript_id 175 | consequence_terms 176 | is_canonical 177 | major_consequence 178 | polyphen_prediction 179 | sift_prediction 180 | lof 181 | lof_filter 182 | lof_flags 183 | } 184 | } 185 | } 186 | `, 187 | 188 | getVariantsInGene: ` 189 | query GetVariantsInGene($geneId: String, $geneSymbol: String, $datasetId: DatasetId!, $referenceGenome: ReferenceGenomeId!) { 190 | gene(gene_id: $geneId, gene_symbol: $geneSymbol, reference_genome: $referenceGenome) { 191 | variants(dataset: $datasetId) { 192 | variant_id 193 | pos 194 | rsids 195 | consequence 196 | hgvsc 197 | hgvsp 198 | lof 199 | exome { 200 | ac 201 | an 202 | af 203 | filters 204 | } 205 | genome { 206 | ac 207 | an 208 | af 209 | filters 210 | } 211 | } 212 | } 213 | } 214 | `, 215 | 216 | getTranscript: ` 217 | query GetTranscript($transcriptId: String!, $referenceGenome: ReferenceGenomeId!) { 218 | transcript(transcript_id: $transcriptId, reference_genome: $referenceGenome) { 219 | transcript_id 220 | transcript_version 221 | reference_genome 222 | chrom 223 | start 224 | stop 225 | strand 226 | gene_id 227 | gene_symbol 228 | gene_version 229 | gnomad_constraint { 230 | exp_lof 231 | exp_mis 232 | exp_syn 233 | obs_lof 234 | obs_mis 235 | obs_syn 236 | oe_lof 237 | oe_lof_lower 238 | oe_lof_upper 239 | oe_mis 240 | oe_mis_lower 241 | oe_mis_upper 242 | oe_syn 243 | oe_syn_lower 244 | oe_syn_upper 245 | lof_z 246 | mis_z 247 | syn_z 248 | pLI 249 | } 250 | } 251 | } 252 | `, 253 | 254 | getRegionVariants: ` 255 | query GetRegionVariants($chrom: String!, $start: Int!, $stop: Int!, $datasetId: DatasetId!, $referenceGenome: ReferenceGenomeId!) { 256 | region(chrom: $chrom, start: $start, stop: $stop, reference_genome: $referenceGenome) { 257 | variants(dataset: $datasetId) { 258 | variant_id 259 | pos 260 | rsids 261 | consequence 262 | hgvsc 263 | hgvsp 264 | lof 265 | exome { 266 | ac 267 | an 268 | af 269 | filters 270 | } 271 | genome { 272 | ac 273 | an 274 | af 275 | filters 276 | } 277 | } 278 | } 279 | } 280 | `, 281 | 282 | getCoverage: ` 283 | query GetCoverage($geneId: String, $geneSymbol: String, $datasetId: DatasetId!, $referenceGenome: ReferenceGenomeId!) { 284 | gene(gene_id: $geneId, gene_symbol: $geneSymbol, reference_genome: $referenceGenome) { 285 | coverage(dataset: $datasetId) { 286 | exome { 287 | pos 288 | mean 289 | median 290 | over_1 291 | over_5 292 | over_10 293 | over_15 294 | over_20 295 | over_25 296 | over_30 297 | over_50 298 | over_100 299 | } 300 | genome { 301 | pos 302 | mean 303 | median 304 | over_1 305 | over_5 306 | over_10 307 | over_15 308 | over_20 309 | over_25 310 | over_30 311 | over_50 312 | over_100 313 | } 314 | } 315 | } 316 | } 317 | `, 318 | 319 | getStructuralVariants: ` 320 | query GetStructuralVariants($chrom: String!, $start: Int!, $stop: Int!, $datasetId: DatasetId!, $referenceGenome: ReferenceGenomeId!) { 321 | region(chrom: $chrom, start: $start, stop: $stop, reference_genome: $referenceGenome) { 322 | structural_variants(dataset: $datasetId) { 323 | variant_id 324 | chrom 325 | pos 326 | end 327 | length 328 | type 329 | alts 330 | ac 331 | an 332 | af 333 | homozygote_count 334 | hemizygote_count 335 | filters 336 | } 337 | } 338 | } 339 | `, 340 | 341 | getMitochondrialVariants: ` 342 | query GetMitochondrialVariants($datasetId: DatasetId!) { 343 | mitochondrial_variants(dataset: $datasetId) { 344 | variant_id 345 | pos 346 | ref 347 | alt 348 | rsids 349 | ac_het 350 | ac_hom 351 | an 352 | af_het 353 | af_hom 354 | max_heteroplasmy 355 | filters 356 | } 357 | } 358 | `, 359 | 360 | searchVariant: ` 361 | query SearchVariant($query: String!, $datasetId: DatasetId!, $referenceGenome: ReferenceGenomeId!) { 362 | searchResults(query: $query, referenceGenome: $referenceGenome, dataset: $datasetId) { 363 | label 364 | value: url 365 | } 366 | } 367 | ` 368 | }; 369 | 370 | // Create the MCP server 371 | const server = new Server( 372 | { 373 | name: "gnomad-mcp-server", 374 | version: "1.0.0", 375 | }, 376 | { 377 | capabilities: { 378 | tools: {}, 379 | }, 380 | } 381 | ); 382 | 383 | // Helper function to validate and parse dataset ID 384 | function parseDatasetId(dataset: string): string { 385 | const validDatasets = [ 386 | "gnomad_r2_1", 387 | "gnomad_r3", 388 | "gnomad_r4", 389 | "gnomad_sv_r2_1", 390 | "gnomad_sv_r4", 391 | "gnomad_cnv_r4", 392 | "exac", 393 | ]; 394 | 395 | const datasetLower = dataset.toLowerCase(); 396 | if (!validDatasets.includes(datasetLower)) { 397 | return "gnomad_r4"; // Default to latest version 398 | } 399 | return datasetLower; 400 | } 401 | 402 | // Helper function to validate reference genome 403 | function parseReferenceGenome(genome: string): string { 404 | const validGenomes = ["GRCh37", "GRCh38"]; 405 | if (!validGenomes.includes(genome)) { 406 | return "GRCh38"; // Default to GRCh38 407 | } 408 | return genome; 409 | } 410 | 411 | // Register tool handlers 412 | server.setRequestHandler(ListToolsRequestSchema, async () => { 413 | return { 414 | tools: [ 415 | { 416 | name: "search", 417 | description: "Search for genes, variants, or regions in gnomAD", 418 | inputSchema: { 419 | type: "object", 420 | properties: { 421 | query: { 422 | type: "string", 423 | description: "Search query (gene symbol, gene ID, variant ID, rsID, etc.)", 424 | }, 425 | reference_genome: { 426 | type: "string", 427 | description: "Reference genome (GRCh37 or GRCh38)", 428 | default: "GRCh38", 429 | }, 430 | dataset: { 431 | type: "string", 432 | description: "Dataset ID (gnomad_r4, gnomad_r3, gnomad_r2_1, etc.)", 433 | default: "gnomad_r4", 434 | }, 435 | }, 436 | required: ["query"], 437 | }, 438 | }, 439 | { 440 | name: "get_gene", 441 | description: "Get detailed information about a gene including constraint scores", 442 | inputSchema: { 443 | type: "object", 444 | properties: { 445 | gene_id: { 446 | type: "string", 447 | description: "Ensembl gene ID (e.g., ENSG00000141510)", 448 | }, 449 | gene_symbol: { 450 | type: "string", 451 | description: "Gene symbol (e.g., TP53)", 452 | }, 453 | reference_genome: { 454 | type: "string", 455 | description: "Reference genome (GRCh37 or GRCh38)", 456 | default: "GRCh38", 457 | }, 458 | }, 459 | }, 460 | }, 461 | { 462 | name: "get_variant", 463 | description: "Get detailed information about a specific variant", 464 | inputSchema: { 465 | type: "object", 466 | properties: { 467 | variant_id: { 468 | type: "string", 469 | description: "Variant ID in format: chr-pos-ref-alt (e.g., 1-55516888-G-A)", 470 | }, 471 | dataset: { 472 | type: "string", 473 | description: "Dataset ID (gnomad_r4, gnomad_r3, gnomad_r2_1, etc.)", 474 | default: "gnomad_r4", 475 | }, 476 | }, 477 | required: ["variant_id"], 478 | }, 479 | }, 480 | { 481 | name: "get_variants_in_gene", 482 | description: "Get all variants in a specific gene", 483 | inputSchema: { 484 | type: "object", 485 | properties: { 486 | gene_id: { 487 | type: "string", 488 | description: "Ensembl gene ID", 489 | }, 490 | gene_symbol: { 491 | type: "string", 492 | description: "Gene symbol", 493 | }, 494 | dataset: { 495 | type: "string", 496 | description: "Dataset ID", 497 | default: "gnomad_r4", 498 | }, 499 | reference_genome: { 500 | type: "string", 501 | description: "Reference genome", 502 | default: "GRCh38", 503 | }, 504 | }, 505 | }, 506 | }, 507 | { 508 | name: "get_transcript", 509 | description: "Get information about a specific transcript", 510 | inputSchema: { 511 | type: "object", 512 | properties: { 513 | transcript_id: { 514 | type: "string", 515 | description: "Ensembl transcript ID (e.g., ENST00000269305)", 516 | }, 517 | reference_genome: { 518 | type: "string", 519 | description: "Reference genome", 520 | default: "GRCh38", 521 | }, 522 | }, 523 | required: ["transcript_id"], 524 | }, 525 | }, 526 | { 527 | name: "get_region_variants", 528 | description: "Get variants in a specific genomic region", 529 | inputSchema: { 530 | type: "object", 531 | properties: { 532 | chrom: { 533 | type: "string", 534 | description: "Chromosome (1-22, X, Y)", 535 | }, 536 | start: { 537 | type: "number", 538 | description: "Start position", 539 | }, 540 | stop: { 541 | type: "number", 542 | description: "Stop position", 543 | }, 544 | dataset: { 545 | type: "string", 546 | description: "Dataset ID", 547 | default: "gnomad_r4", 548 | }, 549 | reference_genome: { 550 | type: "string", 551 | description: "Reference genome", 552 | default: "GRCh38", 553 | }, 554 | }, 555 | required: ["chrom", "start", "stop"], 556 | }, 557 | }, 558 | { 559 | name: "get_coverage", 560 | description: "Get coverage information for a gene", 561 | inputSchema: { 562 | type: "object", 563 | properties: { 564 | gene_id: { 565 | type: "string", 566 | description: "Ensembl gene ID", 567 | }, 568 | gene_symbol: { 569 | type: "string", 570 | description: "Gene symbol", 571 | }, 572 | dataset: { 573 | type: "string", 574 | description: "Dataset ID", 575 | default: "gnomad_r4", 576 | }, 577 | reference_genome: { 578 | type: "string", 579 | description: "Reference genome", 580 | default: "GRCh38", 581 | }, 582 | }, 583 | }, 584 | }, 585 | { 586 | name: "get_structural_variants", 587 | description: "Get structural variants in a genomic region", 588 | inputSchema: { 589 | type: "object", 590 | properties: { 591 | chrom: { 592 | type: "string", 593 | description: "Chromosome", 594 | }, 595 | start: { 596 | type: "number", 597 | description: "Start position", 598 | }, 599 | stop: { 600 | type: "number", 601 | description: "Stop position", 602 | }, 603 | dataset: { 604 | type: "string", 605 | description: "Dataset ID (gnomad_sv_r4, gnomad_sv_r2_1)", 606 | default: "gnomad_sv_r4", 607 | }, 608 | reference_genome: { 609 | type: "string", 610 | description: "Reference genome", 611 | default: "GRCh38", 612 | }, 613 | }, 614 | required: ["chrom", "start", "stop"], 615 | }, 616 | }, 617 | { 618 | name: "get_mitochondrial_variants", 619 | description: "Get mitochondrial variants", 620 | inputSchema: { 621 | type: "object", 622 | properties: { 623 | dataset: { 624 | type: "string", 625 | description: "Dataset ID", 626 | default: "gnomad_r3", 627 | }, 628 | }, 629 | }, 630 | }, 631 | ], 632 | }; 633 | }); 634 | 635 | server.setRequestHandler(CallToolRequestSchema, async (request) => { 636 | const { name, arguments: args } = request.params; 637 | 638 | // Type guard for arguments 639 | if (!args || typeof args !== 'object') { 640 | throw new Error("Invalid arguments provided"); 641 | } 642 | 643 | try { 644 | let result: GnomadResponse; 645 | let formattedResult: any; 646 | 647 | switch (name) { 648 | case "search": 649 | result = await makeGraphQLRequest(QUERIES.searchGene, { 650 | query: args.query as string, 651 | referenceGenome: parseReferenceGenome((args.reference_genome as string) || "GRCh38"), 652 | }); 653 | formattedResult = result.data?.searchResults || []; 654 | break; 655 | 656 | case "get_gene": 657 | if (!args.gene_id && !args.gene_symbol) { 658 | throw new Error("Either gene_id or gene_symbol must be provided"); 659 | } 660 | result = await makeGraphQLRequest(QUERIES.getGene, { 661 | geneId: (args.gene_id as string) || null, 662 | geneSymbol: (args.gene_symbol as string) || null, 663 | referenceGenome: parseReferenceGenome((args.reference_genome as string) || "GRCh38"), 664 | }); 665 | formattedResult = result.data?.gene || null; 666 | break; 667 | 668 | case "get_variant": 669 | result = await makeGraphQLRequest(QUERIES.getVariant, { 670 | variantId: args.variant_id as string, 671 | datasetId: parseDatasetId((args.dataset as string) || "gnomad_r4"), 672 | }); 673 | formattedResult = result.data?.variant || null; 674 | break; 675 | 676 | case "get_variants_in_gene": 677 | if (!args.gene_id && !args.gene_symbol) { 678 | throw new Error("Either gene_id or gene_symbol must be provided"); 679 | } 680 | result = await makeGraphQLRequest(QUERIES.getVariantsInGene, { 681 | geneId: (args.gene_id as string) || null, 682 | geneSymbol: (args.gene_symbol as string) || null, 683 | datasetId: parseDatasetId((args.dataset as string) || "gnomad_r4"), 684 | referenceGenome: parseReferenceGenome((args.reference_genome as string) || "GRCh38"), 685 | }); 686 | formattedResult = result.data?.gene?.variants || []; 687 | break; 688 | 689 | case "get_transcript": 690 | result = await makeGraphQLRequest(QUERIES.getTranscript, { 691 | transcriptId: args.transcript_id as string, 692 | referenceGenome: parseReferenceGenome((args.reference_genome as string) || "GRCh38"), 693 | }); 694 | formattedResult = result.data?.transcript || null; 695 | break; 696 | 697 | case "get_region_variants": 698 | result = await makeGraphQLRequest(QUERIES.getRegionVariants, { 699 | chrom: String(args.chrom), 700 | start: parseInt(String(args.start)), 701 | stop: parseInt(String(args.stop)), 702 | datasetId: parseDatasetId((args.dataset as string) || "gnomad_r4"), 703 | referenceGenome: parseReferenceGenome((args.reference_genome as string) || "GRCh38"), 704 | }); 705 | formattedResult = result.data?.region?.variants || []; 706 | break; 707 | 708 | case "get_coverage": 709 | if (!args.gene_id && !args.gene_symbol) { 710 | throw new Error("Either gene_id or gene_symbol must be provided"); 711 | } 712 | result = await makeGraphQLRequest(QUERIES.getCoverage, { 713 | geneId: (args.gene_id as string) || null, 714 | geneSymbol: (args.gene_symbol as string) || null, 715 | datasetId: parseDatasetId((args.dataset as string) || "gnomad_r4"), 716 | referenceGenome: parseReferenceGenome((args.reference_genome as string) || "GRCh38"), 717 | }); 718 | formattedResult = result.data?.gene?.coverage || null; 719 | break; 720 | 721 | case "get_structural_variants": 722 | result = await makeGraphQLRequest(QUERIES.getStructuralVariants, { 723 | chrom: String(args.chrom), 724 | start: parseInt(String(args.start)), 725 | stop: parseInt(String(args.stop)), 726 | datasetId: parseDatasetId((args.dataset as string) || "gnomad_sv_r4"), 727 | referenceGenome: parseReferenceGenome((args.reference_genome as string) || "GRCh38"), 728 | }); 729 | formattedResult = result.data?.region?.structural_variants || []; 730 | break; 731 | 732 | case "get_mitochondrial_variants": 733 | result = await makeGraphQLRequest(QUERIES.getMitochondrialVariants, { 734 | datasetId: parseDatasetId((args.dataset as string) || "gnomad_r3"), 735 | }); 736 | formattedResult = result.data?.mitochondrial_variants || []; 737 | break; 738 | 739 | default: 740 | throw new Error(`Unknown tool: ${name}`); 741 | } 742 | 743 | // Check for GraphQL errors 744 | if (result.errors && result.errors.length > 0) { 745 | const errorMessages = result.errors.map(e => e.message).join("; "); 746 | throw new Error(`GraphQL errors: ${errorMessages}`); 747 | } 748 | 749 | return { 750 | content: [ 751 | { 752 | type: "text", 753 | text: JSON.stringify(formattedResult, null, 2), 754 | }, 755 | ], 756 | }; 757 | } catch (error) { 758 | const errorMessage = error instanceof Error ? error.message : String(error); 759 | return { 760 | content: [ 761 | { 762 | type: "text", 763 | text: `Error: ${errorMessage}`, 764 | }, 765 | ], 766 | }; 767 | } 768 | }); 769 | 770 | // Start the server 771 | async function main() { 772 | const transport = new StdioServerTransport(); 773 | await server.connect(transport); 774 | console.error("gnomAD MCP server started"); 775 | } 776 | 777 | main().catch((error) => { 778 | console.error("Fatal error:", error); 779 | process.exit(1); 780 | }); ```