npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@guutong/strava-mcp-kit

v0.1.3

Published

Strava MCP toolkit — Model Context Protocol server for the Strava v3 API. Read + write tools for athletes, activities, segments, segment efforts, clubs, gear, routes, uploads, and streams. Includes dry_run snippet generation, an OpenAPI describe meta tool

Readme

strava-mcp-kit

npm version npm downloads types: TypeScript CI License: MIT Node

A Model Context Protocol server for the Strava v3 API. Covers every endpoint in the public swagger spec — 34 endpoint tools across Athletes, Activities, Segments, Segment Efforts, Clubs, Gear, Routes, Uploads, and Streams (read + write), plus 4 meta tools for OAuth bootstrap, conventions, and offline endpoint lookup. Every endpoint tool accepts dry_run: true.

The server is hand-written on top of the spec (instead of swagger-codegen's bulky generated client). It uses fetch directly with Zod-validated tool schemas, which keeps the runtime small and the surface easy to audit.

Meta tools at a glance

| Tool | Why it matters | |---|---| | strava_api_conventions | One call returns the Strava conventions cheat sheet — pagination iteration rule, object representations, polylines, dates, status codes, rate limits, scopes. Skip the docs site. | | strava_describe_endpoint | Look up any endpoint's parameters, response, scopes, rate limit, and inline notes (pagination hint, date format, polyline reminder) without making a call. | | strava_oauth_authorize_url | Build the OAuth authorize URL for the user to open in a browser. Works without existing credentials. | | strava_oauth_exchange_code | Exchange the authorization code for access + refresh tokens. Bootstrap a fresh integration in two tool calls. |

Two modes — pick one (or both)

Mode 1 — End user (fitness analysis)

Set STRAVA_* env vars and every tool calls Strava live. Useful prompts an LLM can answer once connected:

  • Weekly time-in-zone breakdown → polarized / pyramidal / threshold mix
  • TSS / training load by intensity × duration
  • HR drift within a single activity → aerobic decoupling
  • FTP / threshold pace trend via repeated segment efforts
  • Overtraining signals from rising recovery HR
  • Week-over-week comparisons in distance, intensity, and elevation

Mode 2 — Developer (Strava SDK assistant)

No credentials required. The server boots without any env vars and gives an LLM everything it needs to integrate with Strava without alt-tabbing to the docs:

  • strava_api_conventions — full conventions cheat sheet (pagination iteration rule, object representations, polylines, dates, status codes, scopes).
  • strava_describe_endpoint — full OpenAPI spec slice for any endpoint (parameters, response, scopes, rate limits, contextual notes).
  • dry_run: true — every endpoint tool accepts this flag. When true, the server returns the request descriptor plus copy-pasteable curl, TypeScript fetch, and Python requests snippets. Works for both read and write tools — see exactly how POST /activities should send its multipart body before you write any code.
  • strava_oauth_authorize_url + strava_oauth_exchange_code — bootstrap a fresh Strava integration in two tool calls, no curl required.

Ask Claude:

"Show me how to call getActivityStreams for activity 12345 with heartrate and watts. Use dry_run."

Install

Published to npm as @guutong/strava-mcp-kit. Requires Node 18+. CI runs against Node 20 and 22.

Use directly via npx (zero install):

npx -y @guutong/strava-mcp-kit

Or install globally:

npm install -g @guutong/strava-mcp-kit
strava-mcp-kit       # binary name stays unscoped

Or develop from source:

git clone https://github.com/Guutong/strava-mcp-kit.git
cd strava-mcp-kit
npm install
npm run build

Or run from source without a build step:

npm run dev

Open the official MCP inspector pointed at this server:

npm run inspect

OAuth setup (one time)

Option A — bootstrap from inside the MCP host (recommended)

Once the server is connected to your LLM client, ask it:

1. Use strava_oauth_authorize_url with client_id="12345" and scopes=["read","activity:read_all","profile:read_all"].
   Open the returned URL in a browser, click Authorize, and paste the `code` query parameter back here.

2. Then call strava_oauth_exchange_code with that client_id, my client_secret, and the code.
   Save the refresh_token it returns.

The LLM walks you through the redirect (the redirect URL itself fails to load — that is expected). You end up with access_token, refresh_token, expires_at, and the athlete summary in one tool result. Set the refresh token in your MCP host config and you are done.

Option B — manual curl

  1. Create a Strava API application at https://www.strava.com/settings/api. Set Authorization Callback Domain to localhost for dev.
  2. Open this URL in a browser, replacing YOUR_CLIENT_ID:
    https://www.strava.com/oauth/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=http://localhost/exchange_token&approval_prompt=force&scope=read,activity:read_all,profile:read_all,activity:write,profile:write
  3. Copy the code from the redirect URL bar.
  4. Exchange:
    curl -X POST https://www.strava.com/api/v3/oauth/token \
      -d client_id=YOUR_CLIENT_ID -d client_secret=YOUR_CLIENT_SECRET \
      -d code=AUTH_CODE -d grant_type=authorization_code

Scopes

| Scope | Required for | |---|---| | read | Public profile, public clubs, public segments | | activity:read_all | Read all activities including private | | profile:read_all | Athlete weight, FTP, max HR | | activity:write | Tools marked (w) — create/update activity, star segment, upload | | profile:write | strava_update_logged_in_athlete |

Drop write scopes if you only need read access.

Configure environment variables

export STRAVA_CLIENT_ID=12345
export STRAVA_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
export STRAVA_REFRESH_TOKEN=yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy

Shortcut for short experiments: set STRAVA_ACCESS_TOKEN directly. Token expires after ~6 hours.

Use with Claude Desktop

Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):

