# Medical Terminologies MCP

> Diagnoses, drugs & lab codes: ICD-11, LOINC, RxNorm, MeSH, ATC, CID-10. 33 tools, MIT.

- name: io.github.SidneyBissoli/medical-terminologies-mcp
- version: 2.3.0
- connection class: R0 (autonomous)
- trust: 97/100
- quality: 73/100 (needs work)
- owner: not claimed
- descriptor: https://api.protogrid.dev/v1/servers/io.github.SidneyBissoli%2Fmedical-terminologies-mcp
- tools: https://api.protogrid.dev/v1/servers/io.github.SidneyBissoli%2Fmedical-terminologies-mcp/tools

**An agent can connect right now, no human step.** Remote endpoint, no authentication, reachable on the last probe.

## Connection (mcpServers)

```json
{
  "mcpServers": {
    "medical-terminologies-mcp": {
      "type": "http",
      "url": "https://medical.sidneybissoli.com/mcp"
    }
  }
}
```


## Trust

- hygiene: 100
- liveness: 100
- freshness: 100
- provenance: 90
- drivers: +repository +namespace-matches-repo +website +reachable +uptime-99% +updated-5d-ago
- Derived from observable signals (official registry feed and our own credential-free probes); no code audit performed.

## Quality 73/100 (needs work)

### protocol: 88 (weight 25)

- pass `protocol.modern`: supports 2026-07-28 (server/discover)
- pass `protocol.stateless`: answers without a session
- pass `protocol.transport`: streamable HTTP
- warn `protocol.list_ttl`: tools/list declares ttlMs 0, so clients re-fetch it on every use

### authorization: n/a (weight 15)

- n/a `auth.prm`, `auth.as_metadata`, `auth.cimd`: no remote uses OAuth
- n/a `auth.secret_in_url`: no templated URL

### tool hygiene: 86 (weight 30)

- pass `tools.descriptions`: every tool has a description
- warn `tools.description_length`: 6 tools have descriptions over 1,024 characters, which crowds the context
- pass `tools.schemas`: every tool has an object input schema
- pass `tools.annotations`: 33 of 33 tools declare readOnlyHint or destructiveHint
- pass `tools.directory_hints`: every tool declares readOnlyHint, destructiveHint, idempotentHint and openWorldHint
- warn `tools.token_cost`: about 9,641 tokens to load every tool
- pass `tools.api_dump`: tools are not a one-to-one API dump

### stability: 25 (weight 15)

- warn `stability.changes`: 151 tool changes in 30 days
- fail `stability.rug_pull`: 5 tools kept their name but changed most of their description in 30 days; review before trusting them

### dependencies: n/a (weight 15)

- n/a `deps.known_vulns`, `deps.mcp_sdk_version`, `deps.resolvable`: deps.dev does not know medical-terminologies-mcp@2.3.0, medical-terminologies-mcp@2.3.0

