Files
strava-mcp-server/README.md
T

11 KiB
Raw Blame History

🚴 Strava MCP Server

A production-ready Model Context Protocol (MCP) server that exposes the Strava API v3 as MCP-compatible tools, resources, and prompts — enabling AI agents and LLM-based applications to query your Strava training data through a standardized interface.

Built with FastMCP and the MCP Python SDK, compatible with MCP Inspector, Claude Desktop, and any MCP-compliant client.


Features

  • 🛠️ 25+ MCP Tools covering all major Strava API read endpoints (including athlete profile & stats)
  • 💬 2 MCP Prompts for structured AI-driven training analysis
  • 🔄 Automatic OAuth token rotation — tokens are refreshed transparently
  • 🌐 Streamable HTTP transport for broad client compatibility
  • 🔒 Read-only — no write operations, safe to use with AI agents

📋 Table of Contents


Requirements


Installation

# Clone the repository
git clone <your-repo-url>
cd strava-mcp-server

# Install dependencies with uv (also installs the package itself)
uv sync

# Or install directly from PyPI (once published)
uvx strava-mcp-server
# or: pip install strava-mcp-server

Strava API Setup

1. Create a Strava API Application

  1. Go to https://www.strava.com/settings/api
  2. Create a new application
  3. Set Authorization Callback Domain to localhost
  4. Note your Client ID and Client Secret

2. Generate Your OAuth Tokens

The server uses the OAuth 2.0 refresh token flow. Run the token helper script to authorize and retrieve your initial tokens:

uv run strava-mcp-get-token

This will:

  1. Open your browser with the Strava authorization page
  2. Ask you to authorize the application
  3. Automatically capture the authorization code
  4. Exchange it for access and refresh tokens
  5. Save the STRAVA_REFRESH_TOKEN to your .env file

Required OAuth Scopes: activity:read_all,profile:read_all,read

  • activity:read_all — Access all activities including private ones
  • profile:read_all — Access full profile info including heart rate zones
  • read — Access public data

Configuration

Copy the example environment file and fill in your credentials:

cp .env.example .env

Edit .env:

STRAVA_CLIENT_ID=your_client_id_here
STRAVA_CLIENT_SECRET=your_client_secret_here
STRAVA_REFRESH_TOKEN=your_refresh_token_here

Note: The STRAVA_REFRESH_TOKEN is written automatically by get_token.py. You only need to fill in STRAVA_CLIENT_ID and STRAVA_CLIENT_SECRET manually.


Running the Server

uv run strava-mcp

Expected output:

🚀 Starting Strava MCP Server on http://0.0.0.0:8000
   MCP endpoint: http://0.0.0.0:8000/mcp  (Streamable HTTP)
INFO:     Started server process [12345]
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)

The server listens on port 8000 by default and exposes a single endpoint:

http://localhost:8000/mcp

Stopping the Server

Press Ctrl+C for a clean shutdown. If the port is already in use from a previous run:

lsof -ti :8000 | xargs kill -9

Connecting with MCP Inspector

  1. Open MCP Inspector or run it locally
  2. Select transport: Streamable HTTP
  3. Enter URL: http://localhost:8000/mcp
  4. Click Connect

⚠️ Make sure to use http://localhost:8000/mcp without a trailing slash and with the Accept: application/json, text/event-stream header (handled automatically by MCP clients).

Claude Desktop

Add to your claude_desktop_config.json:

{
  "mcpServers": {
    "strava": {
      "url": "http://localhost:8000/mcp",
      "transport": "streamable-http"
    }
  }
}

MCP Primitives

Tools

🏃 Athlete

Tool Parameters Description
get_athlete_profile Full athlete profile: name, city, country, follower count, gear list
get_athlete_stats Training totals: all-time, year-to-date, and last 4 weeks for runs, rides, and swims
get_athlete_zones Heart rate and power zones

🚴 Activities

Tool Parameters Description
list_activities limit, page, before, after Paginated activity list with optional time range filters
get_activity_details activity_id Full activity details incl. segment efforts (IDs returned as strings)
get_activity_laps activity_id Lap splits
get_activity_zones activity_id Heart rate and power zones for a specific activity
get_activity_comments activity_id, limit Comments on an activity
get_activity_kudoers activity_id, limit Athletes who gave kudos
get_activity_streams activity_id, keys Raw GPS/sensor data streams

*before and after are Unix timestamps. Example: after=1704067200 = since 2024-01-01.

