#
tokens: 12324/50000 4/4 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

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

# Files

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

```markdown
![PubChem MCP Server Logo](pubchem-mcp-server-logo.png)
# Unofficial PubChem MCP Server

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.

## Features

### 🔍 **Chemical Search & Retrieval (6 tools)**

- **search_compounds** - Search by name, CAS number, formula, or identifier
- **get_compound_info** - Detailed compound information by CID
- **search_by_smiles** - Exact SMILES string matching
- **search_by_inchi** - InChI/InChI key search
- **search_by_cas_number** - CAS Registry Number lookup
- **get_compound_synonyms** - All names and synonyms

### 🧬 **Structure Analysis & Similarity (5 tools)**

- **search_similar_compounds** - Tanimoto similarity search
- **substructure_search** - Find compounds containing substructures
- **superstructure_search** - Find larger compounds containing query
- **get_3d_conformers** - 3D structural information
- **analyze_stereochemistry** - Chirality and isomer analysis

### ⚗️ **Chemical Properties & Descriptors (6 tools)**

- **get_compound_properties** - Molecular weight, logP, TPSA, etc.
- **calculate_descriptors** - Comprehensive molecular descriptors
- **predict_admet_properties** - ADMET predictions
- **assess_drug_likeness** - Lipinski Rule of Five analysis
- **analyze_molecular_complexity** - Synthetic accessibility
- **get_pharmacophore_features** - Pharmacophore mapping

### 🧪 **Bioassay & Activity Data (5 tools)**

- **search_bioassays** - Find biological assays
- **get_assay_info** - Detailed assay protocols
- **get_compound_bioactivities** - All activity data for compounds
- **search_by_target** - Find compounds tested against targets
- **compare_activity_profiles** - Cross-compound comparisons

### ⚠️ **Safety & Toxicity (4 tools)**

- **get_safety_data** - GHS hazard classifications
- **get_toxicity_info** - LD50, carcinogenicity data
- **assess_environmental_fate** - Biodegradation analysis
- **get_regulatory_info** - FDA, EPA regulations

### 🔗 **Cross-References & Integration (4 tools)**

- **get_external_references** - Links to ChEMBL, DrugBank, etc.
- **search_patents** - Chemical patent information
- **get_literature_references** - PubMed citations
- **batch_compound_lookup** - Bulk processing (up to 200 compounds)

## Resource Templates

Access PubChem data through URI patterns:

- `pubchem://compound/{cid}` - Complete compound data
- `pubchem://structure/{cid}` - 2D/3D structure information
- `pubchem://properties/{cid}` - Molecular properties
- `pubchem://bioassay/{aid}` - Bioassay data
- `pubchem://similarity/{smiles}` - Similarity search results
- `pubchem://safety/{cid}` - Safety and toxicity data

## Installation

```bash
# Clone or create the server directory
cd pubchem-server

# Install dependencies
npm install

# Build the server
npm run build

# Run the server
npm start
```

## Usage

### Basic Compound Search

```javascript
// Search for compounds by name
{
  "tool": "search_compounds",
  "arguments": {
    "query": "aspirin",
    "max_records": 10
  }
}

// Get detailed compound information
{
  "tool": "get_compound_info",
  "arguments": {
    "cid": 2244
  }
}
```

### Structure Analysis

```javascript
// Find similar compounds
{
  "tool": "search_similar_compounds",
  "arguments": {
    "smiles": "CC(=O)OC1=CC=CC=C1C(=O)O",
    "threshold": 85,
    "max_records": 50
  }
}

// Analyze molecular properties
{
  "tool": "get_compound_properties",
  "arguments": {
    "cid": 2244,
    "properties": ["MolecularWeight", "XLogP", "TPSA"]
  }
}
```

### Bioactivity Analysis

```javascript
// Get bioassay information
{
  "tool": "get_assay_info",
  "arguments": {
    "aid": 1159607
  }
}

// Search compounds by target
{
  "tool": "search_by_target",
  "arguments": {
    "target": "cyclooxygenase",
    "max_records": 100
  }
}
```

### Safety Information

```javascript
// Get safety classifications
{
  "tool": "get_safety_data",
  "arguments": {
    "cid": 2244
  }
}
```

### Batch Processing

```javascript
// Process multiple compounds
{
  "tool": "batch_compound_lookup",
  "arguments": {
    "cids": [2244, 5090, 3672],
    "operation": "property"
  }
}
```

## Integration with Other MCP Servers

This PubChem server integrates perfectly with other chemical/biological databases:

### Complete Chemical Informatics Pipeline

```
1. Target Discovery: UniProt → STRING → AlphaFold
2. Chemical Discovery: PubChem ← → ChEMBL
3. Complete Workflow: Protein → Structure → Interactions → Small Molecules → Bioactivity
```

### Cross-Database Workflows

- **UniProt → PubChem**: Find protein targets → Find small molecule ligands
- **PubChem → ChEMBL**: Discover compounds → Analyze bioactivity data
- **STRING → PubChem**: Protein interactions → Chemical modulators

## API Rate Limits

PubChem API guidelines:

- **5 requests per second**
- **400 requests per minute maximum**
- No API key required
- Respectful usage encouraged

## Data Sources

- **110+ million compounds** with full chemical data
- **1.5+ million bioassays** with biological activity
- **Chemical properties** and computed descriptors
- **3D conformers** and structural data
- **Safety classifications** and toxicity information
- **Cross-references** to 500+ external databases

## Error Handling

The server includes comprehensive error handling:

- Invalid compound IDs return clear error messages
- API timeouts are handled gracefully
- Rate limiting compliance built-in
- TypeScript validation for all inputs

## Contributing

This server uses:

- **TypeScript** for type safety
- **Axios** for HTTP requests
- **MCP SDK** for protocol compliance
- **PubChem REST API** for data access

