Skip to main content
Glama
ema-api.js28.5 kB
const axios = require('axios'); const EMA_BASE_URL = 'https://www.ema.europa.eu/en/documents/report'; /** * Generate EMA API URL for different JSON endpoints * @param {string} endpoint - The specific endpoint (e.g., 'medicines-output-medicines_json-report_en.json') * @returns {string} Complete API URL */ function generateEmaUrl(endpoint) { return `${EMA_BASE_URL}/${endpoint}`; } /** * Make HTTP request to EMA JSON API with proper error handling * @param {string} url - API URL to request * @returns {Promise<Array|Object>} Response data */ async function makeEmaRequest(url) { try { const response = await axios.get(url, { timeout: 30000, headers: { 'User-Agent': 'EMA-MCP-Server/0.0.1', 'Accept': 'application/json' } }); const data = typeof response.data === 'string' ? JSON.parse(response.data) : response.data; // Validate response is an array if (!Array.isArray(data)) { throw new Error('EMA API returned non-array response'); } return data; } catch (error) { if (error.code === 'ECONNABORTED') { throw new Error('EMA API request timeout (30s exceeded)'); } else if (error.response) { throw new Error(`EMA API HTTP error ${error.response.status}: ${error.response.statusText}`); } else if (error.request) { throw new Error('EMA API network error: No response received'); } else { throw new Error(`EMA API request failed: ${error.message}`); } } } /** * Make HTTP request to EMA document endpoints (which return {data: [...]} format) * @param {string} url - API URL to request * @returns {Promise<Array>} Response data array */ async function makeEmaDocumentRequest(url) { try { const response = await axios.get(url, { timeout: 30000, headers: { 'User-Agent': 'EMA-MCP-Server/0.0.1', 'Accept': 'application/json' } }); const json = typeof response.data === 'string' ? JSON.parse(response.data) : response.data; // Document endpoints return {data: [...]} format if (json.data && Array.isArray(json.data)) { return json.data; } throw new Error('EMA API document response missing data array'); } catch (error) { if (error.code === 'ECONNABORTED') { throw new Error('EMA API request timeout (30s exceeded)'); } else if (error.response) { throw new Error(`EMA API HTTP error ${error.response.status}: ${error.response.statusText}`); } else if (error.request) { throw new Error('EMA API network error: No response received'); } else { throw new Error(`EMA API request failed: ${error.message}`); } } } /** * Parse EMA date format (DD Month YYYY) to ISO format (YYYY-MM-DD) * @param {string} emaDate - Date in EMA format * @returns {string|null} ISO formatted date or null */ function parseEmaDate(emaDate) { if (!emaDate) return null; try { const months = { 'January': '01', 'February': '02', 'March': '03', 'April': '04', 'May': '05', 'June': '06', 'July': '07', 'August': '08', 'September': '09', 'October': '10', 'November': '11', 'December': '12' }; const parts = emaDate.trim().split(' '); if (parts.length !== 3) return null; const day = parts[0].padStart(2, '0'); const month = months[parts[1]]; const year = parts[2]; return month ? `${year}-${month}-${day}` : null; } catch { return null; } } /** * Search medicines in EMA database * @param {Object} params - Search parameters * @returns {Promise<Object>} Search results with medicines data */ async function searchMedicines(params = {}) { // Validate input parameters if (params.limit && (typeof params.limit !== 'number' || params.limit < 1 || params.limit > 10000)) { throw new Error('limit must be a number between 1 and 10000'); } if (params.status && !['Authorised', 'Withdrawn', 'Refused', 'Suspended'].includes(params.status)) { throw new Error('status must be one of: Authorised, Withdrawn, Refused, Suspended'); } const url = generateEmaUrl('medicines-output-medicines_json-report_en.json'); const allMedicines = await makeEmaRequest(url); let results = allMedicines; // Filter by active substance if (params.active_substance) { const searchTerm = params.active_substance.toLowerCase(); results = results.filter(m => m.active_substance && m.active_substance.toLowerCase().includes(searchTerm) ); } // Filter by therapeutic area if (params.therapeutic_area) { const searchTerm = params.therapeutic_area.toLowerCase(); results = results.filter(m => (m.therapeutic_area_mesh && m.therapeutic_area_mesh.toLowerCase().includes(searchTerm)) || (m.therapeutic_indication && m.therapeutic_indication.toLowerCase().includes(searchTerm)) ); } // Filter by medicine status if (params.status) { results = results.filter(m => m.medicine_status === params.status); } // Filter by regulatory flags if (params.orphan === true) { results = results.filter(m => m.orphan_medicine === 'Yes'); } if (params.prime === true) { results = results.filter(m => m.prime_priority_medicine === 'Yes'); } if (params.biosimilar === true) { results = results.filter(m => m.biosimilar === 'Yes'); } if (params.conditional_approval === true) { results = results.filter(m => m.conditional_approval === 'Yes'); } // Apply limit const limit = params.limit || 100; results = results.slice(0, limit); return { total_count: results.length, results: results, source: 'EMA Medicines Database', source_url: url, last_updated: new Date().toISOString() }; } /** * Get specific medicine by name * @param {string} name - Medicine name to search * @returns {Promise<Object>} Medicine data or null */ async function getMedicineByName(name) { // Validate input if (!name || typeof name !== 'string' || name.trim().length === 0) { throw new Error('name parameter is required and must be a non-empty string'); } const url = generateEmaUrl('medicines-output-medicines_json-report_en.json'); const allMedicines = await makeEmaRequest(url); const searchTerm = name.trim().toLowerCase(); const medicine = allMedicines.find(m => m.name_of_medicine && m.name_of_medicine.toLowerCase().includes(searchTerm) ); if (!medicine) { return { found: false, message: `Medicine "${name}" not found in EMA database`, source: 'EMA Medicines Database', source_url: url }; } return { found: true, medicine: medicine, source: 'EMA Medicines Database', source_url: url, last_updated: new Date().toISOString() }; } /** * Get orphan drug designations * @param {Object} params - Filter parameters * @returns {Promise<Object>} Orphan designations data */ async function getOrphanDesignations(params = {}) { // Validate input parameters if (params.limit && (typeof params.limit !== 'number' || params.limit < 1 || params.limit > 10000)) { throw new Error('limit must be a number between 1 and 10000'); } if (params.year && (typeof params.year !== 'number' || params.year < 1995 || params.year > new Date().getFullYear() + 1)) { throw new Error(`year must be a number between 1995 and ${new Date().getFullYear() + 1}`); } if (params.status && !['Positive', 'Negative', 'Withdrawn'].includes(params.status)) { throw new Error('status must be one of: Positive, Negative, Withdrawn'); } const url = generateEmaUrl('medicines-output-orphan_designations-json-report_en.json'); const allDesignations = await makeEmaRequest(url); let results = allDesignations; // Filter by therapeutic area (searches in intended_use field) if (params.therapeutic_area) { const searchTerm = params.therapeutic_area.toLowerCase(); results = results.filter(d => d.intended_use && d.intended_use.toLowerCase().includes(searchTerm) ); } // Filter by active substance if provided if (params.active_substance) { const searchTerm = params.active_substance.toLowerCase(); results = results.filter(d => d.active_substance && d.active_substance.toLowerCase().includes(searchTerm) ); } // Filter by year if provided (searches in date_of_designation_or_refusal) if (params.year) { results = results.filter(d => { const date = d.date_of_designation_or_refusal; return date && date.includes(params.year.toString()); }); } // Filter by status if provided if (params.status) { results = results.filter(d => d.status && d.status.toLowerCase() === params.status.toLowerCase() ); } // Apply limit const limit = params.limit || 100; results = results.slice(0, limit); return { total_count: results.length, results: results, source: 'EMA Orphan Designations', source_url: url, last_updated: new Date().toISOString() }; } /** * Get medicine supply shortages * @param {Object} params - Filter parameters * @returns {Promise<Object>} Shortage data */ async function getSupplyShortages(params = {}) { // Validate input parameters if (params.limit && (typeof params.limit !== 'number' || params.limit < 1 || params.limit > 10000)) { throw new Error('limit must be a number between 1 and 10000'); } if (params.status && !['Ongoing', 'Resolved', 'ongoing', 'resolved'].includes(params.status)) { throw new Error('status must be one of: Ongoing, Resolved (case-insensitive)'); } const url = generateEmaUrl('shortages-output-json-report_en.json'); const allShortages = await makeEmaRequest(url); let results = allShortages; // Filter by active substance (uses international_non_proprietary_name_inn_or_common_name field) if (params.active_substance) { const searchTerm = params.active_substance.toLowerCase(); results = results.filter(s => (s.international_non_proprietary_name_inn_or_common_name && s.international_non_proprietary_name_inn_or_common_name.toLowerCase().includes(searchTerm)) || (s.medicine_affected && s.medicine_affected.toLowerCase().includes(searchTerm)) ); } // Filter by medicine name if provided if (params.medicine_name) { const searchTerm = params.medicine_name.toLowerCase(); results = results.filter(s => s.medicine_affected && s.medicine_affected.toLowerCase().includes(searchTerm) ); } // Filter by therapeutic area if provided if (params.therapeutic_area) { const searchTerm = params.therapeutic_area.toLowerCase(); results = results.filter(s => s.therapeutic_area_mesh && s.therapeutic_area_mesh.toLowerCase().includes(searchTerm) ); } // Filter by status (uses supply_shortage_status field: "Ongoing" or "Resolved") if (params.status) { results = results.filter(s => s.supply_shortage_status && s.supply_shortage_status.toLowerCase() === params.status.toLowerCase() ); } // Apply limit const limit = params.limit || 50; results = results.slice(0, limit); return { total_count: results.length, results: results, source: 'EMA Medicine Supply Shortages', source_url: url, last_updated: new Date().toISOString() }; } /** * Get referrals (EU-wide safety reviews) * @param {Object} params - Filter parameters * @returns {Promise<Object>} Referrals data */ async function getReferrals(params = {}) { // Validate input parameters if (params.limit && (typeof params.limit !== 'number' || params.limit < 1 || params.limit > 10000)) { throw new Error('limit must be a number between 1 and 10000'); } if (params.safety !== undefined && typeof params.safety !== 'boolean') { throw new Error('safety parameter must be a boolean (true or false)'); } if (params.year && (typeof params.year !== 'number' || params.year < 1995 || params.year > new Date().getFullYear() + 1)) { throw new Error(`year must be a number between 1995 and ${new Date().getFullYear() + 1}`); } const url = generateEmaUrl('referrals-output-json-report_en.json'); const allReferrals = await makeEmaRequest(url); let results = allReferrals; // Filter by safety-related if specified (uses safety_referral field: "Sì" or "No") if (params.safety === true) { results = results.filter(r => r.safety_referral === 'Sì' || r.safety_referral === 'Yes' ); } else if (params.safety === false) { results = results.filter(r => r.safety_referral === 'No' ); } // Filter by active substance if provided if (params.active_substance) { const searchTerm = params.active_substance.toLowerCase(); results = results.filter(r => r.international_non_proprietary_name_inn_common_name && r.international_non_proprietary_name_inn_common_name.toLowerCase().includes(searchTerm) ); } // Filter by status if provided if (params.status) { const searchTerm = params.status.toLowerCase(); results = results.filter(r => r.current_status && r.current_status.toLowerCase().includes(searchTerm) ); } // Filter by year if provided (uses procedure_start_date field) if (params.year) { results = results.filter(r => { const startDate = r.procedure_start_date; return startDate && startDate.includes(params.year.toString()); }); } // Apply limit const limit = params.limit || 50; results = results.slice(0, limit); return { total_count: results.length, results: results, source: 'EMA Referrals', source_url: url, last_updated: new Date().toISOString() }; } /** * Get post-authorization procedures * @param {Object} params - Filter parameters * @returns {Promise<Object>} Post-auth procedures data */ async function getPostAuthProcedures(params = {}) { // Validate input parameters if (params.limit && (typeof params.limit !== 'number' || params.limit < 1 || params.limit > 10000)) { throw new Error('limit must be a number between 1 and 10000'); } const url = generateEmaUrl('medicines-output-post_authorisation_json-report_en.json'); const allProcedures = await makeEmaRequest(url); let results = allProcedures; // Filter by medicine name if provided if (params.medicine_name) { const searchTerm = params.medicine_name.toLowerCase(); results = results.filter(p => p.medicine_name && p.medicine_name.toLowerCase().includes(searchTerm) ); } // Apply limit const limit = params.limit || 50; results = results.slice(0, limit); return { total_count: results.length, results: results, source: 'EMA Post-Authorization Procedures', source_url: url, last_updated: new Date().toISOString() }; } /** * Get Direct Healthcare Professional Communications (DHPCs) * @param {Object} params - Filter parameters * @returns {Promise<Object>} DHPC data */ async function getDhpcs(params = {}) { // Validate input parameters if (params.limit && (typeof params.limit !== 'number' || params.limit < 1 || params.limit > 10000)) { throw new Error('limit must be a number between 1 and 10000'); } if (params.year && (typeof params.year !== 'number' || params.year < 1995 || params.year > new Date().getFullYear() + 1)) { throw new Error(`year must be a number between 1995 and ${new Date().getFullYear() + 1}`); } const url = generateEmaUrl('dhpc-output-json-report_en.json'); const allDhpcs = await makeEmaRequest(url); let results = allDhpcs; // Filter by medicine name if (params.medicine_name) { const searchTerm = params.medicine_name.toLowerCase(); results = results.filter(d => d.name_of_medicine && d.name_of_medicine.toLowerCase().includes(searchTerm) ); } // Filter by active substance if (params.active_substance) { const searchTerm = params.active_substance.toLowerCase(); results = results.filter(d => d.active_substances && d.active_substances.toLowerCase().includes(searchTerm) ); } // Filter by DHPC type if (params.dhpc_type) { results = results.filter(d => d.dhpc_type && d.dhpc_type.toLowerCase() === params.dhpc_type.toLowerCase() ); } // Filter by year (dissemination_date) if (params.year) { results = results.filter(d => { const date = d.dissemination_date; return date && date.includes(params.year.toString()); }); } // Apply limit const limit = params.limit || 50; results = results.slice(0, limit); return { total_count: results.length, results: results, source: 'EMA Direct Healthcare Professional Communications', source_url: url, last_updated: new Date().toISOString() }; } /** * Get Periodic Safety Update Reports (PSUSAs) * @param {Object} params - Filter parameters * @returns {Promise<Object>} PSUSA data */ async function getPsusas(params = {}) { // Validate input parameters if (params.limit && (typeof params.limit !== 'number' || params.limit < 1 || params.limit > 10000)) { throw new Error('limit must be a number between 1 and 10000'); } const url = generateEmaUrl('medicines-output-periodic_safety_update_report_single_assessments-output-json-report_en.json'); const allPsusas = await makeEmaRequest(url); let results = allPsusas; // Filter by active substance if (params.active_substance) { const searchTerm = params.active_substance.toLowerCase(); results = results.filter(p => (p.active_substance && p.active_substance.toLowerCase().includes(searchTerm)) || (p.active_substances_in_scope_of_procedure && p.active_substances_in_scope_of_procedure.toLowerCase().includes(searchTerm)) ); } // Filter by regulatory outcome if (params.regulatory_outcome) { results = results.filter(p => p.regulatory_outcome && p.regulatory_outcome.toLowerCase() === params.regulatory_outcome.toLowerCase() ); } // Apply limit const limit = params.limit || 100; results = results.slice(0, limit); return { total_count: results.length, results: results, source: 'EMA Periodic Safety Update Reports', source_url: url, last_updated: new Date().toISOString() }; } /** * Get Paediatric Investigation Plans (PIPs) * @param {Object} params - Filter parameters * @returns {Promise<Object>} PIP data */ async function getPips(params = {}) { // Validate input parameters if (params.limit && (typeof params.limit !== 'number' || params.limit < 1 || params.limit > 10000)) { throw new Error('limit must be a number between 1 and 10000'); } if (params.year && (typeof params.year !== 'number' || params.year < 1995 || params.year > new Date().getFullYear() + 1)) { throw new Error(`year must be a number between 1995 and ${new Date().getFullYear() + 1}`); } const url = generateEmaUrl('medicines-output-paediatric_investigation_plans-output-json-report_en.json'); const allPips = await makeEmaRequest(url); let results = allPips; // Filter by active substance if (params.active_substance) { const searchTerm = params.active_substance.toLowerCase(); results = results.filter(p => p.active_substance && p.active_substance.toLowerCase().includes(searchTerm) ); } // Filter by therapeutic area if (params.therapeutic_area) { const searchTerm = params.therapeutic_area.toLowerCase(); results = results.filter(p => p.therapeutic_area && p.therapeutic_area.toLowerCase().includes(searchTerm) ); } // Filter by decision type if (params.decision_type) { const searchTerm = params.decision_type.toLowerCase(); results = results.filter(p => p.decision_type && p.decision_type.toLowerCase().includes(searchTerm) ); } // Filter by year (decision_date) if (params.year) { results = results.filter(p => { const date = p.decision_date; return date && date.includes(params.year.toString()); }); } // Apply limit const limit = params.limit || 100; results = results.slice(0, limit); return { total_count: results.length, results: results, source: 'EMA Paediatric Investigation Plans', source_url: url, last_updated: new Date().toISOString() }; } /** * Get Herbal Medicines assessments * NOTE: EMA does not currently publish herbal medicine data as a JSON endpoint. * This method is a placeholder and returns empty results until a valid endpoint is identified. * @param {Object} params - Filter parameters * @returns {Promise<Object>} Herbal medicine data */ async function getHerbalMedicines(params = {}) { // Validate input parameters if (params.limit && (typeof params.limit !== 'number' || params.limit < 1 || params.limit > 10000)) { throw new Error('limit must be a number between 1 and 10000'); } // TODO: Find valid EMA herbal medicines JSON endpoint // Returning empty dataset for now const allHerbal = []; let results = allHerbal; // Filter by herbal substance (if field exists) if (params.substance) { const searchTerm = params.substance.toLowerCase(); results = results.filter(h => (h.herbal_substance && h.herbal_substance.toLowerCase().includes(searchTerm)) || (h.botanical_name && h.botanical_name.toLowerCase().includes(searchTerm)) ); } // Filter by therapeutic area (if field exists) if (params.therapeutic_area) { const searchTerm = params.therapeutic_area.toLowerCase(); results = results.filter(h => h.therapeutic_area && h.therapeutic_area.toLowerCase().includes(searchTerm) ); } // Apply limit const limit = params.limit || 50; results = results.slice(0, limit); return { total_count: results.length, results: results, source: 'EMA Herbal Medicines (placeholder - no JSON endpoint available)', source_url: 'https://www.ema.europa.eu/en/medicines/download-medicine-data', last_updated: new Date().toISOString() }; } /** * Get Medicines for Use Outside EU (Article 58) * NOTE: EMA does not currently publish Article 58 data as a JSON endpoint. * This method is a placeholder and returns empty results until a valid endpoint is identified. * @param {Object} params - Filter parameters * @returns {Promise<Object>} Article 58 medicine data */ async function getArticle58Medicines(params = {}) { // Validate input parameters if (params.limit && (typeof params.limit !== 'number' || params.limit < 1 || params.limit > 10000)) { throw new Error('limit must be a number between 1 and 10000'); } // TODO: Find valid EMA Article 58 JSON endpoint // Returning empty dataset for now const allArticle58 = []; let results = allArticle58; // Filter by active substance (if field exists) if (params.active_substance) { const searchTerm = params.active_substance.toLowerCase(); results = results.filter(m => m.active_substance && m.active_substance.toLowerCase().includes(searchTerm) ); } // Filter by medicine name (if field exists) if (params.medicine_name) { const searchTerm = params.medicine_name.toLowerCase(); results = results.filter(m => m.medicine_name && m.medicine_name.toLowerCase().includes(searchTerm) ); } // Apply limit const limit = params.limit || 50; results = results.slice(0, limit); return { total_count: results.length, results: results, source: 'EMA Medicines for Use Outside EU - Article 58 (placeholder - no JSON endpoint available)', source_url: 'https://www.ema.europa.eu/en/medicines/download-medicine-data', last_updated: new Date().toISOString() }; } /** * Search EPAR Documents * @param {Object} params - Search parameters * @returns {Promise<Object>} EPAR document data */ async function searchEparDocuments(params = {}) { // Validate input parameters if (params.limit && (typeof params.limit !== 'number' || params.limit < 1 || params.limit > 10000)) { throw new Error('limit must be a number between 1 and 10000'); } const url = generateEmaUrl('documents-output-epar_documents_json-report_en.json'); const allDocuments = await makeEmaDocumentRequest(url); let results = allDocuments; // Filter by medicine name (if field exists) if (params.medicine_name) { const searchTerm = params.medicine_name.toLowerCase(); results = results.filter(d => d.medicine_name && d.medicine_name.toLowerCase().includes(searchTerm) ); } // Filter by document type (if field exists) if (params.document_type) { const searchTerm = params.document_type.toLowerCase(); results = results.filter(d => d.document_type && d.document_type.toLowerCase().includes(searchTerm) ); } // Filter by language (if field exists) if (params.language) { const searchTerm = params.language.toLowerCase(); results = results.filter(d => d.language && d.language.toLowerCase() === searchTerm ); } // Apply limit const limit = params.limit || 100; results = results.slice(0, limit); return { total_count: results.length, results: results, source: 'EMA EPAR Documents', source_url: url, last_updated: new Date().toISOString() }; } /** * Search All EMA Documents * @param {Object} params - Search parameters * @returns {Promise<Object>} All document data */ async function searchAllDocuments(params = {}) { // Validate input parameters if (params.limit && (typeof params.limit !== 'number' || params.limit < 1 || params.limit > 10000)) { throw new Error('limit must be a number between 1 and 10000'); } const url = generateEmaUrl('documents-output-json-report_en.json'); const allDocuments = await makeEmaDocumentRequest(url); let results = allDocuments; // Filter by search term in title (if field exists) if (params.search_term) { const searchTerm = params.search_term.toLowerCase(); results = results.filter(d => (d.title && d.title.toLowerCase().includes(searchTerm)) || (d.document_title && d.document_title.toLowerCase().includes(searchTerm)) ); } // Filter by document type (if field exists) if (params.document_type) { const searchTerm = params.document_type.toLowerCase(); results = results.filter(d => d.document_type && d.document_type.toLowerCase().includes(searchTerm) ); } // Filter by category (if field exists) if (params.category) { const searchTerm = params.category.toLowerCase(); results = results.filter(d => d.category && d.category.toLowerCase().includes(searchTerm) ); } // Apply limit const limit = params.limit || 100; results = results.slice(0, limit); return { total_count: results.length, results: results, source: 'EMA All Documents', source_url: url, last_updated: new Date().toISOString() }; } /** * Search Non-EPAR Documents * @param {Object} params - Search parameters * @returns {Promise<Object>} Non-EPAR document data */ async function searchNonEparDocuments(params = {}) { // Validate input parameters if (params.limit && (typeof params.limit !== 'number' || params.limit < 1 || params.limit > 10000)) { throw new Error('limit must be a number between 1 and 10000'); } const url = generateEmaUrl('documents-output-non_epar_documents_json-report_en.json'); const allDocuments = await makeEmaDocumentRequest(url); let results = allDocuments; // Filter by search term (if field exists) if (params.search_term) { const searchTerm = params.search_term.toLowerCase(); results = results.filter(d => (d.title && d.title.toLowerCase().includes(searchTerm)) || (d.document_title && d.document_title.toLowerCase().includes(searchTerm)) ); } // Filter by document type (if field exists) if (params.document_type) { const searchTerm = params.document_type.toLowerCase(); results = results.filter(d => d.document_type && d.document_type.toLowerCase().includes(searchTerm) ); } // Apply limit const limit = params.limit || 100; results = results.slice(0, limit); return { total_count: results.length, results: results, source: 'EMA Non-EPAR Documents', source_url: url, last_updated: new Date().toISOString() }; } module.exports = { searchMedicines, getMedicineByName, getOrphanDesignations, getSupplyShortages, getReferrals, getPostAuthProcedures, getDhpcs, getPsusas, getPips, getHerbalMedicines, getArticle58Medicines, searchEparDocuments, searchAllDocuments, searchNonEparDocuments, parseEmaDate };

Latest Blog Posts

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/openpharma-org/ema-mcp'

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