Skip to main content
Glama

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

get_athlete_profile

Name, location, weight, preferred units and gear with total distance.

get_recent_activities

Activities newest first. Optional sport_type (one or a list, e.g. Run, TrailRun, Hike, AlpineSki, BackcountrySki, NordicSki), after, before and limit.

get_activity_details

One activity by activity_id: summary, description, gear, splits per km, laps and best efforts.

get_athlete_stats

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

  1. Go to strava.com/settings/api.

  2. Create an application. Name, category and website can be anything that describes your personal use.

  3. Set Authorization Callback Domain to localhost.

  4. 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 build

3. Configure credentials

cp .env.example .env

Fill 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

STRAVA_CLIENT_ID

yes

Client ID of your Strava application.

STRAVA_CLIENT_SECRET

yes

Client secret of your Strava application.

STRAVA_TOKEN_PATH

no

Token file location. Defaults to ~/.config/strava-mcp/tokens.json (see below).

STRAVA_AUTH_PORT

no

Local port for the OAuth callback during npm run auth. Defaults to 8765.

STRAVA_MCP_DEBUG

no

Set to 1 to log requests and rate-limit usage to stderr.

4. Authorize with Strava

npm run auth

This 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.json

  • Windows: %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 and npm run auth must resolve the same token file. If they run in different environments (for example Claude Desktop on Windows and the repository inside WSL), set STRAVA_TOKEN_PATH to 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.log on macOS, %APPDATA%\Claude\logs on 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 tests

The 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.js

Architecture

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:

  1. Server (server.ts) validates the input against the tool's zod schema and calls the handler. Any error is converted into a tool result with isError: true, so the assistant can explain it instead of the call failing silently.

  2. 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.

  3. 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 to StravaApiError.

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

MIT

Available Tools

4 tools
get_activity_detailsGet activity detailsA
Read-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.

ParametersJSON Schema
NameRequiredDescriptionDefault
activity_idYesThe Strava activity ID.

TDQS

A4.5/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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 profileA
Read-only

Get the authenticated Strava athlete's profile: name, location, weight, preferred units and gear (shoes and bikes with their total distance).

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.3/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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 statsA
Read-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.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.2/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines3/5

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 activitiesA
Read-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.

ParametersJSON Schema
NameRequiredDescriptionDefault
afterNoOnly activities starting after this ISO 8601 date or date-time (UTC if no offset).
limitNoMaximum number of activities to return. Default 10, capped at 100.
beforeNoOnly activities starting before this ISO 8601 date or date-time (UTC if no offset).
sport_typeNoStrava sport type, or a list of them, for example "Run", "TrailRun", "Hike", "AlpineSki", "BackcountrySki" or "NordicSki". Case-insensitive.

TDQS

A4.4/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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.

  1. 4 tool updatesv0.1.0
    • First observedget_activity_details
    • First observedget_athlete_profile
    • First observedget_athlete_stats
    • First observedget_recent_activities

TDQS

A4.4/5.0

Scored across 4 tools

Disambiguation5/5

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.

Naming Consistency5/5

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.

Tool Count4/5

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.

Completeness4/5

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

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers