Skip to main content
Glama

strava-mcp

A remote MCP server on Cloudflare Workers that lets an AI assistant read your Strava activities on demand. Tell it "I just ran" and it pulls the run: laps, splits, heart rate, and a one-line read of the workout's structure. For rides, it checks Strava's calorie number against the power data.

Built for Grok Bot; works with any MCP client that connects to remote servers with OAuth. You deploy your own copy, with your own Strava API app, and only your Strava account can connect to it.

2 mi WU · 4x3 mi (6:36, 6:31, 6:23, 6:16) w/ 1 mi @ 7:50-8:05 · 3.1 mi CD
2.5 mi WU · 3x1 mi (6:40, 6:32, 6:26) w/ 3:00 rec · 4x200m (41s, 40s, 39s, 38s) w/ 2:00 rec · 2.2 mi CD

Tools

Tool

What it returns

get_latest_activity

Your newest activity in full detail, plus ended_minutes_ago so the assistant can tell when a new activity hasn't synced yet.

list_activities

Compact rows (id, date, name, sport, miles, moving time, pace or watts, HR), filtered by since, until, sport and limit.

get_activity

One activity by id. Runs: summary, laps, splits, workout structure, stops. Rides: summary plus a power-vs-calories check.

All three are read-only. Units are miles, feet and min/mi. The full output format is in docs/DESIGN.md §5.

Related MCP server: strava-mcp

How it works

One Worker is both the OAuth server for your MCP client and the MCP endpoint at /mcp. When you connect, it shows a consent page, sends you to Strava to approve read access, and refuses any Strava account but yours. Strava tokens stay in a Durable Object that refreshes them under a lock, because Strava invalidates the old refresh token when it issues a new one. The design and its reasoning are in docs/DESIGN.md.

Deploy your own

You need a Cloudflare account (the free plan works), a Strava account, and Node 22.13 or later.

1. Work out your Worker's URL

In the Cloudflare dashboard, open Workers & Pages and find your account's workers.dev subdomain (create one if you don't have one). Your Worker will be at https://strava-mcp.<subdomain>.workers.dev.

2. Create a Strava API app

At https://www.strava.com/settings/api:

  • Authorization Callback Domain: the host only, e.g. strava-mcp.<subdomain>.workers.dev. No https://, no path.

  • Website: your Worker URL.

  • Copy the Client ID and Client Secret.

Use a separate app from anything else that reads your Strava data: two services sharing one app would rotate each other's refresh tokens.

Your athlete id is the number in your profile URL, strava.com/athletes/<id>.

3. Configure

npm install
cp wrangler.jsonc wrangler.local.jsonc

In wrangler.local.jsonc (gitignored), set:

"vars": {
  "ALLOWED_ATHLETE_ID": "<your athlete id>",
  "PUBLIC_URL": "https://strava-mcp.<subdomain>.workers.dev"
},

4. First deploy

Wrangler won't create a new Worker without its required secrets, so the first deploy passes them in a file. Log in first:

npx wrangler login

Create the secrets file, readable only by you, with a generated remember-consent secret:

(umask 077; printf 'STRAVA_CLIENT_ID=\nSTRAVA_CLIENT_SECRET=\nCONSENT_REMEMBER_SECRET=%s\n' "$(openssl rand -hex 32)" > .deploy-secrets)

Open .deploy-secrets and paste your Client ID and Client Secret after the =, with no quotes or spaces. Then deploy and delete the file:

npx wrangler deploy -c wrangler.local.jsonc --secrets-file .deploy-secrets
rm .deploy-secrets

The deploy creates the OAUTH_KV namespace and prints its id. Pin it in wrangler.local.jsonc, so later deploys reuse it:

"kv_namespaces": [{ "binding": "OAUTH_KV", "id": "<the id it printed>" }],

From now on, deploy with:

npm run deploy:local

5. Check it

curl https://strava-mcp.<subdomain>.workers.dev/health

It should show {"ok":true,"athleteConfigured":true}.

6. Connect

Grok Bot: add a connector with URL https://strava-mcp.<subdomain>.workers.dev/mcp and OAuth auth, then authorize. Any other MCP client works the same way.

  1. On the consent page, check the app name and the host access goes to, then click Continue to Strava.

  2. On Strava, keep "View data about your private activities" ticked.

To try it first in the MCP Inspector (Streamable HTTP transport, same URL):

npx @modelcontextprotocol/inspector

Suggested instructions for your assistant:

You have a Strava connector. When I say I ran, rode or worked out, call get_latest_activity before responding. If ended_minutes_ago is large, tell me it may not have synced yet instead of reviewing an older activity. For a past activity, find it with list_activities, then call get_activity. For runs, lead with the structure line and use the laps to back it up. For rides, mention energy.check if the calories look off.

Troubleshooting

What you see

What it means

Strava says invalid redirect_uri

The app's callback domain doesn't exactly match your Worker's host.

"Not allowed" after Strava

You signed in to a Strava account other than ALLOWED_ATHLETE_ID.

"Strava permission missing"

The private-activities box was unticked. Connect again with it ticked.

"Can't continue" on the consent page

The page expired (10 minutes) or was reloaded. Start again from your client.

The assistant says reconnect the Strava connector

The stored Strava tokens are missing or were revoked. Reconnect.

/health shows athleteConfigured: false

ALLOWED_ATHLETE_ID is empty in the config you deployed.

Rotating secrets

To replace a secret, for example after regenerating the Client Secret on Strava:

npx wrangler secret put STRAVA_CLIENT_SECRET -c wrangler.local.jsonc

It takes effect immediately. If you change CONSENT_REMEMBER_SECRET, remembered consents are forgotten and the consent page shows again. If Strava access breaks, reconnecting from your client replaces the stored Strava tokens.

Develop

cp .dev.vars.example .dev.vars
npm run dev
npm test
npm run typecheck

After editing wrangler.jsonc, regenerate worker-configuration.d.ts:

npm run cf-typegen

Tests run in plain Node with a stubbed cloudflare:workers, node:sqlite in place of the Durable Object's storage, and an in-memory KV. The test fixtures are synthetic, built by test/fixtures/generate.mjs. For local OAuth against real Strava, add localhost as a callback domain on your Strava app.

Limits

  • One athlete per deployment.

  • Workout structure is read from lap paces: with auto-laps, rep boundaries are only mile-accurate, and hill repeats can be mislabeled.

  • Read limits are Strava's: 100 requests per 15 minutes and 1,000 per day.

License

MIT. Not affiliated with or endorsed by Strava. Your use of Strava data is governed by the Strava API Agreement, which includes terms on using that data with AI models.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI assistants such as Claude, ChatGPT and Gemini to read wearable health data — daily activity, sleep stages, heart rate, SpO2, HRV, workouts, weight and nutrition — through a self-hosted, OAuth-protected Cloudflare Worker endpoint. It also lets users log weight, body fat, water, food, exercises, mood and symptoms by voice or chat, while leaving sensor-recorded data read-only.
    MIT