justlabs-mcp-server 1.0.0 → 1.1.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/dist/__tests__/analysis.test.d.ts +5 -0
- package/dist/__tests__/analysis.test.js +203 -0
- package/dist/analysis/analyzePanelResults.d.ts +45 -0
- package/dist/analysis/analyzePanelResults.js +137 -0
- package/dist/analysis/generatePatientSummary.d.ts +25 -0
- package/dist/analysis/generatePatientSummary.js +94 -0
- package/dist/analysis/suggestNextSteps.d.ts +37 -0
- package/dist/analysis/suggestNextSteps.js +136 -0
- package/dist/index.js +164 -0
- package/dist/ranges/panels.d.ts +28 -0
- package/dist/ranges/panels.js +115 -0
- package/dist/ranges/questRanges.d.ts +53 -0
- package/dist/ranges/questRanges.js +335 -0
- package/package.json +2 -2
- package/src/__tests__/analysis.test.ts +235 -0
- package/src/analysis/analyzePanelResults.ts +205 -0
- package/src/analysis/generatePatientSummary.ts +131 -0
- package/src/analysis/suggestNextSteps.ts +183 -0
- package/src/index.ts +180 -0
- package/src/ranges/panels.ts +145 -0
- package/src/ranges/questRanges.ts +403 -0
package/src/index.ts
CHANGED
|
@@ -7,6 +7,11 @@ import {
|
|
|
7
7
|
ListToolsRequestSchema,
|
|
8
8
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
9
9
|
|
|
10
|
+
// Analysis modules for results interpretation
|
|
11
|
+
import { analyzePanelResults, AnalysisInput } from "./analysis/analyzePanelResults.js";
|
|
12
|
+
import { suggestNextSteps } from "./analysis/suggestNextSteps.js";
|
|
13
|
+
import { generatePatientSummary } from "./analysis/generatePatientSummary.js";
|
|
14
|
+
|
|
10
15
|
// Lab test data (subset of popular tests)
|
|
11
16
|
const labTests = [
|
|
12
17
|
{
|
|
@@ -291,6 +296,91 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
291
296
|
required: ["testIds"],
|
|
292
297
|
},
|
|
293
298
|
},
|
|
299
|
+
// Results analysis tools (educational, conservative)
|
|
300
|
+
{
|
|
301
|
+
name: "analyze_panel_results",
|
|
302
|
+
description: "Analyze lab panel results and provide educational context. IMPORTANT: This provides educational information only, not medical diagnosis. User must acknowledge disclaimer before viewing analysis.",
|
|
303
|
+
inputSchema: {
|
|
304
|
+
type: "object",
|
|
305
|
+
properties: {
|
|
306
|
+
panel_id: {
|
|
307
|
+
type: "string",
|
|
308
|
+
description: "Panel ID (e.g., 'fatigue_panel', 'thyroid_deep_dive', 'metabolic_panel', 'wellness_basic')",
|
|
309
|
+
},
|
|
310
|
+
results: {
|
|
311
|
+
type: "array",
|
|
312
|
+
items: {
|
|
313
|
+
type: "object",
|
|
314
|
+
properties: {
|
|
315
|
+
biomarker_key: { type: "string", description: "Biomarker key (e.g., 'tsh', 'ferritin', 'vitamin-d')" },
|
|
316
|
+
value: { type: "number", description: "The numeric value" },
|
|
317
|
+
unit: { type: "string", description: "The unit (e.g., 'mIU/L', 'ng/mL')" },
|
|
318
|
+
},
|
|
319
|
+
required: ["biomarker_key", "value", "unit"],
|
|
320
|
+
},
|
|
321
|
+
description: "Array of biomarker results",
|
|
322
|
+
},
|
|
323
|
+
sex_at_birth: {
|
|
324
|
+
type: "string",
|
|
325
|
+
enum: ["male", "female"],
|
|
326
|
+
description: "Sex at birth (affects reference ranges for some markers)",
|
|
327
|
+
},
|
|
328
|
+
age: {
|
|
329
|
+
type: "number",
|
|
330
|
+
description: "Age in years (optional, may affect interpretation)",
|
|
331
|
+
},
|
|
332
|
+
fasting: {
|
|
333
|
+
type: "boolean",
|
|
334
|
+
description: "Whether the sample was taken after fasting",
|
|
335
|
+
},
|
|
336
|
+
on_thyroid_meds: {
|
|
337
|
+
type: "boolean",
|
|
338
|
+
description: "Whether currently taking thyroid medication",
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
required: ["panel_id", "results"],
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
name: "suggest_next_steps",
|
|
346
|
+
description: "Based on panel analysis, suggest whether additional testing may provide more detail. Returns at most ONE follow-up panel suggestion. Educational only, not medical advice.",
|
|
347
|
+
inputSchema: {
|
|
348
|
+
type: "object",
|
|
349
|
+
properties: {
|
|
350
|
+
panel_id: {
|
|
351
|
+
type: "string",
|
|
352
|
+
description: "The panel ID that was analyzed",
|
|
353
|
+
},
|
|
354
|
+
analysis: {
|
|
355
|
+
type: "object",
|
|
356
|
+
description: "The analysis object from analyze_panel_results",
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
required: ["panel_id", "analysis"],
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
name: "generate_patient_summary",
|
|
364
|
+
description: "Generate a patient-friendly summary of results with educational context. Includes required disclaimer. MUST be shown to user with disclaimer acknowledgment.",
|
|
365
|
+
inputSchema: {
|
|
366
|
+
type: "object",
|
|
367
|
+
properties: {
|
|
368
|
+
panel_id: {
|
|
369
|
+
type: "string",
|
|
370
|
+
description: "The panel ID",
|
|
371
|
+
},
|
|
372
|
+
analysis: {
|
|
373
|
+
type: "object",
|
|
374
|
+
description: "The analysis object from analyze_panel_results",
|
|
375
|
+
},
|
|
376
|
+
next_steps: {
|
|
377
|
+
type: "object",
|
|
378
|
+
description: "The next steps object from suggest_next_steps",
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
required: ["panel_id", "analysis", "next_steps"],
|
|
382
|
+
},
|
|
383
|
+
},
|
|
294
384
|
],
|
|
295
385
|
};
|
|
296
386
|
});
|
|
@@ -472,6 +562,96 @@ No doctor visit or insurance needed.`,
|
|
|
472
562
|
};
|
|
473
563
|
}
|
|
474
564
|
|
|
565
|
+
// Results analysis tools
|
|
566
|
+
case "analyze_panel_results": {
|
|
567
|
+
const input: AnalysisInput = {
|
|
568
|
+
panel_id: args?.panel_id as string,
|
|
569
|
+
results: args?.results as Array<{ biomarker_key: string; value: number; unit: string }>,
|
|
570
|
+
sex_at_birth: args?.sex_at_birth as "male" | "female" | undefined,
|
|
571
|
+
age: args?.age as number | undefined,
|
|
572
|
+
fasting: args?.fasting as boolean | undefined,
|
|
573
|
+
on_thyroid_meds: args?.on_thyroid_meds as boolean | undefined,
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
const analysis = analyzePanelResults(input);
|
|
577
|
+
|
|
578
|
+
// If red flags detected, return safety message immediately
|
|
579
|
+
if (analysis.red_flags_detected) {
|
|
580
|
+
return {
|
|
581
|
+
content: [
|
|
582
|
+
{
|
|
583
|
+
type: "text",
|
|
584
|
+
text: `# IMPORTANT: Please Contact a Healthcare Provider
|
|
585
|
+
|
|
586
|
+
${analysis.red_flag_message}
|
|
587
|
+
|
|
588
|
+
This analysis cannot continue until these values have been reviewed by a healthcare professional.
|
|
589
|
+
|
|
590
|
+
---
|
|
591
|
+
**Disclaimer:** This is educational information only, not medical advice.`,
|
|
592
|
+
},
|
|
593
|
+
],
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
return {
|
|
598
|
+
content: [
|
|
599
|
+
{
|
|
600
|
+
type: "text",
|
|
601
|
+
text: JSON.stringify(analysis, null, 2),
|
|
602
|
+
},
|
|
603
|
+
],
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
case "suggest_next_steps": {
|
|
608
|
+
const panelId = args?.panel_id as string;
|
|
609
|
+
const analysis = args?.analysis as ReturnType<typeof analyzePanelResults>;
|
|
610
|
+
|
|
611
|
+
const nextSteps = suggestNextSteps(panelId, analysis);
|
|
612
|
+
|
|
613
|
+
return {
|
|
614
|
+
content: [
|
|
615
|
+
{
|
|
616
|
+
type: "text",
|
|
617
|
+
text: JSON.stringify(nextSteps, null, 2),
|
|
618
|
+
},
|
|
619
|
+
],
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
case "generate_patient_summary": {
|
|
624
|
+
const panelId = args?.panel_id as string;
|
|
625
|
+
const analysis = args?.analysis as ReturnType<typeof analyzePanelResults>;
|
|
626
|
+
const nextSteps = args?.next_steps as ReturnType<typeof suggestNextSteps>;
|
|
627
|
+
|
|
628
|
+
const summary = generatePatientSummary(panelId, analysis, nextSteps);
|
|
629
|
+
|
|
630
|
+
// Format as readable text for the user
|
|
631
|
+
let output = `# ${summary.patient_summary.headline}\n\n`;
|
|
632
|
+
|
|
633
|
+
if (summary.safety_warning) {
|
|
634
|
+
output += `**IMPORTANT:** ${summary.safety_warning}\n\n`;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
output += summary.patient_summary.bullets.map((b) => `- ${b}`).join("\n");
|
|
638
|
+
|
|
639
|
+
if (summary.patient_summary.cta) {
|
|
640
|
+
output += `\n\n**Next step for more detail:** [${summary.patient_summary.cta.label}](${summary.patient_summary.cta.url})`;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
output += `\n\n---\n**Disclaimer:** ${summary.patient_summary.disclaimer}`;
|
|
644
|
+
|
|
645
|
+
return {
|
|
646
|
+
content: [
|
|
647
|
+
{
|
|
648
|
+
type: "text",
|
|
649
|
+
text: output,
|
|
650
|
+
},
|
|
651
|
+
],
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
|
|
475
655
|
default:
|
|
476
656
|
return {
|
|
477
657
|
content: [
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Panel definitions with required markers and follow-up logic
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface PanelDefinition {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
requiredMarkers: string[];
|
|
10
|
+
optionalMarkers?: string[];
|
|
11
|
+
price: number;
|
|
12
|
+
url: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface FollowUpRule {
|
|
16
|
+
panelId: string;
|
|
17
|
+
conditions: {
|
|
18
|
+
// If any of these markers are outside range, suggest follow-up
|
|
19
|
+
abnormalMarkers?: string[];
|
|
20
|
+
// If all conditions met, suggest this follow-up
|
|
21
|
+
followUpPanelId: string;
|
|
22
|
+
reason: string;
|
|
23
|
+
}[];
|
|
24
|
+
// Default if no conditions match
|
|
25
|
+
defaultFollowUp?: {
|
|
26
|
+
type: "no_additional_testing" | "clinician_discussion";
|
|
27
|
+
reason: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const PANELS: PanelDefinition[] = [
|
|
32
|
+
{
|
|
33
|
+
id: "fatigue_panel",
|
|
34
|
+
name: "Fatigue Panel",
|
|
35
|
+
description: "Comprehensive testing for common causes of fatigue including thyroid, iron, B12, and vitamin D.",
|
|
36
|
+
requiredMarkers: ["tsh", "ferritin", "vitamin-b12", "vitamin-d", "wbc", "hemoglobin"],
|
|
37
|
+
optionalMarkers: ["free-t4", "folate"],
|
|
38
|
+
price: 55,
|
|
39
|
+
url: "https://justlabs.health/tests/panel/panel-fatigue",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: "thyroid_deep_dive",
|
|
43
|
+
name: "Complete Thyroid Panel",
|
|
44
|
+
description: "Comprehensive thyroid evaluation including TSH, Free T4, Free T3, and thyroid antibodies.",
|
|
45
|
+
requiredMarkers: ["tsh", "free-t4", "free-t3", "tpo-antibodies", "thyroglobulin-antibodies"],
|
|
46
|
+
price: 65,
|
|
47
|
+
url: "https://justlabs.health/tests/panel/panel-thyroid-complete",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: "metabolic_panel",
|
|
51
|
+
name: "Metabolic Health Panel",
|
|
52
|
+
description: "Assess blood sugar regulation and cardiovascular markers.",
|
|
53
|
+
requiredMarkers: ["hemoglobin-a1c", "glucose", "insulin", "total-cholesterol", "hdl-cholesterol", "ldl-cholesterol", "triglycerides"],
|
|
54
|
+
optionalMarkers: ["hs-crp"],
|
|
55
|
+
price: 75,
|
|
56
|
+
url: "https://justlabs.health/tests/panel/panel-metabolic",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: "wellness_basic",
|
|
60
|
+
name: "Basic Wellness Panel",
|
|
61
|
+
description: "Essential health screening including blood counts, metabolic markers, lipids, and thyroid.",
|
|
62
|
+
requiredMarkers: ["wbc", "hemoglobin", "glucose", "creatinine", "alt", "total-cholesterol", "tsh"],
|
|
63
|
+
price: 35,
|
|
64
|
+
url: "https://justlabs.health/tests/panel/panel-wellness-basic",
|
|
65
|
+
},
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
export const FOLLOW_UP_RULES: FollowUpRule[] = [
|
|
69
|
+
{
|
|
70
|
+
panelId: "fatigue_panel",
|
|
71
|
+
conditions: [
|
|
72
|
+
{
|
|
73
|
+
// If thyroid markers abnormal, suggest deep dive
|
|
74
|
+
abnormalMarkers: ["tsh"],
|
|
75
|
+
followUpPanelId: "thyroid_deep_dive",
|
|
76
|
+
reason: "Your TSH result suggests thyroid function may benefit from a closer look. A complete thyroid panel provides more detail about how your thyroid is functioning.",
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
// If metabolic markers present and abnormal, suggest metabolic panel
|
|
80
|
+
abnormalMarkers: ["glucose"],
|
|
81
|
+
followUpPanelId: "metabolic_panel",
|
|
82
|
+
reason: "Your glucose result suggests metabolic health may benefit from a closer look. A metabolic panel provides more detail about blood sugar regulation and cardiovascular markers.",
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
defaultFollowUp: {
|
|
86
|
+
type: "clinician_discussion",
|
|
87
|
+
reason: "Your results are within expected ranges. If fatigue persists, consider discussing with a healthcare provider who can review your complete health picture.",
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
panelId: "thyroid_deep_dive",
|
|
92
|
+
conditions: [
|
|
93
|
+
{
|
|
94
|
+
// If antibodies elevated, no further JustLabs testing needed
|
|
95
|
+
abnormalMarkers: ["tpo-antibodies", "thyroglobulin-antibodies"],
|
|
96
|
+
followUpPanelId: "", // Empty means clinician discussion
|
|
97
|
+
reason: "Your antibody results provide useful information. We recommend discussing these findings with a healthcare provider who can provide personalized guidance.",
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
defaultFollowUp: {
|
|
101
|
+
type: "clinician_discussion",
|
|
102
|
+
reason: "Thyroid results are best interpreted by a healthcare provider who can consider your symptoms and health history.",
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
panelId: "metabolic_panel",
|
|
107
|
+
conditions: [],
|
|
108
|
+
defaultFollowUp: {
|
|
109
|
+
type: "clinician_discussion",
|
|
110
|
+
reason: "Metabolic results are best interpreted by a healthcare provider who can provide personalized guidance on lifestyle and any next steps.",
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
panelId: "wellness_basic",
|
|
115
|
+
conditions: [
|
|
116
|
+
{
|
|
117
|
+
abnormalMarkers: ["tsh"],
|
|
118
|
+
followUpPanelId: "thyroid_deep_dive",
|
|
119
|
+
reason: "Your TSH result suggests thyroid function may benefit from a closer look.",
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
abnormalMarkers: ["glucose"],
|
|
123
|
+
followUpPanelId: "metabolic_panel",
|
|
124
|
+
reason: "Your glucose result suggests metabolic health may benefit from a closer look.",
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
abnormalMarkers: ["hemoglobin", "wbc"],
|
|
128
|
+
followUpPanelId: "fatigue_panel",
|
|
129
|
+
reason: "Your blood count results suggest looking at additional markers that can affect energy levels.",
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
defaultFollowUp: {
|
|
133
|
+
type: "no_additional_testing",
|
|
134
|
+
reason: "Your basic wellness markers are within expected ranges. Consider retesting annually or if new symptoms develop.",
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
];
|
|
138
|
+
|
|
139
|
+
export function getPanel(panelId: string): PanelDefinition | undefined {
|
|
140
|
+
return PANELS.find((p) => p.id === panelId);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function getFollowUpRules(panelId: string): FollowUpRule | undefined {
|
|
144
|
+
return FOLLOW_UP_RULES.find((r) => r.panelId === panelId);
|
|
145
|
+
}
|