Skip to main content
Glama

Harmonize Free-Text Terms to Standard Codes

harmonize_terms
Read-onlyIdempotent

Map lists of free-text clinical terms to standard ICD-11, RxNorm/ATC, or LOINC codes with ranked candidates and confidence labels, enabling reviewable crosswalks.

Instructions

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.

Input Schema

NameRequiredDescriptionDefault
termsYesTerms to harmonize, each with its domain. Up to 50 per call; repeated term+domain pairs are looked up once.
max_candidatesNoCandidates kept per term, best first (1-5, default 3).

Output Schema

NameRequiredDescriptionDefault
totalYesTerms submitted.
countsYesSubmitted terms tallied by outcome (best-candidate match_type, no candidates, or error); the five counts add up to total.
rankingYesSelf-description of the ranking: match_score and match_type are computed by this server, not upstream relevance.
resultsYesOne entry per submitted term, in request order.
provenanceYesOne provenance block per upstream source that contributed to this response (licenses are never merged; each block carries the origin diagnostics of ITS source)
attributionYesCanonical source URLs of this response (attribution list)
unique_lookupsYesDistinct term+domain pairs actually looked up.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed9 schema fields changedv2.4.0
    • addedOutput schema / properties / counts / description
      Added value: +"Submitted terms tallied by outcome (best-candidate match_type, no candidates, or error); the five counts add up to total."
    • addedOutput schema / properties / counts / properties / error / description
      Added value: +"Terms whose lookup failed."
    • addedOutput schema / properties / counts / properties / exact / description
      Added value: +"Terms whose best candidate is an exact match."
    • addedOutput schema / properties / counts / properties / needs_review / description
      Added value: +"Terms whose best candidate needs review."
    • addedOutput schema / properties / counts / properties / no_candidates / description
      Added value: +"Terms the source answered with no candidates."
    • addedOutput schema / properties / counts / properties / strong / description
      Added value: +"Terms whose best candidate is a strong match."
    • addedOutput schema / properties / ranking / description
      Added value: +"Self-description of the ranking: match_score and match_type are computed by this server, not upstream relevance."
    • addedOutput schema / properties / ranking / properties / method / description
      Added value: +"Ranking method: lexical similarity computed by this server."
    • addedOutput schema / properties / ranking / properties / note / description
      Added value: +"How match_score and match_type are computed."
  2. Changed8 schema fields changedv2.3.0
    • changedOutput schema / properties / provenance / description
      Previous value: -"One provenance block per upstream source that contributed to this response (contract v1.1; licenses are never merged; each block carries the origin diagnostics of ITS source)"New value: +"One provenance block per upstream source that contributed to this response (licenses are never merged; each block carries the origin diagnostics of ITS source)"
    • changedOutput schema / properties / provenance / items / description
      Previous value: -"Bloco de proveniência (contrato v1.2): fonte, URL, competência, extração, diagnóstico de origem, citação e licença; e, só quando a resposta junta partes de origens ou momentos distintos, de onde veio cada parte"New value: +"Bloco de proveniência: fonte, URL, competência, extração, diagnóstico de origem, citação e licença; e, só quando há o que dizer, de onde veio cada parte, avisos da fonte, o que o servidor calculou e se o dado ainda pode mudar"
    • addedOutput schema / properties / provenance / items / properties / derivation_note
      Added value: +{
      +  "description": "What this server computed; present together with derived",
      +  "type": "string"
      +}
    • addedOutput schema / properties / provenance / items / properties / derived
      Added value: +{
      +  "description": "Present (true) only when this server computed the value (e.g. a ranking or summary statistics) instead of passing on what the source returned",
      +  "type": "boolean"
      +}
    • changedOutput schema / properties / provenance / items / properties / field_sources / description
      Previous value: -"Present only when the response joins parts from different origins or extraction instants (contract v1.2): for each group of fields, its URL, extraction instant and whether it came from cache. Absent in single-origin responses"New value: +"Present only when the response joins parts from different origins or extraction instants: for each group of fields, its URL, extraction instant and whether it came from cache. Absent in single-origin responses"
    • addedOutput schema / properties / provenance / items / properties / notices
      Added value: +{
      +  "description": "Caveats the source publishes about the data, verbatim (license statements are not repeated here; see the info://licenses resource). Absent when there is none",
      +  "items": {
      +    "type": "string"
      +  },
      +  "minItems": 1,
      +  "type": "array"
      +}
    • changedOutput schema / properties / provenance / items / properties / retrieval / description
      Previous value: -"Origin diagnostics of THIS source in this call (contract v1.1): requests made to the upstream, attempts summed across retries, anomalies worked around (kind + count); unstable=true when any anomaly happened. null when nothing was measured (bundled dataset, or response served entirely from cache)"New value: +"Origin diagnostics of THIS source in this call: requests made to the upstream, attempts summed across retries, anomalies worked around (kind + count); unstable=true when any anomaly happened. null when nothing was measured (bundled dataset, or response served entirely from cache)"
    • addedOutput schema / properties / provenance / items / properties / revision
      Added value: +{
      +  "additionalProperties": false,
      +  "description": "Whether the data can still change at the source: current = the version in force, the source may revise it; provisional = preliminary; final = will not change. Absent when unknown",
      +  "properties": {
      +    "note": {
      +      "description": "O específico da fonte: o que revisa, quando, por quê",
      +      "type": [
      +        "string",
      +        "null"
      +      ]
      +    },
      +    "status": {
      +      "description": "current = versão vigente, a fonte pode revisá-la; provisional = preliminar, incompleta ou sujeita a mudança (não compare como se fosse fechada); final = não muda mais, declarado pela fonte",
      +      "enum": [
      +        "current",
      +        "provisional",
      +        "final"
      +      ],
      +      "type": "string"
      +    }
      +  },
      +  "required": [
      +    "status",
      +    "note"
      +  ],
      +  "type": "object"
      +}
  3. Changed2 schema fields changedv2.1.1
    • changedOutput schema / properties / provenance / items / description
      Previous value: -"Bloco de proveniência (contrato v1.1): fonte, URL, competência, extração, diagnóstico de origem, citação e licença"New value: +"Bloco de proveniência (contrato v1.2): fonte, URL, competência, extração, diagnóstico de origem, citação e licença; e, só quando a resposta junta partes de origens ou momentos distintos, de onde veio cada parte"
    • addedOutput schema / properties / provenance / items / properties / field_sources
      Added value: +{
      +  "description": "Present only when the response joins parts from different origins or extraction instants (contract v1.2): for each group of fields, its URL, extraction instant and whether it came from cache. Absent in single-origin responses",
      +  "items": {
      +    "additionalProperties": false,
      +    "description": "Proveniência de um grupo de campos da resposta: de onde veio e quando foi extraído",
      +    "properties": {
      +      "data_vintage": {
      +        "description": "Vintage/competência da sub-fonte",
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "dataset_id": {
      +        "description": "Identificador do conjunto da sub-fonte",
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "fields": {
      +        "description": "Campos do payload atribuídos a esta sub-fonte",
      +        "items": {
      +          "type": "string"
      +        },
      +        "minItems": 1,
      +        "type": "array"
      +      },
      +      "retrieved_at": {
      +        "description": "ISO-8601 da extração desta sub-fonte na origem",
      +        "type": [
      +          "string",
      +          "null"
      +        ]
      +      },
      +      "served_from_cache": {
      +        "description": "true se esta sub-fonte veio do cache do servidor (retrieved_at é o da extração original); false se foi buscada nesta chamada; null se o servidor não distingue",
      +        "type": [
      +          "boolean",
      +          "null"
      +        ]
      +      },
      +      "source_url": {
      +        "description": "URL canônica da sub-fonte que originou estes campos",
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "fields",
      +      "source_url",
      +      "dataset_id",
      +      "data_vintage",
      +      "retrieved_at"
      +    ],
      +    "type": "object"
      +  },
      +  "minItems": 1,
      +  "type": "array"
      +}
  4. Addedv2.0.0

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare safety/idempotency; the description goes far past them with the 50-term hard limit and its validation error, per-row error isolation ('one failed lookup does not fail the batch'), dedup of repeated term+domain pairs, dropped-candidate rules (no shared word, 'Deprecated' LOINC, postcoordinated clusters), English-only searching, and a de-identification warning because terms go to WHO/NLM APIs.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core action, then a scannable bullet block, then limits, label semantics, and alternatives in that order — the structure is easy to navigate. Some content duplicates the schema (domain routing) and the match_type rules run long, but every sentence carries operational information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a batched, open-world, partially-failing lookup tool this covers everything an agent needs: batching limits, per-row error handling, label semantics for triage, data-sensitivity caveat, and a pointer to terminology_versions plus per-source provenance for reproducibility. Output schema exists, so return-shape detail is correctly omitted.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3; the description still adds value by re-framing domain as a vocabulary route (RxNorm ingredients first, plus ATC classes of the term) and by spelling out that max_candidates keeps 1-5 per term with default 3, and that repeats are looked up once. It doesn't add syntax beyond the schema, so not a 5.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Map a LIST of free-text clinical terms to standard codes') and names the output shape (ranked candidates with confidence labels). It also explicitly differentiates itself from siblings: find_equivalent for single terms across terminologies, cid10_search for pt-BR diagnoses, validate_codes for checking existing codes.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

A dedicated 'Use this tool to' block enumerates the three concrete scenarios (dataset columns, triage by label, auditable crosswalk), and the closing paragraph names each alternative with the condition that selects it. Nothing is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.