🏛️ Clubs

Tool Parameters Description
list_athlete_clubs limit Clubs the athlete is a member of
get_club club_id Club details
get_club_activities club_id, limit Recent club activity feed
get_club_members club_id, limit Club members

🗺️ Routes

Tool Parameters Description
get_routes_by_athlete_id limit Athlete's created routes
get_route_by_id route_id: str Route details including segments
get_route_streams route_id: str Route GPS/elevation streams

⚠️ Route IDs are returned as strings to prevent JavaScript float precision loss (IDs exceed Number.MAX_SAFE_INTEGER).

📍 Segments

Tool Parameters Description
get_segment segment_id Segment details, grade, elevation, effort counts
list_starred_segments limit Segments starred by the athlete
explore_segments bounds, activity_type Discover popular segments in a geographic bounding box
get_segment_streams segment_id, keys Segment GPS/elevation streams

Segment Efforts

Tool Parameters Description
get_segment_effort effort_id: str Details for a specific effort — requires Strava subscription
list_segment_efforts segment_id, start_date_local, end_date_local, limit Athlete's efforts on a segment — requires Strava subscription
get_segment_effort_streams effort_id: str, keys Raw streams for a segment effort — requires Strava subscription

Effort IDs must be copied as strings from get_activity_details response. Do not retype the numbers manually — they exceed JavaScript's safe integer range.

⚙️ Gear

Tool Parameters Description
get_gear_by_id gear_id: str Bike or shoe details: brand, model, total distance

Gear IDs start with b for bikes and g for shoes (e.g. b12345678). Find them in get_activity_details under gear_id.

Prompts

Prompts pre-structure AI conversations with the right tool-calling instructions.

analyze_activity

Parameters: activity_id (str)

Triggers a structured analysis of a specific activity including summary, performance metrics, segment highlights, gear used, and key takeaways.

training_summary

Parameters: weeks (int, default: 4)

Generates a training load report for the last N weeks: volume by sport, highlights, trend vs. year-to-date, and recommendations. Automatically calculates the required Unix timestamp for filtering.


Project Structure

strava-mcp-server/
├── src/
│   └── strava_mcp/          # Installable Python package
│       ├── __init__.py
│       ├── main.py          # Server entrypoint → strava-mcp command
│       ├── tools.py         # All MCP tools, resources, and prompts
│       ├── strava_client.py # Strava API client with auto token rotation
│       └── get_token.py     # OAuth2 flow → strava-mcp-get-token command
├── tests/
│   └── test_strava.py       # Manual API integration tests
├── pyproject.toml           # Package metadata, dependencies, entry points
├── .env                     # Credentials (not committed)
├── .env.example             # Credential template
├── TODO.md                  # Planned next steps
└── README.md                # This file

Known Strava API Limitations

The following endpoints are restricted by Strava and return errors for regular API users:

Endpoint Status Reason
GET /segments/{id}/leaderboard 403 Forbidden Requires Strava API partnership
GET /segment_efforts/{id} 403 Forbidden Requires Strava API partnership
GET /segment_efforts?segment_id=X 200 [] (empty) Silently restricted since 2018 — data accessible via get_activity_details
GET /athlete/zones 401 Unauthorized Requires profile:read_all OAuth scope

Workaround for segment efforts: Use get_activity_details to access segment efforts embedded in activity data. The segment_efforts[] array contains effort IDs, times, heart rate, power, and PR/KOM ranks.


Troubleshooting

[Errno 48] Address already in use

Port 8000 is occupied by a previous server process:

lsof -ti :8000 | xargs kill -9

401 Unauthorized on token refresh

Your refresh token has expired or been rotated. Generate a new one:

uv run strava-mcp-get-token

Empty results from list_segment_efforts

The direct Strava endpoint is restricted. Access segment efforts via get_activity_details instead — the full activity response includes all segment efforts.

406 Not Acceptable from MCP endpoint

The MCP client is missing the required Accept: application/json, text/event-stream header. Use a proper MCP client (MCP Inspector, Claude Desktop) instead of raw curl.

Route or effort 404 Not Found with a large ID

The ID was truncated due to JavaScript float precision. Always use the string IDs returned by the server — never copy-type large numeric IDs manually.


Tech Stack

Component Library
MCP Server FastMCP
HTTP Transport Uvicorn + Starlette (via FastMCP)
Strava API Client httpx
Environment Config python-dotenv
Package Manager uv

License

MIT