omophub-mcp 1.0.0
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/LICENSE +21 -0
- package/README.md +265 -0
- package/dist/client/api.d.ts +10 -0
- package/dist/client/api.d.ts.map +1 -0
- package/dist/client/api.js +168 -0
- package/dist/client/api.js.map +1 -0
- package/dist/client/types.d.ts +86 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +2 -0
- package/dist/client/types.js.map +1 -0
- package/dist/formatters/index.d.ts +26 -0
- package/dist/formatters/index.d.ts.map +1 -0
- package/dist/formatters/index.js +197 -0
- package/dist/formatters/index.js.map +1 -0
- package/dist/health.d.ts +3 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +34 -0
- package/dist/health.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +75 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts/index.d.ts +3 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +56 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/resources/index.d.ts +4 -0
- package/dist/resources/index.d.ts.map +1 -0
- package/dist/resources/index.js +86 -0
- package/dist/resources/index.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +31 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/concepts.d.ts +4 -0
- package/dist/tools/concepts.d.ts.map +1 -0
- package/dist/tools/concepts.js +89 -0
- package/dist/tools/concepts.js.map +1 -0
- package/dist/tools/hierarchy.d.ts +4 -0
- package/dist/tools/hierarchy.d.ts.map +1 -0
- package/dist/tools/hierarchy.js +98 -0
- package/dist/tools/hierarchy.js.map +1 -0
- package/dist/tools/index.d.ts +6 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +6 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/mappings.d.ts +4 -0
- package/dist/tools/mappings.d.ts.map +1 -0
- package/dist/tools/mappings.js +39 -0
- package/dist/tools/mappings.js.map +1 -0
- package/dist/tools/search.d.ts +4 -0
- package/dist/tools/search.d.ts.map +1 -0
- package/dist/tools/search.js +68 -0
- package/dist/tools/search.js.map +1 -0
- package/dist/tools/vocabularies.d.ts +4 -0
- package/dist/tools/vocabularies.d.ts.map +1 -0
- package/dist/tools/vocabularies.js +45 -0
- package/dist/tools/vocabularies.js.map +1 -0
- package/dist/utils/auth.d.ts +2 -0
- package/dist/utils/auth.d.ts.map +1 -0
- package/dist/utils/auth.js +9 -0
- package/dist/utils/auth.js.map +1 -0
- package/dist/utils/cache.d.ts +15 -0
- package/dist/utils/cache.d.ts.map +1 -0
- package/dist/utils/cache.js +52 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/errors.d.ts +10 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +95 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/logger.d.ts +18 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +24 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/package.json +70 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { formatConcept } from '../formatters/index.js';
|
|
3
|
+
import { conceptCache } from '../utils/cache.js';
|
|
4
|
+
import { formatErrorForMcp } from '../utils/errors.js';
|
|
5
|
+
export function registerConceptTools(server, client) {
|
|
6
|
+
server.tool('get_concept', 'Get detailed information about a specific OMOP concept by its numeric concept_id. Returns the concept name, vocabulary, domain, concept class, standard status, valid dates, and synonyms. Use this when you already have a concept_id and need its details.', {
|
|
7
|
+
concept_id: z.number().describe('The OMOP concept_id (numeric identifier)'),
|
|
8
|
+
}, async ({ concept_id }) => {
|
|
9
|
+
try {
|
|
10
|
+
const cacheKey = `concept:${concept_id}`;
|
|
11
|
+
const cached = conceptCache.get(cacheKey);
|
|
12
|
+
if (cached) {
|
|
13
|
+
const { text, json } = formatConcept(cached.data);
|
|
14
|
+
return {
|
|
15
|
+
content: [
|
|
16
|
+
{ type: 'text', text },
|
|
17
|
+
{ type: 'text', text: json },
|
|
18
|
+
],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const response = await client.request(`/concepts/${concept_id}`, undefined, 'get_concept');
|
|
22
|
+
conceptCache.set(cacheKey, response);
|
|
23
|
+
const { text, json } = formatConcept(response.data);
|
|
24
|
+
return {
|
|
25
|
+
content: [
|
|
26
|
+
{ type: 'text', text },
|
|
27
|
+
{ type: 'text', text: json },
|
|
28
|
+
],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
const { text, json } = formatErrorForMcp(error, 'get_concept');
|
|
33
|
+
return {
|
|
34
|
+
content: [
|
|
35
|
+
{ type: 'text', text },
|
|
36
|
+
{ type: 'text', text: json },
|
|
37
|
+
],
|
|
38
|
+
isError: true,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
server.tool('get_concept_by_code', "Look up an OMOP concept using a vocabulary-specific code and vocabulary ID. Both parameters are required to avoid ambiguity — the same code can exist in multiple vocabularies (e.g., 'E11' exists in both ICD10CM and ICD10). If multiple concepts share the same code within a vocabulary, all matches are returned — prefer the one with standard_concept='S'.", {
|
|
43
|
+
vocabulary_id: z
|
|
44
|
+
.string()
|
|
45
|
+
.max(50)
|
|
46
|
+
.describe("The vocabulary system. Examples: 'ICD10CM', 'SNOMED', 'RxNorm', 'LOINC', 'CPT4', 'HCPCS', 'NDC'"),
|
|
47
|
+
concept_code: z
|
|
48
|
+
.string()
|
|
49
|
+
.max(50)
|
|
50
|
+
.describe("The vocabulary-specific code. Examples: 'E11.9' (ICD-10), '44054006' (SNOMED), '4850' (LOINC)"),
|
|
51
|
+
}, async ({ vocabulary_id, concept_code }) => {
|
|
52
|
+
try {
|
|
53
|
+
const cacheKey = `code:${vocabulary_id}:${concept_code}`;
|
|
54
|
+
const cached = conceptCache.get(cacheKey);
|
|
55
|
+
if (cached) {
|
|
56
|
+
const concepts = Array.isArray(cached.data) ? cached.data : [cached.data];
|
|
57
|
+
const formatted = concepts.map((c) => formatConcept(c));
|
|
58
|
+
return {
|
|
59
|
+
content: [
|
|
60
|
+
...formatted.map((f) => ({ type: 'text', text: f.text })),
|
|
61
|
+
{ type: 'text', text: JSON.stringify(concepts) },
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
// Actual API uses /concepts/by-code/{vocabulary_id}/{concept_code}
|
|
66
|
+
const response = await client.request(`/concepts/by-code/${encodeURIComponent(vocabulary_id)}/${encodeURIComponent(concept_code)}`, undefined, 'get_concept_by_code');
|
|
67
|
+
conceptCache.set(cacheKey, response);
|
|
68
|
+
const concepts = Array.isArray(response.data) ? response.data : [response.data];
|
|
69
|
+
const formatted = concepts.map((c) => formatConcept(c));
|
|
70
|
+
return {
|
|
71
|
+
content: [
|
|
72
|
+
...formatted.map((f) => ({ type: 'text', text: f.text })),
|
|
73
|
+
{ type: 'text', text: JSON.stringify(concepts) },
|
|
74
|
+
],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
const { text, json } = formatErrorForMcp(error, 'get_concept_by_code');
|
|
79
|
+
return {
|
|
80
|
+
content: [
|
|
81
|
+
{ type: 'text', text },
|
|
82
|
+
{ type: 'text', text: json },
|
|
83
|
+
],
|
|
84
|
+
isError: true,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=concepts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"concepts.js","sourceRoot":"","sources":["../../src/tools/concepts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hierarchy.d.ts","sourceRoot":"","sources":["../../src/tools/hierarchy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAMtD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,GAAG,IAAI,CAmHrF"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { formatHierarchy } from '../formatters/index.js';
|
|
3
|
+
import { hierarchyCache } from '../utils/cache.js';
|
|
4
|
+
import { formatErrorForMcp } from '../utils/errors.js';
|
|
5
|
+
export function registerHierarchyTools(server, client) {
|
|
6
|
+
server.tool('get_hierarchy', "Navigate the vocabulary hierarchy for a concept. Use direction='up' for ancestors (broader terms like 'Diabetes mellitus' → 'Metabolic disease'), direction='down' for descendants (narrower terms, essential for building concept sets in phenotype definitions), or direction='both' for full hierarchical context. Results are capped at max_results nodes (default 500). If truncated, the response will indicate total available count so you can narrow with vocabulary_ids or reduce max_levels.", {
|
|
7
|
+
concept_id: z.number().describe('The OMOP concept_id'),
|
|
8
|
+
direction: z
|
|
9
|
+
.enum(['up', 'down', 'both'])
|
|
10
|
+
.default('both')
|
|
11
|
+
.describe("Hierarchy direction: 'up' for ancestors, 'down' for descendants, 'both' for full context (default: 'both')"),
|
|
12
|
+
max_levels: z
|
|
13
|
+
.number()
|
|
14
|
+
.optional()
|
|
15
|
+
.describe("Maximum levels to traverse (default 5 for 'up', 10 for 'down', 5/3 for 'both')"),
|
|
16
|
+
max_results: z
|
|
17
|
+
.number()
|
|
18
|
+
.min(1)
|
|
19
|
+
.max(500)
|
|
20
|
+
.default(500)
|
|
21
|
+
.describe('Maximum number of nodes to return (1-500, default 500). Use a smaller value for broad concepts.'),
|
|
22
|
+
vocabulary_ids: z
|
|
23
|
+
.string()
|
|
24
|
+
.max(200)
|
|
25
|
+
.optional()
|
|
26
|
+
.describe('Comma-separated vocabulary IDs to filter results. Leave empty for all.'),
|
|
27
|
+
}, async ({ concept_id, direction, max_levels, max_results, vocabulary_ids }) => {
|
|
28
|
+
try {
|
|
29
|
+
const cacheKey = `hierarchy:${concept_id}:${direction}:${max_levels}:${max_results}:${vocabulary_ids}`;
|
|
30
|
+
const cached = hierarchyCache.get(cacheKey);
|
|
31
|
+
if (cached) {
|
|
32
|
+
const { text, json } = formatHierarchy(cached.response, cached.direction);
|
|
33
|
+
return {
|
|
34
|
+
content: [
|
|
35
|
+
{ type: 'text', text },
|
|
36
|
+
{ type: 'text', text: json },
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
// Route to correct API endpoint based on direction
|
|
41
|
+
const endpointMap = {
|
|
42
|
+
up: 'ancestors',
|
|
43
|
+
down: 'descendants',
|
|
44
|
+
both: 'hierarchy',
|
|
45
|
+
};
|
|
46
|
+
const endpoint = endpointMap[direction ?? 'both'];
|
|
47
|
+
const params = {};
|
|
48
|
+
if (max_levels !== undefined)
|
|
49
|
+
params.max_levels = max_levels;
|
|
50
|
+
if (max_results !== undefined)
|
|
51
|
+
params.page_size = max_results;
|
|
52
|
+
if (vocabulary_ids)
|
|
53
|
+
params.vocabulary_ids = vocabulary_ids;
|
|
54
|
+
const rawResponse = await client.request(`/concepts/${concept_id}/${endpoint}`, params, 'get_hierarchy');
|
|
55
|
+
// API returns flat concept fields — normalize into { concept: {...} } for formatter
|
|
56
|
+
const raw = rawResponse.data;
|
|
57
|
+
const totalAncestors = raw.total_ancestors ?? raw.hierarchy_summary?.total_ancestors;
|
|
58
|
+
const totalDescendants = raw.total_descendants ?? raw.hierarchy_summary?.total_descendants;
|
|
59
|
+
const response = {
|
|
60
|
+
...rawResponse,
|
|
61
|
+
data: {
|
|
62
|
+
concept: {
|
|
63
|
+
concept_id: raw.concept_id,
|
|
64
|
+
concept_name: raw.concept_name ?? `Concept ${raw.concept_id}`,
|
|
65
|
+
vocabulary_id: raw.vocabulary_id ?? '',
|
|
66
|
+
domain_id: '',
|
|
67
|
+
concept_class_id: '',
|
|
68
|
+
standard_concept: null,
|
|
69
|
+
concept_code: '',
|
|
70
|
+
},
|
|
71
|
+
ancestors: raw.ancestors,
|
|
72
|
+
descendants: raw.descendants,
|
|
73
|
+
total_ancestors: totalAncestors,
|
|
74
|
+
total_descendants: totalDescendants,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
hierarchyCache.set(cacheKey, { response, direction: direction ?? 'both' });
|
|
78
|
+
const { text, json } = formatHierarchy(response, direction ?? 'both');
|
|
79
|
+
return {
|
|
80
|
+
content: [
|
|
81
|
+
{ type: 'text', text },
|
|
82
|
+
{ type: 'text', text: json },
|
|
83
|
+
],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
const { text, json } = formatErrorForMcp(error, 'get_hierarchy');
|
|
88
|
+
return {
|
|
89
|
+
content: [
|
|
90
|
+
{ type: 'text', text },
|
|
91
|
+
{ type: 'text', text: json },
|
|
92
|
+
],
|
|
93
|
+
isError: true,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=hierarchy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hierarchy.js","sourceRoot":"","sources":["../../src/tools/hierarchy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { registerConceptTools } from './concepts.js';
|
|
2
|
+
export { registerHierarchyTools } from './hierarchy.js';
|
|
3
|
+
export { registerMappingTools } from './mappings.js';
|
|
4
|
+
export { registerSearchTools } from './search.js';
|
|
5
|
+
export { registerVocabularyTools } from './vocabularies.js';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { registerConceptTools } from './concepts.js';
|
|
2
|
+
export { registerHierarchyTools } from './hierarchy.js';
|
|
3
|
+
export { registerMappingTools } from './mappings.js';
|
|
4
|
+
export { registerSearchTools } from './search.js';
|
|
5
|
+
export { registerVocabularyTools } from './vocabularies.js';
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mappings.d.ts","sourceRoot":"","sources":["../../src/tools/mappings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAKtD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,GAAG,IAAI,CA+CnF"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { formatMappings } from '../formatters/index.js';
|
|
3
|
+
import { formatErrorForMcp } from '../utils/errors.js';
|
|
4
|
+
export function registerMappingTools(server, client) {
|
|
5
|
+
server.tool('map_concept', "Find mappings FROM a source concept TO equivalent concepts in other vocabularies. The concept_id you provide is always the SOURCE — results show what it maps TO. Returns cross-vocabulary mappings with relationship types and mapping quality. If no mappings exist, the response explicitly states 'No mappings found' with mapped=false in JSON — never returns ambiguous empty results. Example: provide a SNOMED concept_id and filter by target_vocabularies='ICD10CM' to get the ICD-10 equivalent.", {
|
|
6
|
+
concept_id: z.number().describe('The source OMOP concept_id to map FROM'),
|
|
7
|
+
target_vocabularies: z
|
|
8
|
+
.string()
|
|
9
|
+
.max(200)
|
|
10
|
+
.optional()
|
|
11
|
+
.describe("Comma-separated vocabulary IDs to map TO. Examples: 'ICD10CM', 'SNOMED', 'RxNorm'. Omit to see all available mappings."),
|
|
12
|
+
}, async ({ concept_id, target_vocabularies }) => {
|
|
13
|
+
try {
|
|
14
|
+
const params = {};
|
|
15
|
+
// Map PRD param to actual API param (target_vocabularies → target_vocabulary)
|
|
16
|
+
if (target_vocabularies)
|
|
17
|
+
params.target_vocabulary = target_vocabularies;
|
|
18
|
+
const response = await client.request(`/concepts/${concept_id}/mappings`, params, 'map_concept');
|
|
19
|
+
const { text, json } = formatMappings(response, concept_id);
|
|
20
|
+
return {
|
|
21
|
+
content: [
|
|
22
|
+
{ type: 'text', text },
|
|
23
|
+
{ type: 'text', text: json },
|
|
24
|
+
],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
const { text, json } = formatErrorForMcp(error, 'map_concept');
|
|
29
|
+
return {
|
|
30
|
+
content: [
|
|
31
|
+
{ type: 'text', text },
|
|
32
|
+
{ type: 'text', text: json },
|
|
33
|
+
],
|
|
34
|
+
isError: true,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=mappings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mappings.js","sourceRoot":"","sources":["../../src/tools/mappings.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,UAAU,oBAAoB,CAAC,MAAiB,EAAE,MAAqB;IAC3E,MAAM,CAAC,IAAI,CACT,aAAa,EACb,6eAA6e,EAC7e;QACE,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,wCAAwC,CAAC;QACzE,mBAAmB,EAAE,CAAC;aACnB,MAAM,EAAE;aACR,GAAG,CAAC,GAAG,CAAC;aACR,QAAQ,EAAE;aACV,QAAQ,CACP,wHAAwH,CACzH;KACJ,EACD,KAAK,EAAE,EAAE,UAAU,EAAE,mBAAmB,EAAE,EAAE,EAAE;QAC5C,IAAI,CAAC;YACH,MAAM,MAAM,GAA0D,EAAE,CAAC;YAEzE,8EAA8E;YAC9E,IAAI,mBAAmB;gBAAE,MAAM,CAAC,iBAAiB,GAAG,mBAAmB,CAAC;YAExE,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,OAAO,CACnC,aAAa,UAAU,WAAW,EAClC,MAAM,EACN,aAAa,CACd,CAAC;YAEF,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,cAAc,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;YAE5D,OAAO;gBACL,OAAO,EAAE;oBACP,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE;oBAC/B,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,EAAE;iBACtC;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,iBAAiB,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;YAC/D,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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/tools/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAKtD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,GAAG,IAAI,CA8ElF"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { formatConceptList } from '../formatters/index.js';
|
|
3
|
+
import { formatErrorForMcp } from '../utils/errors.js';
|
|
4
|
+
export function registerSearchTools(server, client) {
|
|
5
|
+
server.tool('search_concepts', "Search for medical concepts across OHDSI standardized vocabularies by name, synonym, or clinical term. Returns matching concepts with IDs, names, vocabulary, domain, and standard status. Use this when you need to find the OMOP concept ID for a medical term. Examples: 'type 2 diabetes', 'metformin 500mg', 'systolic blood pressure', 'HbA1c'.", {
|
|
6
|
+
query: z
|
|
7
|
+
.string()
|
|
8
|
+
.trim()
|
|
9
|
+
.min(1)
|
|
10
|
+
.max(500)
|
|
11
|
+
.describe('The medical term or concept name to search for'),
|
|
12
|
+
vocabulary_ids: z
|
|
13
|
+
.string()
|
|
14
|
+
.max(200)
|
|
15
|
+
.optional()
|
|
16
|
+
.describe("Comma-separated vocabulary IDs to filter by. Examples: 'SNOMED', 'ICD10CM', 'RxNorm', 'LOINC'. Leave empty to search all vocabularies."),
|
|
17
|
+
domain_ids: z
|
|
18
|
+
.string()
|
|
19
|
+
.max(200)
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("Comma-separated domain IDs to filter by. Examples: 'Condition', 'Drug', 'Measurement', 'Procedure'. Leave empty for all domains."),
|
|
22
|
+
standard_concept: z
|
|
23
|
+
.enum(['S', 'C'])
|
|
24
|
+
.optional()
|
|
25
|
+
.describe("Filter by standard concept status: 'S' for Standard, 'C' for Classification. Omit to search all."),
|
|
26
|
+
page: z.number().min(1).default(1).describe('Page number (1-based, default 1)'),
|
|
27
|
+
page_size: z
|
|
28
|
+
.number()
|
|
29
|
+
.min(1)
|
|
30
|
+
.max(50)
|
|
31
|
+
.default(10)
|
|
32
|
+
.describe('Number of results to return (1-50, default 10)'),
|
|
33
|
+
}, async ({ query, vocabulary_ids, domain_ids, standard_concept, page, page_size }) => {
|
|
34
|
+
try {
|
|
35
|
+
const params = {
|
|
36
|
+
query,
|
|
37
|
+
page: page ?? 1,
|
|
38
|
+
page_size: page_size ?? 10,
|
|
39
|
+
};
|
|
40
|
+
// Map PRD param names to actual API params
|
|
41
|
+
if (vocabulary_ids)
|
|
42
|
+
params.vocabularies = vocabulary_ids;
|
|
43
|
+
if (domain_ids)
|
|
44
|
+
params.domain_ids = domain_ids;
|
|
45
|
+
if (standard_concept)
|
|
46
|
+
params.standard_concept = standard_concept;
|
|
47
|
+
const response = await client.request('/search/concepts', params, 'search_concepts');
|
|
48
|
+
const { text, json } = formatConceptList(response, query);
|
|
49
|
+
return {
|
|
50
|
+
content: [
|
|
51
|
+
{ type: 'text', text },
|
|
52
|
+
{ type: 'text', text: json },
|
|
53
|
+
],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
const { text, json } = formatErrorForMcp(error, 'search_concepts');
|
|
58
|
+
return {
|
|
59
|
+
content: [
|
|
60
|
+
{ type: 'text', text },
|
|
61
|
+
{ type: 'text', text: json },
|
|
62
|
+
],
|
|
63
|
+
isError: true,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/tools/search.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,UAAU,mBAAmB,CAAC,MAAiB,EAAE,MAAqB;IAC1E,MAAM,CAAC,IAAI,CACT,iBAAiB,EACjB,uVAAuV,EACvV;QACE,KAAK,EAAE,CAAC;aACL,MAAM,EAAE;aACR,IAAI,EAAE;aACN,GAAG,CAAC,CAAC,CAAC;aACN,GAAG,CAAC,GAAG,CAAC;aACR,QAAQ,CAAC,gDAAgD,CAAC;QAC7D,cAAc,EAAE,CAAC;aACd,MAAM,EAAE;aACR,GAAG,CAAC,GAAG,CAAC;aACR,QAAQ,EAAE;aACV,QAAQ,CACP,wIAAwI,CACzI;QACH,UAAU,EAAE,CAAC;aACV,MAAM,EAAE;aACR,GAAG,CAAC,GAAG,CAAC;aACR,QAAQ,EAAE;aACV,QAAQ,CACP,kIAAkI,CACnI;QACH,gBAAgB,EAAE,CAAC;aAChB,IAAI,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;aAChB,QAAQ,EAAE;aACV,QAAQ,CACP,kGAAkG,CACnG;QACH,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,kCAAkC,CAAC;QAC/E,SAAS,EAAE,CAAC;aACT,MAAM,EAAE;aACR,GAAG,CAAC,CAAC,CAAC;aACN,GAAG,CAAC,EAAE,CAAC;aACP,OAAO,CAAC,EAAE,CAAC;aACX,QAAQ,CAAC,gDAAgD,CAAC;KAC9D,EACD,KAAK,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,gBAAgB,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE;QACjF,IAAI,CAAC;YACH,MAAM,MAAM,GAA0D;gBACpE,KAAK;gBACL,IAAI,EAAE,IAAI,IAAI,CAAC;gBACf,SAAS,EAAE,SAAS,IAAI,EAAE;aAC3B,CAAC;YAEF,2CAA2C;YAC3C,IAAI,cAAc;gBAAE,MAAM,CAAC,YAAY,GAAG,cAAc,CAAC;YACzD,IAAI,UAAU;gBAAE,MAAM,CAAC,UAAU,GAAG,UAAU,CAAC;YAC/C,IAAI,gBAAgB;gBAAE,MAAM,CAAC,gBAAgB,GAAG,gBAAgB,CAAC;YAEjE,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,OAAO,CACnC,kBAAkB,EAClB,MAAM,EACN,iBAAiB,CAClB,CAAC;YAEF,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,iBAAiB,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;YAE1D,OAAO;gBACL,OAAO,EAAE;oBACP,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE;oBAC/B,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,EAAE;iBACtC;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,iBAAiB,CAAC,KAAK,EAAE,iBAAiB,CAAC,CAAC;YACnE,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"}
|
|
@@ -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 registerVocabularyTools(server: McpServer, client: OmopHubClient): void;
|
|
4
|
+
//# sourceMappingURL=vocabularies.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vocabularies.d.ts","sourceRoot":"","sources":["../../src/tools/vocabularies.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAMtD,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,GAAG,IAAI,CAkDtF"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { formatVocabularyList } from '../formatters/index.js';
|
|
3
|
+
import { vocabularyCache } from '../utils/cache.js';
|
|
4
|
+
import { formatErrorForMcp } from '../utils/errors.js';
|
|
5
|
+
export function registerVocabularyTools(server, client) {
|
|
6
|
+
server.tool('list_vocabularies', 'List all available medical vocabularies in the OMOP standardized vocabulary system with concept counts and metadata. Use this to understand what terminology systems are available (SNOMED CT, ICD-10-CM, RxNorm, LOINC, etc.) and their scope.', {
|
|
7
|
+
search: z
|
|
8
|
+
.string()
|
|
9
|
+
.max(200)
|
|
10
|
+
.optional()
|
|
11
|
+
.describe('Optional search term to filter vocabularies by name'),
|
|
12
|
+
}, async ({ search }) => {
|
|
13
|
+
try {
|
|
14
|
+
const cacheKey = 'vocabularies:all';
|
|
15
|
+
let response = vocabularyCache.get(cacheKey);
|
|
16
|
+
if (!response) {
|
|
17
|
+
const rawResponse = await client.request('/vocabularies', { include_stats: true, page_size: 100 }, 'list_vocabularies');
|
|
18
|
+
// API wraps array in { vocabularies: [...] } — unwrap for formatter
|
|
19
|
+
response = {
|
|
20
|
+
...rawResponse,
|
|
21
|
+
data: rawResponse.data.vocabularies,
|
|
22
|
+
};
|
|
23
|
+
vocabularyCache.set(cacheKey, response);
|
|
24
|
+
}
|
|
25
|
+
const { text, json } = formatVocabularyList(response, search);
|
|
26
|
+
return {
|
|
27
|
+
content: [
|
|
28
|
+
{ type: 'text', text },
|
|
29
|
+
{ type: 'text', text: json },
|
|
30
|
+
],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
const { text, json } = formatErrorForMcp(error, 'list_vocabularies');
|
|
35
|
+
return {
|
|
36
|
+
content: [
|
|
37
|
+
{ type: 'text', text },
|
|
38
|
+
{ type: 'text', text: json },
|
|
39
|
+
],
|
|
40
|
+
isError: true,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=vocabularies.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vocabularies.js","sourceRoot":"","sources":["../../src/tools/vocabularies.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,UAAU,uBAAuB,CAAC,MAAiB,EAAE,MAAqB;IAC9E,MAAM,CAAC,IAAI,CACT,mBAAmB,EACnB,iPAAiP,EACjP;QACE,MAAM,EAAE,CAAC;aACN,MAAM,EAAE;aACR,GAAG,CAAC,GAAG,CAAC;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,qDAAqD,CAAC;KACnE,EACD,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE;QACnB,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,kBAAkB,CAAC;YACpC,IAAI,QAAQ,GAAG,eAAe,CAAC,GAAG,CAAC,QAAQ,CAA0C,CAAC;YAEtF,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,OAAO,CACtC,eAAe,EACf,EAAE,aAAa,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,EACvC,mBAAmB,CACpB,CAAC;gBACF,oEAAoE;gBACpE,QAAQ,GAAG;oBACT,GAAG,WAAW;oBACd,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC,YAAY;iBACpC,CAAC;gBACF,eAAe,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YAC1C,CAAC;YAED,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,oBAAoB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YAE9D,OAAO;gBACL,OAAO,EAAE;oBACP,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE;oBAC/B,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,EAAE;iBACtC;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,iBAAiB,CAAC,KAAK,EAAE,mBAAmB,CAAC,CAAC;YACrE,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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/utils/auth.ts"],"names":[],"mappings":"AAAA,wBAAgB,aAAa,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAWrD"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function resolveApiKey(cliKey) {
|
|
2
|
+
const key = cliKey || process.env.OMOPHUB_API_KEY;
|
|
3
|
+
if (!key) {
|
|
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/dashboard/api-keys');
|
|
6
|
+
}
|
|
7
|
+
return key;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/utils/auth.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,aAAa,CAAC,MAAe;IAC3C,MAAM,GAAG,GAAG,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;IAElD,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACb,6FAA6F;YAC3F,kEAAkE,CACrE,CAAC;IACJ,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare class LRUCache<T> {
|
|
2
|
+
private cache;
|
|
3
|
+
private readonly maxSize;
|
|
4
|
+
private readonly ttlMs;
|
|
5
|
+
constructor(maxSize: number, ttlMinutes: number);
|
|
6
|
+
get(key: string): T | undefined;
|
|
7
|
+
set(key: string, value: T): void;
|
|
8
|
+
has(key: string): boolean;
|
|
9
|
+
clear(): void;
|
|
10
|
+
get size(): number;
|
|
11
|
+
}
|
|
12
|
+
export declare const conceptCache: LRUCache<unknown>;
|
|
13
|
+
export declare const hierarchyCache: LRUCache<unknown>;
|
|
14
|
+
export declare const vocabularyCache: LRUCache<unknown>;
|
|
15
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/utils/cache.ts"],"names":[],"mappings":"AAKA,qBAAa,QAAQ,CAAC,CAAC;IACrB,OAAO,CAAC,KAAK,CAAoC;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;gBAEnB,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM;IAK/C,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAe/B,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;IAmBhC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAIzB,KAAK,IAAI,IAAI;IAIb,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF;AAGD,eAAO,MAAM,YAAY,mBAAiC,CAAC;AAC3D,eAAO,MAAM,cAAc,mBAAiC,CAAC;AAC7D,eAAO,MAAM,eAAe,mBAAgC,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export class LRUCache {
|
|
2
|
+
cache = new Map();
|
|
3
|
+
maxSize;
|
|
4
|
+
ttlMs;
|
|
5
|
+
constructor(maxSize, ttlMinutes) {
|
|
6
|
+
this.maxSize = maxSize;
|
|
7
|
+
this.ttlMs = ttlMinutes * 60 * 1000;
|
|
8
|
+
}
|
|
9
|
+
get(key) {
|
|
10
|
+
const entry = this.cache.get(key);
|
|
11
|
+
if (!entry)
|
|
12
|
+
return undefined;
|
|
13
|
+
if (Date.now() > entry.expiresAt) {
|
|
14
|
+
this.cache.delete(key);
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
// Move to end (most recently used)
|
|
18
|
+
this.cache.delete(key);
|
|
19
|
+
this.cache.set(key, entry);
|
|
20
|
+
return entry.value;
|
|
21
|
+
}
|
|
22
|
+
set(key, value) {
|
|
23
|
+
// Delete first so re-insertion goes to end
|
|
24
|
+
this.cache.delete(key);
|
|
25
|
+
// Evict oldest if at capacity
|
|
26
|
+
if (this.cache.size >= this.maxSize) {
|
|
27
|
+
const oldest = this.cache.keys().next().value;
|
|
28
|
+
if (oldest !== undefined) {
|
|
29
|
+
this.cache.delete(oldest);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
this.cache.set(key, {
|
|
33
|
+
value,
|
|
34
|
+
expiresAt: Date.now() + this.ttlMs,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
// Note: delegates to get(), which promotes the entry in LRU order
|
|
38
|
+
has(key) {
|
|
39
|
+
return this.get(key) !== undefined;
|
|
40
|
+
}
|
|
41
|
+
clear() {
|
|
42
|
+
this.cache.clear();
|
|
43
|
+
}
|
|
44
|
+
get size() {
|
|
45
|
+
return this.cache.size;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Pre-configured caches per the PRD
|
|
49
|
+
export const conceptCache = new LRUCache(500, 60);
|
|
50
|
+
export const hierarchyCache = new LRUCache(200, 30);
|
|
51
|
+
export const vocabularyCache = new LRUCache(10, 60);
|
|
52
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/utils/cache.ts"],"names":[],"mappings":"AAKA,MAAM,OAAO,QAAQ;IACX,KAAK,GAAG,IAAI,GAAG,EAAyB,CAAC;IAChC,OAAO,CAAS;IAChB,KAAK,CAAS;IAE/B,YAAY,OAAe,EAAE,UAAkB;QAC7C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,KAAK,GAAG,UAAU,GAAG,EAAE,GAAG,IAAI,CAAC;IACtC,CAAC;IAED,GAAG,CAAC,GAAW;QACb,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAC;QAE7B,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;YACjC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACvB,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,mCAAmC;QACnC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACvB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC3B,OAAO,KAAK,CAAC,KAAK,CAAC;IACrB,CAAC;IAED,GAAG,CAAC,GAAW,EAAE,KAAQ;QACvB,2CAA2C;QAC3C,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAEvB,8BAA8B;QAC9B,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACpC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;YAC9C,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC;QAED,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE;YAClB,KAAK;YACL,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK;SACnC,CAAC,CAAC;IACL,CAAC;IAED,kEAAkE;IAClE,GAAG,CAAC,GAAW;QACb,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC;IACrC,CAAC;IAED,KAAK;QACH,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;IACzB,CAAC;CACF;AAED,oCAAoC;AACpC,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,QAAQ,CAAU,GAAG,EAAE,EAAE,CAAC,CAAC;AAC3D,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,QAAQ,CAAU,GAAG,EAAE,EAAE,CAAC,CAAC;AAC7D,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,QAAQ,CAAU,EAAE,EAAE,EAAE,CAAC,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare class OmopHubApiError extends Error {
|
|
2
|
+
readonly status: number;
|
|
3
|
+
readonly path: string;
|
|
4
|
+
constructor(status: number, message: string, path: string);
|
|
5
|
+
}
|
|
6
|
+
export declare function formatErrorForMcp(error: unknown, toolName: string): {
|
|
7
|
+
text: string;
|
|
8
|
+
json: string;
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,eAAgB,SAAQ,KAAK;aAEtB,MAAM,EAAE,MAAM;aAEd,IAAI,EAAE,MAAM;gBAFZ,MAAM,EAAE,MAAM,EAC9B,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,MAAM;CAK/B;AAKD,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,MAAM,GACf;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAsFhC"}
|