## License

MIT License - See LICENSE file for details.

## Support

For issues with:

- **Server functionality**: Check error messages and API responses
- **PubChem API**: Refer to official PubChem documentation
- **Chemical data**: Validate compound identifiers and search terms

## Examples

### Drug Discovery Workflow

```javascript
// 1. Search for anti-inflammatory compounds
{
  "tool": "search_compounds",
  "arguments": {
    "query": "anti-inflammatory",
    "max_records": 100
  }
}

// 2. Analyze drug-likeness
{
  "tool": "assess_drug_likeness",
  "arguments": {
    "cid": 2244
  }
}

// 3. Check safety profile
{
  "tool": "get_safety_data",
  "arguments": {
    "cid": 2244
  }
}

// 4. Find bioactivity data
{
  "tool": "search_by_target",
  "arguments": {
    "target": "COX-2",
    "activity_type": "IC50"
  }
}
```

### Chemical Similarity Analysis

```javascript
// 1. Find similar compounds
{
  "tool": "search_similar_compounds",
  "arguments": {
    "smiles": "your_query_smiles",
    "threshold": 90
  }
}

// 2. Compare molecular properties
{
  "tool": "batch_compound_lookup",
  "arguments": {
    "cids": [1234, 5678, 9012],
    "operation": "property"
  }
}

// 3. Analyze structural features
{
  "tool": "analyze_stereochemistry",
  "arguments": {
    "cid": 1234
  }
}
```

## Architecture

The server is built with a modular architecture:

- **Type-safe validation** for all API inputs
- **Comprehensive error handling** with clear messages
- **Efficient batch processing** for multiple compounds
- **Resource templates** for direct data access
- **Integration-ready** for multi-database workflows

This makes it the most comprehensive chemical informatics MCP server available!

## Citation
If you use this project in your research or publications, please cite it as follows:

```bibtex @misc{pubchemmcp2025, 
author = {Moudather Chelbi},
title = {PubChem MCP Server},
year = {2025},
howpublished = {https://github.com/Augmented-Nature/PubChem-MCP-Server},
note = {Accessed: 2025-06-29}

```

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

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Node",
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "forceConsistentCasingInFileNames": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "skipLibCheck": true,
    "resolveJsonModule": true
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules",
    "build"
  ]
}

```

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

```json
{
  "name": "pubchem-server",
  "version": "1.0.0",
  "description": "Model Context Protocol server for PubChem chemical database access",
  "main": "build/index.js",
  "type": "module",
  "scripts": {
    "build": "tsc && chmod +x build/index.js",
    "dev": "tsc --watch",
    "start": "node build/index.js"
  },
  "keywords": [
    "mcp",
    "model-context-protocol",
    "pubchem",
    "chemistry",
    "chemical-informatics",
    "drug-discovery",
    "molecular-properties",
    "bioassays",
    "compounds",
    "ncbi"
  ],
  "author": "Augmented Nature <[email protected]>",
  "license": "MIT",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^0.6.0",
    "axios": "^1.6.0"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "typescript": "^5.0.0"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/augmentednature/pubchem-mcp-server"
  },
  "homepage": "https://augmentednature.ai",
  "engines": {
    "node": ">=18.0.0"
  }
}

```

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

```typescript
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ErrorCode,
  ListResourcesRequestSchema,
  ListResourceTemplatesRequestSchema,
  ListToolsRequestSchema,
  McpError,
  ReadResourceRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import axios, { AxiosInstance } from 'axios';

// PubChem API interfaces
interface CompoundSearchResult {
  IdentifierList: {
    CID: number[];
  };
}

interface PropertyData {
  PropertyTable: {
    Properties: Array<{
      CID: number;
      MolecularFormula?: string;
      MolecularWeight?: number;
      CanonicalSMILES?: string;
      IsomericSMILES?: string;
      InChI?: string;
      InChIKey?: string;
      IUPACName?: string;
      XLogP?: number;
      TPSA?: number;
      Complexity?: number;
      Charge?: number;
      HBondDonorCount?: number;
      HBondAcceptorCount?: number;
      RotatableBondCount?: number;
      HeavyAtomCount?: number;
      AtomStereoCount?: number;
      DefinedAtomStereoCount?: number;
      BondStereoCount?: number;
      DefinedBondStereoCount?: number;
      Volume3D?: number;
      ConformerCount3D?: number;
    }>;
  };
}

// Type guards and validation functions
const isValidCompoundSearchArgs = (
  args: any
): args is { query: string; search_type?: string; max_records?: number } => {
  return (
    typeof args === 'object' &&
    args !== null &&
    typeof args.query === 'string' &&
    args.query.length > 0 &&
    (args.search_type === undefined || ['name', 'smiles', 'inchi', 'sdf', 'cid', 'formula'].includes(args.search_type)) &&
    (args.max_records === undefined || (typeof args.max_records === 'number' && args.max_records > 0 && args.max_records <= 10000))
  );
};

const isValidCidArgs = (
  args: any
): args is { cid: number | string; format?: string } => {
  return (
    typeof args === 'object' &&
    args !== null &&
    (typeof args.cid === 'number' || typeof args.cid === 'string') &&
    (args.format === undefined || ['json', 'sdf', 'xml', 'asnt', 'asnb'].includes(args.format))
  );
};

const isValidSmilesArgs = (
  args: any
): args is { smiles: string; threshold?: number; max_records?: number } => {
  return (
    typeof args === 'object' &&
    args !== null &&
    typeof args.smiles === 'string' &&
    args.smiles.length > 0 &&
    (args.threshold === undefined || (typeof args.threshold === 'number' && args.threshold >= 0 && args.threshold <= 100)) &&
    (args.max_records === undefined || (typeof args.max_records === 'number' && args.max_records > 0 && args.max_records <= 10000))
  );
};

