@phi-longevity/mcp-server 0.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/README.md +61 -0
- package/dist/catalog.js +35 -0
- package/dist/index.js +140 -0
- package/package.json +27 -0
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# @phi-longevity/mcp-server
|
|
2
|
+
|
|
3
|
+
A Model Context Protocol (MCP) server that lets AI agents call **Phi Longevity's PRISM** longevity
|
|
4
|
+
recommendation engine on **synthetic** biomarker panels.
|
|
5
|
+
|
|
6
|
+
> ๐ก๏ธ **Synthetic / de-identified data only.** Do not submit protected health information (PHI). The
|
|
7
|
+
> server is stateless and stores nothing. It is a credibility / agent-discoverability surface โ not a
|
|
8
|
+
> PHI-bearing or clinical-care surface.
|
|
9
|
+
|
|
10
|
+
## Tools
|
|
11
|
+
| Tool | What it does |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `analyze_biomarkers` | Analyze a synthetic panel โ 3-tier, evidence-cited recommendations (THIS WEEK / CONSIDER / ASK YOUR CLINICIAN). |
|
|
14
|
+
| `list_supported_biomarkers` | The scored biomarkers + units + reference ranges, by clinical pillar. |
|
|
15
|
+
| `get_methodology` | How the Phi Score works (5 pillars + weights) + link to the full methodology. |
|
|
16
|
+
|
|
17
|
+
## Install / run
|
|
18
|
+
```bash
|
|
19
|
+
npm install
|
|
20
|
+
npm run build
|
|
21
|
+
PHI_MCP_KEY="phimcp_d29179fdd8b99b70a49ae26f434843cf" node dist/index.js
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Claude Desktop config (`claude_desktop_config.json`)
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"mcpServers": {
|
|
28
|
+
"phi-longevity": {
|
|
29
|
+
"command": "npx",
|
|
30
|
+
"args": ["-y", "@phi-longevity/mcp-server"],
|
|
31
|
+
"env": {
|
|
32
|
+
"PHI_MCP_KEY": "phimcp_d29179fdd8b99b70a49ae26f434843cf"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Environment
|
|
40
|
+
| Var | Required | Notes |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| `PHI_MCP_KEY` | yes | Your `@phi-longevity` access key (sent as `x-phi-mcp-key`). Published + revocable + rate-limited. |
|
|
43
|
+
| `PHI_ENGINE_URL` | no | Override the default Phi MCP gateway URL (rarely needed). |
|
|
44
|
+
|
|
45
|
+
## Status โ v0 scaffold (2026-06-10)
|
|
46
|
+
Built by cloud-cos. **Needs `npm install && npm run build` to validate against the live SDK** (the SDK
|
|
47
|
+
wasn't installed in the scaffold environment). Decisions locked: revocable key + rate-limit ยท npm stdio ยท
|
|
48
|
+
no affiliate URLs by default ยท pkg `@phi-longevity/mcp-server` ยท own repo `phi-mcp-server`.
|
|
49
|
+
|
|
50
|
+
### TODO before publish
|
|
51
|
+
- `npm install && npm run build` โ fix any SDK API drift (the `server.tool(...)` shape).
|
|
52
|
+
- Sync `src/catalog.ts` with the canonical `BIOMARKER_CATALOG` (45 markers).
|
|
53
|
+
- Gateway (`phi-mcp-gateway`) holds the engine secret server-side + validates `x-phi-mcp-key` + rate-limits. โ
|
|
54
|
+
- Local test with the synthetic peri fixtures (`dossier/v2.0/scenarios/`).
|
|
55
|
+
- `agents.philongevity.com` landing page (separate; install snippet + 3 tools + no-PHI terms).
|
|
56
|
+
- `npm publish --access public` under the `@phi-longevity` org.
|
|
57
|
+
|
|
58
|
+
## HIPAA / privacy
|
|
59
|
+
Synthetic-only at the protocol boundary; stateless; aggregate-only telemetry (counts/timing to stderr,
|
|
60
|
+
never values or recommendation text); zero access to Firestore / the HIPAA datastore / user accounts.
|
|
61
|
+
GDPR: v0 processes no personal data (synthetic only) โ minimal exposure.
|
package/dist/catalog.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export const BIOMARKER_CATALOG = [
|
|
2
|
+
// Metabolic
|
|
3
|
+
{ name: "Hemoglobin A1c", unit: "%", pillar: "Metabolic", refLow: 4.0, refHigh: 5.6 },
|
|
4
|
+
{ name: "Fasting Glucose", unit: "mg/dL", pillar: "Metabolic", refLow: 70, refHigh: 99 },
|
|
5
|
+
{ name: "Fasting Insulin", unit: "uIU/mL", pillar: "Metabolic", refLow: 2.6, refHigh: 24.9 },
|
|
6
|
+
{ name: "Triglycerides", unit: "mg/dL", pillar: "Metabolic", refLow: 0, refHigh: 150 },
|
|
7
|
+
// Cardiovascular
|
|
8
|
+
{ name: "ApoB", unit: "mg/dL", pillar: "Cardiovascular", refLow: 0, refHigh: 90 },
|
|
9
|
+
{ name: "Lp(a)", unit: "nmol/L", pillar: "Cardiovascular", refLow: 0, refHigh: 75 },
|
|
10
|
+
{ name: "LDL Cholesterol", unit: "mg/dL", pillar: "Cardiovascular", refLow: 0, refHigh: 100 },
|
|
11
|
+
{ name: "HDL Cholesterol", unit: "mg/dL", pillar: "Cardiovascular", refLow: 40, refHigh: 60 },
|
|
12
|
+
{ name: "Total Cholesterol", unit: "mg/dL", pillar: "Cardiovascular", refLow: 125, refHigh: 200 },
|
|
13
|
+
// Hormonal
|
|
14
|
+
{ name: "TSH", unit: "uIU/mL", pillar: "Hormonal", refLow: 0.4, refHigh: 4.5 },
|
|
15
|
+
{ name: "Free T4", unit: "ng/dL", pillar: "Hormonal", refLow: 0.8, refHigh: 1.8 },
|
|
16
|
+
{ name: "Free T3", unit: "pg/mL", pillar: "Hormonal", refLow: 2.3, refHigh: 4.2 },
|
|
17
|
+
{ name: "Testosterone", unit: "ng/dL", pillar: "Hormonal" },
|
|
18
|
+
{ name: "Estradiol", unit: "pg/mL", pillar: "Hormonal" },
|
|
19
|
+
{ name: "DHEA-S", unit: "ug/dL", pillar: "Hormonal" },
|
|
20
|
+
{ name: "Cortisol", unit: "ug/dL", pillar: "Hormonal", refLow: 5, refHigh: 23 },
|
|
21
|
+
// Inflammation & Immunity
|
|
22
|
+
{ name: "hs-CRP", unit: "mg/L", pillar: "Inflammation & Immunity", refLow: 0, refHigh: 3.0 },
|
|
23
|
+
{ name: "Homocysteine", unit: "umol/L", pillar: "Inflammation & Immunity", refLow: 0, refHigh: 15 },
|
|
24
|
+
{ name: "ESR", unit: "mm/hr", pillar: "Inflammation & Immunity", refLow: 0, refHigh: 20 },
|
|
25
|
+
{ name: "Omega-3 Index", unit: "%", pillar: "Inflammation & Immunity", refLow: 8, refHigh: 12 },
|
|
26
|
+
// Foundational Health
|
|
27
|
+
{ name: "eGFR", unit: "mL/min", pillar: "Foundational Health", refLow: 60, refHigh: 120 },
|
|
28
|
+
{ name: "Creatinine", unit: "mg/dL", pillar: "Foundational Health", refLow: 0.6, refHigh: 1.3 },
|
|
29
|
+
{ name: "ALT", unit: "U/L", pillar: "Foundational Health", refLow: 0, refHigh: 44 },
|
|
30
|
+
{ name: "AST", unit: "U/L", pillar: "Foundational Health", refLow: 0, refHigh: 40 },
|
|
31
|
+
{ name: "Vitamin D 25-OH", unit: "ng/mL", pillar: "Foundational Health", refLow: 30, refHigh: 100 },
|
|
32
|
+
{ name: "Vitamin B12", unit: "pg/mL", pillar: "Foundational Health", refLow: 200, refHigh: 900 },
|
|
33
|
+
{ name: "Ferritin", unit: "ng/mL", pillar: "Foundational Health", refLow: 30, refHigh: 400 },
|
|
34
|
+
{ name: "Hemoglobin", unit: "g/dL", pillar: "Foundational Health", refLow: 13.5, refHigh: 17.5 },
|
|
35
|
+
];
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @philongevity/mcp-server โ Phi Longevity PRISM engine over MCP.
|
|
4
|
+
*
|
|
5
|
+
* Lets AI agents call Phi's longevity recommendation engine on SYNTHETIC biomarker panels
|
|
6
|
+
* and get back the structured 3-tier output (THIS WEEK / CONSIDER / ASK YOUR CLINICIAN) with
|
|
7
|
+
* evidence tiers + citations. A credibility / agent-discoverability surface โ NOT a PHI surface.
|
|
8
|
+
*
|
|
9
|
+
* ๐ก๏ธ HIPAA posture (designed-in):
|
|
10
|
+
* - SYNTHETIC / de-identified data only at the protocol boundary (stated in every tool description).
|
|
11
|
+
* - STATELESS: proxies to the engine and returns; writes nothing, persists nothing.
|
|
12
|
+
* - Aggregate-only telemetry to stderr: counts/timing, NEVER biomarker values or recommendation text.
|
|
13
|
+
* - The x-migration-secret is injected server-side and NEVER exposed to the agent.
|
|
14
|
+
* - Zero access to Firestore / the HIPAA datastore / user accounts. One upstream URL only.
|
|
15
|
+
*
|
|
16
|
+
* Env:
|
|
17
|
+
* PHI_ENGINE_URL (required) โ runRecommendationsForBiomarkers Cloud Function URL
|
|
18
|
+
* PHI_MIGRATION_SECRET (required) โ x-migration-secret header value (server-side only)
|
|
19
|
+
* PHI_MCP_KEY (optional) โ published-but-revocable access key (sent to the engine for
|
|
20
|
+
* rate-limit/validation; enforcement lives server-side)
|
|
21
|
+
*/
|
|
22
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
23
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
24
|
+
import { z } from "zod";
|
|
25
|
+
import { BIOMARKER_CATALOG } from "./catalog.js";
|
|
26
|
+
const GATEWAY_URL = process.env.PHI_ENGINE_URL ?? "https://phi-mcp-gateway-bblwxa6cvq-uc.a.run.app";
|
|
27
|
+
const MCP_KEY = process.env.PHI_MCP_KEY ?? "";
|
|
28
|
+
const SYNTHETIC_WARNING = "For research/education with SYNTHETIC or de-identified data only. Do NOT submit protected health " +
|
|
29
|
+
"information (PHI). This endpoint is stateless and does not store inputs.";
|
|
30
|
+
// โโ Local courtesy rate-limit (defense in depth; real enforcement is server-side) โโโโโโโโโโโโโ
|
|
31
|
+
const RATE = { capacity: 20, refillPerSec: 0.5, tokens: 20, last: Date.now() };
|
|
32
|
+
function allow() {
|
|
33
|
+
const now = Date.now();
|
|
34
|
+
RATE.tokens = Math.min(RATE.capacity, RATE.tokens + ((now - RATE.last) / 1000) * RATE.refillPerSec);
|
|
35
|
+
RATE.last = now;
|
|
36
|
+
if (RATE.tokens < 1)
|
|
37
|
+
return false;
|
|
38
|
+
RATE.tokens -= 1;
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
// โโ Aggregate-only telemetry (stderr; NEVER values) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
42
|
+
function telemetry(ev) {
|
|
43
|
+
try {
|
|
44
|
+
process.stderr.write(JSON.stringify({ t: new Date().toISOString(), ...ev }) + "\n");
|
|
45
|
+
}
|
|
46
|
+
catch { /* noop */ }
|
|
47
|
+
}
|
|
48
|
+
function textResult(text) {
|
|
49
|
+
return { content: [{ type: "text", text }] };
|
|
50
|
+
}
|
|
51
|
+
async function callEngine(body) {
|
|
52
|
+
if (!MCP_KEY) {
|
|
53
|
+
throw new Error("Server not configured: set PHI_MCP_KEY (your @phi-longevity access key).");
|
|
54
|
+
}
|
|
55
|
+
const headers = {
|
|
56
|
+
"Content-Type": "application/json",
|
|
57
|
+
"x-phi-mcp-key": MCP_KEY,
|
|
58
|
+
};
|
|
59
|
+
const res = await fetch(GATEWAY_URL, { method: "POST", headers, body: JSON.stringify(body) });
|
|
60
|
+
if (!res.ok)
|
|
61
|
+
throw new Error(`Gateway returned ${res.status}.`);
|
|
62
|
+
return res.json();
|
|
63
|
+
}
|
|
64
|
+
const server = new McpServer({ name: "phi-longevity", version: "0.1.0" }, {
|
|
65
|
+
instructions: "Phi Longevity PRISM โ longevity-optimized analysis of biomarker panels. " + SYNTHETIC_WARNING,
|
|
66
|
+
});
|
|
67
|
+
// โโ Tool 1: analyze_biomarkers (the core) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
68
|
+
server.tool("analyze_biomarkers", "Analyze a SYNTHETIC biomarker panel with Phi Longevity's PRISM engine. Returns 3-tier, " +
|
|
69
|
+
"evidence-cited recommendations (THIS WEEK / CONSIDER / ASK YOUR CLINICIAN). " + SYNTHETIC_WARNING, {
|
|
70
|
+
biomarkers: z
|
|
71
|
+
.record(z.string(), z.number())
|
|
72
|
+
.describe("Map of biomarker name -> numeric value, e.g. { \"Hemoglobin A1c\": 5.4 }. SYNTHETIC ONLY."),
|
|
73
|
+
conditionFocus: z
|
|
74
|
+
.enum(["general_wellness", "type2_diabetes", "lupus", "cancer_survivorship"])
|
|
75
|
+
.optional()
|
|
76
|
+
.describe("Optional condition track. Default general_wellness."),
|
|
77
|
+
age: z.number().int().min(0).max(120).optional(),
|
|
78
|
+
biologicalSex: z.enum(["male", "female"]).optional(),
|
|
79
|
+
include_partner_options: z
|
|
80
|
+
.boolean()
|
|
81
|
+
.optional()
|
|
82
|
+
.describe("If true, include partner/product options. Default false (clean clinical output)."),
|
|
83
|
+
}, async (args) => {
|
|
84
|
+
const t0 = Date.now();
|
|
85
|
+
if (!allow())
|
|
86
|
+
return textResult("Rate limit reached. Please retry in a moment.");
|
|
87
|
+
try {
|
|
88
|
+
// The engine expects biomarkers as an array of {name, value}; the tool takes an
|
|
89
|
+
// agent-friendly {name: value} map โ transform here.
|
|
90
|
+
const biomarkerArray = Object.entries(args.biomarkers || {}).map(([name, value]) => ({ name, value }));
|
|
91
|
+
const out = await callEngine({
|
|
92
|
+
biomarkers: biomarkerArray,
|
|
93
|
+
conditionFocus: args.conditionFocus ?? "general_wellness",
|
|
94
|
+
age: args.age,
|
|
95
|
+
biologicalSex: args.biologicalSex,
|
|
96
|
+
});
|
|
97
|
+
const recs = args.include_partner_options
|
|
98
|
+
? out.recommendations_v2_with_partners ?? out.recommendations_v2
|
|
99
|
+
: out.recommendations_v2;
|
|
100
|
+
telemetry({
|
|
101
|
+
tool: "analyze_biomarkers",
|
|
102
|
+
n_biomarkers: Object.keys(args.biomarkers || {}).length,
|
|
103
|
+
conditionFocus: args.conditionFocus ?? "general_wellness",
|
|
104
|
+
issues_count: Array.isArray(out.issues) ? out.issues.length : 0,
|
|
105
|
+
elapsed_ms: Date.now() - t0,
|
|
106
|
+
});
|
|
107
|
+
return textResult(JSON.stringify({ recommendations: recs, meta: out.meta, issues: out.issues }, null, 2));
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
telemetry({ tool: "analyze_biomarkers", error: e.message, elapsed_ms: Date.now() - t0 });
|
|
111
|
+
return textResult(`Could not analyze: ${e.message}`);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
// โโ Tool 2: list_supported_biomarkers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
115
|
+
server.tool("list_supported_biomarkers", "List the biomarkers PRISM scores, with units and reference ranges, grouped by clinical pillar. " +
|
|
116
|
+
"Reference data only โ no patient data.", {}, async () => {
|
|
117
|
+
telemetry({ tool: "list_supported_biomarkers", count: BIOMARKER_CATALOG.length });
|
|
118
|
+
return textResult(JSON.stringify({ biomarkers: BIOMARKER_CATALOG }, null, 2));
|
|
119
|
+
});
|
|
120
|
+
// โโ Tool 3: get_methodology โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
121
|
+
server.tool("get_methodology", "Summarize how the Phi Score works (5 clinical pillars + weights) and link to the full methodology.", {}, async () => {
|
|
122
|
+
telemetry({ tool: "get_methodology" });
|
|
123
|
+
return textResult(JSON.stringify({
|
|
124
|
+
phi_score: "0โ100 weighted composite across five clinical pillars: Metabolic (30%), Cardiovascular (25%), " +
|
|
125
|
+
"Hormonal (20%), Inflammation & Immunity (15%), Foundational Health (10%). Compared against " +
|
|
126
|
+
"longevity-optimized reference ranges, not just standard lab normals.",
|
|
127
|
+
methodology_url: "https://philongevity.com/methodology",
|
|
128
|
+
note: "Decision support, not a diagnosis. Recommendations are evidence-tiered with citations. " + SYNTHETIC_WARNING,
|
|
129
|
+
source: "Phi Longevity (philongevity.com)",
|
|
130
|
+
}, null, 2));
|
|
131
|
+
});
|
|
132
|
+
async function main() {
|
|
133
|
+
const transport = new StdioServerTransport();
|
|
134
|
+
await server.connect(transport);
|
|
135
|
+
telemetry({ event: "phi-mcp-server started", tools: 3 });
|
|
136
|
+
}
|
|
137
|
+
main().catch((e) => {
|
|
138
|
+
telemetry({ event: "fatal", error: e.message });
|
|
139
|
+
process.exit(1);
|
|
140
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@phi-longevity/mcp-server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Model Context Protocol server for Phi Longevity โ agents can call the PRISM longevity recommendation engine on SYNTHETIC biomarker panels. Synthetic/de-identified data only; no PHI; stateless.",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"phi-mcp-server": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": ["dist", "README.md"],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"start": "node dist/index.js",
|
|
14
|
+
"dev": "tsc --watch",
|
|
15
|
+
"prepublishOnly": "npm run build"
|
|
16
|
+
},
|
|
17
|
+
"engines": { "node": ">=18" },
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
20
|
+
"zod": "^3.23.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"typescript": "^5.5.0",
|
|
24
|
+
"@types/node": "^20.0.0"
|
|
25
|
+
},
|
|
26
|
+
"keywords": ["mcp", "model-context-protocol", "longevity", "health", "biomarkers", "phi-longevity"]
|
|
27
|
+
}
|