Skip to main content
Glama
index.ts9.71 kB
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import { HevyClient, AxiosHttpClient } from "hevy-ts"; import dotenv from "dotenv"; dotenv.config(); const apiKey = process.env.HEVY_API_KEY || ""; const httpClient = new AxiosHttpClient(apiKey); const hevy = new HevyClient(httpClient); // Create an MCP server const server = new McpServer({ name: "hevy-api-mcp", version: "1.0.0" }); // Hevy API Tools server.registerTool( "getWorkouts", { title: "Get Workouts", description: "List all workouts", inputSchema: { page: z.number().optional(), pageSize: z.number().optional() }, }, async ({ page = 1, pageSize = 10 }) => { const workouts = await hevy.getWorkouts(page, pageSize); return { content: [{ type: "text", text: JSON.stringify(workouts, null, 2) }] }; } ); server.registerTool( "getWorkoutById", { title: "Get Workout By ID", description: "Get a workout by its ID", inputSchema: { workoutId: z.string() }, }, async ({ workoutId }) => { const workout = await hevy.getWorkoutById(workoutId); return { content: [{ type: "text", text: JSON.stringify(workout, null, 2) }] }; } ); server.registerTool( "createWorkout", { title: "Create Workout", description: "Create a new workout", inputSchema: { title: z.string(), description: z.string(), start_time: z.string(), end_time: z.string(), is_private: z.boolean(), exercises: z.array(z.object({ exercise_template_id: z.string(), superset_id: z.number().nullable().optional(), notes: z.string(), sets: z.array(z.object({ type: z.string(), weight_kg: z.number().nullable().optional(), reps: z.number().nullable().optional(), distance_meters: z.number().nullable().optional(), duration_seconds: z.number().nullable().optional(), custom_metric: z.any().optional(), rpe: z.number().nullable().optional(), })), })), }, }, async (input) => { // Corrige superset_id: undefined -> null const fixedInput = { ...input, exercises: input.exercises.map(ex => ({ ...ex, superset_id: ex.superset_id === undefined ? null : ex.superset_id, sets: ex.sets.map(set => ({ ...set, weight_kg: set.weight_kg === undefined ? null : set.weight_kg, reps: set.reps === undefined ? null : set.reps, distance_meters: set.distance_meters === undefined ? null : set.distance_meters, duration_seconds: set.duration_seconds === undefined ? null : set.duration_seconds, rpe: set.rpe === undefined ? null : set.rpe, custom_metric: set.hasOwnProperty('custom_metric') ? set.custom_metric : null, })) })) }; const workout = await hevy.createWorkout(fixedInput); return { content: [{ type: "text", text: JSON.stringify(workout, null, 2) }] }; } ); server.registerTool( "updateWorkout", { title: "Update Workout", description: "Update an existing workout", inputSchema: { workoutId: z.string(), workout: z.object({ title: z.string(), description: z.string(), start_time: z.string(), end_time: z.string(), is_private: z.boolean(), exercises: z.array(z.object({ exercise_template_id: z.string(), superset_id: z.number().nullable().optional(), notes: z.string(), sets: z.array(z.object({ type: z.string(), weight_kg: z.number().nullable().optional(), reps: z.number().nullable().optional(), distance_meters: z.number().nullable().optional(), duration_seconds: z.number().nullable().optional(), custom_metric: z.any().optional(), rpe: z.number().nullable().optional(), })), })), }), }, }, async ({ workoutId, workout }) => { // Corrige superset_id: undefined -> null const fixedWorkout = { ...workout, exercises: workout.exercises.map(ex => ({ ...ex, superset_id: ex.superset_id === undefined ? null : ex.superset_id, sets: ex.sets.map(set => ({ ...set, weight_kg: set.weight_kg === undefined ? null : set.weight_kg, reps: set.reps === undefined ? null : set.reps, distance_meters: set.distance_meters === undefined ? null : set.distance_meters, duration_seconds: set.duration_seconds === undefined ? null : set.duration_seconds, rpe: set.rpe === undefined ? null : set.rpe, custom_metric: set.hasOwnProperty('custom_metric') ? set.custom_metric : null, })) })) }; const updated = await hevy.updateWorkout(workoutId, fixedWorkout); return { content: [{ type: "text", text: JSON.stringify(updated, null, 2) }] }; } ); server.registerTool( "getRoutineFolders", { title: "Get Routine Folders", description: "List all routine folders", inputSchema: { page: z.number().optional(), pageSize: z.number().optional() }, }, async ({ page = 1, pageSize = 10 }) => { const folders = await hevy.getRoutineFolders(page, pageSize); return { content: [{ type: "text", text: JSON.stringify(folders, null, 2) }] }; } ); server.registerTool( "getRoutineFolderById", { title: "Get Routine Folder By ID", description: "Get a routine folder by its ID", inputSchema: { folderId: z.number() }, }, async ({ folderId }) => { const folder = await hevy.getRoutineFolderById(folderId); return { content: [{ type: "text", text: JSON.stringify(folder, null, 2) }] }; } ); server.registerTool( "createRoutineFolder", { title: "Create Routine Folder", description: "Create a new routine folder", inputSchema: { title: z.string() }, }, async ({ title }) => { const folder = await hevy.createRoutineFolder({ title }); return { content: [{ type: "text", text: JSON.stringify(folder, null, 2) }] }; } ); server.registerTool( "getExerciseTemplates", { title: "Get Exercise Templates", description: "List all exercise templates", inputSchema: { page: z.number().optional(), pageSize: z.number().optional() }, }, async ({ page = 1, pageSize = 10 }) => { const templates = await hevy.getExerciseTemplates(page, pageSize); return { content: [{ type: "text", text: JSON.stringify(templates, null, 2) }] }; } ); server.registerTool( "getExerciseTemplateById", { title: "Get Exercise Template By ID", description: "Get an exercise template by its ID", inputSchema: { exerciseTemplateId: z.string() }, }, async ({ exerciseTemplateId }) => { const template = await hevy.getExerciseTemplateById(exerciseTemplateId); return { content: [{ type: "text", text: JSON.stringify(template, null, 2) }] }; } ); server.registerTool( "getWebhookSubscription", { title: "Get Webhook Subscription", description: "Get the current webhook subscription", inputSchema: {}, }, async () => { const webhook = await hevy.getWebhookSubscription(); return { content: [{ type: "text", text: JSON.stringify(webhook, null, 2) }] }; } ); server.registerTool( "createWebhookSubscription", { title: "Create Webhook Subscription", description: "Create a new webhook subscription", inputSchema: { authToken: z.string(), url: z.string() }, }, async ({ authToken, url }) => { const result = await hevy.createWebhookSubscription({ authToken, url }); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; } ); server.registerTool( "deleteWebhookSubscription", { title: "Delete Webhook Subscription", description: "Delete the current webhook subscription", inputSchema: {}, }, async () => { await hevy.deleteWebhookSubscription(); return { content: [{ type: "text", text: "Webhook subscription deleted" }] }; } ); // Add a dynamic greeting resource server.registerResource( "greeting", new ResourceTemplate("greeting://{name}", { list: undefined }), { title: "Greeting Resource", // Display name for UI description: "Dynamic greeting generator" }, async (uri, { name }) => ({ contents: [{ uri: uri.href, text: `Hello, ${name}!` }] }) ); // Start receiving messages on stdin and sending messages on stdout async function main() { const transport = new StdioServerTransport(); await server.connect(transport); } main();

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jcjiron/hevy-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server