const isValidBatchArgs = (
  args: any
): args is { cids: number[]; operation?: string } => {
  return (
    typeof args === 'object' &&
    args !== null &&
    Array.isArray(args.cids) &&
    args.cids.length > 0 &&
    args.cids.length <= 200 &&
    args.cids.every((cid: any) => typeof cid === 'number' && cid > 0) &&
    (args.operation === undefined || ['property', 'synonyms', 'classification', 'description'].includes(args.operation))
  );
};

const isValidConformerArgs = (
  args: any
): args is { cid: number | string; conformer_type?: string } => {
  return (
    typeof args === 'object' &&
    args !== null &&
    (typeof args.cid === 'number' || typeof args.cid === 'string') &&
    (args.conformer_type === undefined || ['3d', '2d'].includes(args.conformer_type))
  );
};

const isValidPropertiesArgs = (
  args: any
): args is { cid: number | string; properties?: string[] } => {
  return (
    typeof args === 'object' &&
    args !== null &&
    (typeof args.cid === 'number' || typeof args.cid === 'string') &&
    (args.properties === undefined || (Array.isArray(args.properties) && args.properties.every((p: any) => typeof p === 'string')))
  );
};

class PubChemServer {
  private server: Server;
  private apiClient: AxiosInstance;

  constructor() {
    this.server = new Server(
      {
        name: 'pubchem-server',
        version: '1.0.0',
      },
      {
        capabilities: {
          resources: {},
          tools: {},
        },
      }
    );

    // Initialize PubChem API client
    this.apiClient = axios.create({
      baseURL: 'https://pubchem.ncbi.nlm.nih.gov/rest/pug',
      timeout: 30000,
      headers: {
        'User-Agent': 'PubChem-MCP-Server/1.0.0',
        'Accept': 'application/json',
      },
    });

    this.setupResourceHandlers();
    this.setupToolHandlers();

    // Error handling
    this.server.onerror = (error: any) => console.error('[MCP Error]', error);
    process.on('SIGINT', async () => {
      await this.server.close();
      process.exit(0);
    });
  }

  private setupResourceHandlers() {
    // List available resource templates
    this.server.setRequestHandler(
      ListResourceTemplatesRequestSchema,
      async () => ({
        resourceTemplates: [
          {
            uriTemplate: 'pubchem://compound/{cid}',
            name: 'PubChem compound entry',
            mimeType: 'application/json',
            description: 'Complete compound information for a PubChem CID',
          },
          {
            uriTemplate: 'pubchem://structure/{cid}',
            name: 'Chemical structure data',
            mimeType: 'application/json',
            description: '2D/3D structure information for a compound',
          },
          {
            uriTemplate: 'pubchem://properties/{cid}',
            name: 'Chemical properties',
            mimeType: 'application/json',
            description: 'Molecular properties and descriptors for a compound',
          },
          {
            uriTemplate: 'pubchem://bioassay/{aid}',
            name: 'PubChem bioassay data',
            mimeType: 'application/json',
            description: 'Bioassay information and results for an AID',
          },
          {
            uriTemplate: 'pubchem://similarity/{smiles}',
            name: 'Similarity search results',
            mimeType: 'application/json',
            description: 'Chemical similarity search results for a SMILES string',
          },
          {
            uriTemplate: 'pubchem://safety/{cid}',
            name: 'Safety and toxicity data',
            mimeType: 'application/json',
            description: 'Safety classifications and toxicity information',
          },
        ],
      })
    );

    // Handle resource requests
    this.server.setRequestHandler(
      ReadResourceRequestSchema,
      async (request: any) => {
        const uri = request.params.uri;

        // Handle compound info requests
        const compoundMatch = uri.match(/^pubchem:\/\/compound\/([0-9]+)$/);
        if (compoundMatch) {
          const cid = compoundMatch[1];
          try {
            const response = await this.apiClient.get(`/compound/cid/${cid}/JSON`);
            return {
              contents: [
                {
                  uri: request.params.uri,
                  mimeType: 'application/json',
                  text: JSON.stringify(response.data, null, 2),
                },
              ],
            };
          } catch (error) {
            throw new McpError(
              ErrorCode.InternalError,
              `Failed to fetch compound ${cid}: ${error instanceof Error ? error.message : 'Unknown error'}`
            );
          }
        }

        // Handle structure requests
        const structureMatch = uri.match(/^pubchem:\/\/structure\/([0-9]+)$/);
        if (structureMatch) {
          const cid = structureMatch[1];
          try {
            const response = await this.apiClient.get(`/compound/cid/${cid}/property/CanonicalSMILES,IsomericSMILES,InChI,InChIKey/JSON`);
            return {
              contents: [
                {
                  uri: request.params.uri,
                  mimeType: 'application/json',
                  text: JSON.stringify(response.data, null, 2),
                },
              ],
            };
          } catch (error) {
            throw new McpError(
              ErrorCode.InternalError,
              `Failed to fetch structure for ${cid}: ${error instanceof Error ? error.message : 'Unknown error'}`
            );
          }
        }

        // Handle properties requests
        const propertiesMatch = uri.match(/^pubchem:\/\/properties\/([0-9]+)$/);
        if (propertiesMatch) {
          const cid = propertiesMatch[1];
          try {
            const response = await this.apiClient.get(`/compound/cid/${cid}/property/MolecularWeight,XLogP,TPSA,HBondDonorCount,HBondAcceptorCount,RotatableBondCount,Complexity/JSON`);
            return {
              contents: [
                {
                  uri: request.params.uri,
                  mimeType: 'application/json',
                  text: JSON.stringify(response.data, null, 2),
                },
              ],
            };
          } catch (error) {
            throw new McpError(
              ErrorCode.InternalError,
              `Failed to fetch properties for ${cid}: ${error instanceof Error ? error.message : 'Unknown error'}`
            );
          }
        }

        // Handle bioassay requests
        const bioassayMatch = uri.match(/^pubchem:\/\/bioassay\/([0-9]+)$/);
        if (bioassayMatch) {
          const aid = bioassayMatch[1];
          try {
            const response = await this.apiClient.get(`/assay/aid/${aid}/JSON`);
            return {
              contents: [
                {
                  uri: request.params.uri,
                  mimeType: 'application/json',
                  text: JSON.stringify(response.data, null, 2),
                },
              ],
            };
          } catch (error) {
            throw new McpError(
              ErrorCode.InternalError,
              `Failed to fetch bioassay ${aid}: ${error instanceof Error ? error.message : 'Unknown error'}`
            );
          }
        }

        // Handle similarity search requests
        const similarityMatch = uri.match(/^pubchem:\/\/similarity\/(.+)$/);
        if (similarityMatch) {
          const smiles = decodeURIComponent(similarityMatch[1]);
          try {
            const response = await this.apiClient.post('/compound/similarity/smiles/JSON', {
              smiles: smiles,
              Threshold: 90,
              MaxRecords: 100,
            });
            return {
              contents: [
                {
                  uri: request.params.uri,
                  mimeType: 'application/json',
                  text: JSON.stringify(response.data, null, 2),
                },
              ],
            };
          } catch (error) {
            throw new McpError(
              ErrorCode.InternalError,
              `Failed to perform similarity search: ${error instanceof Error ? error.message : 'Unknown error'}`
            );
          }
        }

        // Handle safety data requests
        const safetyMatch = uri.match(/^pubchem:\/\/safety\/([0-9]+)$/);
        if (safetyMatch) {
          const cid = safetyMatch[1];
          try {
            const response = await this.apiClient.get(`/compound/cid/${cid}/classification/JSON`);
            return {
              contents: [
                {
                  uri: request.params.uri,
                  mimeType: 'application/json',
                  text: JSON.stringify(response.data, null, 2),
                },
              ],
            };
          } catch (error) {
            throw new McpError(
              ErrorCode.InternalError,
              `Failed to fetch safety data for ${cid}: ${error instanceof Error ? error.message : 'Unknown error'}`
            );
          }
        }

        throw new McpError(
          ErrorCode.InvalidRequest,
          `Invalid URI format: ${uri}`
        );
      }
    );
  }

