@phi-longevity/mcp-server 0.6.1 → 0.6.3
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 +1 -1
- package/dist/index.js +45 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ set `PHI_MCP_KEY` to override it.
|
|
|
42
42
|
Point any MCP client that supports Streamable HTTP at:
|
|
43
43
|
|
|
44
44
|
```
|
|
45
|
-
https://
|
|
45
|
+
https://philongevity.com/mcp
|
|
46
46
|
```
|
|
47
47
|
|
|
48
48
|
Same three tools, same synthetic-only rule, nothing to install.
|
package/dist/index.js
CHANGED
|
@@ -52,6 +52,34 @@ function telemetry(ev) {
|
|
|
52
52
|
function textResult(text) {
|
|
53
53
|
return { content: [{ type: "text", text }] };
|
|
54
54
|
}
|
|
55
|
+
function errorResult(text) {
|
|
56
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Extract a human-actionable detail string from a non-OK engine/gateway response body.
|
|
60
|
+
* The synthetic-only engine returns JSON like {"error": "..."} (and sometimes "detail").
|
|
61
|
+
* Truncated defensively; never contains biomarker values (errors are validation/auth-shaped).
|
|
62
|
+
*/
|
|
63
|
+
async function readErrorDetail(res) {
|
|
64
|
+
try {
|
|
65
|
+
const raw = (await res.text()).slice(0, 500);
|
|
66
|
+
try {
|
|
67
|
+
const j = JSON.parse(raw);
|
|
68
|
+
const msg = [j.error, j.detail].filter((x) => typeof x === "string" && x).join(" — ");
|
|
69
|
+
if (msg)
|
|
70
|
+
return msg.slice(0, 300);
|
|
71
|
+
}
|
|
72
|
+
catch { /* not JSON — fall through */ }
|
|
73
|
+
return raw.slice(0, 300);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return "";
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const EMPTY_PANEL_HELP = "No biomarkers were provided — the `biomarkers` map was empty. Provide at least one biomarker as " +
|
|
80
|
+
'name -> numeric value, e.g. { "Hemoglobin A1c": 5.4, "LDL Cholesterol": 130 }. ' +
|
|
81
|
+
"Call the list_supported_biomarkers tool for the 51 supported names, units, and reference ranges. " +
|
|
82
|
+
SYNTHETIC_WARNING;
|
|
55
83
|
// ── Owner-conversion attribution (Layer 3.6, Steve directive 2026-07-17) ──────────────────────
|
|
56
84
|
// Appended to analysis results so the agent can route its OWNER to a full PRISM report.
|
|
57
85
|
// UTM params make the agent channel truthfully measurable in GA4 (D2 discipline).
|
|
@@ -73,11 +101,15 @@ async function callEngine(body) {
|
|
|
73
101
|
"x-phi-mcp-key": MCP_KEY,
|
|
74
102
|
};
|
|
75
103
|
const res = await fetch(GATEWAY_URL, { method: "POST", headers, body: JSON.stringify(body) });
|
|
76
|
-
if (!res.ok)
|
|
77
|
-
|
|
104
|
+
if (!res.ok) {
|
|
105
|
+
// Surface the engine/gateway error detail so calling agents can self-correct
|
|
106
|
+
// (2026-07-19 lesson: an opaque "Engine returned 400." cost our first external session).
|
|
107
|
+
const detail = await readErrorDetail(res);
|
|
108
|
+
throw new Error(`Gateway returned ${res.status}${detail ? `: ${detail}` : ""}.`);
|
|
109
|
+
}
|
|
78
110
|
return res.json();
|
|
79
111
|
}
|
|
80
|
-
const server = new McpServer({ name: "phi-longevity", version: "0.6.
|
|
112
|
+
const server = new McpServer({ name: "phi-longevity", version: "0.6.3" }, {
|
|
81
113
|
instructions: "Phi Longevity PRISM — longevity-optimized analysis of biomarker panels. " + SYNTHETIC_WARNING,
|
|
82
114
|
});
|
|
83
115
|
// ── Tool 1: analyze_biomarkers (the core) ─────────────────────────────────────────────────────
|
|
@@ -104,6 +136,13 @@ server.tool("analyze_biomarkers", "Analyze a SYNTHETIC biomarker panel with Phi
|
|
|
104
136
|
// The engine expects biomarkers as an array of {name, value}; the tool takes an
|
|
105
137
|
// agent-friendly {name: value} map → transform here.
|
|
106
138
|
const biomarkerArray = Object.entries(args.biomarkers || {}).map(([name, value]) => ({ name, value }));
|
|
139
|
+
// Empty-panel guard: an empty map passes schema validation but the engine will
|
|
140
|
+
// (correctly) reject it. Answer locally with actionable guidance instead of
|
|
141
|
+
// spending an engine round-trip on a guaranteed 400.
|
|
142
|
+
if (biomarkerArray.length === 0) {
|
|
143
|
+
telemetry({ tool: "analyze_biomarkers", error: "empty_panel_guard", elapsed_ms: Date.now() - t0 });
|
|
144
|
+
return errorResult(EMPTY_PANEL_HELP);
|
|
145
|
+
}
|
|
107
146
|
const out = await callEngine({
|
|
108
147
|
biomarkers: biomarkerArray,
|
|
109
148
|
conditionFocus: args.conditionFocus ?? "general_wellness",
|
|
@@ -124,7 +163,9 @@ server.tool("analyze_biomarkers", "Analyze a SYNTHETIC biomarker panel with Phi
|
|
|
124
163
|
}
|
|
125
164
|
catch (e) {
|
|
126
165
|
telemetry({ tool: "analyze_biomarkers", error: e.message, elapsed_ms: Date.now() - t0 });
|
|
127
|
-
return
|
|
166
|
+
return errorResult(`Could not analyze: ${e.message} ` +
|
|
167
|
+
"If the input was rejected, check biomarker names and numeric values via the " +
|
|
168
|
+
"list_supported_biomarkers tool and retry.");
|
|
128
169
|
}
|
|
129
170
|
});
|
|
130
171
|
// ── Tool 2: list_supported_biomarkers ─────────────────────────────────────────────────────────
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phi-longevity/mcp-server",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"description": "Model Context Protocol server for Phi Longevity \u2014 agents can call the PRISM longevity recommendation engine on SYNTHETIC biomarker panels. Synthetic/de-identified data only; no PHI; stateless.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|