UNPKG

@cyanheads/pubmed-mcp-server

Version:

Search PubMed/Europe PMC, fetch articles and full text (PMC/EPMC/Unpaywall), citations, MeSH terms via MCP. STDIO or Streamable HTTP.

121 lines 4.95 kB
/** * @fileoverview Server-specific configuration for NCBI E-utilities. * Lazy-parsed from environment variables. Framework config (transport, logging, etc.) * is handled by @cyanheads/mcp-ts-core. * @module src/config/server-config */ import { z } from '@cyanheads/mcp-ts-core'; import { parseEnvConfig } from '@cyanheads/mcp-ts-core/config'; /** * Treats an unset env var (`undefined`) and a set-but-empty env var (`""`) * identically. Without this, `NCBI_ADMIN_EMAIL=` would fail `z.email()` * validation instead of being interpreted as "no admin email configured". */ const emptyAsUndefined = (v) => (v === '' ? undefined : v); /** * Parse a string env var as a boolean. `z.coerce.boolean()` is unusable for * env vars because it applies JavaScript truthy semantics — `"false"` coerces * to `true`. This mirrors the framework's `envBoolean` so `EUROPEPMC_ENABLED=false` * actually disables the service. */ const envBoolean = z.preprocess((v) => { if (typeof v === 'string') { const s = v.trim().toLowerCase(); if (s === 'true' || s === '1') return true; if (s === 'false' || s === '0' || s === '') return false; } return v; }, z.boolean()); const ServerConfigSchema = z.object({ apiKey: z.preprocess(emptyAsUndefined, z.string().optional()).describe('NCBI API key'), toolIdentifier: z.string().default('pubmed-mcp-server').describe('NCBI tool identifier'), adminEmail: z.preprocess(emptyAsUndefined, z.email().optional()).describe('Admin contact email'), requestDelayMs: z.coerce.number().min(50).max(5000).default(334).describe('Request delay in ms'), maxConcurrent: z.coerce .number() .min(1) .max(16) .default(8) .describe('Max concurrent in-flight NCBI requests'), maxRetries: z.coerce.number().min(0).max(10).default(6).describe('Max retry attempts'), timeoutMs: z.coerce .number() .min(1000) .max(120000) .default(30000) .describe('Per-request HTTP timeout in ms'), totalDeadlineMs: z.coerce .number() .min(5000) .max(600000) .default(60000) .describe('Total deadline across all retry attempts for one NCBI call, in ms'), unpaywallEmail: z .preprocess(emptyAsUndefined, z.email().optional()) .describe('Email for Unpaywall API (enables non-PMC full-text fallback when set)'), unpaywallTimeoutMs: z.coerce .number() .min(1000) .max(120000) .default(20000) .describe('Per-request HTTP timeout for Unpaywall lookups and content fetches, in ms'), europepmcEnabled: envBoolean .default(true) .describe('Enable Europe PMC search tool and `pubmed_fetch_fulltext` JATS fallback chain. Set false to fully disable EPMC calls.'), europepmcEmail: z .preprocess(emptyAsUndefined, z.email().optional()) .describe('Optional contact email sent with Europe PMC requests'), europepmcRequestDelayMs: z.coerce .number() .min(50) .max(5000) .default(200) .describe('Minimum gap between Europe PMC request starts in ms'), europepmcMaxRetries: z.coerce .number() .min(0) .max(10) .default(3) .describe('Max retry attempts for failed Europe PMC requests'), europepmcTimeoutMs: z.coerce .number() .min(1000) .max(120000) .default(20000) .describe('Per-request HTTP timeout for Europe PMC calls, in ms'), }); let _config; export function getServerConfig() { if (!_config) { const parsed = parseEnvConfig(ServerConfigSchema, { apiKey: 'NCBI_API_KEY', toolIdentifier: 'NCBI_TOOL_IDENTIFIER', adminEmail: 'NCBI_ADMIN_EMAIL', requestDelayMs: 'NCBI_REQUEST_DELAY_MS', maxConcurrent: 'NCBI_MAX_CONCURRENT', maxRetries: 'NCBI_MAX_RETRIES', timeoutMs: 'NCBI_TIMEOUT_MS', totalDeadlineMs: 'NCBI_TOTAL_DEADLINE_MS', unpaywallEmail: 'UNPAYWALL_EMAIL', unpaywallTimeoutMs: 'UNPAYWALL_TIMEOUT_MS', europepmcEnabled: 'EUROPEPMC_ENABLED', europepmcEmail: 'EUROPEPMC_EMAIL', europepmcRequestDelayMs: 'EUROPEPMC_REQUEST_DELAY_MS', europepmcMaxRetries: 'EUROPEPMC_MAX_RETRIES', europepmcTimeoutMs: 'EUROPEPMC_TIMEOUT_MS', }); /** * An API key raises NCBI's rate ceiling from ~3 req/s to ~10 req/s. If the * operator hasn't explicitly overridden the delay, tighten from the 334ms * safe default to 100ms when a key is present. */ _config = parsed.apiKey && process.env.NCBI_REQUEST_DELAY_MS === undefined ? { ...parsed, requestDelayMs: 100 } : parsed; } return _config; } //# sourceMappingURL=server-config.js.map