  private setupToolHandlers() {
    this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
      tools: [
        // Chemical Search & Retrieval (6 tools)
        {
          name: 'search_compounds',
          description: 'Search PubChem database for compounds by name, CAS number, formula, or identifier',
          inputSchema: {
            type: 'object',
            properties: {
              query: { type: 'string', description: 'Search query (compound name, CAS, formula, or identifier)' },
              search_type: { type: 'string', enum: ['name', 'smiles', 'inchi', 'sdf', 'cid', 'formula'], description: 'Type of search to perform (default: name)' },
              max_records: { type: 'number', description: 'Maximum number of results (1-10000, default: 100)', minimum: 1, maximum: 10000 },
            },
            required: ['query'],
          },
        },
        {
          name: 'get_compound_info',
          description: 'Get detailed information for a specific compound by PubChem CID',
          inputSchema: {
            type: 'object',
            properties: {
              cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
              format: { type: 'string', enum: ['json', 'sdf', 'xml', 'asnt', 'asnb'], description: 'Output format (default: json)' },
            },
            required: ['cid'],
          },
        },
        {
          name: 'search_by_smiles',
          description: 'Search for compounds by SMILES string (exact match)',
          inputSchema: {
            type: 'object',
            properties: {
              smiles: { type: 'string', description: 'SMILES string of the query molecule' },
            },
            required: ['smiles'],
          },
        },
        {
          name: 'search_by_inchi',
          description: 'Search for compounds by InChI or InChI key',
          inputSchema: {
            type: 'object',
            properties: {
              inchi: { type: 'string', description: 'InChI string or InChI key' },
            },
            required: ['inchi'],
          },
        },
        {
          name: 'search_by_cas_number',
          description: 'Search for compounds by CAS Registry Number',
          inputSchema: {
            type: 'object',
            properties: {
              cas_number: { type: 'string', description: 'CAS Registry Number (e.g., 50-78-2)' },
            },
            required: ['cas_number'],
          },
        },
        {
          name: 'get_compound_synonyms',
          description: 'Get all names and synonyms for a compound',
          inputSchema: {
            type: 'object',
            properties: {
              cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
            },
            required: ['cid'],
          },
        },

        // Structure Analysis & Similarity (5 tools)
        {
          name: 'search_similar_compounds',
          description: 'Find chemically similar compounds using Tanimoto similarity',
          inputSchema: {
            type: 'object',
            properties: {
              smiles: { type: 'string', description: 'SMILES string of the query molecule' },
              threshold: { type: 'number', description: 'Similarity threshold (0-100, default: 90)', minimum: 0, maximum: 100 },
              max_records: { type: 'number', description: 'Maximum number of results (1-10000, default: 100)', minimum: 1, maximum: 10000 },
            },
            required: ['smiles'],
          },
        },
        {
          name: 'substructure_search',
          description: 'Find compounds containing a specific substructure',
          inputSchema: {
            type: 'object',
            properties: {
              smiles: { type: 'string', description: 'SMILES string of the substructure query' },
              max_records: { type: 'number', description: 'Maximum number of results (1-10000, default: 100)', minimum: 1, maximum: 10000 },
            },
            required: ['smiles'],
          },
        },
        {
          name: 'superstructure_search',
          description: 'Find larger compounds that contain the query structure',
          inputSchema: {
            type: 'object',
            properties: {
              smiles: { type: 'string', description: 'SMILES string of the query structure' },
              max_records: { type: 'number', description: 'Maximum number of results (1-10000, default: 100)', minimum: 1, maximum: 10000 },
            },
            required: ['smiles'],
          },
        },
        {
          name: 'get_3d_conformers',
          description: 'Get 3D conformer data and structural information',
          inputSchema: {
            type: 'object',
            properties: {
              cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
              conformer_type: { type: 'string', enum: ['3d', '2d'], description: 'Type of conformer data (default: 3d)' },
            },
            required: ['cid'],
          },
        },
        {
          name: 'analyze_stereochemistry',
          description: 'Analyze stereochemistry, chirality, and isomer information',
          inputSchema: {
            type: 'object',
            properties: {
              cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
            },
            required: ['cid'],
          },
        },

        // Chemical Properties & Descriptors (6 tools)
        {
          name: 'get_compound_properties',
          description: 'Get molecular properties (MW, logP, TPSA, etc.)',
          inputSchema: {
            type: 'object',
            properties: {
              cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
              properties: { type: 'array', items: { type: 'string' }, description: 'Specific properties to retrieve (optional)' },
            },
            required: ['cid'],
          },
        },
        {
          name: 'calculate_descriptors',
          description: 'Calculate comprehensive molecular descriptors and fingerprints',
          inputSchema: {
            type: 'object',
            properties: {
              cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
              descriptor_type: { type: 'string', enum: ['all', 'basic', 'topological', '3d'], description: 'Type of descriptors (default: all)' },
            },
            required: ['cid'],
          },
        },
        {
          name: 'predict_admet_properties',
          description: 'Predict ADMET properties (Absorption, Distribution, Metabolism, Excretion, Toxicity)',
          inputSchema: {
            type: 'object',
            properties: {
              cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
              smiles: { type: 'string', description: 'SMILES string (alternative to CID)' },
            },
            required: [],
          },
        },
        {
          name: 'assess_drug_likeness',
          description: 'Assess drug-likeness using Lipinski Rule of Five, Veber rules, and PAINS filters',
          inputSchema: {
            type: 'object',
            properties: {
              cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
              smiles: { type: 'string', description: 'SMILES string (alternative to CID)' },
            },
            required: [],
          },
        },
        {
          name: 'analyze_molecular_complexity',
          description: 'Analyze molecular complexity and synthetic accessibility',
          inputSchema: {
            type: 'object',
            properties: {
              cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
            },
            required: ['cid'],
          },
        },
        {
          name: 'get_pharmacophore_features',
          description: 'Get pharmacophore features and binding site information',
          inputSchema: {
            type: 'object',
            properties: {
              cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
            },
            required: ['cid'],
          },
        },

        // Bioassay & Activity Data (5 tools)
        {
          name: 'search_bioassays',
          description: 'Search for biological assays by target, description, or source',
          inputSchema: {
            type: 'object',
            properties: {
              query: { type: 'string', description: 'General search query' },
              target: { type: 'string', description: 'Target protein or gene name' },
              source: { type: 'string', description: 'Data source (e.g., ChEMBL, NCGC)' },
              max_records: { type: 'number', description: 'Maximum number of results (1-1000, default: 100)', minimum: 1, maximum: 1000 },
            },
            required: [],
          },
        },
        {
          name: 'get_assay_info',
          description: 'Get detailed information for a specific bioassay by AID',
          inputSchema: {
            type: 'object',
            properties: {
              aid: { type: 'number', description: 'PubChem Assay ID (AID)' },
            },
            required: ['aid'],
          },
        },
        {
          name: 'get_compound_bioactivities',
          description: 'Get all bioassay results and activities for a compound',
          inputSchema: {
            type: 'object',
            properties: {
              cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
              activity_outcome: { type: 'string', enum: ['active', 'inactive', 'inconclusive', 'all'], description: 'Filter by activity outcome (default: all)' },
            },
            required: ['cid'],
          },
        },
        {
          name: 'search_by_target',
          description: 'Find compounds tested against a specific biological target',
          inputSchema: {
            type: 'object',
            properties: {
              target: { type: 'string', description: 'Target name (gene, protein, or pathway)' },
              activity_type: { type: 'string', description: 'Type of activity (e.g., IC50, EC50, Ki)' },
              max_records: { type: 'number', description: 'Maximum number of results (1-1000, default: 100)', minimum: 1, maximum: 1000 },
            },
            required: ['target'],
          },
        },
        {
          name: 'compare_activity_profiles',
          description: 'Compare bioactivity profiles across multiple compounds',
          inputSchema: {
            type: 'object',
            properties: {
              cids: { type: 'array', items: { type: 'number' }, description: 'Array of PubChem CIDs (2-50)', minItems: 2, maxItems: 50 },
              activity_type: { type: 'string', description: 'Specific activity type for comparison (optional)' },
            },
            required: ['cids'],
          },
        },

        // Safety & Toxicity (4 tools)
        {
          name: 'get_safety_data',
          description: 'Get GHS hazard classifications and safety information',
          inputSchema: {
            type: 'object',
            properties: {
              cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
            },
            required: ['cid'],
          },
        },
        {
          name: 'get_toxicity_info',
          description: 'Get toxicity data including LD50, carcinogenicity, and mutagenicity',
          inputSchema: {
            type: 'object',
            properties: {
              cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
            },
            required: ['cid'],
          },
        },
        {
          name: 'assess_environmental_fate',
          description: 'Assess environmental fate including biodegradation and bioaccumulation',
          inputSchema: {
            type: 'object',
            properties: {
              cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
            },
            required: ['cid'],
          },
        },
        {
          name: 'get_regulatory_info',
          description: 'Get regulatory information from FDA, EPA, and international agencies',
          inputSchema: {
            type: 'object',
            properties: {
              cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
            },
            required: ['cid'],
          },
        },

        // Cross-References & Integration (4 tools)
        {
          name: 'get_external_references',
          description: 'Get links to external databases (ChEMBL, DrugBank, KEGG, etc.)',
          inputSchema: {
            type: 'object',
            properties: {
              cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
            },
            required: ['cid'],
          },
        },
        {
          name: 'search_patents',
          description: 'Search for chemical patents and intellectual property information',
          inputSchema: {
            type: 'object',
            properties: {
              cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
              query: { type: 'string', description: 'Patent search query (alternative to CID)' },
            },
            required: [],
          },
        },
        {
          name: 'get_literature_references',
          description: 'Get PubMed citations and scientific literature references',
          inputSchema: {
            type: 'object',
            properties: {
              cid: { type: ['number', 'string'], description: 'PubChem Compound ID (CID)' },
            },
            required: ['cid'],
          },
        },
        {
          name: 'batch_compound_lookup',
          description: 'Process multiple compound IDs efficiently',
          inputSchema: {
            type: 'object',
            properties: {
              cids: { type: 'array', items: { type: 'number' }, description: 'Array of PubChem CIDs (1-200)', minItems: 1, maxItems: 200 },
              operation: { type: 'string', enum: ['property', 'synonyms', 'classification', 'description'], description: 'Operation to perform (default: property)' },
            },
            required: ['cids'],
          },
        },
      ],
    }));

    this.server.setRequestHandler(CallToolRequestSchema, async (request: any) => {
      const { name, arguments: args } = request.params;

      try {
        switch (name) {
          // Chemical Search & Retrieval
          case 'search_compounds':
            return await this.handleSearchCompounds(args);
          case 'get_compound_info':
            return await this.handleGetCompoundInfo(args);
          case 'search_by_smiles':
            return await this.handleSearchBySmiles(args);
          case 'search_by_inchi':
            return await this.handleSearchByInchi(args);
          case 'search_by_cas_number':
            return await this.handleSearchByCasNumber(args);
          case 'get_compound_synonyms':
            return await this.handleGetCompoundSynonyms(args);

          // Structure Analysis & Similarity
          case 'search_similar_compounds':
            return await this.handleSearchSimilarCompounds(args);
          case 'substructure_search':
            return await this.handleSubstructureSearch(args);
          case 'superstructure_search':
            return await this.handleSuperstructureSearch(args);
          case 'get_3d_conformers':
            return await this.handleGet3dConformers(args);
          case 'analyze_stereochemistry':
            return await this.handleAnalyzeStereochemistry(args);

          // Chemical Properties & Descriptors
          case 'get_compound_properties':
            return await this.handleGetCompoundProperties(args);
          case 'calculate_descriptors':
            return await this.handleCalculateDescriptors(args);
          case 'predict_admet_properties':
            return await this.handlePredictAdmetProperties(args);
          case 'assess_drug_likeness':
            return await this.handleAssessDrugLikeness(args);
          case 'analyze_molecular_complexity':
            return await this.handleAnalyzeMolecularComplexity(args);
          case 'get_pharmacophore_features':
            return await this.handleGetPharmacophoreFeatures(args);

          // Bioassay & Activity Data
          case 'search_bioassays':
            return await this.handleSearchBioassays(args);
          case 'get_assay_info':
            return await this.handleGetAssayInfo(args);
          case 'get_compound_bioactivities':
            return await this.handleGetCompoundBioactivities(args);
          case 'search_by_target':
            return await this.handleSearchByTarget(args);
          case 'compare_activity_profiles':
            return await this.handleCompareActivityProfiles(args);

          // Safety & Toxicity
          case 'get_safety_data':
            return await this.handleGetSafetyData(args);
          case 'get_toxicity_info':
            return await this.handleGetToxicityInfo(args);
          case 'assess_environmental_fate':
            return await this.handleAssessEnvironmentalFate(args);
          case 'get_regulatory_info':
            return await this.handleGetRegulatoryInfo(args);

          // Cross-References & Integration
          case 'get_external_references':
            return await this.handleGetExternalReferences(args);
          case 'search_patents':
            return await this.handleSearchPatents(args);
          case 'get_literature_references':
            return await this.handleGetLiteratureReferences(args);
          case 'batch_compound_lookup':
            return await this.handleBatchCompoundLookup(args);

          default:
            throw new McpError(
              ErrorCode.MethodNotFound,
              `Unknown tool: ${name}`
            );
        }
      } catch (error) {
        return {
          content: [
            {
              type: 'text',
              text: `Error executing tool ${name}: ${error instanceof Error ? error.message : 'Unknown error'}`,
            },
          ],
          isError: true,
        };
      }
    });
  }

  // Chemical Search & Retrieval handlers
  private async handleSearchCompounds(args: any) {
    if (!isValidCompoundSearchArgs(args)) {
      throw new McpError(ErrorCode.InvalidParams, 'Invalid compound search arguments');
    }

    try {
      const searchType = args.search_type || 'name';
      const maxRecords = args.max_records || 100;

      const response = await this.apiClient.get(`/compound/${searchType}/${encodeURIComponent(args.query)}/cids/JSON`, {
        params: {
          MaxRecords: maxRecords,
        },
      });

      if (response.data?.IdentifierList?.CID?.length > 0) {
        const cids = response.data.IdentifierList.CID.slice(0, 10);
        const detailsResponse = await this.apiClient.get(`/compound/cid/${cids.join(',')}/property/MolecularFormula,MolecularWeight,CanonicalSMILES,IUPACName/JSON`);

        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify({
                query: args.query,
                search_type: searchType,
                total_found: response.data.IdentifierList.CID.length,
                details: detailsResponse.data,
              }, null, 2),
            },
          ],
        };
      }

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({ message: 'No compounds found', query: args.query }, null, 2),
          },
        ],
      };
    } catch (error) {
      throw new McpError(
        ErrorCode.InternalError,
        `Failed to search compounds: ${error instanceof Error ? error.message : 'Unknown error'}`
      );
    }
  }

  private async handleGetCompoundInfo(args: any) {
    if (!isValidCidArgs(args)) {
      throw new McpError(ErrorCode.InvalidParams, 'Invalid CID arguments');
    }

    try {
      const format = args.format || 'json';
      const response = await this.apiClient.get(`/compound/cid/${args.cid}/${format === 'json' ? 'JSON' : format}`);

      return {
        content: [
          {
            type: 'text',
            text: format === 'json' ? JSON.stringify(response.data, null, 2) : String(response.data),
          },
        ],
      };
    } catch (error) {
      throw new McpError(
        ErrorCode.InternalError,
        `Failed to get compound info: ${error instanceof Error ? error.message : 'Unknown error'}`
      );
    }
  }

  private async handleSearchBySmiles(args: any) {
    if (!isValidSmilesArgs(args)) {
      throw new McpError(ErrorCode.InvalidParams, 'Invalid SMILES arguments');
    }

    try {
      const response = await this.apiClient.get(`/compound/smiles/${encodeURIComponent(args.smiles)}/cids/JSON`);

      if (response.data?.IdentifierList?.CID?.length > 0) {
        const cid = response.data.IdentifierList.CID[0];
        const detailsResponse = await this.apiClient.get(`/compound/cid/${cid}/property/MolecularFormula,MolecularWeight,CanonicalSMILES,IUPACName/JSON`);

        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify({
                query_smiles: args.smiles,
                found_cid: cid,
                details: detailsResponse.data,
              }, null, 2),
            },
          ],
        };
      }

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({ message: 'No exact match found', query_smiles: args.smiles }, null, 2),
          },
        ],
      };
    } catch (error) {
      throw new McpError(
        ErrorCode.InternalError,
        `Failed to search by SMILES: ${error instanceof Error ? error.message : 'Unknown error'}`
      );
    }
  }

  // Simplified implementation handlers (placeholder implementations)
  private async handleSearchByInchi(args: any) {
    return { content: [{ type: 'text', text: JSON.stringify({ message: 'InChI search not yet implemented', args }, null, 2) }] };
  }

  private async handleSearchByCasNumber(args: any) {
    return { content: [{ type: 'text', text: JSON.stringify({ message: 'CAS search not yet implemented', args }, null, 2) }] };
  }

  private async handleGetCompoundSynonyms(args: any) {
    if (!isValidCidArgs(args)) {
      throw new McpError(ErrorCode.InvalidParams, 'Invalid CID arguments');
    }

    try {
      const response = await this.apiClient.get(`/compound/cid/${args.cid}/synonyms/JSON`);
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(response.data, null, 2),
          },
        ],
      };
    } catch (error) {
      throw new McpError(
        ErrorCode.InternalError,
        `Failed to get compound synonyms: ${error instanceof Error ? error.message : 'Unknown error'}`
      );
    }
  }

  private async handleSearchSimilarCompounds(args: any) {
    if (!isValidSmilesArgs(args)) {
      throw new McpError(ErrorCode.InvalidParams, 'Invalid similarity search arguments');
    }

    try {
      const threshold = args.threshold || 90;
      const maxRecords = args.max_records || 100;

      const response = await this.apiClient.post('/compound/similarity/smiles/JSON', {
        smiles: args.smiles,
        Threshold: threshold,
        MaxRecords: maxRecords,
      });

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(response.data, null, 2),
          },
        ],
      };
    } catch (error) {
      throw new McpError(
        ErrorCode.InternalError,
        `Failed to search similar compounds: ${error instanceof Error ? error.message : 'Unknown error'}`
      );
    }
  }

  private async handleSubstructureSearch(args: any) {
    return { content: [{ type: 'text', text: JSON.stringify({ message: 'Substructure search not yet implemented', args }, null, 2) }] };
  }

  private async handleSuperstructureSearch(args: any) {
    return { content: [{ type: 'text', text: JSON.stringify({ message: 'Superstructure search not yet implemented', args }, null, 2) }] };
  }

  private async handleGet3dConformers(args: any) {
    if (!isValidConformerArgs(args)) {
      throw new McpError(ErrorCode.InvalidParams, 'Invalid 3D conformer arguments');
    }

    try {
      const response = await this.apiClient.get(`/compound/cid/${args.cid}/property/Volume3D,ConformerCount3D/JSON`);

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              cid: args.cid,
              conformer_type: args.conformer_type || '3d',
              properties: response.data,
            }, null, 2),
          },
        ],
      };
    } catch (error) {
      throw new McpError(
        ErrorCode.InternalError,
        `Failed to get 3D conformers: ${error instanceof Error ? error.message : 'Unknown error'}`
      );
    }
  }

  private async handleAnalyzeStereochemistry(args: any) {
    if (!isValidCidArgs(args)) {
      throw new McpError(ErrorCode.InvalidParams, 'Invalid stereochemistry arguments');
    }

    try {
      const response = await this.apiClient.get(`/compound/cid/${args.cid}/property/AtomStereoCount,DefinedAtomStereoCount,BondStereoCount,DefinedBondStereoCount,IsomericSMILES/JSON`);

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              cid: args.cid,
              stereochemistry: response.data,
            }, null, 2),
          },
        ],
      };
    } catch (error) {
      throw new McpError(
        ErrorCode.InternalError,
        `Failed to analyze stereochemistry: ${error instanceof Error ? error.message : 'Unknown error'}`
      );
    }
  }

  private async handleGetCompoundProperties(args: any) {
    if (!isValidPropertiesArgs(args)) {
      throw new McpError(ErrorCode.InvalidParams, 'Invalid compound properties arguments');
    }

    try {
      const properties = args.properties || [
        'MolecularWeight', 'XLogP', 'TPSA', 'HBondDonorCount', 'HBondAcceptorCount',
        'RotatableBondCount', 'Complexity', 'HeavyAtomCount', 'Charge'
      ];

      const response = await this.apiClient.get(`/compound/cid/${args.cid}/property/${properties.join(',')}/JSON`);

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(response.data, null, 2),
          },
        ],
      };
    } catch (error) {
      throw new McpError(
        ErrorCode.InternalError,
        `Failed to get compound properties: ${error instanceof Error ? error.message : 'Unknown error'}`
      );
    }
  }

  // Placeholder implementations for remaining methods
  private async handleCalculateDescriptors(args: any) {
    return { content: [{ type: 'text', text: JSON.stringify({ message: 'Descriptor calculation not yet implemented', args }, null, 2) }] };
  }

  private async handlePredictAdmetProperties(args: any) {
    return { content: [{ type: 'text', text: JSON.stringify({ message: 'ADMET prediction not yet implemented', args }, null, 2) }] };
  }

  private async handleAssessDrugLikeness(args: any) {
    return { content: [{ type: 'text', text: JSON.stringify({ message: 'Drug-likeness assessment not yet implemented', args }, null, 2) }] };
  }

  private async handleAnalyzeMolecularComplexity(args: any) {
    return { content: [{ type: 'text', text: JSON.stringify({ message: 'Molecular complexity analysis not yet implemented', args }, null, 2) }] };
  }

  private async handleGetPharmacophoreFeatures(args: any) {
    return { content: [{ type: 'text', text: JSON.stringify({ message: 'Pharmacophore features not yet implemented', args }, null, 2) }] };
  }

  private async handleSearchBioassays(args: any) {
    return { content: [{ type: 'text', text: JSON.stringify({ message: 'Bioassay search not yet implemented', args }, null, 2) }] };
  }

  private async handleGetAssayInfo(args: any) {
    try {
      const response = await this.apiClient.get(`/assay/aid/${args.aid}/JSON`);
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(response.data, null, 2),
          },
        ],
      };
    } catch (error) {
      throw new McpError(
        ErrorCode.InternalError,
        `Failed to get assay info: ${error instanceof Error ? error.message : 'Unknown error'}`
      );
    }
  }

  private async handleGetCompoundBioactivities(args: any) {
    return { content: [{ type: 'text', text: JSON.stringify({ message: 'Bioactivity search not yet implemented', args }, null, 2) }] };
  }

  private async handleSearchByTarget(args: any) {
    return { content: [{ type: 'text', text: JSON.stringify({ message: 'Target search not yet implemented', args }, null, 2) }] };
  }

  private async handleCompareActivityProfiles(args: any) {
    return { content: [{ type: 'text', text: JSON.stringify({ message: 'Activity profile comparison not yet implemented', args }, null, 2) }] };
  }

  private async handleGetSafetyData(args: any) {
    if (!isValidCidArgs(args)) {
      throw new McpError(ErrorCode.InvalidParams, 'Invalid CID arguments');
    }

    try {
      const response = await this.apiClient.get(`/compound/cid/${args.cid}/classification/JSON`);
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(response.data, null, 2),
          },
        ],
      };
    } catch (error) {
      throw new McpError(
        ErrorCode.InternalError,
        `Failed to get safety data: ${error instanceof Error ? error.message : 'Unknown error'}`
      );
    }
  }

  private async handleGetToxicityInfo(args: any) {
    return { content: [{ type: 'text', text: JSON.stringify({ message: 'Toxicity info not yet implemented', args }, null, 2) }] };
  }

  private async handleAssessEnvironmentalFate(args: any) {
    return { content: [{ type: 'text', text: JSON.stringify({ message: 'Environmental fate assessment not yet implemented', args }, null, 2) }] };
  }

  private async handleGetRegulatoryInfo(args: any) {
    return { content: [{ type: 'text', text: JSON.stringify({ message: 'Regulatory info not yet implemented', args }, null, 2) }] };
  }

  private async handleGetExternalReferences(args: any) {
    return { content: [{ type: 'text', text: JSON.stringify({ message: 'External references not yet implemented', args }, null, 2) }] };
  }

  private async handleSearchPatents(args: any) {
    return { content: [{ type: 'text', text: JSON.stringify({ message: 'Patent search not yet implemented', args }, null, 2) }] };
  }

  private async handleGetLiteratureReferences(args: any) {
    return { content: [{ type: 'text', text: JSON.stringify({ message: 'Literature references not yet implemented', args }, null, 2) }] };
  }

  private async handleBatchCompoundLookup(args: any) {
    if (!isValidBatchArgs(args)) {
      throw new McpError(ErrorCode.InvalidParams, 'Invalid batch arguments');
    }

    try {
      const results = [];
      for (const cid of args.cids.slice(0, 10)) {
        try {
          const response = await this.apiClient.get(`/compound/cid/${cid}/property/MolecularWeight,CanonicalSMILES,IUPACName/JSON`);
          results.push({ cid, data: response.data, success: true });
        } catch (error) {
          results.push({ cid, error: error instanceof Error ? error.message : 'Unknown error', success: false });
        }
      }

      return { content: [{ type: 'text', text: JSON.stringify({ batch_results: results }, null, 2) }] };
    } catch (error) {
      throw new McpError(ErrorCode.InternalError, `Batch lookup failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
    }
  }

  async run() {
    const transport = new StdioServerTransport();
    await this.server.connect(transport);
    console.error('PubChem MCP server running on stdio');
  }
}

const server = new PubChemServer();
server.run().catch(console.error);

```