#
tokens: 10480/50000 10/10 files
lines: on (toggle) GitHub
raw markdown copy reset
# 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 | });
```