#
tokens: 17230/50000 4/4 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── LICENSE
├── package-lock.json
├── package.json
├── pubchem-mcp-server-logo.png
├── README.md
├── src
│   └── index.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | ![PubChem MCP Server Logo](pubchem-mcp-server-logo.png)
  2 | # Unofficial PubChem MCP Server
  3 | 
  4 | A comprehensive Model Context Protocol (MCP) server for accessing the PubChem chemical database. This server provides access to over 110 million chemical compounds with extensive molecular properties, bioassay data, and chemical informatics tools.
  5 | 
  6 | ## Features
  7 | 
  8 | ### 🔍 **Chemical Search & Retrieval (6 tools)**
  9 | 
 10 | - **search_compounds** - Search by name, CAS number, formula, or identifier
 11 | - **get_compound_info** - Detailed compound information by CID
 12 | - **search_by_smiles** - Exact SMILES string matching
 13 | - **search_by_inchi** - InChI/InChI key search
 14 | - **search_by_cas_number** - CAS Registry Number lookup
 15 | - **get_compound_synonyms** - All names and synonyms
 16 | 
 17 | ### 🧬 **Structure Analysis & Similarity (5 tools)**
 18 | 
 19 | - **search_similar_compounds** - Tanimoto similarity search
 20 | - **substructure_search** - Find compounds containing substructures
 21 | - **superstructure_search** - Find larger compounds containing query
 22 | - **get_3d_conformers** - 3D structural information
 23 | - **analyze_stereochemistry** - Chirality and isomer analysis
 24 | 
 25 | ### ⚗️ **Chemical Properties & Descriptors (6 tools)**
 26 | 
 27 | - **get_compound_properties** - Molecular weight, logP, TPSA, etc.
 28 | - **calculate_descriptors** - Comprehensive molecular descriptors
 29 | - **predict_admet_properties** - ADMET predictions
 30 | - **assess_drug_likeness** - Lipinski Rule of Five analysis
 31 | - **analyze_molecular_complexity** - Synthetic accessibility
 32 | - **get_pharmacophore_features** - Pharmacophore mapping
 33 | 
 34 | ### 🧪 **Bioassay & Activity Data (5 tools)**
 35 | 
 36 | - **search_bioassays** - Find biological assays
 37 | - **get_assay_info** - Detailed assay protocols
 38 | - **get_compound_bioactivities** - All activity data for compounds
 39 | - **search_by_target** - Find compounds tested against targets
 40 | - **compare_activity_profiles** - Cross-compound comparisons
 41 | 
 42 | ### ⚠️ **Safety & Toxicity (4 tools)**
 43 | 
 44 | - **get_safety_data** - GHS hazard classifications
 45 | - **get_toxicity_info** - LD50, carcinogenicity data
 46 | - **assess_environmental_fate** - Biodegradation analysis
 47 | - **get_regulatory_info** - FDA, EPA regulations
 48 | 
 49 | ### 🔗 **Cross-References & Integration (4 tools)**
 50 | 
 51 | - **get_external_references** - Links to ChEMBL, DrugBank, etc.
 52 | - **search_patents** - Chemical patent information
 53 | - **get_literature_references** - PubMed citations
 54 | - **batch_compound_lookup** - Bulk processing (up to 200 compounds)
 55 | 
 56 | ## Resource Templates
 57 | 
 58 | Access PubChem data through URI patterns:
 59 | 
 60 | - `pubchem://compound/{cid}` - Complete compound data
 61 | - `pubchem://structure/{cid}` - 2D/3D structure information
 62 | - `pubchem://properties/{cid}` - Molecular properties
 63 | - `pubchem://bioassay/{aid}` - Bioassay data
 64 | - `pubchem://similarity/{smiles}` - Similarity search results
 65 | - `pubchem://safety/{cid}` - Safety and toxicity data
 66 | 
 67 | ## Installation
 68 | 
 69 | ```bash
 70 | # Clone or create the server directory
 71 | cd pubchem-server
 72 | 
 73 | # Install dependencies
 74 | npm install
 75 | 
 76 | # Build the server
 77 | npm run build
 78 | 
 79 | # Run the server
 80 | npm start
 81 | ```
 82 | 
 83 | ## Usage
 84 | 
 85 | ### Basic Compound Search
 86 | 
 87 | ```javascript
 88 | // Search for compounds by name
 89 | {
 90 |   "tool": "search_compounds",
 91 |   "arguments": {
 92 |     "query": "aspirin",
 93 |     "max_records": 10
 94 |   }
 95 | }
 96 | 
 97 | // Get detailed compound information
 98 | {
 99 |   "tool": "get_compound_info",
100 |   "arguments": {
101 |     "cid": 2244
102 |   }
103 | }
104 | ```
105 | 
106 | ### Structure Analysis
107 | 
108 | ```javascript
109 | // Find similar compounds
110 | {
111 |   "tool": "search_similar_compounds",
112 |   "arguments": {
113 |     "smiles": "CC(=O)OC1=CC=CC=C1C(=O)O",
114 |     "threshold": 85,
115 |     "max_records": 50
116 |   }
117 | }
118 | 
119 | // Analyze molecular properties
120 | {
121 |   "tool": "get_compound_properties",
122 |   "arguments": {
123 |     "cid": 2244,
124 |     "properties": ["MolecularWeight", "XLogP", "TPSA"]
125 |   }
126 | }
127 | ```
128 | 
129 | ### Bioactivity Analysis
130 | 
131 | ```javascript
132 | // Get bioassay information
133 | {
134 |   "tool": "get_assay_info",
135 |   "arguments": {
136 |     "aid": 1159607
137 |   }
138 | }
139 | 
140 | // Search compounds by target
141 | {
142 |   "tool": "search_by_target",
143 |   "arguments": {
144 |     "target": "cyclooxygenase",
145 |     "max_records": 100
146 |   }
147 | }
148 | ```
149 | 
150 | ### Safety Information
151 | 
152 | ```javascript
153 | // Get safety classifications
154 | {
155 |   "tool": "get_safety_data",
156 |   "arguments": {
157 |     "cid": 2244
158 |   }
159 | }
160 | ```
161 | 
162 | ### Batch Processing
163 | 
164 | ```javascript
165 | // Process multiple compounds
166 | {
167 |   "tool": "batch_compound_lookup",
168 |   "arguments": {
169 |     "cids": [2244, 5090, 3672],
170 |     "operation": "property"
171 |   }
172 | }
173 | ```
174 | 
175 | ## Integration with Other MCP Servers
176 | 
177 | This PubChem server integrates perfectly with other chemical/biological databases:
178 | 
179 | ### Complete Chemical Informatics Pipeline
180 | 
181 | ```
182 | 1. Target Discovery: UniProt → STRING → AlphaFold
183 | 2. Chemical Discovery: PubChem ← → ChEMBL
184 | 3. Complete Workflow: Protein → Structure → Interactions → Small Molecules → Bioactivity
185 | ```
186 | 
187 | ### Cross-Database Workflows
188 | 
189 | - **UniProt → PubChem**: Find protein targets → Find small molecule ligands
190 | - **PubChem → ChEMBL**: Discover compounds → Analyze bioactivity data
191 | - **STRING → PubChem**: Protein interactions → Chemical modulators
192 | 
193 | ## API Rate Limits
194 | 
195 | PubChem API guidelines:
196 | 
197 | - **5 requests per second**
198 | - **400 requests per minute maximum**
199 | - No API key required
200 | - Respectful usage encouraged
201 | 
202 | ## Data Sources
203 | 
204 | - **110+ million compounds** with full chemical data
205 | - **1.5+ million bioassays** with biological activity
206 | - **Chemical properties** and computed descriptors
207 | - **3D conformers** and structural data
208 | - **Safety classifications** and toxicity information
209 | - **Cross-references** to 500+ external databases
210 | 
211 | ## Error Handling
212 | 
213 | The server includes comprehensive error handling:
214 | 
215 | - Invalid compound IDs return clear error messages
216 | - API timeouts are handled gracefully
217 | - Rate limiting compliance built-in
218 | - TypeScript validation for all inputs
219 | 
220 | ## Contributing
221 | 
222 | This server uses:
223 | 
224 | - **TypeScript** for type safety
225 | - **Axios** for HTTP requests
226 | - **MCP SDK** for protocol compliance
227 | - **PubChem REST API** for data access
228 | 
229 | ## License
230 | 
231 | MIT License - See LICENSE file for details.
232 | 
233 | ## Support
234 | 
235 | For issues with:
236 | 
237 | - **Server functionality**: Check error messages and API responses
238 | - **PubChem API**: Refer to official PubChem documentation
239 | - **Chemical data**: Validate compound identifiers and search terms
240 | 
241 | ## Examples
242 | 
243 | ### Drug Discovery Workflow
244 | 
245 | ```javascript
246 | // 1. Search for anti-inflammatory compounds
247 | {
248 |   "tool": "search_compounds",
249 |   "arguments": {
250 |     "query": "anti-inflammatory",
251 |     "max_records": 100
252 |   }
253 | }
254 | 
255 | // 2. Analyze drug-likeness
256 | {
257 |   "tool": "assess_drug_likeness",
258 |   "arguments": {
259 |     "cid": 2244
260 |   }
261 | }
262 | 
263 | // 3. Check safety profile
264 | {
265 |   "tool": "get_safety_data",
266 |   "arguments": {
267 |     "cid": 2244
268 |   }
269 | }
270 | 
271 | // 4. Find bioactivity data
272 | {
273 |   "tool": "search_by_target",
274 |   "arguments": {
275 |     "target": "COX-2",
276 |     "activity_type": "IC50"
277 |   }
278 | }
279 | ```
280 | 
281 | ### Chemical Similarity Analysis
282 | 
283 | ```javascript
284 | // 1. Find similar compounds
285 | {
286 |   "tool": "search_similar_compounds",
287 |   "arguments": {
288 |     "smiles": "your_query_smiles",
289 |     "threshold": 90
290 |   }
291 | }
292 | 
293 | // 2. Compare molecular properties
294 | {
295 |   "tool": "batch_compound_lookup",
296 |   "arguments": {
297 |     "cids": [1234, 5678, 9012],
298 |     "operation": "property"
299 |   }
300 | }
301 | 
302 | // 3. Analyze structural features
303 | {
304 |   "tool": "analyze_stereochemistry",
305 |   "arguments": {
306 |     "cid": 1234
307 |   }
308 | }
309 | ```
310 | 
311 | ## Architecture
312 | 
313 | The server is built with a modular architecture:
314 | 
315 | - **Type-safe validation** for all API inputs
316 | - **Comprehensive error handling** with clear messages
317 | - **Efficient batch processing** for multiple compounds
318 | - **Resource templates** for direct data access
319 | - **Integration-ready** for multi-database workflows
320 | 
321 | This makes it the most comprehensive chemical informatics MCP server available!
322 | 
323 | ## Citation
324 | If you use this project in your research or publications, please cite it as follows:
325 | 
326 | ```bibtex @misc{pubchemmcp2025, 
327 | author = {Moudather Chelbi},
328 | title = {PubChem MCP Server},
329 | year = {2025},
330 | howpublished = {https://github.com/Augmented-Nature/PubChem-MCP-Server},
331 | note = {Accessed: 2025-06-29}
332 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "ESNext",
 5 |     "moduleResolution": "Node",
 6 |     "outDir": "./build",
 7 |     "rootDir": "./src",
 8 |     "strict": true,
 9 |     "esModuleInterop": true,
10 |     "allowSyntheticDefaultImports": true,
11 |     "forceConsistentCasingInFileNames": true,
12 |     "declaration": true,
13 |     "declarationMap": true,
14 |     "sourceMap": true,
15 |     "skipLibCheck": true,
16 |     "resolveJsonModule": true
17 |   },
18 |   "include": [
19 |     "src/**/*"
20 |   ],
21 |   "exclude": [
22 |     "node_modules",
23 |     "build"
24 |   ]
25 | }
26 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "pubchem-server",
 3 |   "version": "1.0.0",
 4 |   "description": "Model Context Protocol server for PubChem chemical database access",
 5 |   "main": "build/index.js",
 6 |   "type": "module",
 7 |   "scripts": {
 8 |     "build": "tsc && chmod +x build/index.js",
 9 |     "dev": "tsc --watch",
10 |     "start": "node build/index.js"
11 |   },
12 |   "keywords": [
13 |     "mcp",
14 |     "model-context-protocol",
15 |     "pubchem",
16 |     "chemistry",
17 |     "chemical-informatics",
18 |     "drug-discovery",
19 |     "molecular-properties",
20 |     "bioassays",
21 |     "compounds",
22 |     "ncbi"
23 |   ],
24 |   "author": "Augmented Nature <[email protected]>",
25 |   "license": "MIT",
26 |   "dependencies": {
27 |     "@modelcontextprotocol/sdk": "^0.6.0",
28 |     "axios": "^1.6.0"
29 |   },
30 |   "devDependencies": {
31 |     "@types/node": "^20.0.0",
32 |     "typescript": "^5.0.0"
33 |   },
34 |   "repository": {
35 |     "type": "git",
36 |     "url": "https://github.com/augmentednature/pubchem-mcp-server"
37 |   },
38 |   "homepage": "https://augmentednature.ai",
39 |   "engines": {
40 |     "node": ">=18.0.0"
41 |   }
42 | }
43 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
   1 | #!/usr/bin/env node
   2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
   3 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
   4 | import {
   5 |   CallToolRequestSchema,
   6 |   ErrorCode,
   7 |   ListResourcesRequestSchema,
   8 |   ListResourceTemplatesRequestSchema,
   9 |   ListToolsRequestSchema,
  10 |   McpError,
  11 |   ReadResourceRequestSchema,
  12 | } from '@modelcontextprotocol/sdk/types.js';
  13 | import axios, { AxiosInstance } from 'axios';
  14 | 
  15 | // PubChem API interfaces
  16 | interface CompoundSearchResult {
  17 |   IdentifierList: {
  18 |     CID: number[];
  19 |   };
  20 | }
  21 | 
  22 | interface PropertyData {
  23 |   PropertyTable: {
  24 |     Properties: Array<{
  25 |       CID: number;
  26 |       MolecularFormula?: string;
  27 |       MolecularWeight?: number;
  28 |       CanonicalSMILES?: string;
  29 |       IsomericSMILES?: string;
  30 |       InChI?: string;
  31 |       InChIKey?: string;
  32 |       IUPACName?: string;
  33 |       XLogP?: number;
  34 |       TPSA?: number;
  35 |       Complexity?: number;
  36 |       Charge?: number;
  37 |       HBondDonorCount?: number;
  38 |       HBondAcceptorCount?: number;
  39 |       RotatableBondCount?: number;
  40 |       HeavyAtomCount?: number;
  41 |       AtomStereoCount?: number;
  42 |       DefinedAtomStereoCount?: number;
  43 |       BondStereoCount?: number;
  44 |       DefinedBondStereoCount?: number;
  45 |       Volume3D?: number;
  46 |       ConformerCount3D?: number;
  47 |     }>;
  48 |   };
  49 | }
  50 | 
  51 | // Type guards and validation functions
  52 | const isValidCompoundSearchArgs = (
  53 |   args: any
  54 | ): args is { query: string; search_type?: string; max_records?: number } => {
  55 |   return (
  56 |     typeof args === 'object' &&
  57 |     args !== null &&
  58 |     typeof args.query === 'string' &&
  59 |     args.query.length > 0 &&
  60 |     (args.search_type === undefined || ['name', 'smiles', 'inchi', 'sdf', 'cid', 'formula'].includes(args.search_type)) &&
  61 |     (args.max_records === undefined || (typeof args.max_records === 'number' && args.max_records > 0 && args.max_records <= 10000))
  62 |   );
  63 | };
  64 | 
  65 | const isValidCidArgs = (
  66 |   args: any
  67 | ): args is { cid: number | string; format?: string } => {
  68 |   return (
  69 |     typeof args === 'object' &&
  70 |     args !== null &&
  71 |     (typeof args.cid === 'number' || typeof args.cid === 'string') &&
  72 |     (args.format === undefined || ['json', 'sdf', 'xml', 'asnt', 'asnb'].includes(args.format))
  73 |   );
  74 | };
  75 | 
  76 | const isValidSmilesArgs = (
  77 |   args: any
  78 | ): args is { smiles: string; threshold?: number; max_records?: number } => {
  79 |   return (
  80 |     typeof args === 'object' &&
  81 |     args !== null &&
  82 |     typeof args.smiles === 'string' &&
  83 |     args.smiles.length > 0 &&
  84 |     (args.threshold === undefined || (typeof args.threshold === 'number' && args.threshold >= 0 && args.threshold <= 100)) &&
  85 |     (args.max_records === undefined || (typeof args.max_records === 'number' && args.max_records > 0 && args.max_records <= 10000))
  86 |   );
  87 | };
  88 | 
  89 | const isValidBatchArgs = (
  90 |   args: any
  91 | ): args is { cids: number[]; operation?: string } => {
  92 |   return (
  93 |     typeof args === 'object' &&
  94 |     args !== null &&
  95 |     Array.isArray(args.cids) &&
  96 |     args.cids.length > 0 &&
  97 |     args.cids.length <= 200 &&
  98 |     args.cids.every((cid: any) => typeof cid === 'number' && cid > 0) &&
  99 |     (args.operation === undefined || ['property', 'synonyms', 'classification', 'description'].includes(args.operation))
 100 |   );
 101 | };
 102 | 
 103 | const isValidConformerArgs = (
 104 |   args: any
 105 | ): args is { cid: number | string; conformer_type?: string } => {
 106 |   return (
 107 |     typeof args === 'object' &&
 108 |     args !== null &&
 109 |     (typeof args.cid === 'number' || typeof args.cid === 'string') &&
 110 |     (args.conformer_type === undefined || ['3d', '2d'].includes(args.conformer_type))
 111 |   );
 112 | };
 113 | 
 114 | const isValidPropertiesArgs = (
 115 |   args: any
 116 | ): args is { cid: number | string; properties?: string[] } => {
 117 |   return (
 118 |     typeof args === 'object' &&
 119 |     args !== null &&
 120 |     (typeof args.cid === 'number' || typeof args.cid === 'string') &&
 121 |     (args.properties === undefined || (Array.isArray(args.properties) && args.properties.every((p: any) => typeof p === 'string')))
 122 |   );
 123 | };
 124 | 
 125 | class PubChemServer {
 126 |   private server: Server;
 127 |   private apiClient: AxiosInstance;
 128 | 
 129 |   constructor() {
 130 |     this.server = new Server(
 131 |       {
 132 |         name: 'pubchem-server',
 133 |         version: '1.0.0',
 134 |       },
 135 |       {
 136 |         capabilities: {
 137 |           resources: {},
 138 |           tools: {},
 139 |         },
 140 |       }
 141 |     );
 142 | 
 143 |     // Initialize PubChem API client
 144 |     this.apiClient = axios.create({
 145 |       baseURL: 'https://pubchem.ncbi.nlm.nih.gov/rest/pug',
 146 |       timeout: 30000,
 147 |       headers: {
 148 |         'User-Agent': 'PubChem-MCP-Server/1.0.0',
 149 |         'Accept': 'application/json',
 150 |       },
 151 |     });
 152 | 
 153 |     this.setupResourceHandlers();
 154 |     this.setupToolHandlers();
 155 | 
 156 |     // Error handling
 157 |     this.server.onerror = (error: any) => console.error('[MCP Error]', error);
 158 |     process.on('SIGINT', async () => {
 159 |       await this.server.close();
 160 |       process.exit(0);
 161 |     });
 162 |   }
 163 | 
 164 |   private setupResourceHandlers() {
 165 |     // List available resource templates
 166 |     this.server.setRequestHandler(
 167 |       ListResourceTemplatesRequestSchema,
 168 |       async () => ({
 169 |         resourceTemplates: [
 170 |           {
 171 |             uriTemplate: 'pubchem://compound/{cid}',
 172 |             name: 'PubChem compound entry',
 173 |             mimeType: 'application/json',
 174 |             description: 'Complete compound information for a PubChem CID',
 175 |           },
 176 |           {
 177 |             uriTemplate: 'pubchem://structure/{cid}',
 178 |             name: 'Chemical structure data',
 179 |             mimeType: 'application/json',
 180 |             description: '2D/3D structure information for a compound',
 181 |           },
 182 |           {
 183 |             uriTemplate: 'pubchem://properties/{cid}',
 184 |             name: 'Chemical properties',
 185 |             mimeType: 'application/json',
 186 |             description: 'Molecular properties and descriptors for a compound',
 187 |           },
 188 |           {
 189 |             uriTemplate: 'pubchem://bioassay/{aid}',
 190 |             name: 'PubChem bioassay data',
 191 |             mimeType: 'application/json',
 192 |             description: 'Bioassay information and results for an AID',
 193 |           },
 194 |           {
 195 |             uriTemplate: 'pubchem://similarity/{smiles}',
 196 |             name: 'Similarity search results',
 197 |             mimeType: 'application/json',
 198 |             description: 'Chemical similarity search results for a SMILES string',
 199 |           },
 200 |           {
 201 |             uriTemplate: 'pubchem://safety/{cid}',
 202 |             name: 'Safety and toxicity data',
 203 |             mimeType: 'application/json',
 204 |             description: 'Safety classifications and toxicity information',
 205 |           },
 206 |         ],
 207 |       })
 208 |     );
 209 | 
 210 |     // Handle resource requests
 211 |     this.server.setRequestHandler(
 212 |       ReadResourceRequestSchema,
 213 |       async (request: any) => {
 214 |         const uri = request.params.uri;
 215 | 
 216 |         // Handle compound info requests
 217 |         const compoundMatch = uri.match(/^pubchem:\/\/compound\/([0-9]+)$/);
 218 |         if (compoundMatch) {
 219 |           const cid = compoundMatch[1];
 220 |           try {
 221 |             const response = await this.apiClient.get(`/compound/cid/${cid}/JSON`);
 222 |             return {
 223 |               contents: [
 224 |                 {
 225 |                   uri: request.params.uri,
 226 |                   mimeType: 'application/json',
 227 |                   text: JSON.stringify(response.data, null, 2),
 228 |                 },
 229 |               ],
 230 |             };
 231 |           } catch (error) {
 232 |             throw new McpError(
 233 |               ErrorCode.InternalError,
 234 |               `Failed to fetch compound ${cid}: ${error instanceof Error ? error.message : 'Unknown error'}`
 235 |             );
 236 |           }
 237 |         }
 238 | 
 239 |         // Handle structure requests
 240 |         const structureMatch = uri.match(/^pubchem:\/\/structure\/([0-9]+)$/);
 241 |         if (structureMatch) {
 242 |           const cid = structureMatch[1];
 243 |           try {
 244 |             const response = await this.apiClient.get(`/compound/cid/${cid}/property/CanonicalSMILES,IsomericSMILES,InChI,InChIKey/JSON`);
 245 |             return {
 246 |               contents: [
 247 |                 {
 248 |                   uri: request.params.uri,
 249 |                   mimeType: 'application/json',
 250 |                   text: JSON.stringify(response.data, null, 2),
 251 |                 },
 252 |               ],
 253 |             };
 254 |           } catch (error) {
 255 |             throw new McpError(
 256 |               ErrorCode.InternalError,
 257 |               `Failed to fetch structure for ${cid}: ${error instanceof Error ? error.message : 'Unknown error'}`
 258 |             );
 259 |           }
 260 |         }
 261 | 
 262 |         // Handle properties requests
 263 |         const propertiesMatch = uri.match(/^pubchem:\/\/properties\/([0-9]+)$/);
 264 |         if (propertiesMatch) {
 265 |           const cid = propertiesMatch[1];
 266 |           try {
 267 |             const response = await this.apiClient.get(`/compound/cid/${cid}/property/MolecularWeight,XLogP,TPSA,HBondDonorCount,HBondAcceptorCount,RotatableBondCount,Complexity/JSON`);
 268 |             return {
 269 |               contents: [
 270 |                 {
 271 |                   uri: request.params.uri,
 272 |                   mimeType: 'application/json',
 273 |                   text: JSON.stringify(response.data, null, 2),
 274 |                 },
 275 |               ],
 276 |             };
 277 |           } catch (error) {
 278 |             throw new McpError(
 279 |               ErrorCode.InternalError,
 280 |               `Failed to fetch properties for ${cid}: ${error instanceof Error ? error.message : 'Unknown error'}`
 281 |             );
 282 |           }
 283 |         }
 284 | 
 285 |         // Handle bioassay requests
 286 |         const bioassayMatch = uri.match(/^pubchem:\/\/bioassay\/([0-9]+)$/);
 287 |         if (bioassayMatch) {
 288 |           const aid = bioassayMatch[1];
 289 |           try {
 290 |             const response = await this.apiClient.get(`/assay/aid/${aid}/JSON`);
 291 |             return {
 292 |               contents: [
 293 |                 {
 294 |                   uri: request.params.uri,
 295 |                   mimeType: 'application/json',
 296 |                   text: JSON.stringify(response.data, null, 2),
 297 |                 },
 298 |               ],
 299 |             };
 300 |           } catch (error) {
 301 |             throw new McpError(
 302 |               ErrorCode.InternalError,
 303 |               `Failed to fetch bioassay ${aid}: ${error instanceof Error ? error.message : 'Unknown error'}`
 304 |             );
 305 |           }
 306 |         }
 307 | 
 308 |         // Handle similarity search requests
 309 |         const similarityMatch = uri.match(/^pubchem:\/\/similarity\/(.+)$/);
 310 |         if (similarityMatch) {
 311 |           const smiles = decodeURIComponent(similarityMatch[1]);
 312 |           try {
 313 |             const response = await this.apiClient.post('/compound/similarity/smiles/JSON', {
 314 |               smiles: smiles,
 315 |               Threshold: 90,
 316 |               MaxRecords: 100,
 317 |             });
 318 |             return {
 319 |               contents: [
 320 |                 {
 321 |                   uri: request.params.uri,
 322 |                   mimeType: 'application/json',
 323 |                   text: JSON.stringify(response.data, null, 2),
 324 |                 },
 325 |               ],
 326 |             };
 327 |           } catch (error) {
 328 |             throw new McpError(
 329 |               ErrorCode.InternalError,
 330 |               `Failed to perform similarity search: ${error instanceof Error ? error.message : 'Unknown error'}`
 331 |             );
 332 |           }
 333 |         }
 334 | 
 335 |         // Handle safety data requests
 336 |         const safetyMatch = uri.match(/^pubchem:\/\/safety\/([0-9]+)$/);
 337 |         if (safetyMatch) {
 338 |           const cid = safetyMatch[1];
 339 |           try {
 340 |             const response = await this.apiClient.get(`/compound/cid/${cid}/classification/JSON`);
 341 |             return {
 342 |               contents: [
 343 |                 {
 344 |                   uri: request.params.uri,
 345 |                   mimeType: 'application/json',
 346 |                   text: JSON.stringify(response.data, null, 2),
 347 |                 },
 348 |               ],
 349 |             };
 350 |           } catch (error) {
 351 |             throw new McpError(
 352 |               ErrorCode.InternalError,
 353 |               `Failed to fetch safety data for ${cid}: ${error instanceof Error ? error.message : 'Unknown error'}`
 354 |             );
 355 |           }
 356 |         }
 357 | 
 358 |         throw new McpError(
 359 |           ErrorCode.InvalidRequest,
 360 |           `Invalid URI format: ${uri}`
 361 |         );
 362 |       }
 363 |     );
 364 |   }
 365 | 
 366 |   private setupToolHandlers() {
 367 |     this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
 368 |       tools: [
 369 |         // Chemical Search & Retrieval (6 tools)
 370 |         {
 371 |           name: 'search_compounds',
 372 |           description: 'Search PubChem database for compounds by name, CAS number, formula, or identifier',
 373 |           inputSchema: {
 374 |             type: 'object',
 375 |             properties: {
 376 |               query: { type: 'string', description: 'Search query (compound name, CAS, formula, or identifier)' },
 377 |               search_type: { type: 'string', enum: ['name', 'smiles', 'inchi', 'sdf', 'cid', 'formula'], description: 'Type of search to perform (default: name)' },
 378 |               max_records: { type: 'number', description: 'Maximum number of results (1-10000, default: 100)', minimum: 1, maximum: 10000 },
 379 |             },
 380 |             required: ['query'],
 381 |           },
 382 |         },
 383 |         {
 384 |           name: 'get_compound_info',
 385 |           description: 'Get detailed information for a specific compound by PubChem CID',
 386 |           inputSchema: {
 387 |             type: 'object',
 388 |             properties: {
 389 |               cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
 390 |               format: { type: 'string', enum: ['json', 'sdf', 'xml', 'asnt', 'asnb'], description: 'Output format (default: json)' },
 391 |             },
 392 |             required: ['cid'],
 393 |           },
 394 |         },
 395 |         {
 396 |           name: 'search_by_smiles',
 397 |           description: 'Search for compounds by SMILES string (exact match)',
 398 |           inputSchema: {
 399 |             type: 'object',
 400 |             properties: {
 401 |               smiles: { type: 'string', description: 'SMILES string of the query molecule' },
 402 |             },
 403 |             required: ['smiles'],
 404 |           },
 405 |         },
 406 |         {
 407 |           name: 'search_by_inchi',
 408 |           description: 'Search for compounds by InChI or InChI key',
 409 |           inputSchema: {
 410 |             type: 'object',
 411 |             properties: {
 412 |               inchi: { type: 'string', description: 'InChI string or InChI key' },
 413 |             },
 414 |             required: ['inchi'],
 415 |           },
 416 |         },
 417 |         {
 418 |           name: 'search_by_cas_number',
 419 |           description: 'Search for compounds by CAS Registry Number',
 420 |           inputSchema: {
 421 |             type: 'object',
 422 |             properties: {
 423 |               cas_number: { type: 'string', description: 'CAS Registry Number (e.g., 50-78-2)' },
 424 |             },
 425 |             required: ['cas_number'],
 426 |           },
 427 |         },
 428 |         {
 429 |           name: 'get_compound_synonyms',
 430 |           description: 'Get all names and synonyms for a compound',
 431 |           inputSchema: {
 432 |             type: 'object',
 433 |             properties: {
 434 |               cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
 435 |             },
 436 |             required: ['cid'],
 437 |           },
 438 |         },
 439 | 
 440 |         // Structure Analysis & Similarity (5 tools)
 441 |         {
 442 |           name: 'search_similar_compounds',
 443 |           description: 'Find chemically similar compounds using Tanimoto similarity',
 444 |           inputSchema: {
 445 |             type: 'object',
 446 |             properties: {
 447 |               smiles: { type: 'string', description: 'SMILES string of the query molecule' },
 448 |               threshold: { type: 'number', description: 'Similarity threshold (0-100, default: 90)', minimum: 0, maximum: 100 },
 449 |               max_records: { type: 'number', description: 'Maximum number of results (1-10000, default: 100)', minimum: 1, maximum: 10000 },
 450 |             },
 451 |             required: ['smiles'],
 452 |           },
 453 |         },
 454 |         {
 455 |           name: 'substructure_search',
 456 |           description: 'Find compounds containing a specific substructure',
 457 |           inputSchema: {
 458 |             type: 'object',
 459 |             properties: {
 460 |               smiles: { type: 'string', description: 'SMILES string of the substructure query' },
 461 |               max_records: { type: 'number', description: 'Maximum number of results (1-10000, default: 100)', minimum: 1, maximum: 10000 },
 462 |             },
 463 |             required: ['smiles'],
 464 |           },
 465 |         },
 466 |         {
 467 |           name: 'superstructure_search',
 468 |           description: 'Find larger compounds that contain the query structure',
 469 |           inputSchema: {
 470 |             type: 'object',
 471 |             properties: {
 472 |               smiles: { type: 'string', description: 'SMILES string of the query structure' },
 473 |               max_records: { type: 'number', description: 'Maximum number of results (1-10000, default: 100)', minimum: 1, maximum: 10000 },
 474 |             },
 475 |             required: ['smiles'],
 476 |           },
 477 |         },
 478 |         {
 479 |           name: 'get_3d_conformers',
 480 |           description: 'Get 3D conformer data and structural information',
 481 |           inputSchema: {
 482 |             type: 'object',
 483 |             properties: {
 484 |               cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
 485 |               conformer_type: { type: 'string', enum: ['3d', '2d'], description: 'Type of conformer data (default: 3d)' },
 486 |             },
 487 |             required: ['cid'],
 488 |           },
 489 |         },
 490 |         {
 491 |           name: 'analyze_stereochemistry',
 492 |           description: 'Analyze stereochemistry, chirality, and isomer information',
 493 |           inputSchema: {
 494 |             type: 'object',
 495 |             properties: {
 496 |               cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
 497 |             },
 498 |             required: ['cid'],
 499 |           },
 500 |         },
 501 | 
 502 |         // Chemical Properties & Descriptors (6 tools)
 503 |         {
 504 |           name: 'get_compound_properties',
 505 |           description: 'Get molecular properties (MW, logP, TPSA, etc.)',
 506 |           inputSchema: {
 507 |             type: 'object',
 508 |             properties: {
 509 |               cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
 510 |               properties: { type: 'array', items: { type: 'string' }, description: 'Specific properties to retrieve (optional)' },
 511 |             },
 512 |             required: ['cid'],
 513 |           },
 514 |         },
 515 |         {
 516 |           name: 'calculate_descriptors',
 517 |           description: 'Calculate comprehensive molecular descriptors and fingerprints',
 518 |           inputSchema: {
 519 |             type: 'object',
 520 |             properties: {
 521 |               cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
 522 |               descriptor_type: { type: 'string', enum: ['all', 'basic', 'topological', '3d'], description: 'Type of descriptors (default: all)' },
 523 |             },
 524 |             required: ['cid'],
 525 |           },
 526 |         },
 527 |         {
 528 |           name: 'predict_admet_properties',
 529 |           description: 'Predict ADMET properties (Absorption, Distribution, Metabolism, Excretion, Toxicity)',
 530 |           inputSchema: {
 531 |             type: 'object',
 532 |             properties: {
 533 |               cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
 534 |               smiles: { type: 'string', description: 'SMILES string (alternative to CID)' },
 535 |             },
 536 |             required: [],
 537 |           },
 538 |         },
 539 |         {
 540 |           name: 'assess_drug_likeness',
 541 |           description: 'Assess drug-likeness using Lipinski Rule of Five, Veber rules, and PAINS filters',
 542 |           inputSchema: {
 543 |             type: 'object',
 544 |             properties: {
 545 |               cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
 546 |               smiles: { type: 'string', description: 'SMILES string (alternative to CID)' },
 547 |             },
 548 |             required: [],
 549 |           },
 550 |         },
 551 |         {
 552 |           name: 'analyze_molecular_complexity',
 553 |           description: 'Analyze molecular complexity and synthetic accessibility',
 554 |           inputSchema: {
 555 |             type: 'object',
 556 |             properties: {
 557 |               cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
 558 |             },
 559 |             required: ['cid'],
 560 |           },
 561 |         },
 562 |         {
 563 |           name: 'get_pharmacophore_features',
 564 |           description: 'Get pharmacophore features and binding site information',
 565 |           inputSchema: {
 566 |             type: 'object',
 567 |             properties: {
 568 |               cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
 569 |             },
 570 |             required: ['cid'],
 571 |           },
 572 |         },
 573 | 
 574 |         // Bioassay & Activity Data (5 tools)
 575 |         {
 576 |           name: 'search_bioassays',
 577 |           description: 'Search for biological assays by target, description, or source',
 578 |           inputSchema: {
 579 |             type: 'object',
 580 |             properties: {
 581 |               query: { type: 'string', description: 'General search query' },
 582 |               target: { type: 'string', description: 'Target protein or gene name' },
 583 |               source: { type: 'string', description: 'Data source (e.g., ChEMBL, NCGC)' },
 584 |               max_records: { type: 'number', description: 'Maximum number of results (1-1000, default: 100)', minimum: 1, maximum: 1000 },
 585 |             },
 586 |             required: [],
 587 |           },
 588 |         },
 589 |         {
 590 |           name: 'get_assay_info',
 591 |           description: 'Get detailed information for a specific bioassay by AID',
 592 |           inputSchema: {
 593 |             type: 'object',
 594 |             properties: {
 595 |               aid: { type: 'number', description: 'PubChem Assay ID (AID)' },
 596 |             },
 597 |             required: ['aid'],
 598 |           },
 599 |         },
 600 |         {
 601 |           name: 'get_compound_bioactivities',
 602 |           description: 'Get all bioassay results and activities for a compound',
 603 |           inputSchema: {
 604 |             type: 'object',
 605 |             properties: {
 606 |               cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
 607 |               activity_outcome: { type: 'string', enum: ['active', 'inactive', 'inconclusive', 'all'], description: 'Filter by activity outcome (default: all)' },
 608 |             },
 609 |             required: ['cid'],
 610 |           },
 611 |         },
 612 |         {
 613 |           name: 'search_by_target',
 614 |           description: 'Find compounds tested against a specific biological target',
 615 |           inputSchema: {
 616 |             type: 'object',
 617 |             properties: {
 618 |               target: { type: 'string', description: 'Target name (gene, protein, or pathway)' },
 619 |               activity_type: { type: 'string', description: 'Type of activity (e.g., IC50, EC50, Ki)' },
 620 |               max_records: { type: 'number', description: 'Maximum number of results (1-1000, default: 100)', minimum: 1, maximum: 1000 },
 621 |             },
 622 |             required: ['target'],
 623 |           },
 624 |         },
 625 |         {
 626 |           name: 'compare_activity_profiles',
 627 |           description: 'Compare bioactivity profiles across multiple compounds',
 628 |           inputSchema: {
 629 |             type: 'object',
 630 |             properties: {
 631 |               cids: { type: 'array', items: { type: 'number' }, description: 'Array of PubChem CIDs (2-50)', minItems: 2, maxItems: 50 },
 632 |               activity_type: { type: 'string', description: 'Specific activity type for comparison (optional)' },
 633 |             },
 634 |             required: ['cids'],
 635 |           },
 636 |         },
 637 | 
 638 |         // Safety & Toxicity (4 tools)
 639 |         {
 640 |           name: 'get_safety_data',
 641 |           description: 'Get GHS hazard classifications and safety information',
 642 |           inputSchema: {
 643 |             type: 'object',
 644 |             properties: {
 645 |               cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
 646 |             },
 647 |             required: ['cid'],
 648 |           },
 649 |         },
 650 |         {
 651 |           name: 'get_toxicity_info',
 652 |           description: 'Get toxicity data including LD50, carcinogenicity, and mutagenicity',
 653 |           inputSchema: {
 654 |             type: 'object',
 655 |             properties: {
 656 |               cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
 657 |             },
 658 |             required: ['cid'],
 659 |           },
 660 |         },
 661 |         {
 662 |           name: 'assess_environmental_fate',
 663 |           description: 'Assess environmental fate including biodegradation and bioaccumulation',
 664 |           inputSchema: {
 665 |             type: 'object',
 666 |             properties: {
 667 |               cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
 668 |             },
 669 |             required: ['cid'],
 670 |           },
 671 |         },
 672 |         {
 673 |           name: 'get_regulatory_info',
 674 |           description: 'Get regulatory information from FDA, EPA, and international agencies',
 675 |           inputSchema: {
 676 |             type: 'object',
 677 |             properties: {
 678 |               cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
 679 |             },
 680 |             required: ['cid'],
 681 |           },
 682 |         },
 683 | 
 684 |         // Cross-References & Integration (4 tools)
 685 |         {
 686 |           name: 'get_external_references',
 687 |           description: 'Get links to external databases (ChEMBL, DrugBank, KEGG, etc.)',
 688 |           inputSchema: {
 689 |             type: 'object',
 690 |             properties: {
 691 |               cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
 692 |             },
 693 |             required: ['cid'],
 694 |           },
 695 |         },
 696 |         {
 697 |           name: 'search_patents',
 698 |           description: 'Search for chemical patents and intellectual property information',
 699 |           inputSchema: {
 700 |             type: 'object',
 701 |             properties: {
 702 |               cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
 703 |               query: { type: 'string', description: 'Patent search query (alternative to CID)' },
 704 |             },
 705 |             required: [],
 706 |           },
 707 |         },
 708 |         {
 709 |           name: 'get_literature_references',
 710 |           description: 'Get PubMed citations and scientific literature references',
 711 |           inputSchema: {
 712 |             type: 'object',
 713 |             properties: {
 714 |               cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
 715 |             },
 716 |             required: ['cid'],
 717 |           },
 718 |         },
 719 |         {
 720 |           name: 'batch_compound_lookup',
 721 |           description: 'Process multiple compound IDs efficiently',
 722 |           inputSchema: {
 723 |             type: 'object',
 724 |             properties: {
 725 |               cids: { type: 'array', items: { type: 'number' }, description: 'Array of PubChem CIDs (1-200)', minItems: 1, maxItems: 200 },
 726 |               operation: { type: 'string', enum: ['property', 'synonyms', 'classification', 'description'], description: 'Operation to perform (default: property)' },
 727 |             },
 728 |             required: ['cids'],
 729 |           },
 730 |         },
 731 |       ],
 732 |     }));
 733 | 
 734 |     this.server.setRequestHandler(CallToolRequestSchema, async (request: any) => {
 735 |       const { name, arguments: args } = request.params;
 736 | 
 737 |       try {
 738 |         switch (name) {
 739 |           // Chemical Search & Retrieval
 740 |           case 'search_compounds':
 741 |             return await this.handleSearchCompounds(args);
 742 |           case 'get_compound_info':
 743 |             return await this.handleGetCompoundInfo(args);
 744 |           case 'search_by_smiles':
 745 |             return await this.handleSearchBySmiles(args);
 746 |           case 'search_by_inchi':
 747 |             return await this.handleSearchByInchi(args);
 748 |           case 'search_by_cas_number':
 749 |             return await this.handleSearchByCasNumber(args);
 750 |           case 'get_compound_synonyms':
 751 |             return await this.handleGetCompoundSynonyms(args);
 752 | 
 753 |           // Structure Analysis & Similarity
 754 |           case 'search_similar_compounds':
 755 |             return await this.handleSearchSimilarCompounds(args);
 756 |           case 'substructure_search':
 757 |             return await this.handleSubstructureSearch(args);
 758 |           case 'superstructure_search':
 759 |             return await this.handleSuperstructureSearch(args);
 760 |           case 'get_3d_conformers':
 761 |             return await this.handleGet3dConformers(args);
 762 |           case 'analyze_stereochemistry':
 763 |             return await this.handleAnalyzeStereochemistry(args);
 764 | 
 765 |           // Chemical Properties & Descriptors
 766 |           case 'get_compound_properties':
 767 |             return await this.handleGetCompoundProperties(args);
 768 |           case 'calculate_descriptors':
 769 |             return await this.handleCalculateDescriptors(args);
 770 |           case 'predict_admet_properties':
 771 |             return await this.handlePredictAdmetProperties(args);
 772 |           case 'assess_drug_likeness':
 773 |             return await this.handleAssessDrugLikeness(args);
 774 |           case 'analyze_molecular_complexity':
 775 |             return await this.handleAnalyzeMolecularComplexity(args);
 776 |           case 'get_pharmacophore_features':
 777 |             return await this.handleGetPharmacophoreFeatures(args);
 778 | 
 779 |           // Bioassay & Activity Data
 780 |           case 'search_bioassays':
 781 |             return await this.handleSearchBioassays(args);
 782 |           case 'get_assay_info':
 783 |             return await this.handleGetAssayInfo(args);
 784 |           case 'get_compound_bioactivities':
 785 |             return await this.handleGetCompoundBioactivities(args);
 786 |           case 'search_by_target':
 787 |             return await this.handleSearchByTarget(args);
 788 |           case 'compare_activity_profiles':
 789 |             return await this.handleCompareActivityProfiles(args);
 790 | 
 791 |           // Safety & Toxicity
 792 |           case 'get_safety_data':
 793 |             return await this.handleGetSafetyData(args);
 794 |           case 'get_toxicity_info':
 795 |             return await this.handleGetToxicityInfo(args);
 796 |           case 'assess_environmental_fate':
 797 |             return await this.handleAssessEnvironmentalFate(args);
 798 |           case 'get_regulatory_info':
 799 |             return await this.handleGetRegulatoryInfo(args);
 800 | 
 801 |           // Cross-References & Integration
 802 |           case 'get_external_references':
 803 |             return await this.handleGetExternalReferences(args);
 804 |           case 'search_patents':
 805 |             return await this.handleSearchPatents(args);
 806 |           case 'get_literature_references':
 807 |             return await this.handleGetLiteratureReferences(args);
 808 |           case 'batch_compound_lookup':
 809 |             return await this.handleBatchCompoundLookup(args);
 810 | 
 811 |           default:
 812 |             throw new McpError(
 813 |               ErrorCode.MethodNotFound,
 814 |               `Unknown tool: ${name}`
 815 |             );
 816 |         }
 817 |       } catch (error) {
 818 |         return {
 819 |           content: [
 820 |             {
 821 |               type: 'text',
 822 |               text: `Error executing tool ${name}: ${error instanceof Error ? error.message : 'Unknown error'}`,
 823 |             },
 824 |           ],
 825 |           isError: true,
 826 |         };
 827 |       }
 828 |     });
 829 |   }
 830 | 
 831 |   // Chemical Search & Retrieval handlers
 832 |   private async handleSearchCompounds(args: any) {
 833 |     if (!isValidCompoundSearchArgs(args)) {
 834 |       throw new McpError(ErrorCode.InvalidParams, 'Invalid compound search arguments');
 835 |     }
 836 | 
 837 |     try {
 838 |       const searchType = args.search_type || 'name';
 839 |       const maxRecords = args.max_records || 100;
 840 | 
 841 |       const response = await this.apiClient.get(`/compound/${searchType}/${encodeURIComponent(args.query)}/cids/JSON`, {
 842 |         params: {
 843 |           MaxRecords: maxRecords,
 844 |         },
 845 |       });
 846 | 
 847 |       if (response.data?.IdentifierList?.CID?.length > 0) {
 848 |         const cids = response.data.IdentifierList.CID.slice(0, 10);
 849 |         const detailsResponse = await this.apiClient.get(`/compound/cid/${cids.join(',')}/property/MolecularFormula,MolecularWeight,CanonicalSMILES,IUPACName/JSON`);
 850 | 
 851 |         return {
 852 |           content: [
 853 |             {
 854 |               type: 'text',
 855 |               text: JSON.stringify({
 856 |                 query: args.query,
 857 |                 search_type: searchType,
 858 |                 total_found: response.data.IdentifierList.CID.length,
 859 |                 details: detailsResponse.data,
 860 |               }, null, 2),
 861 |             },
 862 |           ],
 863 |         };
 864 |       }
 865 | 
 866 |       return {
 867 |         content: [
 868 |           {
 869 |             type: 'text',
 870 |             text: JSON.stringify({ message: 'No compounds found', query: args.query }, null, 2),
 871 |           },
 872 |         ],
 873 |       };
 874 |     } catch (error) {
 875 |       throw new McpError(
 876 |         ErrorCode.InternalError,
 877 |         `Failed to search compounds: ${error instanceof Error ? error.message : 'Unknown error'}`
 878 |       );
 879 |     }
 880 |   }
 881 | 
 882 |   private async handleGetCompoundInfo(args: any) {
 883 |     if (!isValidCidArgs(args)) {
 884 |       throw new McpError(ErrorCode.InvalidParams, 'Invalid CID arguments');
 885 |     }
 886 | 
 887 |     try {
 888 |       const format = args.format || 'json';
 889 |       const response = await this.apiClient.get(`/compound/cid/${args.cid}/${format === 'json' ? 'JSON' : format}`);
 890 | 
 891 |       return {
 892 |         content: [
 893 |           {
 894 |             type: 'text',
 895 |             text: format === 'json' ? JSON.stringify(response.data, null, 2) : String(response.data),
 896 |           },
 897 |         ],
 898 |       };
 899 |     } catch (error) {
 900 |       throw new McpError(
 901 |         ErrorCode.InternalError,
 902 |         `Failed to get compound info: ${error instanceof Error ? error.message : 'Unknown error'}`
 903 |       );
 904 |     }
 905 |   }
 906 | 
 907 |   private async handleSearchBySmiles(args: any) {
 908 |     if (!isValidSmilesArgs(args)) {
 909 |       throw new McpError(ErrorCode.InvalidParams, 'Invalid SMILES arguments');
 910 |     }
 911 | 
 912 |     try {
 913 |       const response = await this.apiClient.get(`/compound/smiles/${encodeURIComponent(args.smiles)}/cids/JSON`);
 914 | 
 915 |       if (response.data?.IdentifierList?.CID?.length > 0) {
 916 |         const cid = response.data.IdentifierList.CID[0];
 917 |         const detailsResponse = await this.apiClient.get(`/compound/cid/${cid}/property/MolecularFormula,MolecularWeight,CanonicalSMILES,IUPACName/JSON`);
 918 | 
 919 |         return {
 920 |           content: [
 921 |             {
 922 |               type: 'text',
 923 |               text: JSON.stringify({
 924 |                 query_smiles: args.smiles,
 925 |                 found_cid: cid,
 926 |                 details: detailsResponse.data,
 927 |               }, null, 2),
 928 |             },
 929 |           ],
 930 |         };
 931 |       }
 932 | 
 933 |       return {
 934 |         content: [
 935 |           {
 936 |             type: 'text',
 937 |             text: JSON.stringify({ message: 'No exact match found', query_smiles: args.smiles }, null, 2),
 938 |           },
 939 |         ],
 940 |       };
 941 |     } catch (error) {
 942 |       throw new McpError(
 943 |         ErrorCode.InternalError,
 944 |         `Failed to search by SMILES: ${error instanceof Error ? error.message : 'Unknown error'}`
 945 |       );
 946 |     }
 947 |   }
 948 | 
 949 |   // Simplified implementation handlers (placeholder implementations)
 950 |   private async handleSearchByInchi(args: any) {
 951 |     return { content: [{ type: 'text', text: JSON.stringify({ message: 'InChI search not yet implemented', args }, null, 2) }] };
 952 |   }
 953 | 
 954 |   private async handleSearchByCasNumber(args: any) {
 955 |     return { content: [{ type: 'text', text: JSON.stringify({ message: 'CAS search not yet implemented', args }, null, 2) }] };
 956 |   }
 957 | 
 958 |   private async handleGetCompoundSynonyms(args: any) {
 959 |     if (!isValidCidArgs(args)) {
 960 |       throw new McpError(ErrorCode.InvalidParams, 'Invalid CID arguments');
 961 |     }
 962 | 
 963 |     try {
 964 |       const response = await this.apiClient.get(`/compound/cid/${args.cid}/synonyms/JSON`);
 965 |       return {
 966 |         content: [
 967 |           {
 968 |             type: 'text',
 969 |             text: JSON.stringify(response.data, null, 2),
 970 |           },
 971 |         ],
 972 |       };
 973 |     } catch (error) {
 974 |       throw new McpError(
 975 |         ErrorCode.InternalError,
 976 |         `Failed to get compound synonyms: ${error instanceof Error ? error.message : 'Unknown error'}`
 977 |       );
 978 |     }
 979 |   }
 980 | 
 981 |   private async handleSearchSimilarCompounds(args: any) {
 982 |     if (!isValidSmilesArgs(args)) {
 983 |       throw new McpError(ErrorCode.InvalidParams, 'Invalid similarity search arguments');
 984 |     }
 985 | 
 986 |     try {
 987 |       const threshold = args.threshold || 90;
 988 |       const maxRecords = args.max_records || 100;
 989 | 
 990 |       const response = await this.apiClient.post('/compound/similarity/smiles/JSON', {
 991 |         smiles: args.smiles,
 992 |         Threshold: threshold,
 993 |         MaxRecords: maxRecords,
 994 |       });
 995 | 
 996 |       return {
 997 |         content: [
 998 |           {
 999 |             type: 'text',
1000 |             text: JSON.stringify(response.data, null, 2),
1001 |           },
1002 |         ],
1003 |       };
1004 |     } catch (error) {
1005 |       throw new McpError(
1006 |         ErrorCode.InternalError,
1007 |         `Failed to search similar compounds: ${error instanceof Error ? error.message : 'Unknown error'}`
1008 |       );
1009 |     }
1010 |   }
1011 | 
1012 |   private async handleSubstructureSearch(args: any) {
1013 |     return { content: [{ type: 'text', text: JSON.stringify({ message: 'Substructure search not yet implemented', args }, null, 2) }] };
1014 |   }
1015 | 
1016 |   private async handleSuperstructureSearch(args: any) {
1017 |     return { content: [{ type: 'text', text: JSON.stringify({ message: 'Superstructure search not yet implemented', args }, null, 2) }] };
1018 |   }
1019 | 
1020 |   private async handleGet3dConformers(args: any) {
1021 |     if (!isValidConformerArgs(args)) {
1022 |       throw new McpError(ErrorCode.InvalidParams, 'Invalid 3D conformer arguments');
1023 |     }
1024 | 
1025 |     try {
1026 |       const response = await this.apiClient.get(`/compound/cid/${args.cid}/property/Volume3D,ConformerCount3D/JSON`);
1027 | 
1028 |       return {
1029 |         content: [
1030 |           {
1031 |             type: 'text',
1032 |             text: JSON.stringify({
1033 |               cid: args.cid,
1034 |               conformer_type: args.conformer_type || '3d',
1035 |               properties: response.data,
1036 |             }, null, 2),
1037 |           },
1038 |         ],
1039 |       };
1040 |     } catch (error) {
1041 |       throw new McpError(
1042 |         ErrorCode.InternalError,
1043 |         `Failed to get 3D conformers: ${error instanceof Error ? error.message : 'Unknown error'}`
1044 |       );
1045 |     }
1046 |   }
1047 | 
1048 |   private async handleAnalyzeStereochemistry(args: any) {
1049 |     if (!isValidCidArgs(args)) {
1050 |       throw new McpError(ErrorCode.InvalidParams, 'Invalid stereochemistry arguments');
1051 |     }
1052 | 
1053 |     try {
1054 |       const response = await this.apiClient.get(`/compound/cid/${args.cid}/property/AtomStereoCount,DefinedAtomStereoCount,BondStereoCount,DefinedBondStereoCount,IsomericSMILES/JSON`);
1055 | 
1056 |       return {
1057 |         content: [
1058 |           {
1059 |             type: 'text',
1060 |             text: JSON.stringify({
1061 |               cid: args.cid,
1062 |               stereochemistry: response.data,
1063 |             }, null, 2),
1064 |           },
1065 |         ],
1066 |       };
1067 |     } catch (error) {
1068 |       throw new McpError(
1069 |         ErrorCode.InternalError,
1070 |         `Failed to analyze stereochemistry: ${error instanceof Error ? error.message : 'Unknown error'}`
1071 |       );
1072 |     }
1073 |   }
1074 | 
1075 |   private async handleGetCompoundProperties(args: any) {
1076 |     if (!isValidPropertiesArgs(args)) {
1077 |       throw new McpError(ErrorCode.InvalidParams, 'Invalid compound properties arguments');
1078 |     }
1079 | 
1080 |     try {
1081 |       const properties = args.properties || [
1082 |         'MolecularWeight', 'XLogP', 'TPSA', 'HBondDonorCount', 'HBondAcceptorCount',
1083 |         'RotatableBondCount', 'Complexity', 'HeavyAtomCount', 'Charge'
1084 |       ];
1085 | 
1086 |       const response = await this.apiClient.get(`/compound/cid/${args.cid}/property/${properties.join(',')}/JSON`);
1087 | 
1088 |       return {
1089 |         content: [
1090 |           {
1091 |             type: 'text',
1092 |             text: JSON.stringify(response.data, null, 2),
1093 |           },
1094 |         ],
1095 |       };
1096 |     } catch (error) {
1097 |       throw new McpError(
1098 |         ErrorCode.InternalError,
1099 |         `Failed to get compound properties: ${error instanceof Error ? error.message : 'Unknown error'}`
1100 |       );
1101 |     }
1102 |   }
1103 | 
1104 |   // Placeholder implementations for remaining methods
1105 |   private async handleCalculateDescriptors(args: any) {
1106 |     return { content: [{ type: 'text', text: JSON.stringify({ message: 'Descriptor calculation not yet implemented', args }, null, 2) }] };
1107 |   }
1108 | 
1109 |   private async handlePredictAdmetProperties(args: any) {
1110 |     return { content: [{ type: 'text', text: JSON.stringify({ message: 'ADMET prediction not yet implemented', args }, null, 2) }] };
1111 |   }
1112 | 
1113 |   private async handleAssessDrugLikeness(args: any) {
1114 |     return { content: [{ type: 'text', text: JSON.stringify({ message: 'Drug-likeness assessment not yet implemented', args }, null, 2) }] };
1115 |   }
1116 | 
1117 |   private async handleAnalyzeMolecularComplexity(args: any) {
1118 |     return { content: [{ type: 'text', text: JSON.stringify({ message: 'Molecular complexity analysis not yet implemented', args }, null, 2) }] };
1119 |   }
1120 | 
1121 |   private async handleGetPharmacophoreFeatures(args: any) {
1122 |     return { content: [{ type: 'text', text: JSON.stringify({ message: 'Pharmacophore features not yet implemented', args }, null, 2) }] };
1123 |   }
1124 | 
1125 |   private async handleSearchBioassays(args: any) {
1126 |     return { content: [{ type: 'text', text: JSON.stringify({ message: 'Bioassay search not yet implemented', args }, null, 2) }] };
1127 |   }
1128 | 
1129 |   private async handleGetAssayInfo(args: any) {
1130 |     try {
1131 |       const response = await this.apiClient.get(`/assay/aid/${args.aid}/JSON`);
1132 |       return {
1133 |         content: [
1134 |           {
1135 |             type: 'text',
1136 |             text: JSON.stringify(response.data, null, 2),
1137 |           },
1138 |         ],
1139 |       };
1140 |     } catch (error) {
1141 |       throw new McpError(
1142 |         ErrorCode.InternalError,
1143 |         `Failed to get assay info: ${error instanceof Error ? error.message : 'Unknown error'}`
1144 |       );
1145 |     }
1146 |   }
1147 | 
1148 |   private async handleGetCompoundBioactivities(args: any) {
1149 |     return { content: [{ type: 'text', text: JSON.stringify({ message: 'Bioactivity search not yet implemented', args }, null, 2) }] };
1150 |   }
1151 | 
1152 |   private async handleSearchByTarget(args: any) {
1153 |     return { content: [{ type: 'text', text: JSON.stringify({ message: 'Target search not yet implemented', args }, null, 2) }] };
1154 |   }
1155 | 
1156 |   private async handleCompareActivityProfiles(args: any) {
1157 |     return { content: [{ type: 'text', text: JSON.stringify({ message: 'Activity profile comparison not yet implemented', args }, null, 2) }] };
1158 |   }
1159 | 
1160 |   private async handleGetSafetyData(args: any) {
1161 |     if (!isValidCidArgs(args)) {
1162 |       throw new McpError(ErrorCode.InvalidParams, 'Invalid CID arguments');
1163 |     }
1164 | 
1165 |     try {
1166 |       const response = await this.apiClient.get(`/compound/cid/${args.cid}/classification/JSON`);
1167 |       return {
1168 |         content: [
1169 |           {
1170 |             type: 'text',
1171 |             text: JSON.stringify(response.data, null, 2),
1172 |           },
1173 |         ],
1174 |       };
1175 |     } catch (error) {
1176 |       throw new McpError(
1177 |         ErrorCode.InternalError,
1178 |         `Failed to get safety data: ${error instanceof Error ? error.message : 'Unknown error'}`
1179 |       );
1180 |     }
1181 |   }
1182 | 
1183 |   private async handleGetToxicityInfo(args: any) {
1184 |     return { content: [{ type: 'text', text: JSON.stringify({ message: 'Toxicity info not yet implemented', args }, null, 2) }] };
1185 |   }
1186 | 
1187 |   private async handleAssessEnvironmentalFate(args: any) {
1188 |     return { content: [{ type: 'text', text: JSON.stringify({ message: 'Environmental fate assessment not yet implemented', args }, null, 2) }] };
1189 |   }
1190 | 
1191 |   private async handleGetRegulatoryInfo(args: any) {
1192 |     return { content: [{ type: 'text', text: JSON.stringify({ message: 'Regulatory info not yet implemented', args }, null, 2) }] };
1193 |   }
1194 | 
1195 |   private async handleGetExternalReferences(args: any) {
1196 |     return { content: [{ type: 'text', text: JSON.stringify({ message: 'External references not yet implemented', args }, null, 2) }] };
1197 |   }
1198 | 
1199 |   private async handleSearchPatents(args: any) {
1200 |     return { content: [{ type: 'text', text: JSON.stringify({ message: 'Patent search not yet implemented', args }, null, 2) }] };
1201 |   }
1202 | 
1203 |   private async handleGetLiteratureReferences(args: any) {
1204 |     return { content: [{ type: 'text', text: JSON.stringify({ message: 'Literature references not yet implemented', args }, null, 2) }] };
1205 |   }
1206 | 
1207 |   private async handleBatchCompoundLookup(args: any) {
1208 |     if (!isValidBatchArgs(args)) {
1209 |       throw new McpError(ErrorCode.InvalidParams, 'Invalid batch arguments');
1210 |     }
1211 | 
1212 |     try {
1213 |       const results = [];
1214 |       for (const cid of args.cids.slice(0, 10)) {
1215 |         try {
1216 |           const response = await this.apiClient.get(`/compound/cid/${cid}/property/MolecularWeight,CanonicalSMILES,IUPACName/JSON`);
1217 |           results.push({ cid, data: response.data, success: true });
1218 |         } catch (error) {
1219 |           results.push({ cid, error: error instanceof Error ? error.message : 'Unknown error', success: false });
1220 |         }
1221 |       }
1222 | 
1223 |       return { content: [{ type: 'text', text: JSON.stringify({ batch_results: results }, null, 2) }] };
1224 |     } catch (error) {
1225 |       throw new McpError(ErrorCode.InternalError, `Batch lookup failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
1226 |     }
1227 |   }
1228 | 
1229 |   async run() {
1230 |     const transport = new StdioServerTransport();
1231 |     await this.server.connect(transport);
1232 |     console.error('PubChem MCP server running on stdio');
1233 |   }
1234 | }
1235 | 
1236 | const server = new PubChemServer();
1237 | server.run().catch(console.error);
1238 | 
```