find_equivalent
Find the same medical concept across ICD-11, SNOMED CT, LOINC, RxNorm, and MeSH. Supports terminology mapping and data integration by comparing how terminologies represent a term.
Instructions
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, SNOMED CT, 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.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| term | Yes | Medical term to search (e.g., "diabetes", "aspirin") | |
| source_terminology | No | If set, this terminology is excluded from the search. Use this when the term came from this terminology and you want equivalents in the others. Combines with target_terminologies by subtraction (source is removed from the target list). | |
| target_terminologies | No | Limit the search to these terminologies. If omitted, all five are searched. |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| term | Yes | ||
| source_terminology | Yes | ||
| searched_terminologies | Yes | ||
| results | Yes |
Implementation Reference
- src/tools/crosswalk.ts:293-482 (handler)The main handler function for the find_equivalent tool. Parses params, computes target terminologies, searches up to 5 terminologies in parallel (ICD-11, SNOMED CT, LOINC, RxNorm, MeSH), builds markdown and structured output.
async function handleFindEquivalent(args: Record<string, unknown>): Promise<CallToolResult> { try { const params = FindEquivalentParamsSchema.parse(args); const term = params.term; const requestedTargets = params.target_terminologies ?? [...ALL_TERMINOLOGIES]; const targets: TerminologyKey[] = (params.source_terminology ? requestedTargets.filter((t) => t !== params.source_terminology) : requestedTargets) as TerminologyKey[]; if (targets.length === 0) { const requested = params.target_terminologies ? `target_terminologies=${JSON.stringify(params.target_terminologies)}` : 'all terminologies'; const empty: FindEquivalentOutput = { term, source_terminology: params.source_terminology ?? null, searched_terminologies: [], results: {}, }; return { content: [{ type: 'text', text: `# Cross-Terminology Search: "${term}"\n\nNo terminologies left to search after excluding source_terminology="${params.source_terminology}" from ${requested}. Widen target_terminologies or drop source_terminology.`, }], structuredContent: empty, }; } // Build a single typed map keyed by enum key. Markdown is derived from // this same map below — no duplicated data. const entries: Partial<Record<TerminologyKey, FindEquivalentEntry>> = {}; const searches: Promise<void>[] = []; const ok = (items: FindEquivalentEntry['items']): FindEquivalentEntry => ({ found: items.length > 0, error: null, items, }); const fail = (error: string): FindEquivalentEntry => ({ found: false, error, items: [] }); if (targets.includes('icd11')) { searches.push( (async () => { try { const client = getWHOClient(); const response = await client.search(term, 'en', 5); const icdResults = response.destinationEntities ?? []; entries.icd11 = ok( icdResults.slice(0, 5).map((r) => ({ code: r.theCode ?? 'N/A', title: r.title ?? 'N/A', })), ); } catch (e) { entries.icd11 = fail(e instanceof Error ? e.message : 'Error'); } })(), ); } if (targets.includes('snomed')) { if (!SNOMED_TOOLS_ENABLED) { entries.snomed = fail(SNOMED_DISABLED_NOTE); } else { searches.push( (async () => { try { const client = getSNOMEDClient(); const snomedResults = await client.searchConcepts(term, true, 5); entries.snomed = ok( snomedResults.map((r) => ({ code: r.conceptId, title: r.pt })), ); } catch (e) { const errMsg = e instanceof Error ? e.message : 'Error'; entries.snomed = fail(errMsg.includes('ETIMEDOUT') ? 'Server unavailable' : errMsg); } })(), ); } } if (targets.includes('loinc')) { searches.push( (async () => { try { const client = getNLMClient(); const loincResponse = await client.searchLOINC(term, 5); const loincResults = loincResponse.items ?? []; entries.loinc = ok( loincResults.map((r) => ({ code: r.LOINC_NUM, title: r.LONG_COMMON_NAME })), ); } catch (e) { entries.loinc = fail(e instanceof Error ? e.message : 'Error'); } })(), ); } if (targets.includes('rxnorm')) { searches.push( (async () => { try { const client = getRxNormClient(); const rxResults = await client.searchDrugs(term); entries.rxnorm = ok( rxResults.drugs.slice(0, 5).map((r) => ({ code: r.rxcui, title: r.name })), ); } catch (e) { entries.rxnorm = fail(e instanceof Error ? e.message : 'Error'); } })(), ); } if (targets.includes('mesh')) { searches.push( (async () => { try { const client = getMeSHClient(); const meshResults = await client.searchDescriptors(term, 'contains', 5); entries.mesh = ok( meshResults.map((r) => ({ code: r.id, title: r.label })), ); } catch (e) { entries.mesh = fail(e instanceof Error ? e.message : 'Error'); } })(), ); } await Promise.all(searches); // Markdown derived from the same entries map, in target order so output // is stable regardless of which API resolved first. const lines: string[] = []; lines.push(`# Cross-Terminology Search: "${term}"`); if (params.source_terminology) { lines.push(`_Excluding source_terminology=\`${params.source_terminology}\` from the search._`); } lines.push(''); for (const key of targets) { const entry = entries[key]; if (!entry) continue; lines.push(`## ${TERMINOLOGY_LABELS[key]}`); lines.push(''); if (entry.error) { lines.push(`⚠️ ${entry.error}`); } else if (!entry.found) { lines.push('No matches found.'); } else { for (const item of entry.items) { lines.push(`- ${item.code} - ${item.title}`); } } lines.push(''); } const foundIn = targets .filter((k) => entries[k]?.found) .map((k) => TERMINOLOGY_LABELS[k]); lines.push('---'); lines.push(''); if (foundIn.length > 0) { lines.push(`**Found in:** ${foundIn.join(', ')}`); } else { lines.push('**No matches found in any terminology.**'); } if (targets.includes('snomed') && SNOMED_TOOLS_ENABLED) { lines.push(''); lines.push(SNOMED_DISCLAIMER); } const structured: FindEquivalentOutput = { term, source_terminology: params.source_terminology ?? null, searched_terminologies: targets, results: entries, }; return { content: [{ type: 'text', text: lines.join('\n') }], structuredContent: structured, }; } catch (error) { return handleToolError(error); } } - src/types/index.ts:571-623 (schema)Input and output Zod schemas for the find_equivalent tool. Input takes term, optional source_terminology (excluded), and optional target_terminologies (limit search). Output has normalized {code, title} items per terminology.
export const FindEquivalentParamsSchema = z.object({ term: z.string().min(1).describe('Medical term to search (e.g., "diabetes", "aspirin")'), source_terminology: TerminologyEnum .optional() .describe( 'If set, this terminology is excluded from the search. Use this when the term came from this terminology and you want equivalents in the others. Combines with target_terminologies by subtraction (source is removed from the target list).', ), target_terminologies: z .array(TerminologyEnum) .optional() .describe('Limit the search to these terminologies. If omitted, all five are searched.'), }); // ============================================================================ // find_equivalent output schema (structuredContent) // // Shape note: per-terminology items are normalized to { code, title } so a // consumer can iterate uniformly across results. The native identifier // shape (LOINC_NUM vs conceptId vs rxcui vs MeSH ID) is collapsed into the // generic "code" field. If an LLM/client needs the native shape, it should // call the per-terminology search tool directly with the relevant code. // ============================================================================ const FindEquivalentItemSchema = z.object({ code: z.string(), title: z.string(), }); const FindEquivalentTerminologyResultSchema = z.object({ found: z.boolean(), // Populated when the upstream call failed (timeout, server error, or — for // SNOMED — when the SNOMED tools are disabled in this server). error: z.string().nullable(), items: z.array(FindEquivalentItemSchema), }); export const FindEquivalentOutputSchema = z.object({ term: z.string(), source_terminology: TerminologyEnum.nullable(), searched_terminologies: z.array(TerminologyEnum), // Each terminology key is present only if it was actually searched (i.e. // it's in searched_terminologies). Absent keys mean "not requested", // empty items+found:false means "searched, no hits". results: z.object({ icd11: FindEquivalentTerminologyResultSchema.optional(), snomed: FindEquivalentTerminologyResultSchema.optional(), loinc: FindEquivalentTerminologyResultSchema.optional(), rxnorm: FindEquivalentTerminologyResultSchema.optional(), mesh: FindEquivalentTerminologyResultSchema.optional(), }), }); export type FindEquivalentOutput = z.infer<typeof FindEquivalentOutputSchema>; - src/tools/crosswalk.ts:490-490 (registration)Registration of the find_equivalent tool with the tool registry, linking the tool definition to its handler function.
toolRegistry.register(findEquivalentTool, handleFindEquivalent); - src/tools/crosswalk.ts:86-99 (registration)Tool definition object for find_equivalent, including name, description, input/output schemas, and read-only annotations.
const findEquivalentTool: Tool = { name: 'find_equivalent', description: `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, SNOMED CT, 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.`, inputSchema: buildInputSchema(FindEquivalentParamsSchema), outputSchema: buildOutputSchema(FindEquivalentOutputSchema), annotations: READ_ONLY_TOOL_ANNOTATIONS, };