fhirsmith 0.9.3 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,11 +5,31 @@ All notable changes to the Health Intersections Node Server will be documented i
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [v0.9.4] - 2026-05-01
9
+
10
+ ### Added
11
+
12
+ - Draft MCP specification from Ontoserver
13
+
14
+ ### Changed
15
+
16
+ - Update Danish SNOMED CT Extension to 2026-03-31 version.
17
+
18
+ ### Fixed
19
+
20
+ - fix problem with version specific code system resolution
21
+ - fix hgvs handling of error response
22
+ - update NPM dependencies
23
+
24
+ ### Tx Conformance Statement
25
+
26
+ FHIRsmith passed all 1649 HL7 terminology service tests (modes tx.fhir.org+omop+general+snomed, tests v1.9.1, runner v6.9.7)
27
+
8
28
  ## [v0.9.3] - 2026-04-10
9
29
 
10
30
  ### Added
11
31
 
12
- - Add support for handling contained value sets
32
+ - Add support for handling contained value sets
13
33
  - Add beta support for ECL
14
34
 
15
35
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fhirsmith",
3
- "version": "0.9.3",
3
+ "version": "0.9.4",
4
4
  "txVersion": "1.9.1",
5
5
  "description": "A Node.js server that provides a collection of tools to serve the FHIR ecosystem",
6
6
  "main": "server.js",
