@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 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.
@@ -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
+ }