GarMCP is a Model Context Protocol (MCP) server that lets LLM clients (for example Claude) securely query Garmin Connect data through tools.
It is built for:
- athletes who want Garmin insights directly inside LLM chat
- developers who want a self-hosted Garmin MCP backend
- teams that want a cloud endpoint (
/mcp) instead of local-only stdio
What it provides:
- 97 Garmin tools across activity, sleep, recovery, profile, trends, and training data (includes write tools)
- Streamable HTTP MCP endpoint for Claude Web/Mobile custom connectors
- local stdio mode for desktop/dev workflows
- OAuth-protected connector flow and per-user Garmin token storage
Project scope:
- v1 is focused on single-user cloud deployments
- read-only by default (
MCP_ENABLE_WRITE_TOOLS=false) - optimized for Vercel + Upstash Redis REST token storage
Notes:
- This project is community-built and not affiliated with Garmin.
- This project in particular is based on
python-garminconnectby cyberjunky andgarmin-connect-mcpby Nicolasvegam.
For personal/private usage, this setup can run at $0 on:
- Vercel Hobby (free plan)
- Upstash Redis Free (includes free usage limits)
As long as you stay within the free quotas, no charges are expected. If you exceed limits or switch to paid tiers, billing may apply.
This section shows the full path from Deploy on Vercel to a working Claude custom connector.
Choose your Git scope, give the project a name, choose your Vercel team and click Create.
In Add Products, click Add for Upstash for Redis.
In the integration modal, accept terms and continue.
Choose your primary region and select a plan (for private use: typically Free).
Pick a database name (for example garmcp-kv) and click Create.
Select your project, Development, Preview, and Production, then strictly set custom prefix to KV.
This auto-creates env vars like KV_REST_API_URL and KV_REST_API_TOKEN.
Fill all required GarMCP env vars and click Deploy. Important values:
MCP_OAUTH_CLIENT_SECRET: long random secret (32+ chars)MCP_OAUTH_SIGNING_SECRET: different long random secret (48+ chars)GARMIN_TOKEN_STORE=vercel-kvMCP_ALLOWED_ORIGINS=https://claude.aiMCP_ENABLE_WRITE_TOOLS=falseGARMIN_MAX_CONCURRENT_REQUESTS=1
In Claude custom connectors:
- Name: A custom name at will for your MCP in Claude
- URL:
https://<your-deployment>/mcp - OAuth Client ID: your
MCP_OAUTH_CLIENT_ID - OAuth Client Secret: your
MCP_OAUTH_CLIENT_SECRET
After successful OAuth authorization, Garmin tools are available in Claude.
- If deploy logs show
invalid Node.js Version: "24.x", set your Vercel project runtime to Node 22.x and redeploy. - First Garmin login can occasionally hit temporary Garmin
429limits; wait a bit and retry.
GarMCP server with two modes:
- local stdio mode (
npm run start:stdio) - remote Streamable HTTP mode (
npm start) for Claude Web/Mobile custom connectors
npm run start:stdio: local MCP over stdio (manual/debug run)npm start/npm run start:http: HTTP MCP server- Vercel serverless handlers in
api/*expose OAuth + MCP endpoints
Required:
GARMIN_EMAILGARMIN_PASSWORD
For Claude Desktop (and most MCP clients), you usually do not run
npm run start:stdio manually. The client starts the stdio process itself
from its MCP config.
Build once (and rebuild after code changes):
npm install
npm run buildThen configure your MCP client with:
- command:
node - args:
["/absolute/path/to/garmin-connect-mcp/build/index.js"] - env:
GARMIN_EMAIL,GARMIN_PASSWORD(optionalGARMIN_TOKEN_DIR)
Use manual start only for debugging or direct CLI testing:
GARMIN_EMAIL=you@email.com GARMIN_PASSWORD=yourpass npm run start:stdioRequired:
MCP_OAUTH_ENABLED=trueMCP_OAUTH_CLIENT_ID=<your-client-id>MCP_OAUTH_CLIENT_SECRET=<your-client-secret>MCP_OAUTH_SIGNING_SECRET=<long-random-secret>MCP_ALLOWED_ORIGINS=https://claude.aiGARMIN_TOKEN_STORE=vercel-kvKV_REST_API_URL=<from Upstash/Vercel integration>KV_REST_API_TOKEN=<from Upstash/Vercel integration>
Recommended:
MCP_ENABLE_WRITE_TOOLS=falseGARMIN_MAX_CONCURRENT_REQUESTS=1
Optional:
GARMIN_TOKEN_DIR=/tmp/garmin-mcp(namespace base for token keys)GARMIN_EMAILandGARMIN_PASSWORD(fallback when token storage is unavailable)MCP_API_KEYandMCP_OAUTH_ALLOW_API_KEY_FALLBACK=true(Inspector/API-key testing)MCP_OAUTH_REDIRECT_URIS(restrict OAuth redirect targets)
npm install
npm run check
vercel --prod- URL:
https://<your-domain>/mcp - OAuth Client ID: same value as
MCP_OAUTH_CLIENT_ID - OAuth Client Secret: same value as
MCP_OAUTH_CLIENT_SECRET
Connector flow:
- Claude hits
/mcp - OAuth starts at
/authorize - User enters Garmin credentials
- Server stores Garmin tokens per OAuth subject
- Tool calls use stored Garmin tokens
GET /healthPOST|GET|DELETE /mcpGET|POST /authorizePOST /tokenGET /.well-known/oauth-authorization-serverGET /.well-known/oauth-protected-resource/mcp
-
Garmin re-authorization required: no stored Garmin tokens...- Cause: token storage not reachable or empty in current runtime.
- Fix: verify
GARMIN_TOKEN_STORE,KV_REST_API_URL,KV_REST_API_TOKEN; reconnect OAuth once.
-
Request failed with status code 429- Cause: Garmin rate limit.
- Fix: wait and retry, keep request volume low, use
GARMIN_MAX_CONCURRENT_REQUESTS=1.
-
Session not found- Cause: stale/expired MCP session id.
- Fix: reconnect client (Inspector/Claude), re-run call.
-
Vercel Runtime Timeout Error: Task timed out after 300 seconds- Usually a long-lived/idle stream request in serverless runtime.
- If tool calls still succeed in Claude, this is often non-fatal reconnect noise.
-
Unauthorizedon/mcp- With OAuth enabled, client must send valid OAuth bearer token.
- For Inspector testing, use API-key fallback only if explicitly configured.
# clone your fork (or open this repo locally)
# git clone <your-fork-url>
# cd <repo-folder>
npm install
npm run typecheck
npm run buildMIT