- tools: 33, about 9,641 tokens to load them all
- tool set hash: 1089a111137bc4f2445fbda64637a9ad
- tools last changed: 2026-10-16T00:05:43.580Z
- badge: [![protogrid quality](https://protogrid.dev/badge/io.github.SidneyBissoli/medical-terminologies-mcp.svg)](https://protogrid.dev/servers/io.github.SidneyBissoli/medical-terminologies-mcp)
- Checks run on what our credential-free, read-only probes observe; tools are never called and no code audit is performed.

### Tool changes

- 2026-10-16 `fetch` changed (outputSchema)
- 2026-10-16 `search` changed (outputSchema)
- 2026-10-16 `terminology_diff` changed (outputSchema)
- 2026-10-16 `terminology_versions` changed (outputSchema)
- 2026-10-16 `cid10_chapter` changed (outputSchema)
- 2026-10-16 `cid10_chapters` changed (outputSchema)
- 2026-10-16 `cid10_lookup` changed (outputSchema)
- 2026-10-16 `cid10_search` changed (outputSchema)
- 2026-10-16 `atc_members` changed (outputSchema)
- 2026-10-16 `atc_lookup` changed (outputSchema)

Full history: https://api.protogrid.dev/v1/servers/io.github.SidneyBissoli%2Fmedical-terminologies-mcp/changes

## Dependencies

- npm medical-terminologies-mcp@2.3.0: unknown
- npm medical-terminologies-mcp@2.3.0: unknown

Dependency graphs from deps.dev; advisories from OSV.dev, including the GitHub Advisory Database and the PyPI Advisory Database; all CC BY 4.0.

## Remotes

- https://medical.sidneybissoli.com/mcp (streamable-http, auth none, reachable true, uptime30d 0.99159664)

## Packages

- npm medical-terminologies-mcp@2.3.0 secrets: WHO_CLIENT_SECRET
- npm medical-terminologies-mcp@2.3.0 secrets: WHO_CLIENT_SECRET

## Tools (33)

- `atc_classify`: Look up the WHO ATC (Anatomical Therapeutic Chemical) classification(s) for a drug by name.

Use this tool to:
- Find the ATC code for a medication (e.g., "metformin" → A10BA02)
- Identify the therapeutic and pharmacological class hierarchy
- Cross-reference drugs with their international ATC codes

Returns one entry per ATC code the drug belongs to. A single-ingredient drug typically maps to one substance-level code; combination products map to multiple. ATC codes are international (WHO Collaborating Centre); this tool retrieves them via NLM RxClass.

Input is a drug NAME (brand or generic, English/US naming as in RxNorm). A name RxNorm does not recognize, or a drug without an ATC mapping, returns an empty `matches` list with an explanatory note — not an error; try the generic name, or resolve the name first with rxnorm_search.

Which ATC tool: start here when you have a drug name; use atc_lookup when you already have a class code (level 1-4, e.g. "A10BA") and want its name; use atc_members to list the drugs inside a class.
- `atc_lookup`: Look up an ATC code at level 1-4 to get its name and hierarchy level.

Use this tool to:
- Resolve an ATC code (e.g., "A10BA") to its class name ("Biguanides")
- Confirm a code exists in the current ATC index
- Identify the level (anatomical / therapeutic / pharmacological / chemical)

Accepts codes 1-5 characters long: "A" (anatomical), "A10" (therapeutic), "A10B" (pharmacological), "A10BA" (chemical). Substance-level codes (7 chars, e.g., "A10BA02") are not exposed by this endpoint — use atc_classify with the drug name to retrieve the substance code.
- `atc_members`: List the drugs (substances) that belong to an ATC class.

Use this tool to:
- Enumerate all members of a therapeutic class (e.g., "A10BA" → metformin, phenformin)
- Build a list of drugs sharing a pharmacological mechanism
- Explore an ATC subtree at any level

Each member includes its substance-level (7-char) ATC code via source_atc_code, useful for disambiguation when the queried class is at level 1-4. RxNorm's catalog is US-centric; the ATC class names and codes themselves are international.
- `cid10_chapter`: Get one CID-10 chapter and its constituent groups (e.g., "Chapter IX → I00-I02 Febre reumática aguda, I05-I09 Doenças reumáticas crônicas do coração, ...").

Use this tool to:
- Drill from a chapter into its groups (code ranges with Portuguese titles)
- Build hierarchical browsers
- Find which group contains a code range

Provide the chapter number `num` as an integer 1-22 (chapter I = 1, IX = 9). Numbers outside 1-22 are rejected with a validation error. Returns the chapter (title and code range) plus ALL its groups in one response — no pagination; the 22 chapters hold 275 groups in total. Answered locally from the bundled CID-10 V2008; no network call.

Which CID-10 tool: use cid10_chapters first if you do not know the chapter number (it lists all 22 with code ranges); use cid10_lookup for one specific code and cid10_search to find codes by Portuguese text. For the international ICD-11, use icd11_chapters.
- `cid10_chapters`: List the 22 chapters of CID-10 with their code ranges and Portuguese titles.

Use this tool to:
- See the top-level structure of CID-10 (chapters I-XXII, e.g., "I. Algumas doenças infecciosas e parasitárias", "IX. Doenças do aparelho circulatório")
- Map a code to its chapter by code range (e.g., I00-I99 → chapter IX)
- Build a navigable table of contents for downstream tooling

Returns 22 entries — CID-10 V2008 has not been updated since 2008.
- `cid10_lookup`: Look up a specific CID-10 code and return its Portuguese name.

Use this tool to:
- Resolve a code to its Brazilian description ("I21" → "Infarto agudo do miocárdio")
- Confirm a 3-char category or 4-char subcategory exists in CID-10
- Retrieve gender / cause-of-death restriction flags when applicable

Accepts both dotted ("A00.1") and undotted ("A001") forms; returns the canonical display.
- `cid10_search`: Search the Brazilian CID-10 (Classificação Estatística Internacional de Doenças, 10ª Revisão) by Portuguese text.

Use this tool to:
- Find CID-10 codes for Brazilian SUS / ANVISA contexts ("infarto", "diabetes", "tuberculose")
- Look up the official Portuguese (CBCD/USP) translation of a clinical term
- Locate codes for billing, epidemiology, and clinical documentation in Brazil

Returns matches from CID-10 categories (3-char) and/or subcategories (4-char). Search is diacritic-insensitive: typing "infeccoes" matches "infecções". Every word must match (AND), and everyday Portuguese is resolved to the CID-10's own wording (câncer→neoplasia maligna, AVC→acidente vascular cerebral, pressão alta→hipertensão, suicídio→lesão autoprovocada, aids→HIV); when that happens the response says so in vocabulary_notes. This tool searches the Brazilian Portuguese CID-10 V2008 — for the international ICD-11 (current WHO revision, in English by default), use icd11_search.
- `fetch`: Returns the full document for an id obtained from `search`, as { id, title, text, url, metadata }: `text` is the readable content (Markdown) and `url` the canonical public page to cite.

Companion of `search` in the OpenAI Deep Research contract, over the medical terminologies (CID-10 categories and chapters, ICD-11, LOINC, RxNorm, MeSH, terminology version records) catalog. Only ids returned by `search` are valid; an unknown id returns an error.
The terminology tools (`icd11_*`, `cid10_*`, `loinc_*`, `rxnorm_*`, `mesh_*`, `atc_*`, `map_*`, `find_equivalent`, `validate_codes`) remain the tools for data queries.

Behavior: read-only and idempotent — a live GET against the public source when the document needs it.
- `find_equivalent`: Ranked unified search for equivalent terms across multiple medical terminologies.

Use this tool to:
- Find the same concept in different coding systems
- Compare how terminologies represent a concept
- Support terminology mapping and data integration

Searches across: ICD-11, LOINC, RxNorm, and MeSH. Set `target_terminologies` to limit which are searched, or set `source_terminology` to exclude one (e.g. when you already have a code from that terminology and want equivalents elsewhere). The two combine: source is subtracted from targets. `limit` caps candidates per terminology (default 5, max 10).

Every candidate carries `match_score` (lexical similarity to the search term, 0-1) and `rank` (global position across all searched terminologies) — both computed by this server, since upstreams don't expose comparable relevance scores. Candidates from different terminologies whose titles are lexically identical are clustered in `groups` — a strong same-concept signal (absence of a group is NOT evidence of non-equivalence).

Searches upstreams in English. For official pt-BR content, use the dedicated tools: `icd11_search`/`mesh_search` accept `language: "pt"`, and `cid10_search` is natively Portuguese.
- `harmonize_terms`: Map a LIST of free-text clinical terms to standard codes in one call, with ranked candidates and a confidence label for each — the building block of a reviewable crosswalk.

Use this tool to:
- Harmonize a column of diagnoses, drugs or lab names from a dataset to ICD-11 / RxNorm (+ ATC) / LOINC
- Triage which terms map cleanly (exact / strong) and which need a person (needs_review)
- Build a crosswalk you can audit: every row keeps its candidates, scores and sources

Give each term its `domain`: diagnosis → ICD-11; drug → RxNorm concepts (ingredients first) plus the ATC classes of the term; lab → LOINC. Up to 50 terms per call — a longer list is refused with a validation error: split it into batches of 50. Repeated term+domain pairs are looked up once. `max_candidates` keeps 1-5 per term (default 3).

Every candidate carries `match_score` (lexical, 0-1, the find_equivalent formula) and `match_type`: exact = same words after normalization; strong = every term word is in the title (or the matched synonym) and score ≥ 0.85; needs_review = anything else. A one-word term is exact or needs_review, never strong ("Tylenol" vs "Tylenol PM" is a different product). Synonyms, abbreviations ("MI", "HbA1c") and misspellings land in needs_review or no_candidates — the label errs toward asking a person. Candidates sharing no word with the term are dropped, as are LOINC codes named "Deprecated". For diagnoses, a candidate is also scored against the synonyms WHO matched (e.g. "hypertension NOS" for Essential hypertension), reported in `matched_label`; postcoordinated clusters (codes with "/" or "&") are left out — build those with icd11_postcoordination. Lab names are ambiguous without specimen and property: "glucose" matches over a thousand LOINC codes, so write "glucose serum" or expect needs_review. One failed lookup does not fail the batch: that row comes back with status "error".

Terms are searched in English and sent to the WHO and NLM APIs — de-identify the list first. For Brazilian Portuguese diagnoses use cid10_search; to check codes you already have, use validate_codes; for one term across every terminology, use find_equivalent. Record the vocabulary versions with the provenance blocks (one per source) and terminology_versions.
- `icd11_chapters`: List all ICD-11 chapters (top-level categories) of the pinned WHO release.

Use this tool to:
- Get an overview of ICD-11 structure
- Find which chapter covers a body system or condition type
- Get chapter URIs to drill down with icd11_hierarchy (direction 'children')

Returns 28 entries in one response, no pagination — chapters 01-26 plus the supplementary sections V (functioning) and X (extension codes) — each with number, code, title and URI. Each chapter is fetched separately from WHO; if one fetch fails, that entry keeps its URI and carries an `error` instead of a title, and the rest still come back. Set `language` for WHO's official translations (e.g. `language: "pt"`); the result is cached, so repeated calls are cheap.

When NOT to use: to find a specific disease, use icd11_search; for the Brazilian CID-10 (ICD-10) chapters, use cid10_chapters.
- `icd11_hierarchy`: Navigate the ICD-11 hierarchy to find parent or child entities.

Use this tool to:
- Find broader categories (parents) of a condition
- Find specific subtypes (children) of a condition
- Understand the classification structure

Name the entity by `code` (a leaf code like "5A11", or a block range like "5A10-5A2Y" — blocks come back from 'parents' with an empty code and a code_range) or by `uri` (the URI any previous answer returned). Direction 'parents' returns ancestor categories, 'children' returns subcategories. ICD-10 codes (like "E11") are not ICD-11 codes: convert them first with map_icd10_to_icd11.
- `icd11_lookup`: Get detailed information about ONE ICD-11 entity you already have a code or URI for.

Use this tool to:
- Get the full definition of a disease
- Retrieve coding notes, inclusions and exclusions
- Get the official title and index terms (synonyms)

Provide `code` (e.g., "BA00") or `uri` (any URI a previous answer returned) — at least one is required; calling with neither returns a validation error naming both. Set `language` for WHO's official translations (e.g. `language: "pt"` for official Portuguese).

Returns a single entity (no pagination). A code WHO does not know comes back as a "not found" error, never an empty record.

When NOT to use: to find a code from a disease name, use icd11_search first; to walk parents/children, use icd11_hierarchy; for an ICD-10 code (like "E11"), convert it with map_icd10_to_icd11 — ICD-10 codes are not ICD-11 codes.
- `icd11_postcoordination`: List the postcoordination axes WHO allows for one ICD-11 stem code (MMS linearization, pinned release).

Postcoordination means attaching extra detail to a stem code — severity, laterality, anatomy, causing agent, etc. — to build a composite (cluster) code.

Use this tool to:
- See which axes a stem code accepts before building a composite code
- Check which axes are REQUIRED vs optional
- See whether an axis takes one or several values, and how many values it offers

Provide an ICD-11 `code` (e.g., "BA00"). Returns one entry per axis with `axis_name`, `required`, `allow_multiple` and `value_count` — the count of allowed values, not the values themselves. A code with no postcoordination, or one WHO does not know, returns an empty `axes` list (not an error), so check the code with icd11_lookup if the list is unexpectedly empty.

When NOT to use: this does not build or validate a composite code, and it does not list the allowed values; to get a code from a disease name, use icd11_search.
- `icd11_search`: Search for medical conditions, diseases, and health problems in ICD-11 (International Classification of Diseases, 11th Revision).

Use this tool to:
- Find ICD-11 codes for diagnoses
- Search for diseases by name or keyword
- Look up conditions in multiple languages

Set `language` for WHO's official translations — e.g. `language: "pt"` searches and returns the official Portuguese (pt-BR) ICD-11 labels. Never machine-translated.

Returns matching entities with codes, titles, and relevance scores.
- `loinc_answers`: Get the list of valid answers for a LOINC questionnaire item.

Use this tool to:
- Find valid response options for survey questions
- Get answer codes (LA…) for data entry validation
- Get the item scores of scored instruments (e.g. PHQ-9: "Not at all" = 0 … "Nearly every day" = 3)

Returns each answer with its LOINC answer code (LA…), text, display order and score (null when the list has none). A valid LOINC code without a defined answer list (e.g. a numeric lab such as 2339-0) returns an empty list; a code LOINC does not know returns a "not found" error.

Only applicable to LOINC codes that represent questions with defined answer sets. For the questions of a whole questionnaire, use loinc_panels; to find a code by name, use loinc_search.
- `loinc_details`: Get detailed information about a specific LOINC code.

Use this tool to:
- Get the full name and description of a LOINC code
- Find the component, property, timing, and system
- Check the scale type and method

Provide a LOINC number in format "XXXXX-X" (e.g., "2339-0" for Glucose).
- `loinc_panels`: Get the structure of a LOINC panel or form.

Use this tool to:
- See all tests included in a panel (e.g., CBC, metabolic panel)
- Get the structure of assessment forms
- Find related observations grouped together

Returns the list of LOINC codes that make up the panel.
- `loinc_search`: Search for laboratory tests, clinical observations, and measurements in LOINC (Logical Observation Identifiers Names and Codes).

Use this tool to:
- Find LOINC codes for lab tests (e.g., "glucose", "hemoglobin")
- Search for clinical measurements and vital signs
- Look up diagnostic observations

Returns matching LOINC codes with names, components, and properties.
- `map_icd10_to_icd11`: Authoritative ICD-10 → ICD-11 mapping using WHO transition tables (release 2025-01, bundled with the server).

Returns the primary 1:1 ICD-11 category for the ICD-10 code plus any alternative ICD-11 candidates that WHO documents (some ICD-10 concepts split into multiple ICD-11 entities). For each mapping, includes the ICD-11 code, title, chapter, and the Foundation URI / Linearization URI for navigating to the full entity definition.

Use this for clinical coding, billing migration, retrospective analysis, and any workflow that needs authoritative mapping rather than text-search candidates. Coverage: 11,243 ICD-10 categories (excludes chapters and blocks like "A00-A09" which aren't used in clinical coding).

Provide a code like "E11" (Type 2 diabetes), "I21" (Acute MI), or "A07.8" (4 alternatives in WHO's table). Both dotted ("A07.8") and undotted ("A078") forms are accepted.

Returns "no mapping" when the code isn't in the WHO category-level table — that's the honest answer rather than a fuzzy search fallback.
- `mesh_descriptor`: Get detailed information about a MeSH descriptor by ID.

Use this tool to:
- Get the full definition (scope note) of a MeSH term
- View tree numbers showing hierarchy location
- See related concepts and synonyms

Provide a MeSH Descriptor ID like "D015242" (Ofloxacin). Set `language` to request NLM's official translations where they exist (e.g. `language: "pt"`).
- `mesh_qualifiers`: Get allowed qualifiers (subheadings) for a MeSH descriptor.

Use this tool to:
- Find which qualifiers can be combined with a descriptor
- Build precise MeSH search queries
- Understand aspects that can be specified

Qualifiers refine descriptors (e.g., "Diabetes Mellitus/drug therapy").
- `mesh_search`: Search for MeSH (Medical Subject Headings) descriptors.

Use this tool to:
- Find MeSH terms for indexing medical literature
- Look up subject headings for PubMed searches
- Find controlled vocabulary terms

Set `language` to request NLM's official translations where they exist (e.g. `language: "pt"` for Portuguese labels); content is never machine-translated.

Returns matching descriptors with MeSH IDs and labels.
- `mesh_tree`: Get the tree hierarchy location(s) for a MeSH descriptor.

Use this tool to:
- See where a term fits in the MeSH hierarchy
- Understand broader/narrower relationships
- Find related terms in the same branch

MeSH tree numbers show the hierarchical path (e.g., C14.280.647 for Myocardial Infarction).
- `rxnorm_classes`: Get therapeutic and pharmacologic classes for a drug.

Use this tool to:
- Find the drug class (e.g., "Beta-blockers", "NSAIDs")
- Identify therapeutic categories
- Look up mechanism of action classifications

Returns class IDs, names, and classification sources.
- `rxnorm_concept`: Get detailed information about a specific RxNorm concept by RxCUI.

Use this tool to:
- Get the full name and synonyms for a drug
- Check the concept status (active, remapped, etc.)
- View related concepts (ingredients, brands, forms)

Provide an RxCUI (RxNorm Concept Unique Identifier) like "161" — as a string of digits or as an integer.
- `rxnorm_ingredients`: Get active ingredients for a drug by RxCUI.

Use this tool to:
- Find the active ingredients in a medication
- Check for single vs. multiple ingredient products
- Identify the generic components of brand drugs

Returns ingredient RxCUIs and names.
- `rxnorm_ndc`: Map between RxNorm concepts and National Drug Codes (NDC).

Use this tool to:
- Get all NDC codes for a drug (by RxCUI)
- Find the RxCUI for an NDC code
- Cross-reference between coding systems

Provide either an RxCUI to get NDCs, or an NDC to get the RxCUI.
- `rxnorm_search`: Search for drugs in RxNorm (Normalized names for clinical drugs).

Use this tool to:
- Find drug concepts by brand or generic name
- Look up medications for prescribing
- Search for drug formulations

Returns matching drugs with RxCUI identifiers, names, and term types.
- `search`: Searches the medical terminologies (CID-10 categories and chapters, ICD-11, LOINC, RxNorm, MeSH, terminology version records) catalog and returns up to 10 matching documents as { id, title, url }, ordered by relevance (an empty list means nothing matched).

This tool exists for the OpenAI Deep Research contract: ChatGPT deep research, company knowledge and research workflows over the Responses API require exactly the tools `search` and `fetch`. Pass one of the returned ids to `fetch` to read the document.
For direct questions and for data (values, series, rankings) prefer the terminology tools (`icd11_*`, `cid10_*`, `loinc_*`, `rxnorm_*`, `mesh_*`, `atc_*`, `map_*`, `find_equivalent`, `validate_codes`), which return the actual data with provenance — this is a catalog index, not a data query.

Query: natural language or keywords, Portuguese or English; accents and case are ignored.

Behavior: read-only and idempotent — the catalog comes from the public source and is cached in memory.
- `terminology_diff`: Report what diff data is available between two versions of a terminology.

For most terminologies this is **guidance only** — the server doesn't ship historical snapshots, so the tool points at the publisher's official changelog and explains the cadence. `bundled_versions` lists the version(s) this server actually has on hand.

For **ICD-10 vs ICD-11** specifically, the tool surfaces a real cross-revision summary from the bundled WHO transition tables (the ICD-10 → ICD-11 case is a structural diff between two WHO revisions). Use `terminology: "icd10"` with no `to_version` to get the cross-revision summary: total mapped ICD-10 categories, how many are 1:1 vs split into multiple ICD-11 codes, and the average number of alternatives when split.

Inputs:
- `terminology` (required): which terminology to report on.
- `from_version` (optional): the version you have data from. If omitted, the tool reports against the currently-bundled version.
- `to_version` (optional): the version you want to compare to. If omitted, the tool reports against the publisher's latest known release.

This tool is intentionally a metadata + guidance layer, not a diff engine — for terminologies that change frequently (LOINC, RxNorm, MeSH), the publisher's official changelog is the authoritative source.
- `terminology_versions`: List the current version, release date, publisher, source URL, and update cadence of every terminology this server queries against.

Useful for pipeline maintainers who need to:
- Confirm which release of ICD-11 / LOINC / RxNorm / MeSH / ATC the server is querying before a batch run.
- Verify the bundled CID-10 (frozen at V2008) and ICD-10 → ICD-11 transition tables (currently 2025-01) match expectations.
- Cite the data version in research artifacts.

Pass `terminology` to filter to a single entry; otherwise the full set of 7 is returned. The ICD-10 → ICD-11 version reads live from the bundled dataset; everything else is metadata maintained alongside the project release.
- `validate_codes`: Validate a mixed batch of medical codes against their source terminologies. Useful for retrospective analysis of legacy databases — flag codes that no longer exist, surface ICD-10 → ICD-11 replacements, and grade activity status where the terminology exposes it.

For each input `{ code, terminology }`, returns:
- **valid**: whether the code exists in the source terminology.
- **active**: whether the code is currently active. Null when the source doesn't expose an explicit active/inactive distinction at category level (CID-10, ATC, ICD-11, RxNorm, MeSH all return null today; LOINC returns a real boolean).
- **title**: the official label/name when available.
- **replaced_by**: a successor code, populated today only for ICD-10 codes that have a primary ICD-11 mapping in the bundled WHO transition tables.
- **source**: human-readable provenance of the validation (terminology + release/version).
- **error**: non-null only when validation couldn't be performed (network error, upstream outage, etc.). `valid: false` + `error: null` means "code not found"; `valid: false` + `error: set` means "couldn't validate".

Terminology is **required per code** — auto-detection isn't supported because category codes like "A00" exist in both ICD-10 and CID-10. Accepted values: `icd11`, `icd10`, `loinc`, `rxnorm`, `mesh`, `atc`, `cid10`.

Hard cap of 50 codes per call; codes are validated in parallel through their respective clients, so total wall time scales with the slowest upstream + its rate limit (worst case ~10 s for a full batch hitting ICD-11).
