@omophub/omophub-mcp 1.1.0 → 1.2.2
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/README.md +17 -5
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +6 -3
- package/dist/server.js.map +1 -1
- package/dist/tools/explore.d.ts +4 -0
- package/dist/tools/explore.d.ts.map +1 -0
- package/dist/tools/explore.js +182 -0
- package/dist/tools/explore.js.map +1 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +3 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/semantic-search.d.ts +4 -0
- package/dist/tools/semantic-search.d.ts.map +1 -0
- package/dist/tools/semantic-search.js +92 -0
- package/dist/tools/semantic-search.js.map +1 -0
- package/dist/tools/similar.d.ts +4 -0
- package/dist/tools/similar.d.ts.map +1 -0
- package/dist/tools/similar.js +139 -0
- package/dist/tools/similar.js.map +1 -0
- package/dist/utils/auth.js +1 -1
- package/dist/utils/errors.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -50,7 +50,7 @@ Claude: Found it - E11.9 (Type 2 diabetes mellitus without complications)
|
|
|
50
50
|
|
|
51
51
|
### 1. Get an API Key
|
|
52
52
|
|
|
53
|
-
Sign up at [omophub.com](https://omophub.com) → create an API key in your [dashboard](https://omophub.com/
|
|
53
|
+
Sign up at [omophub.com](https://omophub.com) → create an API key in your [dashboard](https://dashboard.omophub.com/api-keys).
|
|
54
54
|
|
|
55
55
|
### 2. Add to Your AI Client
|
|
56
56
|
|
|
@@ -177,6 +177,9 @@ docker run -i -e OMOPHUB_API_KEY=oh_your_key_here omophub/omophub-mcp --transpor
|
|
|
177
177
|
| `map_concept` | Map a concept to equivalent concepts in other vocabularies |
|
|
178
178
|
| `get_hierarchy` | Navigate concept hierarchy - ancestors, descendants, or both |
|
|
179
179
|
| `list_vocabularies` | List available medical vocabularies with statistics |
|
|
180
|
+
| `semantic_search` | Search using natural language with neural embeddings (understands clinical meaning) |
|
|
181
|
+
| `find_similar_concepts` | Find concepts similar to a reference concept, name, or description |
|
|
182
|
+
| `explore_concept` | Get concept details, hierarchy, and cross-vocabulary mappings in one call |
|
|
180
183
|
|
|
181
184
|
### Resources
|
|
182
185
|
|
|
@@ -208,6 +211,15 @@ docker run -i -e OMOPHUB_API_KEY=oh_your_key_here omophub/omophub-mcp --transpor
|
|
|
208
211
|
**Validate a code →** `get_concept_by_code` → `map_concept`
|
|
209
212
|
> "Is ICD-10 code E11.9 valid? What does it map to in SNOMED?"
|
|
210
213
|
|
|
214
|
+
**Semantic search →** `semantic_search`
|
|
215
|
+
> "Find concepts related to 'heart attack'"
|
|
216
|
+
|
|
217
|
+
**Explore a concept →** `explore_concept`
|
|
218
|
+
> "Give me everything about SNOMED concept 201826"
|
|
219
|
+
|
|
220
|
+
**Find similar →** `find_similar_concepts`
|
|
221
|
+
> "What concepts are similar to 'Type 2 diabetes mellitus'?"
|
|
222
|
+
|
|
211
223
|
---
|
|
212
224
|
|
|
213
225
|
## Configuration
|
|
@@ -244,7 +256,7 @@ In **HTTP mode**, the health endpoint is available at `/health` on the same port
|
|
|
244
256
|
```bash
|
|
245
257
|
npx @omophub/omophub-mcp --transport=http --port=3100 --api-key=oh_your_key
|
|
246
258
|
curl http://localhost:3100/health
|
|
247
|
-
# → {"status":"ok","version":"1.
|
|
259
|
+
# → {"status":"ok","version":"1.2.2","uptime_seconds":42}
|
|
248
260
|
```
|
|
249
261
|
|
|
250
262
|
In **stdio mode**, use `--health-port` for a standalone health endpoint:
|
|
@@ -281,8 +293,8 @@ OMOPHUB_API_KEY=oh_your_key npx tsx src/index.ts
|
|
|
281
293
|
| Error | Solution |
|
|
282
294
|
| :--- | :--- |
|
|
283
295
|
| `API key required` | Set `OMOPHUB_API_KEY` in your environment or MCP config |
|
|
284
|
-
| `Authentication failed` | API key may be invalid or expired - [generate a new one](https://omophub.com/
|
|
285
|
-
| `Rate limit exceeded` | Automatic retries are built in. For higher limits, [upgrade your plan](https://omophub.com/
|
|
296
|
+
| `Authentication failed` | API key may be invalid or expired - [generate a new one](https://dashboard.omophub.com/api-keys) |
|
|
297
|
+
| `Rate limit exceeded` | Automatic retries are built in. For higher limits, [upgrade your plan](https://dashboard.omophub.com/billing) |
|
|
286
298
|
| Tools not appearing | Restart your AI client, verify `npx @omophub/omophub-mcp` runs without errors, check config path |
|
|
287
299
|
|
|
288
300
|
---
|
|
@@ -290,7 +302,7 @@ OMOPHUB_API_KEY=oh_your_key npx tsx src/index.ts
|
|
|
290
302
|
## Links
|
|
291
303
|
|
|
292
304
|
- [Documentation](https://docs.omophub.com)
|
|
293
|
-
- [Get an API Key](https://omophub.com/
|
|
305
|
+
- [Get an API Key](https://dashboard.omophub.com/api-keys)
|
|
294
306
|
- [Python SDK](https://github.com/OMOPHub/omophub-python)
|
|
295
307
|
- [Community & Support](https://github.com/OMOPHub/omophub-mcp/issues)
|
|
296
308
|
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAiBpE,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CA+BxE"}
|
package/dist/server.js
CHANGED
|
@@ -2,7 +2,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
2
2
|
import { OmopHubClient } from './client/api.js';
|
|
3
3
|
import { registerPrompts } from './prompts/index.js';
|
|
4
4
|
import { registerResources } from './resources/index.js';
|
|
5
|
-
import { registerConceptTools, registerHierarchyTools, registerMappingTools, registerSearchTools, registerVocabularyTools, } from './tools/index.js';
|
|
5
|
+
import { registerConceptTools, registerExploreTools, registerHierarchyTools, registerMappingTools, registerSearchTools, registerSemanticSearchTools, registerSimilarTools, registerVocabularyTools, } from './tools/index.js';
|
|
6
6
|
import { logger } from './utils/logger.js';
|
|
7
7
|
import { VERSION } from './version.js';
|
|
8
8
|
export function createServer(apiKey, baseUrl) {
|
|
@@ -12,9 +12,12 @@ export function createServer(apiKey, baseUrl) {
|
|
|
12
12
|
description: 'Access OHDSI standardized medical vocabularies — search concepts, navigate hierarchies, and map between clinical coding systems like SNOMED CT, ICD-10, RxNorm, and LOINC.',
|
|
13
13
|
});
|
|
14
14
|
const client = new OmopHubClient(apiKey, baseUrl);
|
|
15
|
-
// Register
|
|
15
|
+
// Register tools
|
|
16
16
|
registerSearchTools(server, client);
|
|
17
|
+
registerSemanticSearchTools(server, client);
|
|
18
|
+
registerSimilarTools(server, client);
|
|
17
19
|
registerConceptTools(server, client);
|
|
20
|
+
registerExploreTools(server, client);
|
|
18
21
|
registerMappingTools(server, client);
|
|
19
22
|
registerHierarchyTools(server, client);
|
|
20
23
|
registerVocabularyTools(server, client);
|
|
@@ -22,7 +25,7 @@ export function createServer(apiKey, baseUrl) {
|
|
|
22
25
|
registerResources(server, client);
|
|
23
26
|
registerPrompts(server);
|
|
24
27
|
logger.info('OMOPHub MCP server initialized', {
|
|
25
|
-
tools:
|
|
28
|
+
tools: 9,
|
|
26
29
|
resources: 2,
|
|
27
30
|
prompts: 2,
|
|
28
31
|
});
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,OAAgB;IAC3D,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,OAAO;QAChB,WAAW,EACT,4KAA4K;KAC/K,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAElD,
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EACnB,2BAA2B,EAC3B,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,OAAgB;IAC3D,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,OAAO;QAChB,WAAW,EACT,4KAA4K;KAC/K,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAElD,iBAAiB;IACjB,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,2BAA2B,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,sBAAsB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,uBAAuB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAExC,iCAAiC;IACjC,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,eAAe,CAAC,MAAM,CAAC,CAAC;IAExB,MAAM,CAAC,IAAI,CAAC,gCAAgC,EAAE;QAC5C,KAAK,EAAE,CAAC;QACR,SAAS,EAAE,CAAC;QACZ,OAAO,EAAE,CAAC;KACX,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explore.d.ts","sourceRoot":"","sources":["../../src/tools/explore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAsBtD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,GAAG,IAAI,CA0OnF"}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { formatErrorForMcp } from '../utils/errors.js';
|
|
3
|
+
export function registerExploreTools(server, client) {
|
|
4
|
+
server.tool('explore_concept', 'Get a comprehensive view of a medical concept in one call: detailed info, ancestors/descendants hierarchy, and cross-vocabulary mappings. Use this instead of calling get_concept + get_hierarchy + map_concept separately. Ideal for understanding what a concept is, where it sits in the classification tree, and how it maps to other coding systems.', {
|
|
5
|
+
concept_id: z.number().describe('The OMOP concept_id to explore'),
|
|
6
|
+
include_hierarchy: z
|
|
7
|
+
.boolean()
|
|
8
|
+
.default(true)
|
|
9
|
+
.describe('Include ancestors and descendants. Default true.'),
|
|
10
|
+
hierarchy_levels: z
|
|
11
|
+
.number()
|
|
12
|
+
.min(1)
|
|
13
|
+
.max(5)
|
|
14
|
+
.default(2)
|
|
15
|
+
.describe('How many hierarchy levels to fetch (1-5, default 2)'),
|
|
16
|
+
include_mappings: z
|
|
17
|
+
.boolean()
|
|
18
|
+
.default(true)
|
|
19
|
+
.describe('Include cross-vocabulary mappings. Default true.'),
|
|
20
|
+
target_vocabularies: z
|
|
21
|
+
.string()
|
|
22
|
+
.max(200)
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("Comma-separated vocabulary IDs to filter mappings. Examples: 'ICD10CM', 'SNOMED'."),
|
|
25
|
+
}, async ({ concept_id, include_hierarchy, include_mappings, hierarchy_levels, target_vocabularies, }) => {
|
|
26
|
+
try {
|
|
27
|
+
// Build parallel requests
|
|
28
|
+
const requests = [];
|
|
29
|
+
// 1. Always fetch concept details
|
|
30
|
+
const conceptReq = client.request(`/concepts/${concept_id}`, {}, 'explore_concept');
|
|
31
|
+
requests.push(conceptReq);
|
|
32
|
+
// 2. Optionally fetch hierarchy
|
|
33
|
+
let hierarchyReq = null;
|
|
34
|
+
if (include_hierarchy !== false) {
|
|
35
|
+
const levels = hierarchy_levels ?? 2;
|
|
36
|
+
hierarchyReq = client.request(`/concepts/${concept_id}/hierarchy`, { max_levels: levels }, 'explore_concept');
|
|
37
|
+
requests.push(hierarchyReq);
|
|
38
|
+
}
|
|
39
|
+
// 3. Optionally fetch mappings via relationships endpoint
|
|
40
|
+
// (The /mappings endpoint has a schema resolution bug — using /relationships instead)
|
|
41
|
+
let mappingsReq = null;
|
|
42
|
+
if (include_mappings !== false) {
|
|
43
|
+
mappingsReq = client.request(`/concepts/${concept_id}/relationships`, { page_size: 100 }, 'explore_concept');
|
|
44
|
+
requests.push(mappingsReq);
|
|
45
|
+
}
|
|
46
|
+
// Execute all in parallel
|
|
47
|
+
const settled = await Promise.allSettled(requests);
|
|
48
|
+
// Extract results
|
|
49
|
+
const conceptResult = settled[0];
|
|
50
|
+
const concept = conceptResult.status === 'fulfilled'
|
|
51
|
+
? conceptResult.value.data
|
|
52
|
+
: null;
|
|
53
|
+
let hierarchy = null;
|
|
54
|
+
let relationshipsData = null;
|
|
55
|
+
let idx = 1;
|
|
56
|
+
if (hierarchyReq) {
|
|
57
|
+
const r = settled[idx++];
|
|
58
|
+
if (r.status === 'fulfilled') {
|
|
59
|
+
hierarchy = r.value.data;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (mappingsReq) {
|
|
63
|
+
const r = settled[idx];
|
|
64
|
+
if (r.status === 'fulfilled') {
|
|
65
|
+
relationshipsData = r.value.data;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (!concept) {
|
|
69
|
+
// If the request was rejected, propagate the original error
|
|
70
|
+
if (conceptResult.status === 'rejected') {
|
|
71
|
+
const { text, json } = formatErrorForMcp(conceptResult.reason, 'explore_concept');
|
|
72
|
+
return {
|
|
73
|
+
content: [
|
|
74
|
+
{ type: 'text', text },
|
|
75
|
+
{ type: 'text', text: json },
|
|
76
|
+
],
|
|
77
|
+
isError: true,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
content: [{ type: 'text', text: `Concept ${concept_id} not found.` }],
|
|
82
|
+
isError: true,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
// Build human-readable output
|
|
86
|
+
const sections = [];
|
|
87
|
+
// Concept info
|
|
88
|
+
const std = concept.standard_concept === 'S'
|
|
89
|
+
? ' [Standard]'
|
|
90
|
+
: concept.standard_concept === 'C'
|
|
91
|
+
? ' [Classification]'
|
|
92
|
+
: '';
|
|
93
|
+
sections.push(`## ${concept.concept_name}${std}\n` +
|
|
94
|
+
`- **Concept ID:** ${concept.concept_id}\n` +
|
|
95
|
+
`- **Vocabulary:** ${concept.vocabulary_id}\n` +
|
|
96
|
+
`- **Domain:** ${concept.domain_id}\n` +
|
|
97
|
+
`- **Class:** ${concept.concept_class_id}\n` +
|
|
98
|
+
`- **Code:** ${concept.concept_code}`);
|
|
99
|
+
// Hierarchy
|
|
100
|
+
if (hierarchy) {
|
|
101
|
+
const ancestors = hierarchy.ancestors ?? [];
|
|
102
|
+
const descendants = hierarchy.descendants ?? [];
|
|
103
|
+
if (ancestors.length > 0 || descendants.length > 0) {
|
|
104
|
+
const hierarchyLines = ['## Hierarchy'];
|
|
105
|
+
if (ancestors.length > 0) {
|
|
106
|
+
hierarchyLines.push(`\n### Ancestors (${ancestors.length})`);
|
|
107
|
+
for (const a of ancestors.slice(0, 10)) {
|
|
108
|
+
const level = a.min_levels_of_separation ?? a.level ?? '';
|
|
109
|
+
hierarchyLines.push(` ${' '.repeat(Number(level) || 0)}↑ ${a.concept_name} (${a.vocabulary_id}, ID: ${a.concept_id})`);
|
|
110
|
+
}
|
|
111
|
+
if (ancestors.length > 10)
|
|
112
|
+
hierarchyLines.push(` ... and ${ancestors.length - 10} more`);
|
|
113
|
+
}
|
|
114
|
+
if (descendants.length > 0) {
|
|
115
|
+
hierarchyLines.push(`\n### Descendants (${descendants.length})`);
|
|
116
|
+
for (const d of descendants.slice(0, 10)) {
|
|
117
|
+
const level = d.min_levels_of_separation ?? d.level ?? '';
|
|
118
|
+
hierarchyLines.push(` ${' '.repeat(Number(level) || 0)}↓ ${d.concept_name} (${d.vocabulary_id}, ID: ${d.concept_id})`);
|
|
119
|
+
}
|
|
120
|
+
if (descendants.length > 10)
|
|
121
|
+
hierarchyLines.push(` ... and ${descendants.length - 10} more`);
|
|
122
|
+
}
|
|
123
|
+
sections.push(hierarchyLines.join('\n'));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Mappings — extracted from relationships with "Maps to" type
|
|
127
|
+
if (relationshipsData) {
|
|
128
|
+
const allRels = relationshipsData.relationships ?? [];
|
|
129
|
+
// Filter for mapping relationships
|
|
130
|
+
const mappingRels = allRels.filter((r) => r.relationship_id === 'Maps to' || r.relationship_id === 'Mapped from');
|
|
131
|
+
// Optionally filter by target vocabularies
|
|
132
|
+
const vocabFilter = target_vocabularies
|
|
133
|
+
? target_vocabularies
|
|
134
|
+
.split(',')
|
|
135
|
+
.map((v) => v.trim())
|
|
136
|
+
.filter(Boolean)
|
|
137
|
+
: null;
|
|
138
|
+
const filtered = vocabFilter
|
|
139
|
+
? mappingRels.filter((r) => vocabFilter.includes(r.concept_2?.vocabulary_id ?? ''))
|
|
140
|
+
: mappingRels;
|
|
141
|
+
if (filtered.length > 0) {
|
|
142
|
+
const mapLines = [`## Cross-Vocabulary Mappings (${filtered.length})`];
|
|
143
|
+
for (const r of filtered.slice(0, 15)) {
|
|
144
|
+
const c2 = r.concept_2;
|
|
145
|
+
if (c2) {
|
|
146
|
+
mapLines.push(`- **${c2.concept_name}** → ${c2.vocabulary_id} (${c2.concept_code}) via _${r.relationship_id}_`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (filtered.length > 15)
|
|
150
|
+
mapLines.push(`... and ${filtered.length - 15} more`);
|
|
151
|
+
sections.push(mapLines.join('\n'));
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
const isStandard = concept.standard_concept === 'S';
|
|
155
|
+
const hint = isStandard
|
|
156
|
+
? ' Standard concepts are typically the *target* of mappings, not the source. To find what maps to this concept, try searching for a non-standard source concept (e.g., an ICD-10 code) and use explore_concept on that.'
|
|
157
|
+
: '';
|
|
158
|
+
sections.push(`## Mappings\n\nNo cross-vocabulary mappings found.${hint}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const text = sections.join('\n\n');
|
|
162
|
+
const jsonData = { concept, hierarchy, relationships: relationshipsData };
|
|
163
|
+
return {
|
|
164
|
+
content: [
|
|
165
|
+
{ type: 'text', text },
|
|
166
|
+
{ type: 'text', text: JSON.stringify(jsonData) },
|
|
167
|
+
],
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
const { text, json } = formatErrorForMcp(error, 'explore_concept');
|
|
172
|
+
return {
|
|
173
|
+
content: [
|
|
174
|
+
{ type: 'text', text },
|
|
175
|
+
{ type: 'text', text: json },
|
|
176
|
+
],
|
|
177
|
+
isError: true,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=explore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explore.js","sourceRoot":"","sources":["../../src/tools/explore.ts"],"names":[],"mappings":"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"}
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export { registerConceptTools } from './concepts.js';
|
|
2
|
+
export { registerExploreTools } from './explore.js';
|
|
2
3
|
export { registerHierarchyTools } from './hierarchy.js';
|
|
3
4
|
export { registerMappingTools } from './mappings.js';
|
|
4
5
|
export { registerSearchTools } from './search.js';
|
|
6
|
+
export { registerSemanticSearchTools } from './semantic-search.js';
|
|
7
|
+
export { registerSimilarTools } from './similar.js';
|
|
5
8
|
export { registerVocabularyTools } from './vocabularies.js';
|
|
6
9
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,2BAA2B,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/tools/index.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export { registerConceptTools } from './concepts.js';
|
|
2
|
+
export { registerExploreTools } from './explore.js';
|
|
2
3
|
export { registerHierarchyTools } from './hierarchy.js';
|
|
3
4
|
export { registerMappingTools } from './mappings.js';
|
|
4
5
|
export { registerSearchTools } from './search.js';
|
|
6
|
+
export { registerSemanticSearchTools } from './semantic-search.js';
|
|
7
|
+
export { registerSimilarTools } from './similar.js';
|
|
5
8
|
export { registerVocabularyTools } from './vocabularies.js';
|
|
6
9
|
//# sourceMappingURL=index.js.map
|
package/dist/tools/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,2BAA2B,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { OmopHubClient } from '../client/api.js';
|
|
3
|
+
export declare function registerSemanticSearchTools(server: McpServer, client: OmopHubClient): void;
|
|
4
|
+
//# sourceMappingURL=semantic-search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semantic-search.d.ts","sourceRoot":"","sources":["../../src/tools/semantic-search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAStD,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,GAAG,IAAI,CAyG1F"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { formatErrorForMcp } from '../utils/errors.js';
|
|
3
|
+
export function registerSemanticSearchTools(server, client) {
|
|
4
|
+
server.tool('semantic_search', "Search for medical concepts using natural language with neural embeddings. Unlike keyword search, semantic search understands clinical meaning — 'heart attack' finds 'Myocardial infarction', 'high blood sugar' finds 'Hyperglycemia'. Returns concepts ranked by similarity score. Use this when the user describes symptoms, conditions, or treatments in everyday language rather than exact medical terminology.", {
|
|
5
|
+
query: z
|
|
6
|
+
.string()
|
|
7
|
+
.trim()
|
|
8
|
+
.min(1)
|
|
9
|
+
.max(500)
|
|
10
|
+
.describe('Natural language description of the medical concept to find'),
|
|
11
|
+
vocabulary_ids: z
|
|
12
|
+
.string()
|
|
13
|
+
.max(200)
|
|
14
|
+
.optional()
|
|
15
|
+
.describe("Comma-separated vocabulary IDs to filter by. Examples: 'SNOMED', 'ICD10CM', 'RxNorm'."),
|
|
16
|
+
domain_ids: z
|
|
17
|
+
.string()
|
|
18
|
+
.max(200)
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("Comma-separated domain IDs to filter by. Examples: 'Condition', 'Drug', 'Measurement'."),
|
|
21
|
+
standard_concept: z
|
|
22
|
+
.enum(['S', 'C'])
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("Filter by standard concept status: 'S' for Standard, 'C' for Classification."),
|
|
25
|
+
threshold: z
|
|
26
|
+
.number()
|
|
27
|
+
.min(0)
|
|
28
|
+
.max(1)
|
|
29
|
+
.default(0.5)
|
|
30
|
+
.describe('Minimum similarity score (0.0-1.0). Higher = stricter matching. Default 0.5.'),
|
|
31
|
+
page_size: z
|
|
32
|
+
.number()
|
|
33
|
+
.min(1)
|
|
34
|
+
.max(50)
|
|
35
|
+
.default(10)
|
|
36
|
+
.describe('Number of results to return (1-50, default 10)'),
|
|
37
|
+
}, async ({ query, vocabulary_ids, domain_ids, standard_concept, threshold, page_size }) => {
|
|
38
|
+
try {
|
|
39
|
+
const params = {
|
|
40
|
+
query,
|
|
41
|
+
page_size: page_size ?? 10,
|
|
42
|
+
threshold: threshold ?? 0.5,
|
|
43
|
+
};
|
|
44
|
+
if (vocabulary_ids)
|
|
45
|
+
params.vocabulary_ids = vocabulary_ids;
|
|
46
|
+
if (domain_ids)
|
|
47
|
+
params.domain_ids = domain_ids;
|
|
48
|
+
if (standard_concept)
|
|
49
|
+
params.standard_concept = standard_concept;
|
|
50
|
+
const response = await client.request('/concepts/semantic-search', params, 'semantic_search');
|
|
51
|
+
const results = response.data?.results ?? [];
|
|
52
|
+
if (results.length === 0) {
|
|
53
|
+
return {
|
|
54
|
+
content: [
|
|
55
|
+
{
|
|
56
|
+
type: 'text',
|
|
57
|
+
text: `No semantic matches found for "${query}". Try different wording or lower the similarity threshold.`,
|
|
58
|
+
},
|
|
59
|
+
{ type: 'text', text: JSON.stringify({ results: [], query, threshold }) },
|
|
60
|
+
],
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const lines = results.map((r, i) => {
|
|
64
|
+
const std = r.standard_concept === 'S'
|
|
65
|
+
? ' [Standard]'
|
|
66
|
+
: r.standard_concept === 'C'
|
|
67
|
+
? ' [Classification]'
|
|
68
|
+
: '';
|
|
69
|
+
const score = Number.isFinite(r.similarity_score) ? r.similarity_score.toFixed(2) : 'N/A';
|
|
70
|
+
return `${i + 1}. **${r.concept_name}** (ID: ${r.concept_id}) — similarity: ${score}\n ${r.vocabulary_id} | ${r.domain_id} | Code: ${r.concept_code}${std}`;
|
|
71
|
+
});
|
|
72
|
+
const text = `Found ${results.length} semantic matches for "${query}":\n\n${lines.join('\n\n')}`;
|
|
73
|
+
return {
|
|
74
|
+
content: [
|
|
75
|
+
{ type: 'text', text },
|
|
76
|
+
{ type: 'text', text: JSON.stringify({ results, query, threshold }) },
|
|
77
|
+
],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
const { text, json } = formatErrorForMcp(error, 'semantic_search');
|
|
82
|
+
return {
|
|
83
|
+
content: [
|
|
84
|
+
{ type: 'text', text },
|
|
85
|
+
{ type: 'text', text: json },
|
|
86
|
+
],
|
|
87
|
+
isError: true,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=semantic-search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semantic-search.js","sourceRoot":"","sources":["../../src/tools/semantic-search.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"similar.d.ts","sourceRoot":"","sources":["../../src/tools/similar.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AA2BtD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,GAAG,IAAI,CAmKnF"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { formatErrorForMcp } from '../utils/errors.js';
|
|
3
|
+
export function registerSimilarTools(server, client) {
|
|
4
|
+
server.tool('find_similar_concepts', "Find medical concepts similar to a reference concept, name, or natural language query. Supports three algorithms: 'semantic' (neural embeddings — best for meaning), 'lexical' (text matching — best for typos), 'hybrid' (combined — default). Provide exactly ONE of: concept_id, concept_name, or query. Use this to explore related concepts, find alternative codes, or build phenotype concept sets. Tip: For drug vocabularies like RxNorm, use drug class names ('ACE inhibitors', 'beta blockers', 'antihypertensives') rather than symptom descriptions ('medications for high blood pressure') — the embedding model aligns better with clinical terminology than lay language.", {
|
|
5
|
+
concept_id: z.number().optional().describe('Find concepts similar to this OMOP concept ID'),
|
|
6
|
+
concept_name: z
|
|
7
|
+
.string()
|
|
8
|
+
.max(500)
|
|
9
|
+
.optional()
|
|
10
|
+
.describe('Find concepts similar to this concept name'),
|
|
11
|
+
query: z
|
|
12
|
+
.string()
|
|
13
|
+
.max(500)
|
|
14
|
+
.optional()
|
|
15
|
+
.describe('Find concepts matching this natural language description'),
|
|
16
|
+
algorithm: z
|
|
17
|
+
.enum(['semantic', 'lexical', 'hybrid'])
|
|
18
|
+
.default('hybrid')
|
|
19
|
+
.describe("Similarity algorithm: 'semantic' (meaning), 'lexical' (text), 'hybrid' (both). Default 'hybrid'."),
|
|
20
|
+
similarity_threshold: z
|
|
21
|
+
.number()
|
|
22
|
+
.min(0)
|
|
23
|
+
.max(1)
|
|
24
|
+
.default(0.7)
|
|
25
|
+
.describe('Minimum similarity score (0.0-1.0). Default 0.7.'),
|
|
26
|
+
page_size: z
|
|
27
|
+
.number()
|
|
28
|
+
.min(1)
|
|
29
|
+
.max(100)
|
|
30
|
+
.default(20)
|
|
31
|
+
.describe('Number of results (1-100, default 20)'),
|
|
32
|
+
vocabulary_ids: z
|
|
33
|
+
.string()
|
|
34
|
+
.max(200)
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("Comma-separated vocabulary IDs to filter results. Examples: 'SNOMED', 'ICD10CM'."),
|
|
37
|
+
domain_ids: z
|
|
38
|
+
.string()
|
|
39
|
+
.max(200)
|
|
40
|
+
.optional()
|
|
41
|
+
.describe("Comma-separated domain IDs to filter results. Examples: 'Condition', 'Drug'."),
|
|
42
|
+
}, async ({ concept_id, concept_name, query, algorithm, similarity_threshold, page_size, vocabulary_ids, domain_ids, }) => {
|
|
43
|
+
try {
|
|
44
|
+
// Validate exactly one input source
|
|
45
|
+
const provided = [concept_id, concept_name, query].filter((v) => v !== undefined).length;
|
|
46
|
+
if (provided !== 1) {
|
|
47
|
+
return {
|
|
48
|
+
content: [
|
|
49
|
+
{
|
|
50
|
+
type: 'text',
|
|
51
|
+
text: 'Error: Provide exactly one of concept_id, concept_name, or query.',
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
isError: true,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const body = {
|
|
58
|
+
algorithm: algorithm ?? 'hybrid',
|
|
59
|
+
similarity_threshold: similarity_threshold ?? 0.7,
|
|
60
|
+
page_size: page_size ?? 20,
|
|
61
|
+
};
|
|
62
|
+
if (concept_id !== undefined)
|
|
63
|
+
body.concept_id = concept_id;
|
|
64
|
+
if (concept_name !== undefined)
|
|
65
|
+
body.concept_name = concept_name;
|
|
66
|
+
if (query !== undefined)
|
|
67
|
+
body.query = query;
|
|
68
|
+
if (vocabulary_ids)
|
|
69
|
+
body.vocabulary_ids = vocabulary_ids
|
|
70
|
+
.split(',')
|
|
71
|
+
.map((s) => s.trim())
|
|
72
|
+
.filter(Boolean);
|
|
73
|
+
if (domain_ids)
|
|
74
|
+
body.domain_ids = domain_ids
|
|
75
|
+
.split(',')
|
|
76
|
+
.map((s) => s.trim())
|
|
77
|
+
.filter(Boolean);
|
|
78
|
+
const response = await client.post('/search/similar', body, 'find_similar_concepts');
|
|
79
|
+
const data = response.data;
|
|
80
|
+
const concepts = data?.similar_concepts ?? [];
|
|
81
|
+
const meta = data?.search_metadata;
|
|
82
|
+
if (concepts.length === 0) {
|
|
83
|
+
const source = concept_id ? `concept ${concept_id}` : (concept_name ?? query ?? '');
|
|
84
|
+
return {
|
|
85
|
+
content: [
|
|
86
|
+
{
|
|
87
|
+
type: 'text',
|
|
88
|
+
text: `No similar concepts found for ${source}. Try lowering the similarity threshold or using a different algorithm.`,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
type: 'text',
|
|
92
|
+
text: JSON.stringify({ similar_concepts: [], search_metadata: meta }),
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const source = concept_id
|
|
98
|
+
? `concept ID ${concept_id}`
|
|
99
|
+
: concept_name
|
|
100
|
+
? `"${concept_name}"`
|
|
101
|
+
: `"${query}"`;
|
|
102
|
+
const lines = concepts.map((c, i) => {
|
|
103
|
+
const std = c.standard_concept === 'S'
|
|
104
|
+
? ' [Standard]'
|
|
105
|
+
: c.standard_concept === 'C'
|
|
106
|
+
? ' [Classification]'
|
|
107
|
+
: '';
|
|
108
|
+
const score = Number.isFinite(c.similarity_score) ? c.similarity_score.toFixed(2) : 'N/A';
|
|
109
|
+
let line = `${i + 1}. **${c.concept_name}** (ID: ${c.concept_id}) — score: ${score}\n ${c.vocabulary_id} | ${c.domain_id} | Code: ${c.concept_code}${std}`;
|
|
110
|
+
if (c.similarity_explanation) {
|
|
111
|
+
line += `\n _${c.similarity_explanation}_`;
|
|
112
|
+
}
|
|
113
|
+
return line;
|
|
114
|
+
});
|
|
115
|
+
const algoLabel = meta?.algorithm_used ?? algorithm ?? 'hybrid';
|
|
116
|
+
const text = `Found ${concepts.length} concepts similar to ${source} (${algoLabel} algorithm):\n\n${lines.join('\n\n')}`;
|
|
117
|
+
return {
|
|
118
|
+
content: [
|
|
119
|
+
{ type: 'text', text },
|
|
120
|
+
{
|
|
121
|
+
type: 'text',
|
|
122
|
+
text: JSON.stringify({ similar_concepts: concepts, search_metadata: meta }),
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
const { text, json } = formatErrorForMcp(error, 'find_similar_concepts');
|
|
129
|
+
return {
|
|
130
|
+
content: [
|
|
131
|
+
{ type: 'text', text },
|
|
132
|
+
{ type: 'text', text: json },
|
|
133
|
+
],
|
|
134
|
+
isError: true,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=similar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"similar.js","sourceRoot":"","sources":["../../src/tools/similar.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AA0BvD,MAAM,UAAU,oBAAoB,CAAC,MAAiB,EAAE,MAAqB;IAC3E,MAAM,CAAC,IAAI,CACT,uBAAuB,EACvB,4pBAA4pB,EAC5pB;QACE,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,+CAA+C,CAAC;QAC3F,YAAY,EAAE,CAAC;aACZ,MAAM,EAAE;aACR,GAAG,CAAC,GAAG,CAAC;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,4CAA4C,CAAC;QACzD,KAAK,EAAE,CAAC;aACL,MAAM,EAAE;aACR,GAAG,CAAC,GAAG,CAAC;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,0DAA0D,CAAC;QACvE,SAAS,EAAE,CAAC;aACT,IAAI,CAAC,CAAC,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;aACvC,OAAO,CAAC,QAAQ,CAAC;aACjB,QAAQ,CACP,kGAAkG,CACnG;QACH,oBAAoB,EAAE,CAAC;aACpB,MAAM,EAAE;aACR,GAAG,CAAC,CAAC,CAAC;aACN,GAAG,CAAC,CAAC,CAAC;aACN,OAAO,CAAC,GAAG,CAAC;aACZ,QAAQ,CAAC,kDAAkD,CAAC;QAC/D,SAAS,EAAE,CAAC;aACT,MAAM,EAAE;aACR,GAAG,CAAC,CAAC,CAAC;aACN,GAAG,CAAC,GAAG,CAAC;aACR,OAAO,CAAC,EAAE,CAAC;aACX,QAAQ,CAAC,uCAAuC,CAAC;QACpD,cAAc,EAAE,CAAC;aACd,MAAM,EAAE;aACR,GAAG,CAAC,GAAG,CAAC;aACR,QAAQ,EAAE;aACV,QAAQ,CACP,kFAAkF,CACnF;QACH,UAAU,EAAE,CAAC;aACV,MAAM,EAAE;aACR,GAAG,CAAC,GAAG,CAAC;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,8EAA8E,CAAC;KAC5F,EACD,KAAK,EAAE,EACL,UAAU,EACV,YAAY,EACZ,KAAK,EACL,SAAS,EACT,oBAAoB,EACpB,SAAS,EACT,cAAc,EACd,UAAU,GACX,EAAE,EAAE;QACH,IAAI,CAAC;YACH,oCAAoC;YACpC,MAAM,QAAQ,GAAG,CAAC,UAAU,EAAE,YAAY,EAAE,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,MAAM,CAAC;YACzF,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;gBACnB,OAAO;oBACL,OAAO,EAAE;wBACP;4BACE,IAAI,EAAE,MAAe;4BACrB,IAAI,EAAE,mEAAmE;yBAC1E;qBACF;oBACD,OAAO,EAAE,IAAI;iBACd,CAAC;YACJ,CAAC;YAED,MAAM,IAAI,GAA4B;gBACpC,SAAS,EAAE,SAAS,IAAI,QAAQ;gBAChC,oBAAoB,EAAE,oBAAoB,IAAI,GAAG;gBACjD,SAAS,EAAE,SAAS,IAAI,EAAE;aAC3B,CAAC;YAEF,IAAI,UAAU,KAAK,SAAS;gBAAE,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;YAC3D,IAAI,YAAY,KAAK,SAAS;gBAAE,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;YACjE,IAAI,KAAK,KAAK,SAAS;gBAAE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;YAC5C,IAAI,cAAc;gBAChB,IAAI,CAAC,cAAc,GAAG,cAAc;qBACjC,KAAK,CAAC,GAAG,CAAC;qBACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;qBACpB,MAAM,CAAC,OAAO,CAAC,CAAC;YACrB,IAAI,UAAU;gBACZ,IAAI,CAAC,UAAU,GAAG,UAAU;qBACzB,KAAK,CAAC,GAAG,CAAC;qBACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;qBACpB,MAAM,CAAC,OAAO,CAAC,CAAC;YAErB,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,IAAI,CAChC,iBAAiB,EACjB,IAAI,EACJ,uBAAuB,CACxB,CAAC;YAEF,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC;YAC3B,MAAM,QAAQ,GAAG,IAAI,EAAE,gBAAgB,IAAI,EAAE,CAAC;YAC9C,MAAM,IAAI,GAAG,IAAI,EAAE,eAAe,CAAC;YAEnC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC1B,MAAM,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,WAAW,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,IAAI,KAAK,IAAI,EAAE,CAAC,CAAC;gBACpF,OAAO;oBACL,OAAO,EAAE;wBACP;4BACE,IAAI,EAAE,MAAe;4BACrB,IAAI,EAAE,iCAAiC,MAAM,yEAAyE;yBACvH;wBACD;4BACE,IAAI,EAAE,MAAe;4BACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,gBAAgB,EAAE,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE,CAAC;yBACtE;qBACF;iBACF,CAAC;YACJ,CAAC;YAED,MAAM,MAAM,GAAG,UAAU;gBACvB,CAAC,CAAC,cAAc,UAAU,EAAE;gBAC5B,CAAC,CAAC,YAAY;oBACZ,CAAC,CAAC,IAAI,YAAY,GAAG;oBACrB,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC;YAEnB,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;gBAClC,MAAM,GAAG,GACP,CAAC,CAAC,gBAAgB,KAAK,GAAG;oBACxB,CAAC,CAAC,aAAa;oBACf,CAAC,CAAC,CAAC,CAAC,gBAAgB,KAAK,GAAG;wBAC1B,CAAC,CAAC,mBAAmB;wBACrB,CAAC,CAAC,EAAE,CAAC;gBACX,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;gBAC1F,IAAI,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,YAAY,WAAW,CAAC,CAAC,UAAU,cAAc,KAAK,QAAQ,CAAC,CAAC,aAAa,MAAM,CAAC,CAAC,SAAS,YAAY,CAAC,CAAC,YAAY,GAAG,GAAG,EAAE,CAAC;gBAC7J,IAAI,CAAC,CAAC,sBAAsB,EAAE,CAAC;oBAC7B,IAAI,IAAI,SAAS,CAAC,CAAC,sBAAsB,GAAG,CAAC;gBAC/C,CAAC;gBACD,OAAO,IAAI,CAAC;YACd,CAAC,CAAC,CAAC;YAEH,MAAM,SAAS,GAAG,IAAI,EAAE,cAAc,IAAI,SAAS,IAAI,QAAQ,CAAC;YAChE,MAAM,IAAI,GAAG,SAAS,QAAQ,CAAC,MAAM,wBAAwB,MAAM,KAAK,SAAS,mBAAmB,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YAEzH,OAAO;gBACL,OAAO,EAAE;oBACP,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE;oBAC/B;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,gBAAgB,EAAE,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,CAAC;qBAC5E;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,iBAAiB,CAAC,KAAK,EAAE,uBAAuB,CAAC,CAAC;YACzE,OAAO;gBACL,OAAO,EAAE;oBACP,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE;oBAC/B,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,EAAE;iBACtC;gBACD,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
package/dist/utils/auth.js
CHANGED
|
@@ -2,7 +2,7 @@ export function resolveApiKey(cliKey) {
|
|
|
2
2
|
const key = cliKey || process.env.OMOPHUB_API_KEY;
|
|
3
3
|
if (!key) {
|
|
4
4
|
throw new Error('OMOPHub API key required. Set OMOPHUB_API_KEY environment variable or pass --api-key=KEY.\n' +
|
|
5
|
-
'Get your free API key at: https://omophub.com/
|
|
5
|
+
'Get your free API key at: https://dashboard.omophub.com/api-keys');
|
|
6
6
|
}
|
|
7
7
|
return key;
|
|
8
8
|
}
|
package/dist/utils/errors.js
CHANGED
|
@@ -8,8 +8,8 @@ export class OmopHubApiError extends Error {
|
|
|
8
8
|
this.name = 'OmopHubApiError';
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
|
-
const UPGRADE_URL = 'https://omophub.com/
|
|
12
|
-
const API_KEYS_URL = 'https://omophub.com/
|
|
11
|
+
const UPGRADE_URL = 'https://dashboard.omophub.com/billing';
|
|
12
|
+
const API_KEYS_URL = 'https://dashboard.omophub.com/api-keys';
|
|
13
13
|
export function formatErrorForMcp(error, toolName) {
|
|
14
14
|
if (error instanceof OmopHubApiError) {
|
|
15
15
|
switch (error.status) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omophub/omophub-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"mcpName": "io.github.OMOPHub/omophub-mcp",
|
|
5
5
|
"description": "MCP server for OHDSI OMOP standardized medical vocabularies — search, lookup, map, and navigate concepts via AI agents",
|
|
6
6
|
"type": "module",
|