# 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 | 
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 |
```