{
  "mcpServers": {
    "strava": {
      "command": "npx",
      "args": ["-y", "@guutong/strava-mcp-kit"],
      "env": {
        "STRAVA_CLIENT_ID": "12345",
        "STRAVA_CLIENT_SECRET": "your-client-secret",
        "STRAVA_REFRESH_TOKEN": "your-refresh-token"
      }
    }
  }
}

If you cloned from source, point at the local build instead:

{
  "command": "node",
  "args": ["/absolute/path/to/strava-mcp-kit/dist/index.js"]
}

Restart Claude Desktop and verify that the strava_* tools show up in the tools panel.

Use with Claude Code

claude mcp add strava \
  --env STRAVA_CLIENT_ID=... \
  --env STRAVA_CLIENT_SECRET=... \
  --env STRAVA_REFRESH_TOKEN=... \
  -- node /absolute/path/to/strava-mcp-kit/dist/index.js

Tools (38 total)

(w) marks a tool that needs activity:write or profile:write scope.

| Group | Tool | Purpose | |---|---|---| | Meta | strava_api_conventions | Returns the Strava conventions cheat sheet (pagination, dates, polylines, status codes, scopes). No credentials needed. | | | strava_describe_endpoint | Full OpenAPI spec slice (params, response, scopes, rate limits, contextual notes) — no credentials needed | | | strava_oauth_authorize_url | Build the OAuth authorize URL for any client_id and scope set | | | strava_oauth_exchange_code | Exchange the authorization code for access + refresh tokens | | Athlete | strava_get_logged_in_athlete | Profile, weight, FTP, max HR | | | strava_update_logged_in_athlete (w) | Update athlete weight | | | strava_get_logged_in_athlete_zones | HR / power zones | | | strava_get_athlete_stats | Year-to-date and all-time totals | | Activities | strava_get_logged_in_athlete_activities | List activities (filter with after / before epoch) | | | strava_get_activity_by_id | Detailed activity | | | strava_create_activity (w) | Create a manual activity | | | strava_update_activity_by_id (w) | Update name, sport_type, gear, etc. | | | strava_get_activity_laps | Lap data | | | strava_get_activity_zones | Time-in-zone splits (Strava Summit only) | | | strava_get_activity_streams | Raw time-series (HR, watts, cadence, latlng, altitude...) | | | strava_get_activity_comments | Comments | | | strava_get_activity_kudoers | Athletes who gave kudos | | Segments | strava_get_segment_by_id | Segment details | | | strava_get_starred_segments | Starred segments | | | strava_star_segment (w) | Star or unstar a segment | | | strava_explore_segments | Find segments inside a bounding box | | | strava_get_segment_streams | Streams | | Segment Efforts | strava_get_segment_efforts | All your efforts on a segment over time | | | strava_get_segment_effort_by_id | Single effort | | | strava_get_segment_effort_streams | Streams | | Clubs | strava_get_logged_in_athlete_clubs, strava_get_club_by_id, strava_get_club_members, strava_get_club_admins, strava_get_club_activities | Club info | | Gear | strava_get_gear_by_id | Bike or shoe details | | Routes | strava_get_routes_by_athlete, strava_get_route_by_id, strava_get_route_streams | Routes | | | strava_export_route_gpx, strava_export_route_tcx | Export route to GPS file | | Uploads | strava_create_upload (w) | Upload a GPX/TCX/FIT activity (dry_run does not read the file) | | | strava_get_upload_by_id | Poll upload status |

Every endpoint tool accepts a special dry_run: boolean argument. When true, the tool does not call the Strava API; it returns the request descriptor and copy-pasteable code snippets in curl, TypeScript, and Python.

Supported stream keys

time, distance, latlng, altitude, velocity_smooth, heartrate, cadence, watts, temp, moving, grade_smooth

Strava API conventions

The single most important rule the LLM should know: paginate by iterating until you receive an empty array — Strava can return fewer than per_page items even when more pages remain. The full set of conventions (object representations via resource_state, polyline encoding, ISO-8601 dates, rate limits, scopes) lives behind the strava_api_conventions tool. Call it once at the start of an integration session and the LLM has the rules it needs.

strava_describe_endpoint also embeds the relevant subset of these conventions per endpoint as notes — pagination warning where applicable, ISO-8601 reminder for date params, multipart hint for upload bodies, polyline reminder for endpoints that return map.polyline.

Example prompts — Developer mode (no credentials)

