Skip to main content
Glama

PubChem MCP Server

index.tsโ€ข44.2 kB
#!/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);

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Augmented-Nature/PubChem-MCP-Server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server