package/registry/api.js CHANGED
@@ -33,7 +33,8 @@ class RegistryAPI {
33
33
  const isAuth = codeSystem ? ServerRegistryUtilities.hasMatchingCodeSystem(
34
34
  codeSystem,
35
35
  server.authCSList,
36
- true // support wildcards
36
+ true, // support wildcards,
37
+ false // allow version matching
37
38
  ) : false;
38
39
 
39
40
  server.versions.forEach(versionInfo => {
@@ -398,12 +399,6 @@ class RegistryAPI {
398
399
  const matchedServers = [];
399
400
  const data = this.crawler.getData();
400
401
 
401
- // Extract base code system URL (before any pipe)
402
- let baseCodeSystem = codeSystem;
403
- if (codeSystem.includes('|')) {
404
- baseCodeSystem = codeSystem.substring(0, codeSystem.indexOf('|'));
405
- }
406
-
407
402
  data.registries.forEach(registry => {
408
403
  registry.servers.forEach(server => {
409
404
  let added = false;
@@ -421,9 +416,12 @@ class RegistryAPI {
421
416
  // Test against both the full URL and the base URL
422
417
  let content = {};
423
418
  const hasMatchingCS =
424
- ServerRegistryUtilities.hasMatchingCodeSystem(baseCodeSystem, version.codeSystems, false, content) ||
425
- (baseCodeSystem !== codeSystem &&
426
- ServerRegistryUtilities.hasMatchingCodeSystem(codeSystem, version.codeSystems, false, content));
419
+ // ServerRegistryUtilities.hasMatchingCodeSystem(baseCodeSystem, version.codeSystems, false, content) ||
420
+ // (baseCodeSystem !== codeSystem &&
421
+ ServerRegistryUtilities.hasMatchingCodeSystem(codeSystem, version.codeSystems, false, content,
422
+ // we don't want cross version matching at this point for SNOMED. If a version is specified, we want a match
423
+ // to be decided: what about other code systems?
424
+ codeSystem.includes("snomed"));
427
425
 
428
426
  if (hasMatchingCS) {
429
427
  if (isAuth) {
package/registry/model.js CHANGED
@@ -392,12 +392,12 @@ class ServerRegistryUtilities {
392
392
  return value === mask;
393
393
  }
394
394
 
395
- static hasMatchingCodeSystem(cs, list, supportMask, content) {
395
+ static hasMatchingCodeSystem(cs, list, supportMask, content, noVersionIndependentMatching = false) {
396
396
  if (!cs || list.length === 0) return false;
397
397
 
398
398
  // Handle URLs with pipes - extract base URL
399
399
  let baseCs = cs;
400
- if (cs.includes('|')) {
400
+ if (cs.includes('|') && !noVersionIndependentMatching) {
401
401
  baseCs = cs.substring(0, cs.indexOf('|'));
402
402
  }
403
403
 
package/tx/cs/cs-hgvs.js CHANGED
@@ -136,16 +136,24 @@ class HGVSServices extends CodeSystemProvider {
136
136
  let valid = false;
137
137
  let message = '';
138
138
 
139
- // Parse the FHIR Parameters response
140
- if (json.parameter && Array.isArray(json.parameter)) {
141
- for (const param of json.parameter) {
142
- if (param.name === 'result' && param.valueBoolean) {
143
- valid = true;
144
- } else if (param.name === 'message' && param.valueString) {
145
- if (message) message += ', ';
146
- message += param.valueString;
139
+ if (!json.resourceType) {
140
+ message = 'Invalid response format';
141
+ } else if (json.resourceType == 'OperationOutcome') {
142
+ message = json.issue?.[0]?.details?.text || 'Unknown error';
143
+ } else if (json.resourceType == 'Parameters') {
144
+ // Parse the FHIR Parameters response
145
+ if (json.parameter && Array.isArray(json.parameter)) {
146
+ for (const param of json.parameter) {
147
+ if (param.name === 'result' && param.valueBoolean) {
148
+ valid = true;
149
+ } else if (param.name === 'message' && param.valueString) {
150
+ if (message) message += ', ';
151
+ message += param.valueString;
152
+ }
147
153
  }
148
154
  }
155
+ } else {
156
+ message = 'Invalid response resource type: ' + json.resourceType;
149
157
  }
150
158
 
151
159
  resolve({ valid, message });
package/tx/mcp.md ADDED
@@ -0,0 +1,375 @@
1
+ # ontoserver.app/mcp — MCP Interface Reference
2
+
3
+ Reference notes for the Ontoserver-hosted terminology MCP server, captured by directly probing the live endpoint at `https://ontoserver.app/mcp` on **2026-04-26**. Intended as input for designing the FHIRsmith terminology MCP server.
4
+
5
+ ## 1. Transport & protocol
6
+
7
+ | Aspect | Value |
8
+ |---|---|
9
+ | Endpoint | `https://ontoserver.app/mcp` |
10
+ | Legacy alternate | `https://ontoserver.app/sse` (advertised via `Link: </sse>; rel="alternate"; type="text/event-stream"`) |
11
+ | Browser landing | `/mcp` serves an HTML page when `Accept: text/html` |
12
+ | Transport | MCP **Streamable HTTP** (POST returning `text/event-stream`) |
13
+ | Protocol version (advertised) | `2025-06-18` |
14
+ | Server identity | `name: "ontoserver-mcp"`, `version: "1.1.0"` |
15
+ | Session header | `mcp-session-id` (issued on `initialize`, required on subsequent requests) |
16
+ | Required client headers | `Content-Type: application/json`, `Accept: application/json, text/event-stream`, `MCP-Protocol-Version: 2025-06-18` |
17
+ | CORS | `*`, exposes `mcp-session-id`; allows `GET, POST, DELETE, OPTIONS`; allows `Content-Type, Accept, mcp-session-id, mcp-protocol-version` headers |
18
+ | Auth | None observed (open endpoint) |
19
+
20
+ A bare `GET` on `/mcp` with `Accept: application/json` returns 406; only the MCP HTTP transport (or HTML browser request) is honoured.
21
+
22
+ ### Server-declared capabilities
23
+
24
+ ```json
25
+ {
26
+ "resources": { "listChanged": true },
27
+ "tools": { "listChanged": true },
28
+ "prompts": { "listChanged": true }
29
+ }
30
+ ```
31
+
32
+ ### Server instructions returned on initialize
33
+
34
+ > *IMPORTANT: Do not include patient names, identifiers, or other PII in requests. Use only clinical terms, codes, and general medical concepts.*
35
+
36
+ This is delivered via the standard MCP `instructions` field on the `initialize` response — worth replicating in FHIRsmith.
37
+
38
+ ## 2. Tools (7)
39
+
40
+ All tools share `execution: { taskSupport: "forbidden" }`, i.e. synchronous request/response with no long-running task semantics.
41
+ All tools advertise both an `inputSchema` and an `outputSchema` (JSON Schema draft-07), and tool results carry both a human-readable `content[].text` block and a machine-readable `structuredContent` object conforming to the `outputSchema`.
42
+
43
+ All tools that take a `system` parameter accept either:
44
+
45
+ - A **CodeSystem URI** (e.g. `http://www.whocc.no/atc`, `urn:oid:...`), or
46
+ - An **alias**: `snomed`, `loinc`, `icd10`, `rxnorm`, `atc`, `ucum`, `cpt`, `ndc`, `cvx`, `icd10cm`, `icd10pcs`, `icd10am`.
47
+
48
+ All tools that take a `version` parameter treat it as system-specific (edition string).
49
+
50
+ ### 2.1 `search_concepts`
51
+
52
+ > Search concepts by term in a terminology system. Returns matching concepts with codes and display names. Use `valueset` to constrain search to a specific ValueSet (e.g. SNOMED descendants). Do not include PII in queries.
53
+
54
+ | Param | Type | Required | Notes |
55
+ |---|---|---|---|
56
+ | `system` | string | yes | URI or alias |
57
+ | `query` | string | yes | Search term, e.g. `"heart attack"` |
58
+ | `limit` | number | no | Default 10 |
59
+ | `version` | string | no | |
60
+ | `valueset` | string | no | ValueSet URL — see ECL/VCL guides below |
61
+ | `includeInactive` | boolean | no | Default `false` |
62
+
63
+ Output schema: `{ results: [{ code, display }] }`.
64
+ Backed conceptually by `ValueSet/$expand` with a text filter.
65
+
66
+ **Example response** for `system=snomed, query=asthma, limit=2`:
67
+
68
+ ```json
69
+ {
70
+ "content": [{ "type": "text", "text": "195967001: Asthma\n400987003: Asthma trigger" }],
71
+ "structuredContent": {
72
+ "results": [
73
+ { "code": "195967001", "display": "Asthma" },
74
+ { "code": "400987003", "display": "Asthma trigger" }
75
+ ]
76
+ }
77
+ }
78
+ ```
79
+
80
+ ### 2.2 `lookup_concept`
81
+
82
+ > Get detailed information about a concept including display name, definition, properties, and synonyms.
83
+
84
+ | Param | Type | Required |
85
+ |---|---|---|
86
+ | `system` | string | yes |
87
+ | `code` | string | yes |
88
+ | `version` | string | no |
89
+
90
+ Output schema: `{ display?, definition?, properties: {[k]: string}, designations: [{ language?, use?, value }] }`.
91
+ Backed by `CodeSystem/$lookup`.
92
+ The text channel includes display, system label, properties (including SNOMED `normalForm` / `normalFormTerse` when applicable), and designations with language tags.
93
+
94
+ **Example response** for `system=snomed, code=22298006` (truncated for brevity):
95
+
96
+ ```json
97
+ {
98
+ "content": [{ "type": "text", "text": "Code: 22298006\nSystem: SNOMED CT\nDisplay: Myocardial infarction\n\nProperties:\n 116676008: 55641003\n 363698007: 74281007\n parent: 251061000\n child: 738061000168100\n effectiveTime: 20020131\n moduleId: 900000000000207008\n normalFormTerse: ===414545008+251061000:{...}\n normalForm: === 414545008|Ischemic heart disease|+251061000|Myocardial necrosis|:{...}\n\nDesignations:\n Cardiac infarction (Synonym) [en]\n Heart attack (Synonym) [en]\n Myocardial infarction (disorder) (Fully specified name) [en]\n ..." }],
99
+ "structuredContent": {
100
+ "display": "Myocardial infarction",
101
+ "properties": {
102
+ "116676008": "55641003",
103
+ "363698007": "74281007",
104
+ "parent": "251061000",
105
+ "child": "738061000168100",
106
+ "effectiveTime": "20020131",
107
+ "moduleId": "900000000000207008",
108
+ "normalFormTerse": "===414545008+251061000:{116676008=55641003,363698007=74281007}",
109
+ "normalForm": "=== 414545008|Ischemic heart disease|+251061000|Myocardial necrosis|:{116676008|Associated morphology|=55641003|Infarct|,363698007|Finding site|=74281007|Myocardium structure|}"
110
+ },
111
+ "designations": [
112
+ { "language": "en", "use": "Synonym", "value": "Cardiac infarction" },
113
+ { "language": "en", "use": "Synonym", "value": "Heart attack" },
114
+ { "language": "en", "use": "Fully specified name", "value": "Myocardial infarction (disorder)" }
115
+ ]
116
+ }
117
+ }
118
+ ```
119
+
120
+ ### 2.3 `validate_concept`
121
+
122
+ > Check if a code is valid in a terminology system and optionally verify its display name.
123
+
124
+ | Param | Type | Required |
125
+ |---|---|---|
126
+ | `system` | string | yes |
127
+ | `code` | string | yes |
128
+ | `display` | string | no |
129
+ | `version` | string | no |
130
+
131
+ Output schema: `{ valid: boolean, display?: string, message?: string }`.
132
+ Backed by `CodeSystem/$validate-code`. Example:
133
+
134
+ ```json
135
+ {
136
+ "content": [{ "type": "text", "text": "Code: 2345-7\nSystem: LOINC\nValid: Yes\nDisplay: Glucose [Mass/volume] in Serum or Plasma" }],
137
+ "structuredContent": { "valid": true, "display": "Glucose [Mass/volume] in Serum or Plasma" }
138
+ }
139
+ ```
140
+
141
+ ### 2.4 `check_subsumption`
142
+
143
+ > Check the hierarchical relationship between two concepts. Returns whether codeA subsumes codeB, is subsumed by codeB, they are equivalent, or not related. Not all systems support subsumption.
144
+
145
+ | Param | Type | Required |
146
+ |---|---|---|
147
+ | `system` | string | yes |
148
+ | `codeA` | string | yes |
149
+ | `codeB` | string | yes |
150
+ | `version` | string | no |
151
+
152
+ Output schema: `{ outcome: "equivalent" | "subsumes" | "subsumed-by" | "not-subsumed" }`.
153
+ Backed by `CodeSystem/$subsumes`.
154
+
155
+ ```
156
+ Outcome: subsumes
157
+ 56265001 subsumes (is broader than) 22298006
158
+ ```
159
+
160
+ ### 2.5 `search_codesystems`
161
+
162
+ > Search for CodeSystems by metadata (name, title, URL, status). Useful for discovering available code systems. Paginated with default page size 100.
163
+
164
+ | Param | Type | Required | Notes |
165
+ |---|---|---|---|
166
+ | `name` | string | no | |
167
+ | `title` | string | no | |
168
+ | `url` | string | no | |
169
+ | `status` | enum | no | `draft \| active \| retired \| unknown` |
170
+ | `_count` | integer | no | Default 100, min 1, max 1000 |
171
+ | `_offset` | integer | no | Default 0 |
172
+
173
+ Output schema: `{ results: [{ id, url?, name?, title?, status?, version? }], total?, offset, count, hasMore }`.
174
+ A thin wrapper over the FHIR `CodeSystem` search. Output includes a `Total:` line (1029 active CodeSystems on this instance, for context) followed by per-system blocks with title, URL, status. Note: no required parameter, it’ll list all if none given.
175
+
176
+ ### 2.6 `expand_valueset`
177
+
178
+ > Expand a ValueSet (`$expand` without filter), returning all member concepts. Paginated with default page size 100. Consult `ecl://guide` (SNOMED) or `vcl://guide` (other systems) for ValueSet URL syntax.
179
+
180
+ | Param | Type | Required | Notes |
181
+ |---|---|---|---|
182
+ | `valueset` | string | yes | ValueSet URL, e.g. `http://snomed.info/sct?fhir_vs=ecl/<<73211009` |
183
+ | `_count` | integer | no | Default 100, min 1, **max 10000** |
184
+ | `_offset` | integer | no | Default 0 |
185
+ | `activeOnly` | boolean | no | Default: server decides |
186
+
187
+ Output schema: `{ results: [{ code, display, system? }], total?, offset, count, hasMore }`.
188
+ Backed by `ValueSet/$expand`. Complements `search_concepts` for the case where the LLM wants the full set (or a paginated walk) rather than a text-filtered slice.
189
+
190
+ **Example response** for `valueset=http://snomed.info/sct?fhir_vs=ecl/<<73211009, _count=3`:
191
+
192
+ ```json
193
+ {
194
+ "content": [{ "type": "text", "text": "Showing 1-3 of 121\n\n609578001: Maturity-onset diabetes of the young, type 11\n609564002: Pre-existing type 1 diabetes in pregnancy\n609566000: Pregnancy and type 1 diabetes\n\nMore results available — call again with _offset=3" }],
195
+ "structuredContent": {
196
+ "results": [
197
+ { "code": "609578001", "display": "Maturity-onset diabetes of the young, type 11", "system": "http://snomed.info/sct" },
198
+ { "code": "609564002", "display": "Pre-existing type 1 diabetes in pregnancy", "system": "http://snomed.info/sct" },
199
+ { "code": "609566000", "display": "Pregnancy and type 1 diabetes", "system": "http://snomed.info/sct" }
200
+ ],
201
+ "total": 121, "offset": 0, "count": 3, "hasMore": true
202
+ }
203
+ }
204
+ ```
205
+
206
+ ### 2.7 `find_mappings`
207
+
208
+ > Map free-text terms to coded concepts using automap strategies. Default target is SNOMED CT. Do not include patient names or identifiers in the term.
209
+
210
+ | Param | Type | Required | Notes |
211
+ |---|---|---|---|
212
+ | `term` | string | yes | Free-text term |
213
+ | `target` | string | no | ValueSet URL; default `http://snomed.info/sct?fhir_vs` |
214
+ | `strategy` | enum | no | `default \| strict \| MML` |
215
+ | `minScore` | number | no | 0–1, filter threshold |
216
+ | `maxResults` | number | no | |
217
+
218
+ Output schema: `{ inputTerm, strategyUrl, matches: [{ code, system, display, equivalence?, score?, source? }] }`.
219
+ Backed by Ontoserver's automap `ConceptMap/$translate` extension (referenced in output as `http://ontoserver.csiro.au/fhir/ConceptMap/automapstrategy-default`).
220
+ This is a **proprietary Ontoserver feature** — there is no standard FHIR equivalent. Output text includes match strength labels like `(inexact)`.
221
+
222
+ Example:
223
+
224
+ ```
225
+ Mappings for "heart attack" (strategy: default):
226
+
227
+ 22298006: Myocardial infarction (disorder) (inexact)
228
+ System: http://snomed.info/sct
229
+ Source: http://ontoserver.csiro.au/fhir/ConceptMap/automapstrategy-default
230
+ ...
231
+ ```
232
+
233
+ For FHIRsmith, options here are:
234
+ 1. Skip this tool.
235
+ 2. Implement a simpler equivalent based on `$expand` text search ranked by match score.
236
+ 3. Implement the full automap (likely out of scope short-term).
237
+
238
+ ## 3. Resources (2)
239
+
240
+ Resources are static documentation — both are quick-reference markdown guides for constructing ValueSet URLs that get passed to `search_concepts.valueset` and `expand_valueset.valueset`.
241
+
242
+ | URI | Name | Purpose |
243
+ |---|---|---|
244
+ | `ecl://guide` | `ecl-guide` | SNOMED CT Expression Constraint Language reference |
245
+ | `vcl://guide` | `vcl-guide` | ValueSet Compose Language reference (generic, any CodeSystem) |
246
+
247
+ No resource templates. No resources for actual terminology data — code systems are queried via tools, not exposed as resources.
248
+
249
+ ### 3.1 `ecl://guide` highlights
250
+
251
+ Tells the LLM how to build URLs of the form:
252
+
253
+ ```
254
+ http://snomed.info/sct?fhir_vs=ecl/{URL-encoded ECL expression}
255
+ ```
256
+
257
+ Documents hierarchy operators (`<`, `<<`, `>`, `>>`, `<!`, `>!`), set operations (`AND`, `OR`, `MINUS`), refset membership (`^`), attribute refinements (`: attr = val`, grouped with `{...}`, wildcard `*`), and percent-encoding for `<`, `>`, `:`, `=`, space.
258
+
259
+ ### 3.2 `vcl://guide` highlights
260
+
261
+ Generic version for any CodeSystem. URL form:
262
+
263
+ ```
264
+ http://fhir.org/VCL?v1={URL-encoded VCL expression}
265
+ ```
266
+
267
+ Documents hierarchy (`<<`, `<`, `>>`, `<!`, `!!<`), set operations (`,` AND, `;` OR, `-` exclusion, `()` grouping), membership (`^`, `~^`, `~<<`), property predicates (`prop = val`, `prop?true`, `.prop` reverse, `*`), regex (`/regex/`), and CodeSystem scoping `(uri)expr`. Also covers quoting rules for non-alphanumerics.
268
+
269
+ VCL is from the FHIR IG Guidance build at `build.fhir.org/ig/FHIR/ig-guidance/vcl.html`.
270
+
271
+ ## 4. Prompts (5)
272
+
273
+ The server exposes a `prompts` capability. Each prompt is a parameterised instruction template that the host can surface to the user; on `prompts/get` the server returns a `messages` array (single user-role text message) with concrete steps for the LLM. All prompts conclude with a PII reminder.
274
+
275
+ | Name | Required args | Optional args | Purpose |
276
+ |---|---|---|---|
277
+ | `find-code` | `term` | `system` (default `snomed`) | Find the best terminology code for a clinical term |
278
+ | `map-code` | `code`, `fromSystem`, `toSystem` | — | Translate a code between terminology systems |
279
+ | `validate-coding` | `code`, `system` | `valueSet` | Validate a coding against a system or ValueSet |
280
+ | `explore-hierarchy` | `code` | `system` (default `snomed`) | Walk parents and children of a concept |
281
+ | `summary-to-codes` | `text` | `system` (default `snomed`) | Extract coded concepts from a clinical summary |
282
+
283
+ The prompts orchestrate the existing tools — e.g. `find-code` instructs the LLM to call `search_concepts`, narrow with ECL/VCL if ambiguous, then `lookup_concept` to confirm. They amortise the “how do I use this server well” reasoning across users and sessions.
284
+
285
+ ## 5. Response shape
286
+
287
+ All tool responses use the v2025-06-18 dual-content shape:
288
+
289
+ ```json
290
+ {
291
+ "result": {
292
+ "content": [
293
+ { "type": "text", "text": "...formatted human-readable response..." }
294
+ ],
295
+ "structuredContent": { /* matches the tool's outputSchema */ }
296
+ }
297
+ }
298
+ ```
299
+
300
+ The text payload is human-readable and consistent per tool: property codes are colon-prefixed (`22298006: Myocardial infarction`), properties indented two spaces, designations tagged with type and language. The `structuredContent` is the same data in a programmatically parseable form, validated against the tool's declared `outputSchema`.
301
+
302
+ Paginated tools (`expand_valueset`, `search_codesystems`) wrap their text content with a `Showing {offset+1}-{offset+count} of {total}` header and, when `hasMore` is true, a `More results available — call again with _offset={offset+count}` footer — letting an LLM consuming only the text channel still walk pages without parsing `structuredContent`.
303
+
304
+ No resource links, no embedded resources. No errors observed beyond the standard JSON-RPC error envelope (`-32601 Method not found` for unsupported methods, `-32000` `Unsupported Media Type` if `Content-Type` is wrong).
305
+
306
+ ## 6. Design takeaways for FHIRsmith terminology MCP
307
+
308
+ Patterns worth keeping:
309
+
310
+ 1. **Seven-tool surface area** — `search_concepts`, `lookup_concept`, `validate_concept`, `check_subsumption`, `search_codesystems`, `expand_valueset`, plus an automap-style mapper. This maps cleanly onto the FHIR terminology operations FHIRsmith already supports (`$expand`, `$lookup`, `$validate-code`, `$subsumes`, `$translate`). The split between text-filtered `search_concepts` and unfiltered paginated `expand_valueset` is a useful pair — the first is for “what's the code for X”, the second is for “show me the contents of this set”.
311
+ 2. **Alias list for `system`** alongside full URIs — significant ergonomics win for LLMs. FHIRsmith should support at minimum `snomed, loinc, icd10, rxnorm, atc, ucum, cvx`, and probably more given the breadth of code systems supported.
312
+ 3. **`valueset` parameter on `search_concepts`** that takes an ECL/VCL-encoded ValueSet URL — pushes ValueSet construction into the LLM rather than exposing a separate ValueSet expansion tool. Clean.
313
+ 4. **ECL and VCL quick-reference resources** as MCP resources. These exist purely so the LLM can self-serve learning the URL syntax. Cheap to include and clearly useful. FHIRsmith should ship equivalents — and given Grahame's recent ECL filter work in `cs-snomed.js`, ECL support is already in place.
314
+ 5. **`instructions` field on initialize** carrying a PII warning. Worth including; healthcare-specific MCP servers all need this.
315
+ 6. **Both `text` content and `structuredContent`**. Programmatic clients get the validated object; LLM-only clients get a clean human-readable form. Output-schema-validated `structuredContent` is a 2025-06-18 feature that's cheap to ship and worth shipping.
316
+ 7. **Prompts that orchestrate tool sequences** (`find-code`, `map-code`, `validate-coding`, `explore-hierarchy`, `summary-to-codes`) — encode best-practice tool-use patterns once, server-side, instead of relying on every host to figure them out. Particularly valuable for the “narrow with ECL when ambiguous” idiom.
317
+ 8. **Pagination with explicit `offset/count/hasMore`** in `structuredContent`, plus standard `_count`/`_offset` params. Lets agents walk large sets safely.
318
+
319
+ Patterns worth reconsidering:
320
+
321
+ 1. **`search_codesystems` has no required parameter** — easy footgun for an LLM to dump everything (1029+ entries). Consider requiring at least one filter, or capping the page size more aggressively for the unfiltered case.
322
+ 2. **`find_mappings` is Ontoserver-proprietary**. FHIRsmith equivalent should either be built on `$translate` with declared ConceptMaps, or skipped in v1.
323
+ 3. **`expand_valueset.activeOnly`** is the only “server decides” default in the surface — most other defaults are explicit. Worth deciding upfront for FHIRsmith and documenting.
324
+ 4. **No `terminology_capabilities` tool** exposing `metadata` / `TerminologyCapabilities`. FHIRsmith already supports `TerminologyCapabilities`; surfacing it would let agents probe what a given deployment can do (which CodeSystems, which ops) before calling.
325
+
326
+ Open questions for FHIRsmith design:
327
+
328
+ - Do we want a `translate_concept` tool that wraps `ConceptMap/$translate` for declared maps (e.g. SNOMED → ICD-10-AM)? Different from automap. Note that `map-code` here is a *prompt*, not a tool — it currently relies on the LLM gluing things together.
329
+ - Resources for IPS-related ValueSets? Given the IPS work and the package management infrastructure, exposing canonical ValueSet/CodeSystem package metadata as resources might be a FHIRsmith differentiator.
330
+ - Test fixture — the [Comparing FHIR Terminology Services](https://en.rath.asia/blog/2025/04/27/comparing-3-fhir-terminology-services/) blog post covers the kind of operations a terminology MCP needs to handle competently and could seed an eval set.
331
+
332
+ ## 6. Reproducing this probe
333
+
334
+ ```bash
335
+ # 1. Initialize (capture mcp-session-id from response headers)
336
+ curl -i -X POST https://ontoserver.app/mcp \
337
+ -H "Content-Type: application/json" \
338
+ -H "Accept: application/json, text/event-stream" \
339
+ -H "MCP-Protocol-Version: 2025-06-18" \
340
+ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{
341
+ "protocolVersion":"2025-06-18","capabilities":{},
342
+ "clientInfo":{"name":"probe","version":"0.1"}}}'
343
+
344
+ SESSION=... # value of mcp-session-id response header
345
+
346
+ # 2. Confirm initialization
347
+ curl -X POST https://ontoserver.app/mcp \
348
+ -H "mcp-session-id: $SESSION" \
349
+ -H "Content-Type: application/json" \
350
+ -H "Accept: application/json, text/event-stream" \
351
+ -H "MCP-Protocol-Version: 2025-06-18" \
352
+ -d '{"jsonrpc":"2.0","method":"notifications/initialized"}'
353
+
354
+ # 3. List tools / resources / prompts
355
+ for METHOD in tools/list resources/list prompts/list resources/templates/list; do
356
+ curl -X POST https://ontoserver.app/mcp \
357
+ -H "mcp-session-id: $SESSION" \
358
+ -H "Content-Type: application/json" \
359
+ -H "Accept: application/json, text/event-stream" \
360
+ -H "MCP-Protocol-Version: 2025-06-18" \
361
+ -d "{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"$METHOD\"}"
362
+ done
363
+
364
+ # 4. Call a tool (note structuredContent in the result)
365
+ curl -X POST https://ontoserver.app/mcp \
366
+ -H "mcp-session-id: $SESSION" \
367
+ -H "Content-Type: application/json" \
368
+ -H "Accept: application/json, text/event-stream" \
369
+ -H "MCP-Protocol-Version: 2025-06-18" \
370
+ -d '{"jsonrpc":"2.0","id":10,"method":"tools/call","params":{
371
+ "name":"validate_concept",
372
+ "arguments":{"system":"loinc","code":"2345-7"}}}'
373
+ ```
374
+
375
+ Responses come back as SSE frames — strip the `event: message\ndata: ` prefix to get JSON-RPC.
@@ -22,7 +22,7 @@ sources:
22
22
  # - snomed:sct_au_20230731.cache
23
23
  # - snomed:sct_be_20231115.cache
24
24
  - snomed:sct_ch_20230607.cache
25
- # - snomed:sct_dk_20250930.cache
25
+ - snomed:sct_dk_20260331.cache
26
26
  - snomed:sct_ips_20241216.cache
27
27
  - snomed:sct_nl_20240930.cache
28
28
  - snomed:sct_uk_20230412.cache