Skip to main content
Glama

fitness-mcp

CI

A personal remote MCP (Model Context Protocol) server that lets Claude read and manage your Hevy workout data directly in conversation. Deployed on Vercel's free Hobby tier.

License

MIT

Related MCP server: garmin-connect-mcp-server

Status

  • Hevy: implemented — read: recent workouts, workout detail, body measurements, exercise template search, routine folder listing, routine listing, routine detail. Write: create/update routines (workout plan templates) and routine folders, so a training menu designed in conversation can be pushed directly into the Hevy app. Uses the official Hevy REST API directly.

Tools exposed

Tool

Type

Description

get_recent_workouts

read

List recent Hevy workouts (title, time, exercises)

get_workout_detail

read

Full sets/reps/weight detail for one workout

get_body_measurements

read

Recent Hevy body measurement entries (weight, body fat %)

search_exercise_templates

read

Search Hevy's exercise library by name to resolve the exercise_template_id needed by create_routine/update_routine

list_routines

read

List existing Hevy routines (id, title, folder, exercise count, updated time), optionally filtered by folder, to find a routineId

get_routine_detail

read

Full exercise/set/rep(-range)/weight/rest-time detail for one routine (template) — read the current contents before update_routine overwrites them

create_routine

write

Create a new Hevy routine (workout plan template)

update_routine

write

Replace an existing Hevy routine's title/notes/exercises entirely (folder assignment cannot be changed via update — see below)

list_routine_folders

read

List existing routine folders (id, title, index) to resolve a folderId by name

create_routine_folder

write

Create a folder to organize routines

The write tools make real changes to the user's Hevy account (creating/replacing routines and folders). Their tool descriptions instruct the calling LLM to show the user the full planned content and get explicit confirmation before calling, and they require a confirm: true argument as a structural nudge in the same direction — but since that argument is set by the same LLM deciding whether to call the tool at all, it is not a guarantee of human confirmation, only a deliberate extra step. There is no scope separation between read and write tools at the authentication layer (see Authentication below) — any authenticated caller can invoke any tool.

Authentication

Claude's "Request headers" option for custom connectors (a static Authorization: Bearer <token> header) is still in beta and not available on every account. So this server also implements a minimal OAuth 2.1 authorization server (Authorization Code + PKCE) at /api/oauth/authorize and /api/oauth/token, purely so Claude's standard OAuth Client ID/Secret fields work as an always-available fallback.

There's no login screen and no client database — /authorize auto-approves. That's safe because the real credential check happens at /token: a code can only be exchanged for an access token by presenting the correct OAUTH_CLIENT_SECRET, which never appears in a browser-visible URL (only client_id does, at /authorize). The access token it returns is just MCP_BEARER_TOKEN itself, so the resource-server check (lib/auth.ts) doesn't change depending on which path a client used to get it. See lib/oauth.ts for the full reasoning, including the accepted tradeoffs (no server-side session/code storage — codes are self-contained, HMAC-signed, and expire in 60s).

Generating the three secrets from one memorable passphrase

MCP_BEARER_TOKEN, OAUTH_CLIENT_ID, and OAUTH_CLIENT_SECRET can all be derived deterministically from a single master passphrase, so losing the stored values isn't a disaster — just re-derive them. Paste this into a fresh terminal (it isn't installed anywhere permanent on purpose — see below) and it'll prompt for the passphrase once per session instead of making you type it into every command:

derive() {
  if [ -z "$MASTER_PASSPHRASE" ]; then
    printf "Master passphrase: "
    read -rs MASTER_PASSPHRASE
    echo
  fi
  echo -n "$1" | openssl dgst -sha256 -hmac "$MASTER_PASSPHRASE" -hex | awk '{print $2}'
}

derive "fitness-mcp:bearer-token"        # → MCP_BEARER_TOKEN
derive "fitness-mcp:oauth-client-id"     # → OAUTH_CLIENT_ID
derive "fitness-mcp:oauth-client-secret" # → OAUTH_CLIENT_SECRET

The label strings aren't secret (they're safe to keep in this README) — only the passphrase is. Running derive again with the same passphrase always reproduces the same values.

This is deliberately left as a copy-paste snippet rather than something installed into ~/.bashrc: putting the function alone in a shell rc file is fine, but putting export MASTER_PASSPHRASE=... there too means the passphrase sits in plaintext on disk indefinitely — a tradeoff we're choosing not to make by default. If you don't mind that tradeoff on your own machine, adding both to ~/.bashrc works and skips the per-session prompt entirely.

Local development

npm install
cp .env.example .env.local   # fill in real values
vercel dev

Smoke test (replace $MCP_BEARER_TOKEN):

curl -X POST http://localhost:3000/api/mcp \
  -H "Authorization: Bearer $MCP_BEARER_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Should return the 10 tools above. A request with a missing/wrong token should get 401.

Testing

Three layers, all run in CI (.github/workflows/ci.yml) on every push/PR — none require real Hevy secrets, so they work the same in a public repo:

npm run test        # unit + integration (vitest) — pure logic, plus the real Next.js
                     # route handler exercised with fetch mocked
npm run build
npm run test:e2e     # starts a real `next start` server and hits it over real HTTP
                      # (node's built-in test runner, no extra dependency)
  • Unit (lib/*.test.ts): bearer-token verification, OAuth code signing/PKCE/redirect-URI allowlisting (including the RFC 7636 PKCE test vector), Hevy routine request-body construction and validation (@ rejection in notes, set-type enum, read-only field stripping, exercise-template search caching/pagination, routine-folder listing/pagination-walk, routine listing/folder-filtering/pagination-walk, routine detail read-shape unwrapping).

  • Integration (test/integration/*.test.ts): the real app/api/mcp/route.ts handler wired to real lib/auth.ts/lib/hevy.ts with only fetch mocked, including a full search_exercise_templatescreate_routine_foldercreate_routine × 3 walkthrough of a real 3-day/week training program; the real /api/oauth/authorize and /api/oauth/token route handlers; and the .well-known OAuth metadata routes.

  • E2E (test/e2e/*.e2e.test.mjs): boots the production build and asserts over real HTTP — health check, 401 on bad/missing auth, tools/list returns all 10 tools, OAuth discovery metadata, and a full authorization-code + PKCE round trip that ends with a working access token against /api/mcp. Doesn't exercise real Hevy data (CI has no real credentials by design).

Manually verifying Hevy write operations

CI never touches real Hevy data, so the routine-write tools (create_routine, update_routine, create_routine_folder) — and list_routine_folders, list_routines, get_routine_detail, which read the same resource — need a one-off manual check against a real Hevy Pro account after any change to them:

  1. Set a real HEVY_API_KEY in .env.local, then run vercel dev.

  2. Call search_exercise_templates with a real query (e.g. via the smoke-test curl pattern above, using tools/call instead of tools/list) and confirm real candidates come back.

  3. Call create_routine with an obviously-throwaway title (e.g. "fitness-mcp manual test — delete me") and confirm: true, and note the returned id.

  4. Open the Hevy app or web app and visually confirm the routine was created with the expected exercises, sets, reps, and weights.

  5. Check whether the returned webUrl (https://hevy.com/routines/{id}) actually opens the routine — it's an unverified best-effort guess at Hevy's URL pattern, not a documented API field. If it doesn't resolve, that's worth a follow-up to remove or fix the field.

  6. Call get_routine_detail with that id and confirm the returned exercises/sets/reps/weights match what you just created — this also confirms the title and superset_id fields actually come back on a real GET response (see the "unverified" comments on those fields in lib/hevy.ts). Call list_routines (with no folderId, then with the folder's id, then with folderId: null) and confirm the new routine shows up in the right buckets.

  7. Optionally call update_routine against the same id to verify the overwrite path (note it has no folderId parameter — Hevy's update endpoint has no folder_id field at all, and sending one, even null, 400s, so a routine's folder can only be set at creation), and create_routine_folder followed by create_routine with its returned folderId to verify folder filing. Call list_routine_folders afterward and confirm the newly created folder shows up with a matching id/title.

  8. Delete the test routine manually in the Hevy app. Hevy's public API has no documented DELETE /v1/routines endpoint, so this server cannot clean up after itself — there is intentionally no delete_routine tool.

  9. Never commit a real HEVY_API_KEY, and never run this check in CI.

Environment variables

Variable

Purpose

HEVY_API_KEY

Hevy Pro API key from https://hevy.com/settings?developer (read + write — workouts, routines, routine folders)

MCP_BEARER_TOKEN

Shared secret this server requires on every request, and the access_token our OAuth flow issues — see Authentication above

OAUTH_CLIENT_ID / OAUTH_CLIENT_SECRET

Credentials for this server's own minimal OAuth authorization server — see Authentication above

OAUTH_ALLOWED_REDIRECT_HOSTS

Optional. Comma-separated allowlist for /api/oauth/authorize's redirect_uri. Defaults to claude.ai,claude.com

Set these in the Vercel project's Environment Variables (Production + Preview). Never commit real values — .env.example only documents the names.

Deploy

  1. vercel link

  2. vercel env add HEVY_API_KEY / vercel env add MCP_BEARER_TOKEN / vercel env add OAUTH_CLIENT_ID / vercel env add OAUTH_CLIENT_SECRET (repeat for each environment you use)

  3. Connect this GitHub repo in the Vercel dashboard for auto-deploy on push to main, or run vercel --prod manually.

  4. Note the deployed URL. fitness-mcp.vercel.app is often already taken by an unrelated project on Vercel's shared .vercel.app namespace — check the actual assigned domain under Project → Settings → Domains (or vercel inspect <deployment-url>). This project's production URL is https://fitness-mcp-eight.vercel.app/api/mcp.

Connect to Claude

Custom connectors can only be added from claude.ai (web) or the desktop app — not from the mobile app. Once added there, they're usable from mobile automatically.

  1. On claude.ai: Settings → Connectors → Add custom connector.

  2. Name: Fitness Data. URL: https://fitness-mcp-eight.vercel.app/api/mcp.

  3. If your account has the "Request headers" beta: add Authorization: Bearer <MCP_BEARER_TOKEN> there and skip to step 5.

  4. Otherwise, open Advanced settings and fill in OAuth Client ID / OAuth Client Secret with the OAUTH_CLIENT_ID / OAUTH_CLIENT_SECRET values set in Vercel. Claude will discover the /authorize and /token endpoints automatically via this server's .well-known metadata.

  5. Save. Claude should list the 10 tools above.

Try asking: "直近のワークアウトを教えて" (tell me about my recent workouts), or "3日/週の筋トレメニューを考えてHevyに登録して" (design a 3-day/week training menu and register it in Hevy).

A
license - permissive license
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    Python MCP server for the Hevy fitness app. Gives Claude full access to your Hevy data. Log workouts, manage routines, track body measurements, browse exercises, and more. Covers all 25 endpoints of the official Hevy API.
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    MCP server that connects Claude to the Hevy fitness app, enabling AI-driven personalized strength coaching by reading lifting history and programming workouts.
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Hosted MCP server that syncs health data from Apple Health, Fitbit, Oura, and Google Health Connect, enabling Claude and ChatGPT to query workouts, sleep, nutrition, and recovery in plain English with interactive charts.
    MIT

View all related MCP servers

Related MCP Connectors

  • WHOOP recovery, strain, sleep and workouts in Claude via official WHOOP OAuth. Free, open source.

  • Create Hevy routines and analyze your training from chat. Unofficial; BYO Hevy PRO API key.

  • Garmin data in Claude: 135 tools — activities, sleep, HRV, training, workouts. Free, open source.

View all MCP Connectors

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/ikeike443/fitness-mcp'

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