mcp-phenoage-clock
Version:
MCP server for calculating biological age using the Morgan Levine PhenoAge clock
157 lines • 7.1 kB
JavaScript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { ListToolsRequestSchema, CallToolRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
import { calculatePhenoAge, getReferenceRanges } from "./phenoage.js";
// Define the biomarkers schema for validation
const BiomarkersSchema = z.object({
age: z.number().min(0).max(120).describe("Chronological age in years"),
albumin: z.number().min(1).max(6).describe("Albumin in g/dL"),
creatinine: z.number().min(0.1).max(15).describe("Creatinine in mg/dL"),
glucose: z.number().min(30).max(500).describe("Glucose in mg/dL (fasting)"),
crp: z.number().min(0.01).max(100).describe("C-reactive protein (CRP) in mg/L"),
lymphocytePercent: z.number().min(0).max(100).describe("Lymphocyte percentage"),
meanCellVolume: z.number().min(50).max(150).describe("Mean cell volume (MCV) in fL"),
redCellDistWidth: z.number().min(5).max(30).describe("Red cell distribution width (RDW) in %"),
alkalinePhosphatase: z.number().min(10).max(500).describe("Alkaline phosphatase in U/L"),
whiteBloodCellCount: z.number().min(1).max(50).describe("White blood cell count in 1000 cells/μL")
});
// Define tools
const TOOLS = [
{
name: "calculate_phenoage",
description: "Calculate biological age using the Morgan Levine PhenoAge clock based on blood biomarkers",
inputSchema: {
type: "object",
properties: {
biomarkers: {
type: "object",
properties: {
age: { type: "number", description: "Chronological age in years" },
albumin: { type: "number", description: "Albumin in g/dL" },
creatinine: { type: "number", description: "Creatinine in mg/dL" },
glucose: { type: "number", description: "Glucose in mg/dL (fasting)" },
crp: { type: "number", description: "C-reactive protein (CRP) in mg/L" },
lymphocytePercent: { type: "number", description: "Lymphocyte percentage" },
meanCellVolume: { type: "number", description: "Mean cell volume (MCV) in fL" },
redCellDistWidth: { type: "number", description: "Red cell distribution width (RDW) in %" },
alkalinePhosphatase: { type: "number", description: "Alkaline phosphatase in U/L" },
whiteBloodCellCount: { type: "number", description: "White blood cell count in 1000 cells/μL" }
},
required: ["age", "albumin", "creatinine", "glucose", "crp", "lymphocytePercent",
"meanCellVolume", "redCellDistWidth", "alkalinePhosphatase", "whiteBloodCellCount"],
description: "Blood biomarker values for PhenoAge calculation"
}
},
required: ["biomarkers"]
}
},
{
name: "get_biomarker_ranges",
description: "Get reference ranges and optimal values for PhenoAge biomarkers",
inputSchema: {
type: "object",
properties: {}
}
}
];
// Create the server
const server = new Server({
name: "mcp-phenoage-clock",
version: "1.0.0",
description: "MCP server for calculating biological age using the Morgan Levine PhenoAge clock"
}, {
capabilities: {
tools: {}
}
});
// Handle tool listing
server.setRequestHandler(ListToolsRequestSchema, async () => {
return { tools: TOOLS };
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
if (name === "calculate_phenoage") {
// Validate input
if (!args || !args.biomarkers) {
throw new Error("Missing biomarkers parameter");
}
const validated = BiomarkersSchema.parse(args.biomarkers);
// Calculate PhenoAge
const result = calculatePhenoAge(validated);
return {
content: [
{
type: "text",
text: JSON.stringify({
success: true,
result: {
phenoAge: result.phenoAge,
chronologicalAge: validated.age,
ageDifference: result.ageDifference,
mortalityScore: result.mortalityScore,
interpretation: result.interpretation,
summary: `Your PhenoAge is ${result.phenoAge} years (${result.ageDifference > 0 ? '+' : ''}${result.ageDifference} years ${result.ageDifference > 0 ? 'older' : 'younger'} than your chronological age)`,
...(result.wasClamped && {
warning: "Mortality score was at the mathematical limit and was adjusted to allow calculation"
})
}
}, null, 2)
}
]
};
}
else if (name === "get_biomarker_ranges") {
const ranges = getReferenceRanges();
return {
content: [
{
type: "text",
text: JSON.stringify({
success: true,
ranges,
notes: {
units: "Make sure to use the correct units for each biomarker",
fasting: "Glucose should be measured after fasting",
crp: "CRP is a marker of inflammation - lower is generally better"
}
}, null, 2)
}
]
};
}
else {
throw new Error(`Unknown tool: ${name}`);
}
}
catch (error) {
return {
content: [
{
type: "text",
text: JSON.stringify({
success: false,
error: error instanceof Error ? error.message : "Unknown error occurred"
}, null, 2)
}
]
};
}
});
// Main function
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
// Log startup to stderr
console.error("PhenoAge Clock MCP server running");
console.error("Based on: Levine et al. (2018) 'An epigenetic biomarker of aging for lifespan and healthspan'");
}
// Run the server
main().catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});
//# sourceMappingURL=index.js.map