Skip to main content
Glama
chamilqs

Athlete MCP

by chamilqs
README.md
# Athlete MCP

An open-source, Strava-first remote MCP server for ChatGPT, Claude, and Gemini clients.

Athlete MCP lets each user authorize with Strava and then ask an AI about their own training. It is a hosted, multi-user MCP service—not a local script and not a shared Strava token. The initial release is read-only by design.

## Why this exists

FitMCP demonstrates a strong product shape: hosted onboarding, per-user sports connections, and one remote MCP URL. Athlete MCP keeps that low-friction shape but starts much narrower and deeper:

- One-click delegated Strava authorization; no separate Athlete MCP password.
- Streamable HTTP at `/mcp`, OAuth 2.1-style authorization code + PKCE, protected-resource metadata, refresh-token rotation, and dynamic client registration.
- Encrypted Strava access and refresh tokens in PostgreSQL.
- Deterministic analysis instead of asking the model to calculate from thousands of samples.
- Rate-limit-aware caching and Strava webhook invalidation.
- No raw GPS coordinates in tool requests or responses.
- Read-only tools and least-privilege MCP annotations.

## Tools

| Tool | Best use |
| --- | --- |
| `strava_get_profile` | Athlete identity, recent totals, lifetime totals |
| `strava_list_activities` | Find workouts and answer calendar/history questions |
| `strava_get_activity` | Exact laps, splits, best efforts, and activity facts |
| `strava_analyze_activity` | Power, HR zones, splits, aerobic decoupling, pacing |
| `strava_training_summary` | Weekly volume, sport mix, consistency, Relative Effort |
| `strava_compare_activities` | Compare repeated routes, races, or workouts |

The analysis layer labels estimates and missing sensor data. Strava's activity API does **not** provide the sleep, HRV, Body Battery, or readiness data that a Garmin-native integration can expose.

## Architecture

```mermaid
flowchart LR
  C["ChatGPT / Claude / Gemini client"] -->|"OAuth + Streamable HTTP"| M["Athlete MCP"]
  M -->|"Authorization code"| S["Strava OAuth"]
  M -->|"Encrypted token storage + cache"| P[("PostgreSQL")]
  M -->|"V3 API"| A["Strava API"]
  A -->|"Activity webhooks"| M
```

There are two separate trust boundaries:

1. The MCP client authenticates to Athlete MCP. Athlete MCP issues short-lived access tokens and rotating refresh tokens.
2. Athlete MCP authenticates to Strava for that athlete. Strava tokens are encrypted with AES-256-GCM before storage.

The user's Strava login is the Athlete MCP account. The authorization endpoint delegates directly to Strava, then returns an MCP authorization code to the requesting client.

## Local setup

Requirements: Node.js 22+, Docker, and a [registered Strava API application](https://www.strava.com/settings/api).

New Strava applications begin with an athlete capacity of **1** (Single Player Mode). The API settings dashboard can raise this to 10 once the app is configured; connecting more than 10 athletes requires Strava review. A technically public MCP server does not bypass this platform limit.

1. Copy `.env.example` to `.env` and fill in the values.
2. Set the Strava application's authorization callback domain to the hostname in `PUBLIC_BASE_URL`.
3. Start PostgreSQL:

   ```bash
   docker compose up -d postgres
   ```

4. Install and run:

   ```bash
   npm install
   npm run dev
   ```

Strava must redirect to:

```text
https://YOUR_HOST/oauth/strava/callback
```

For local development, use an HTTPS tunnel and set `PUBLIC_BASE_URL` to the public tunnel URL. Remote AI clients cannot call `localhost` directly.

## Webhooks

Register one Strava webhook subscription for the application. One subscription receives events for all athletes who authorized that Strava app.

```bash
curl -X POST https://www.strava.com/api/v3/push_subscriptions \
  -F client_id="$STRAVA_CLIENT_ID" \
  -F client_secret="$STRAVA_CLIENT_SECRET" \
  -F callback_url="https://YOUR_HOST/webhooks/strava" \
  -F verify_token="$STRAVA_WEBHOOK_VERIFY_TOKEN"
```

Activity events invalidate cached activity lists. Athlete deauthorization deletes the athlete connection and cascades deletion of MCP tokens and cached data.

## Connect clients

Use the same endpoint for every client:

```text
https://YOUR_HOST/mcp
```

### Claude

In Claude, go to **Customize → Connectors → Add custom connector**, enter the MCP URL, and complete the OAuth flow. Claude remote connectors support Streamable HTTP, OAuth, refresh tokens, and dynamic client registration. See [Anthropic's current custom connector guide](https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp).

### Gemini CLI

```bash
gemini mcp add --transport http athlete-mcp https://YOUR_HOST/mcp
```

Then run `/mcp auth athlete-mcp` if authentication does not start automatically. Gemini CLI supports OAuth discovery and dynamic client registration for remote HTTP MCP servers. See the [Gemini CLI MCP documentation](https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md).

### ChatGPT

Create a custom app in **Settings → Apps** (or the workspace Apps settings), supply the MCP URL, scan tools, and complete OAuth. Availability is currently plan/workspace dependent; consult the [current ChatGPT developer-mode documentation](https://help.openai.com/en/articles/12584461-developer-mode-apps-and-full-mcp-connectors-in-chatgpt-beta).

## Production checklist

- Terminate TLS at a trusted public hostname; never deploy OAuth over plain HTTP.
- Run the SQL migrations before starting the app.
- Store `TOKEN_ENCRYPTION_KEY` and the Strava client secret in a secret manager.
- Back up PostgreSQL and test token-key rotation before production.
- Add request-level rate limiting at the edge for registration, authorization, and token endpoints.
- Add OpenTelemetry/audit events without logging bearer tokens, refresh tokens, activity descriptions, or tool payloads.
- Configure Strava webhooks; default Strava limits are small enough that polling is not a viable multi-user design.
- Upgrade the Strava application's athlete capacity and complete Strava review before promising open signup.
- Complete a security review before enabling any future `activity:write` tool.
- Publish a privacy policy and data deletion path before inviting external users.

## Current limitations

- No user-facing dashboard or token-revocation UI yet.
- No weather correlation, route matching, or FIT-file ingestion yet.
- No write tools. Strava cannot schedule structured workouts through the V3 activity API.
- PostgreSQL migrations are plain SQL; there is no migration runner yet.
- Tool output is English-only.

## Development

```bash
npm run typecheck
npm test
npm run build
```

The project follows the [MCP Streamable HTTP recommendation](https://ts.sdk.modelcontextprotocol.io/server), the [MCP authorization specification](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization), and Strava's official [OAuth](https://developers.strava.com/docs/authentication/), [API reference](https://developers.strava.com/docs/reference/), [webhook](https://developers.strava.com/docs/webhooks/), and [rate-limit](https://developers.strava.com/docs/rate-limits/) documentation.

## License

MIT. See `LICENSE`.