Call strava_api_conventions and show me the pagination rule and rate limits.
Use strava_describe_endpoint for getActivityStreams. Show me the parameters,
response shape, and which OAuth scope I need.
Call strava_get_activity_streams with dry_run=true, id=12345678,
keys=["heartrate","watts"]. Give me the Python requests snippet.
I want to upload a FIT file from my app. Use strava_create_upload with
dry_run, file_path="/tmp/ride.fit", data_type="fit". Show me the multipart
body shape so I can replicate it.

Sample dry-run output:

{
  "request": {
    "method": "GET",
    "url": "https://www.strava.com/api/v3/activities/12345678/streams?keys=heartrate%2Cwatts&key_by_type=true",
    "headers": {
      "Authorization": "Bearer <STRAVA_ACCESS_TOKEN>",
      "Accept": "application/json"
    }
  },
  "snippets": {
    "curl": "curl -X GET '...' -H 'Authorization: Bearer <STRAVA_ACCESS_TOKEN>' ...",
    "typescript": "const res = await fetch(...);\nconst data = await res.json();",
    "python": "import requests\n\nurl = '...'\n..."
  },
  "notes": [
    "Authorization header uses a placeholder. Replace <STRAVA_ACCESS_TOKEN> with a real bearer token."
  ]
}

Example prompts — End-user mode (fitness analysis)

Pull my last 30 days of activities. Compute total distance, time, and weekly
load. Flag any week-over-week jumps over 10%. Estimate what % of time I
spent in Zone 2 vs Zones 4-5.
Find the segment I rode most often this month. Compare my last 5 efforts.
Watch pace, average HR, and HR drift. Am I getting fitter or fatigued?
For activity 12345678, fetch heartrate + watts + velocity_smooth streams.
Build a time-in-zone histogram. Compare TSS to my FTP from
strava_get_logged_in_athlete.

Environment variables

| Var | Required | Description | |---|---|---| | STRAVA_ACCESS_TOKEN | Either this or the trio below | Short-lived bearer token (~6 hours). | | STRAVA_CLIENT_ID | For refresh-token mode | From your Strava app settings. | | STRAVA_CLIENT_SECRET | For refresh-token mode | From your Strava app settings. | | STRAVA_REFRESH_TOKEN | For refresh-token mode | Long-lived refresh token. | | STRAVA_API_BASE_URL | No | Override API base. Default https://www.strava.com/api/v3. |

If you only use developer mode (dry_run + describe_endpoint) you can leave all of these unset. Live tools then return a clear Strava credentials missing error if you forget to pass dry_run.

Project layout

src/
├── index.ts          Entry — boots McpServer with stdio transport
├── client.ts         REST client, OAuth refresh, buildRequest() (used by dry_run)
├── snippets.ts       Formats a built request into curl / TypeScript / Python
├── spec.ts           Loads strava-spec.json, exposes describeOperation() with notes
├── conventions.ts    Conventions cheat sheet text + OAuth helpers (URL builder, code exchange)
├── tools.ts          Zod-typed tool registry: 34 endpoint tools + 4 meta tools
└── strava-spec.json  Strava swagger 2.0 spec (copied to dist/ at build time)
tests/                Vitest unit tests (no network, no fetch mocks)
.github/workflows/    CI (typecheck, lint, test, build) + tag-gated npm publish

Contributing

Pull requests are welcome. Please read CONTRIBUTING.md for the dev setup, the descriptor pattern used by every tool, and the step-by-step recipe for adding a new Strava endpoint. Contributors are expected to follow CODE_OF_CONDUCT.md. For AI assistants helping on the repo, see CLAUDE.md.

Quick start for contributors:

npm install
npm run dev          # run the server with tsx
npm run inspect      # MCP inspector UI
npm run lint:fix     # Biome format + lint with autofix
npm test             # vitest

CI (.github/workflows/ci.yml) runs typecheck, lint, tests, and build on every PR against Node 20 and 22. A tag push (v*) additionally publishes to npm — set NPM_TOKEN in repo secrets to enable that.

Troubleshooting

Strava credentials missing No env vars set, and dry_run was not passed. Either configure STRAVA_ACCESS_TOKEN or the CLIENT_ID + CLIENT_SECRET + REFRESH_TOKEN trio, or pass dry_run: true.

StravaApiError 401 Access token expired and there is no refresh token configured, or the refresh token was revoked. Re-run the OAuth flow.

StravaApiError 403 Missing scope. Reading a private activity needs activity:read_all. Writes need activity:write or profile:write.

StravaApiError 429 Rate limit hit. Strava allows 100 requests / 15 min and 1000 / day per access token, shared across all endpoints. Wait until the next 15-minute window or 00:00 UTC.

Limitations

  • strava_get_activity_zones requires Strava Summit on the athlete account.
  • Stream endpoints default to key_by_type=true (matches Strava's own recommendation).
  • strava_get_athlete_stats only returns data for the authenticated athlete's id.
  • The bundled strava-spec.json reflects the Strava swagger at the time of release. If the API gains new endpoints, refresh the spec and add a tool — see CONTRIBUTING.md.

License

MIT — see LICENSE.