strava-mcp
Provides read-only access to a user's Strava data, including athlete profile, recent activities, activity details, and athlete stats, with sport-aware formatting for runs, ski days, hikes, and cycling.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@strava-mcpWhat were the splits from my last run?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
strava-mcp
A Model Context Protocol server that gives AI assistants such as Claude Desktop read access to your own Strava data. Ask about your training in plain language and the assistant pulls the activities, splits and totals it needs.
It is built for athletes who do more than one sport: runs are described by pace, ski days by speed, and hikes and ski tours lead with the elevation you climbed.
Demo
Related MCP server: Strava MCP Server
Example questions
"How has my running pace developed over the last 3 months?"
"Summarize my ski days this season."
"How much climbing did I do on hikes and ski tours this year?"
"Break down the splits of my last tempo run. Did I fade in the second half?"
"How many kilometers are on my trail shoes?"
"Compare my running volume this year with my all-time average."
Features
Four read-only tools covering profile, activity lists, activity details and totals.
Sport-aware formatting: pace (min/km) for runs, speed (km/h) for skiing and cycling, elevation-first summaries for hikes and backcountry skiing. Always metric.
Compact output for LLMs: readable text plus structured content with only the fields that matter, instead of raw API responses.
Automatic token refresh: expired access tokens are refreshed and saved transparently.
Rate-limit aware: limits are read from Strava's response headers, and rate-limit errors say which limit was hit and when it resets.
Clear errors for missing authorization, unknown IDs, rate limits and network failures.
Local and private: runs on your machine over stdio; tokens are stored outside the repository with owner-only file permissions.
Tools
Tool | Description |
| Name, location, weight, preferred units and gear with total distance. |
| Activities newest first. Optional |
| One activity by |
| Last 4 weeks, year-to-date and all-time totals for runs, rides and swims, as reported by Strava. |
get_recent_activities returns 10 activities by default and at most 100. Strava's API cannot
filter by sport, so the server pages through your activities and filters them itself, scanning at
most 2,000 activities per call to protect your rate limit.
get_athlete_stats is limited by Strava itself: it only counts activities with "Everyone"
visibility and has no totals for hiking or skiing. Ask about those sports through
get_recent_activities instead.
Requirements
Node.js 20.12 or newer
A Strava account
An MCP client, for example Claude Desktop
Setup
1. Create a Strava API application
Go to strava.com/settings/api.
Create an application. Name, category and website can be anything that describes your personal use.
Set Authorization Callback Domain to
localhost.Note the Client ID and Client Secret.
2. Install and build
git clone https://github.com/<your-username>/strava-mcp.git
cd strava-mcp
npm install
npm run build3. Configure credentials
cp .env.example .envFill in STRAVA_CLIENT_ID and STRAVA_CLIENT_SECRET. The .env file is only read by
npm run auth; the MCP client passes the same values to the server through its own configuration.
Variable | Required | Description |
| yes | Client ID of your Strava application. |
| yes | Client secret of your Strava application. |
| no | Token file location. Defaults to |
| no | Local port for the OAuth callback during |
| no | Set to |
4. Authorize with Strava
npm run authThis starts a temporary server on 127.0.0.1, prints the Strava authorization URL and tries to
open it in your browser. Approve the requested permissions (read, activity:read_all and
profile:read_all) and the tokens are saved; the temporary server then shuts down.
Tokens are stored in $XDG_CONFIG_HOME/strava-mcp/tokens.json (usually
~/.config/strava-mcp/tokens.json) on macOS and Linux, and in
%APPDATA%\strava-mcp\tokens.json on Windows, with 0600 permissions. Run npm run auth again at
any time to reconnect or switch accounts.
Use with Claude Desktop
Open the Claude Desktop configuration file:
macOS:
~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:
%APPDATA%\Claude\claude_desktop_config.json
Add the server, using the absolute path to your clone:
{
"mcpServers": {
"strava": {
"command": "node",
"args": ["/absolute/path/to/strava-mcp/dist/index.js"],
"env": {
"STRAVA_CLIENT_ID": "12345",
"STRAVA_CLIENT_SECRET": "your-client-secret"
}
}
}
}Restart Claude Desktop. The Strava tools appear in the tools menu of a new chat.
Troubleshooting
"No Strava tokens found": run
npm run auth. The server andnpm run authmust resolve the same token file. If they run in different environments (for example Claude Desktop on Windows and the repository inside WSL), setSTRAVA_TOKEN_PATHto the same file in both places.Server logs go to stderr, which Claude Desktop writes to its MCP log files (
~/Library/Logs/Claude/mcp-server-strava.logon macOS,%APPDATA%\Claude\logson Windows).
Development
npm run dev # run the server from source with tsx
npm run build # compile to dist/
npm test # run the test suite
npm run test:watch # tests in watch mode
npm run test:coverage # tests with coverage report
npm run lint # ESLint
npm run typecheck # TypeScript without emitting
npm run format # Prettier
npm run check # typecheck, lint, format check and testsThe tests never call the real Strava API. HTTP is mocked at the fetch level with realistic
fixtures for runs, hikes and ski tours in tests/fixtures/.
To inspect the server interactively, use the MCP Inspector:
npx @modelcontextprotocol/inspector node dist/index.jsArchitecture
src/
index.ts entry point: config, client, stdio transport
server.ts creates the MCP server, registers tools, maps errors to tool results
config.ts environment variables and token file location
strava/
client.ts the only place that talks to the Strava API
auth.ts token storage, OAuth code exchange and refresh
errors.ts Strava error types and HTTP status mapping
rateLimit.ts rate-limit header parsing and reset times
types.ts the subset of Strava response models in use
tools/
index.ts list of registered tools
types.ts the shared tool contract
get*.ts one file per tool
utils/
format.ts unit conversion and formatting
activity.ts sport profiles and activity summaries
logger.ts stderr-only logger
scripts/
auth.ts one-time OAuth login (npm run auth)A tool call flows through three layers:
Server (
server.ts) validates the input against the tool's zod schema and calls the handler. Any error is converted into a tool result withisError: true, so the assistant can explain it instead of the call failing silently.Tool (
tools/*.ts) calls typed methods on the shared client, turns the response into a compact summary and returns it as text plus structured content.Client (
strava/client.ts) adds the bearer token, refreshes it when it is about to expire (sharing one refresh between concurrent calls), and maps HTTP failures toStravaApiError.
Because stdout carries the MCP protocol, the server never writes to it; all logging goes to stderr,
and ESLint forbids console in src/.
Adding a tool
Create a file in src/tools/:
import { z } from 'zod';
import { defineTool, READ_ONLY_ANNOTATIONS } from './types.js';
export const getGear = defineTool({
name: 'get_gear',
title: 'Get gear',
description: 'Get details for a piece of gear by its ID.',
inputSchema: {
gear_id: z.string().min(1).describe('Strava gear ID, for example "g12345".'),
},
annotations: READ_ONLY_ANNOTATIONS,
async handler({ gear_id: gearId }, { client }) {
const gear = await client.get<{ name: string; distance: number }>(`/gear/${gearId}`);
return { text: `${gear.name}: ${(gear.distance / 1000).toFixed(1)} km` };
},
});Then add it to the list in src/tools/index.ts. Input validation, error handling and registration
come from the shared tool contract.
Roadmap
Weekly training summary: volume, time and elevation per week and sport.
Personal records: best efforts over standard distances and how they changed.
Compare two activities: side-by-side pace, heart rate and splits.
Training load trends: acute and chronic load based on duration and heart rate.
Strava API usage
This project is intended for personal use with your own Strava data. You create and use your own Strava API application, and you are responsible for following the Strava API Agreement and Strava's brand guidelines. This project is not affiliated with or endorsed by Strava.
License
Available Tools
4 toolsget_activity_detailsGet activity detailsARead-only
Get full details for one Strava activity: summary metrics, description, gear, splits per km, laps and best efforts when available. Find activity IDs with get_recent_activities.
| Name | Required | Description | Default |
|---|---|---|---|
| activity_id | Yes | The Strava activity ID. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark the call read-only and open-world; the description adds that results like splits and best efforts are included 'when available', signaling variable data availability. It does not describe error handling or exact units, but given annotation coverage this is sufficient.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
One compact sentence with the most important information front-loaded and no filler; the sibling pointer is appended naturally.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a one-parameter, read-only lookup tool, the description gives the agent the resource, the content scope, the availability caveat, and how to obtain a valid ID. Without an output schema, it still conveys expected return topics adequately.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The only parameter activity_id is already fully documented by the schema, so the baseline is 3. The description adds value by pointing to get_recent_activities as the source for valid IDs, which is useful but not extensive.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Clearly states it retrieves full details for a single Strava activity and enumerates the content (summary metrics, description, gear, splits per km, laps, best efforts), distinguishing it from athlete-level and list siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit workflow guidance by telling the agent to find activity IDs via get_recent_activities. It does not spell out exclusion rules for get_athlete_profile or get_athlete_stats, so it falls just short of a 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_athlete_profileGet athlete profileARead-only
Get the authenticated Strava athlete's profile: name, location, weight, preferred units and gear (shoes and bikes with their total distance).
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, so the agent knows this is a safe read operation. The description adds the fact that it returns the authenticated athlete's profile and enumerates fields, which is helpful. However, it does not disclose edge cases (e.g., missing gear, unit defaults) or any further behavior beyond the read-only nature, and with annotations covering the safety profile, the bar for extra credit is higher.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, focused sentence that front-loads the purpose and then enumerates the returned fields. Every clause adds value; there is no fluff, redundancy, or unnecessary context.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-parameter, read-only profile tool with no output schema, the description tells the agent exactly what data will be returned and that it's the authenticated user. There is nothing an agent would need to invoke this tool correctly that is missing from the combination of description and annotations.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters, and the schema is empty (100% coverage). The description appropriately adds no parameter specifics because none exist. Baseline for 0 params is 4, and the description complies by not introducing extraneous parameter discussion.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('Get'), resource ('authenticated Strava athlete's profile'), and enumerates the returned contents (name, location, weight, units, gear with distances). This clearly distinguishes it from siblings like get_recent_activities, get_activity_details, and get_athlete_stats, which cover different resources and scopes.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives clear context: this tool is for the profile of the authenticated athlete, not activities or stats. It does not explicitly name 'when not to use' or alternatives, but the scoping to 'profile' and the detailed list of returned fields makes the intended usage unambiguous relative to sibling tools.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_athlete_statsGet athlete statsARead-only
Get Strava totals for runs, rides and swims over the last 4 weeks, year to date and all time (count, distance, moving time, elevation gain). Strava only includes public activities and no other sports here.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark this as read-only and open-world, and the description adds meaningful context: results only include public activities and exclude non-swim/ride/run sports. This clarifies data scope beyond what the annotations convey.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single, dense sentence with no redundant wording. The most important information—what is retrieved and over what periods—is front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-parameter read-only tool, this is nearly complete: it specifies sports, periods, metrics, and limitations. It does not state units or output structure, but the absence of an output schema makes that a minor gap.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters, so the baseline is 4. The description fully describes what the return will contain, so no parameter guidance is needed.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names a specific verb ('Get'), resource ('Strava totals'), and scope (runs, rides, swims, over defined periods). It lists concrete metrics and distinguishes itself clearly from profile, recent-activity, and activity-detail siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The intended use is implied: call this when aggregate totals are needed rather than individual activities or profile data. However, it does not explicitly name alternatives or state when not to use this tool.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_recent_activitiesGet recent activitiesARead-only
List the athlete's Strava activities, newest first, with distance, time, pace (runs) or speed (skiing, cycling), elevation gain and heart rate. Filter by sport type and date range. Use get_activity_details with an activity ID for splits and laps.
| Name | Required | Description | Default |
|---|---|---|---|
| after | No | Only activities starting after this ISO 8601 date or date-time (UTC if no offset). | |
| limit | No | Maximum number of activities to return. Default 10, capped at 100. | |
| before | No | Only activities starting before this ISO 8601 date or date-time (UTC if no offset). | |
| sport_type | No | Strava sport type, or a list of them, for example "Run", "TrailRun", "Hike", "AlpineSki", "BackcountrySki" or "NordicSki". Case-insensitive. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=true, so the safety profile is covered. The description adds useful behavioral context: newest-first ordering, the specific metrics returned, and the fact that splits/laps are NOT included (delegated to get_activity_details). It does not mention pagination or the default/cap on limit, but the schema already documents those. The description adds value beyond annotations without contradicting them.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, each earning its place: the first states the core function and output fields, the second covers filtering, and the third routes to the sibling for more detail. The most important information (what it lists and how it's ordered) is front-loaded. No filler or repetition of schema details.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only list tool with 100% schema coverage and no output schema, the description is nearly complete. It covers what is returned, the ordering, and the filtering options, and it points to the sibling for deeper detail. The only minor gap is that it doesn't explicitly state the default limit of 10 or the 100 cap, but those are already in the schema, so the description need not repeat them. The openWorldHint annotation also signals that the list may not be exhaustive, which is useful context.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents all four parameters (after, before, limit, sport_type) with types and examples. The description adds the filter concept ('Filter by sport type and date range') but does not add new meaning beyond the schema. Baseline 3 is appropriate because the schema carries the heavy lifting.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('List'), a clear resource ('the athlete's Strava activities'), and the ordering ('newest first'). It also enumerates the returned fields (distance, time, pace/speed, elevation gain, heart rate), which distinguishes it from sibling tools like get_athlete_profile or get_athlete_stats. The final sentence explicitly names the sibling for splits/laps, reinforcing differentiation.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly says when to use this tool (for a list of activities with summary metrics) and when to use an alternative ('Use get_activity_details with an activity ID for splits and laps'). It also mentions the available filters (sport type and date range), giving clear context for invocation. This is strong routing guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
4 tool updates
v0.1.0- First observed
get_activity_details - First observed
get_athlete_profile - First observed
get_athlete_stats - First observed
get_recent_activities
TDQS
Scored across 4 tools
Each tool targets a distinct resource: athlete profile, recent activity list, individual activity details, and athlete stats. The descriptions cross-reference each other clearly (e.g., using get_recent_activities to find IDs for get_activity_details), eliminating ambiguity.
All tool names follow a consistent get_<noun> pattern, using snake_case throughout. The naming clearly distinguishes list/detail operations without introducing mixed conventions or vague verbs.
Four tools is a compact but reasonable set for a read-only Strava personal data server. While slightly small, the count is appropriate for the focused scope and each tool covers a necessary data retrieval need.
The server covers the core personal data lifecycle: profile, stats, activity list, and activity details. It lacks streams/segments or social features, but these are not obvious gaps for the described purpose of retrieving an athlete's summary data.
Maintenance
Related MCP Connectors
- freddyOAuthcoach.freddy
Connect your wearables, rings and training apps, then ask your AI about your own health data.
Garmin data in Claude & ChatGPT via the Garmin Health API. OAuth sign-in, no password sharing.
Connect your health, fitness, nutrition, sleep, and wearable data to your AI assistant.
Give any AI assistant real-time access to your phone's GPS and location history.
Related MCP Servers
- AlicenseBqualityDmaintenanceIntegrates with the Strava API to allow AI assistants to access fitness data including athlete profiles, activity history, and segment statistics. It enables users to query detailed performance metrics and explore geographic segment data through natural language commands.843 npmMIT
- AlicenseBqualityDmaintenanceEnables users to interact with their Strava data through natural language to analyze workouts, track fitness progress, and explore routes. It supports retrieving detailed activity stats, heart rate data, and segment insights directly within AI assistants.26206 npmMIT
- AlicenseNot gradedqualityCmaintenanceEnables AI assistants to access Strava running data, route planning, and weather information for intelligent running coaching.15MIT
- AlicenseAqualityDmaintenanceEnables AI assistants to directly access and analyze Strava activity data, including runs, rides, and swims, through natural language queries.45 npmMIT