Skip to main content
Glama
didikz

stravamcp

by didikz
README.md
# stravamcp

Single-user Strava MCP server on Cloudflare Workers. Connects one Strava account and exposes read-only athlete, activity, route, segment, stream, and gear data through the Model Context Protocol.

## Tools

| Tool | Description |
| --- | --- |
| `list_activities` | Recent activities with optional sport_type / commute filters |
| `get_activity` | Activity detail by id or id_str |
| `get_activity_streams` | Time-series streams by key |
| `get_activity_zones` | HR and power time-in-zone distribution |
| `summarize_activity_streams` | Compact summary; full arrays stay on the server |
| `get_athlete` | Connected athlete profile |
| `get_athlete_stats` | YTD, recent, and all-time totals |
| `get_athlete_zones` | HR / power zone ranges |
| `list_athlete_routes` | Saved routes |
| `get_route` | Route detail by id_str |
| `list_starred_segments` | Starred segments |
| `get_segment` | Segment detail by id |
| `list_segment_efforts` | Your efforts on a segment |
| `get_gear` | Bike / shoe detail by id |
| `get_usage` | Tool, API, and cache counts for 24h / 7d / 30d |

IDs are accepted as strings (`id_str`) or numbers. Use `id_str` for routes and any large id to avoid JavaScript 64-bit precision loss.

## Local setup

1. Copy the env template and fill in the credentials:

```sh
cp .dev.vars.example .dev.vars
```

2. Apply the local D1 migration:

```sh
npx wrangler d1 migrations apply stravamcp
```

3. Start the dev server:

```sh
npm run dev
```

Local MCP endpoint: `http://localhost:8787/mcp`

`GET /` serves a landing page describing the server and its tools.

## Cloudflare deployment

1. Create a Strava API app at `https://www.strava.com/settings/api`.
2. Set the Authorization Callback Domain to your domain, e.g. `stravamcp.didiktrisusanto.dev`.
3. Apply the remote migration and secrets:

```sh
npx wrangler d1 migrations apply stravamcp --remote
npx wrangler secret put STRAVA_CLIENT_ID
npx wrangler secret put STRAVA_CLIENT_SECRET
npx wrangler secret put MCP_SECRET
npx wrangler secret put SETUP_TOKEN
npx wrangler deploy
```

Generate random secrets with `openssl rand -hex 32`.

`STRAVA_API_BASE` is set in `wrangler.jsonc`. Change it when Strava's new base URL becomes mandatory.

4. Connect the Strava account once. The setup token is only accepted in the header:

```sh
curl -i -H "Authorization: Bearer <SETUP_TOKEN>" https://stravamcp.didiktrisusanto.dev/authorize
```

Open the `Location` URL from the 302 response in a browser. Rotate the setup token after setup if it may have been exposed.

5. Use the MCP endpoint with `MCP_SECRET`:

```json
{
  "url": "https://stravamcp.didiktrisusanto.dev/mcp",
  "headers": {
    "Authorization": "Bearer <MCP_SECRET>"
  }
}
```

## Disconnect and delete data

```sh
curl -X POST -H "Authorization: Bearer <MCP_SECRET>" https://stravamcp.didiktrisusanto.dev/disconnect
```

This revokes the Strava token and deletes the token row, cached Strava data, and pending OAuth states.

Strava's Standard Tier requires an active Strava subscription.

## Usage

`get_usage` returns rolling 24-hour, 7-day, and 30-day counts for tool calls, Strava data API calls, and cache lookups. The same JSON is available over HTTP:

```sh
curl -H "Authorization: Bearer <MCP_SECRET>" https://stravamcp.didiktrisusanto.dev/usage
```

Notes:

- Cache counts are lookups, split into hits and misses. A hit makes no Strava call.
- API counts are outbound Strava data requests, including 401 retries. Token calls are not counted.
- Calls rejected by input validation never reach the tool runner, so they are not counted.
- Concurrent cache misses share one in-flight load; they still count as multiple lookups.
- Usage rows are kept for 90 days. Usage survives `/disconnect` because it contains no Strava data.
- Apply the D1 migrations before deploying. If `usage_events` is missing, usage writes are skipped and logged.

## Verification

```sh
npm run typecheck
npm test
npx wrangler deploy --dry-run
```

Cache entries expire by TTL. No write tools or webhooks are enabled.