HL7 v2.5.1 Reference
MCP Server
A Model Context Protocol server that gives LLMs structured access to the HL7 v2.5.1 standard — segment definitions, field metadata, message structures, code tables, a message parser, and a validator with deep profiles for ORM^O01, ADT^A08, ADT^A31, ADT^A40 and ORU^R01. Built for PACS administrators, healthcare IT engineers, and interface analysts.
server.js below and run it locally (or deploy it to Railway, Render,
or Cloudflare Workers). The endpoint will be http://localhost:3000/mcp
by default.
Available Tools
Eight tools covering the full HL7 v2.5.1 reference surface.
Deep validation profiles
Beyond the base MSH, delimiter, timestamp and code-table checks that run on every message, these five types are validated against their specific semantics:
| Message | What the profile checks beyond the basics |
|---|---|
| ORM^O01 | ORC-1 against table 0119; that an order number exists in ORC-2 or ORC-3; that action codes (CA, DC, HD, RL, RP, SC, XO) actually identify an existing order; SC sent without ORC-5, the classic duplicate-worklist cause; ORC↔OBR pairing across the repeating ORDER group; OBR-4 present; accession recoverable from OBR-18/2/3; and ZDS Study Instance UID validated as a real DICOM UID — digits and dots only, ≤64 chars, no leading-zero components. |
| ADT^A08 | EVN present and second; EVN-1 agrees with MSH-9.2; PV1 required per the ADT_A01 structure; PV1-2 against table 0004; missing PV1-19 flagged, since a visit-level update with no visit number can land on the wrong encounter; MRG rejected outright. |
| ADT^A31 | PV1 required by the ADT_A05 structure even though A31 is person-level; warns when PV1-19 is populated, because a person-level event carrying a visit number is the usual A08/A31 mix-up; notes when Patient Class is something other than N. |
| ADT^A40 | MRG required; PID↔MRG pairing across the repeating PATIENT group of ADT_A39, so multi-merge messages are checked pair by pair; MRG ordered after its PID; self-merge detection when PID-3 equals MRG-1; and a warning when the two identifiers come from different assigning authorities. |
| ORU^R01 | OBX segments grouped under their owning OBR; OBX appearing before any OBR flagged; OBR-25 against table 0123 with an explicit callout when a table 0085 code is used by mistake; OBX-11 against 0085; cross-checks OBR-25 against every OBX-11 so a "Final" report containing preliminary lines is caught; NM values that are not numeric; and repeated OBX-3 within a group with no OBX-4 Sub-ID. |
Quick Start
-
Download the server
Right-click and save, or clone the repo.
shellcurl -O https://coffeemilktea.github.io/mcp/server.js -
Install dependencies
shellnpm install @modelcontextprotocol/sdk express cors zod -
Run the server
shellnode server.js # HL7 v2.5.1 MCP server listening on http://localhost:3000/mcp
-
Add to Claude Desktop
Edit
~/Library/Application Support/Claude/claude_desktop_config.json(Mac) or%APPDATA%\Claude\claude_desktop_config.json(Windows).claude_desktop_config.json{ "mcpServers": { "hl7-reference": { "type": "http", "url": "http://localhost:3000/mcp" } } }Restart Claude Desktop. The eight tools will appear in the tool panel.
-
Or use with Claude Code
Add to your project's
.claude/settings.json:.claude/settings.json{ "mcpServers": { "hl7-reference": { "type": "http", "url": "http://localhost:3000/mcp" } } }
Deploy Remotely
Run on Railway or Render for a persistent endpoint you can share across machines.
Railway (one command)
railway init # answer prompts railway up # deploys; gives you https://yourapp.up.railway.app
Set PORT env var if needed. Railway auto-detects Node.js.
Render
Create a new Web Service, point to the repo, set Start Command to node server.js.
The deployed URL becomes your MCP endpoint — use https://yourapp.onrender.com/mcp.
Environment variables
| Variable | Default | Description |
|---|---|---|
| PORT | 3000 | HTTP port the server listens on |
Supported HL7 Content
Segments
| Code | Name | Notes |
|---|---|---|
| MSH | Message Header | All 21 fields, delimiter rules, version check |
| MSA | Message Acknowledgment | ACK codes AA/AE/AR/CA/CE/CR |
| EVN | Event Type | Recorded vs. planned date, reason codes |
| PID | Patient Identification | All 38 v2.5.1 fields, deprecated ones included and marked |
| PV1 | Patient Visit | Patient class, location, attending/referring/admitting physicians |
| ORC | Common Order | Order control codes, placer/filler numbers |
| OBR | Observation Request | OBR-18 accession, OBR-25 result status, diagnostic service section |
| OBX | Observation/Result | All value types, abnormal flags, result status |
| NTE | Notes and Comments | Source codes, formatted text |
| MRG | Merge Patient Information | Prior patient ID — used in ADT^A40 |
| AL1 | Patient Allergy Information | Allergen type, severity, reaction |
| DG1 | Diagnosis | ICD codes, diagnosis type, priority |
| IN1 | Insurance | Plan, company, insured relationship |
| SCH | Scheduling Activity Information | SIU/SRM messages |
| ERR | Error | HL7 error condition codes (Table 0357) |
| ZDS | Study Instance UID | IHE RAD extension — one field only, RP data type |
Message Types
| Type | Description |
|---|---|
| ADT^A01 | Admit/Visit Notification |
| ADT^A02 | Transfer a Patient |
| ADT^A03 | Discharge/End Visit |
| ADT^A04 | Register a Patient |
| ADT^A05 | Pre-Admit a Patient |
| ADT^A08 | Update Patient Information |
| ADT^A11 | Cancel Admit/Visit Notification |
| ADT^A13 | Cancel Discharge/End Visit |
| ADT^A28 | Add Person Information |
| ADT^A31 | Update Person Information |
| ADT^A40 | Merge Patient — Patient Identifier List |
| ORM^O01 | Order Message (with IHE RAD ZDS) |
| ORR^O02 | Order Response |
| ORU^R01 | Unsolicited Observation Result |
| ACK | General Acknowledgment |
| SIU^S12 | Notification of New Appointment Booking |
Code Tables
| Table | Name |
|---|---|
| 0001 | Administrative Sex (F/M/O/U/A/N) |
| 0003 | Event Type (A01–A45, O01, R01, T02…) |
| 0004 | Patient Class (I/O/E/P/B/U…) |
| 0007 | Admission Type (Accident/Elective/Emergency/Routine/Urgent) |
| 0008 | Acknowledgment Code (AA/AE/AR/CA/CE/CR) |
| 0074 | Diagnostic Service Section ID (RAD/CT/NMR/LAB/CH/HM…) |
| 0078 | Abnormal Flags (L/H/LL/HH/A/N/U/D/S/R/I…) |
| 0085 | Observation Result Status — OBX-11 (C/D/F/I/N/O/P/R/S/U/W/X) |
| 0119 | Order Control Codes (NW/CA/DC/HD/RL/RP/SC/OK/UA…) |
| 0123 | Result Status — OBR-25 (O/I/S/A/P/C/R/F/X/Y/Z — no M, N, U or W) |
| 0155 | Accept/Application Acknowledgment Conditions (AL/ER/NE/SU) |
| 0190 | Address Type (H/B/M/C/O/P/TM…) |
| 0357 | Message Error Condition Codes (0/100/101/200/204…) |
Version Traps This Server Encodes
Most HL7 reference material circulating online is a blend of v2.3 through v2.9. These are the points where v2.5.1 specifically differs from what people expect, and where the reference data here has been pinned to the 2.5.1 standard.
| Trap | The v2.5.1 position |
|---|---|
| CE vs CWE | In v2.5.1 the coded fields are CE, not CWE — OBR-4, OBX-3, MSH-19, NTE-4, DG1-3, AL1-2/3/4, IN1-2/17, SCH-6/7/25. CWE replaced CE from v2.7. The exceptions are ERR-3 and ERR-5, which genuinely are CWE in 2.5.1 because ERR was rewritten in v2.5. |
| Table 0123 vs 0085 | OBR-25 uses table 0123 (O, I, S, A, P, C, R, F, X, Y, Z). OBX-11 uses table 0085 (C, D, F, I, N, O, P, R, S, U, W, X). M, N, U and W are not valid in OBR-25 — putting an OBX-11 code there is the single most common ORU build error, and the validator names it explicitly. |
| PID-39 | Does not exist in v2.5.1. PID ends at PID-38 (Production Class Code). Tribal Citizenship arrived in v2.6. |
| ZDS | IHE RAD TF-2 defines exactly one field, and its data type is RP (Reference Pointer), not ST — components are Pointer^Application ID^Type of Data^Subtype, so the UID sits in ZDS-1.1. Anything in ZDS-2 onward is a site-local invention. |
| ORC in ORU | ORC is optional inside the ORDER_OBSERVATION group of ORU^R01, and the whole PATIENT group — so PID too — is optional in the abstract syntax. Real interfaces require both, but a message without them is still conformant, so the validator warns rather than errors. |
| Repeating groups | ADT_A39 repeats { PID [PD1] MRG [PV1] }, so one A40 can carry several merges. ORM_O01 repeats the ORDER group and ORU_R01 repeats both PATIENT_RESULT and ORDER_OBSERVATION. Flattened references that show one of each are wrong, and the validator pairs the repeats up properly. |
| SCH-9 vs SCH-11 | Appointment Duration is SCH-9, with its units in SCH-10. SCH-11 is Appointment Timing Quantity (TQ). |
| Table 0078 | The off-scale abnormal flags are < and >, not <= and >=. |
| OBR-18 accession | Mapping the DICOM Accession Number (0008,0050) to OBR-18 is an IHE RAD convention, not an HL7 requirement. Sites also use OBR-2, OBR-3 or ORC-3. parse_message therefore reports which field it actually found the accession in rather than assuming. |
| MSH-3 / MSH-5 | Optional in the standard, despite nearly every site treating them as mandatory. Reported as warnings, not errors. |
Example Prompts
Once connected, you can ask Claude questions like:
# Field lookup "What does PID-3 contain and what's its data type?" → get_field(segment="PID", field=3) # Message structure "What segments are required in an ADT A40 merge message?" → get_message_structure(messageType="ADT^A40") # Parse a real message "Parse this HL7 message and tell me the patient's MRN and accession number." → parse_message(message="MSH|^~\&|RIS|HOSP|PACS|HOSP|...") # Code table lookup "What does ORC-1 code 'SC' mean?" → lookup_code_table(tableNumber="0119", filter="SC") # Validate before sending "Validate this ORM^O01 and tell me what's missing." → validate_message(message="MSH|^~\&|...") # Merge integrity "Is this A40 safe to send? Check the merge direction." → validate_message(...) → catches self-merge, unpaired PID/MRG, cross-authority merge # Report status consistency "This ORU says Final but the EMR shows it as preliminary. Why?" → validate_message(...) → cross-checks OBR-25 vs OBX-11 # Segment overview "Explain the ZDS segment and what it's used for in IHE." → get_segment(segment="ZDS")