strava-mcp-bridge
Bridges local stdio MCP clients like Codex and OpenCode to Strava's official MCP endpoint, enabling Strava data access via imported OAuth credentials from Claude Code without a developer app.
README
strava-mcp-bridge
Use the official Strava MCP from local stdio AI coding tools — no Strava developer app required.
This is an unofficial, community-built project. It is not affiliated with or endorsed by Strava, Anthropic (Claude Code), OpenAI (Codex), or the OpenCode maintainers.
This project is an experimental compatibility bridge for MCP clients that can run local stdio servers, such as Codex, OpenCode, and similar coding agents. It forwards safe MCP requests to Strava's official remote MCP endpoint:
stdio MCP client
-> strava-mcp-bridge
-> https://mcp.strava.com/mcp
Quick Start
# One-time prerequisite: add and authorize the official Strava MCP in Claude Code
claude mcp add --transport http strava https://mcp.strava.com/mcp
# ...then run /mcp inside Claude Code and complete the Strava authorization.
# Install the bridge (Apple Silicon macOS; requires Xcode Command Line Tools)
npm install -g strava-mcp-bridge
# Import the credential into the bridge's own Keychain item
strava-mcp-bridge bootstrap
bootstrap prints a config snippet; add it to the target project's
.codex/config.toml. See Usage for the full flow and
Safety Defaults for what is allowed before you enable any
tools.
Current Scope
This bridge is macOS Apple Silicon only (darwin arm64).
The current bootstrap assumption is:
- The user authorizes Strava MCP through Claude Code.
- Claude Code stores the official Strava MCP OAuth token locally.
- The user explicitly runs
strava-mcp-bridge bootstraporstrava-mcp-bridge auth import. - The bridge imports the Strava MCP OAuth credential into its own macOS Keychain item.
- After that import, this bridge reads and refreshes its own Keychain credential and no longer depends on Claude Code.
You can use Claude Code with Anthropic's normal model backend, or launch Claude Code through Ollama or another Claude Code-compatible backend. The model backend is not the important part. The important part is that Claude Code completed the official Strava MCP OAuth flow.
How This Differs From Other Strava MCP Projects
- Developer-app-free bootstrap — no Strava developer app, client ID, or secret to get started.
- Claude Code OAuth bootstrap — the initial credential comes from an official Strava MCP OAuth flow completed in Claude Code, imported on an explicit user action.
- Bridge-owned Keychain credential — after import, the credential lives in the bridge's own macOS Keychain item and is refreshed independently.
- Talks to the official Strava MCP — a bridge to Strava's official endpoint, not a self-built MCP server wrapping the Strava REST API.
What This Does Not Do
- It does not bypass Strava authorization.
- It does not provide shared Strava credentials.
- It does not require a Strava developer app for the current Claude Code bootstrap mode.
- It does not call Strava data tools by default.
- It does not print OAuth token values.
- It does not store OAuth token values outside the bridge-owned macOS Keychain item.
- It does not modify Claude Code config or Keychain credentials.
- It does not write refreshed tokens back into Claude Code's Keychain item.
- The MCP server startup path does not import from Claude Code. Import is a
separate, explicit
bootstraporauth importcommand.
Safety Defaults
Allowed by default:
initializenotifications/initializedtools/list
Blocked by default:
tools/call- every other JSON-RPC method
tools/list is also filtered. The MCP client only sees tools that are present
in the local --allow-tool list, even if the upstream Strava MCP server exposes
more tools.
If get_activity_streams is explicitly allowed, the bridge still requires an
explicit stream whitelist and blocks location-like streams before forwarding the
request. The currently allowed stream names are:
timeheart_ratevelocity_smoothcadencealtitudedistancetempwattsgrade_smoothmoving
Location/GPS/polyline-like stream names are blocked.
For other JSON tool responses, the bridge redacts common location-like fields
before returning content to the MCP client context, including location,
latlng, start_latlng, end_latlng, lat, lng, latitude, longitude,
polyline, summary_polyline, and map. This is a defensive filter around an
upstream service whose response schemas can change; keep new tools disabled
until you have reviewed their output and privacy implications.
Full get_activity_streams arrays can be very large, so the bridge writes them
to disk and returns only a small summary containing streams_file, stream names,
and point counts. This avoids putting full activity streams into the coding
agent context.
By default, local Strava data is stored under:
~/Library/Application Support/strava-mcp-bridge/
Stream files go to:
~/Library/Application Support/strava-mcp-bridge/streams/
Use --data-dir to move all bridge-managed local data, or
--stream-output-dir to override only the stream file sink.
The equivalent environment variables are STRAVA_MCP_DATA_DIR and
STRAVA_MCP_STREAM_OUTPUT_DIR.
Security-sensitive environment variables are treated the same as command-line
flags. Review MCP client env blocks before trusting a config. In particular,
STRAVA_MCP_TOKEN_ENDPOINT together with
STRAVA_MCP_ALLOW_TOKEN_ENDPOINT_OVERRIDE=1 can redirect OAuth refresh requests
and must be used only for controlled local diagnosis.
Other supported environment variables, grouped by purpose:
- Upstream and protocol:
STRAVA_MCP_URL,MCP_PROTOCOL_VERSION - Auth and credential handling:
STRAVA_MCP_AUTH,STRAVA_MCP_BRIDGE_KEYCHAIN_SERVICE,STRAVA_MCP_CLAIM_ON_IMPORT,STRAVA_MCP_NO_CLAIM_ON_IMPORT,STRAVA_MCP_REFRESH_SKEW_SECONDS,STRAVA_MCP_ACCESS_TOKEN(only read with--auth env) - Timeouts:
STRAVA_MCP_OAUTH_TIMEOUT_MS,STRAVA_MCP_UPSTREAM_TIMEOUT_MS,STRAVA_MCP_KEYCHAIN_TIMEOUT_MS - Tool allowlist and Keychain plumbing:
STRAVA_MCP_ALLOWED_TOOLS,STRAVA_MCP_KEYCHAIN_BACKEND,STRAVA_MCP_KEYCHAIN_HELPER
Requirements
- Node.js 20.11+
- Apple Silicon macOS (
darwin arm64) - Swift compiler / Xcode Command Line Tools, used to build the native Keychain
helper. Install them first with
xcode-select --install;npm install -gcompiles the helper and fails with instructions when they are missing. - A prior Claude Code Strava MCP authorization for the first import (see
Usage for the one-time
claude mcp addprerequisite)
Usage
Install or set up the native Keychain helper:
npm install -g strava-mcp-bridge
When running from a source checkout:
npm run setup
# or:
node bin/strava-mcp-bridge.js setup
Run a read-only local check:
strava-mcp-bridge doctor
strava-mcp-bridge doctor --json
The first credential comes from Claude Code, so add and authorize the official Strava MCP there once:
claude mcp add --transport http strava https://mcp.strava.com/mcp
Then, inside Claude Code, run /mcp, select strava, and complete the OAuth
authorization in the browser. This is a one-time prerequisite; after the import
below, the bridge no longer depends on Claude Code.
Once Claude Code has authorized Strava MCP, bootstrap the bridge-owned credential:
strava-mcp-bridge bootstrap
bootstrap builds the native helper if it is missing, imports the Claude Code
Strava MCP OAuth credential only if the bridge-owned Keychain item is missing
or incomplete, refreshes it only when a refresh is due, and always prints a
Codex MCP config snippet. Token values are never printed.
If Claude Code has no Strava MCP credential yet, or if the copied refresh token is stale, the command exits with a specific next action. Typical fixes are:
- Add and authorize Strava MCP in Claude Code first (see the
claude mcp addcommand above), then rerunstrava-mcp-bridge bootstrap. - If macOS shows a Keychain permission dialog, click "Always Allow" (see "Why Does macOS Ask For Keychain Permission?"), then rerun the command if it timed out while waiting.
- If the native helper is missing, run
strava-mcp-bridge setup.
You can also explicitly import the credential into the bridge-owned Keychain item:
strava-mcp-bridge auth import
auth import refreshes once by default. That claims the refresh-token chain for
the bridge-owned credential, which is what makes later operation independent
from Claude Code. Because Strava refresh tokens rotate, Claude Code's copied
Strava MCP refresh token may become stale after this. Claude Code can
re-authenticate Strava later if it needs its own fresh credential again.
To import without immediately claiming the refresh token chain:
strava-mcp-bridge auth import --no-claim-on-import
Inspect bridge credential metadata without printing token values:
strava-mcp-bridge auth status
strava-mcp-bridge auth status --json
Generate Codex MCP config without touching any file:
strava-mcp-bridge config codex --profile minimal
strava-mcp-bridge config codex --profile training-sync \
--stream-output-dir /absolute/path/to/strava-data/mcp-streams
Run with a token provided by environment variable (requires --auth env;
without it, the default bridge-keychain mode ignores the variable):
STRAVA_MCP_ACCESS_TOKEN=... node bin/strava-mcp-bridge.js --auth env
Run with the bridge-owned macOS Keychain credential. This is the default mode:
strava-mcp-bridge --auth bridge-keychain
bridge-keychain reads a separate bridge-owned Keychain item named:
Strava MCP Bridge Native-credentials
If this item does not exist, the server exits and tells you to run
strava-mcp-bridge auth import. This avoids hidden mutation of Claude Code
credentials during MCP server startup.
Keychain reads and writes use a small Swift helper built on macOS
Security.framework. Secrets are passed to the helper over stdin/stdout, not as
command-line arguments. The helper only accepts Keychain services with the
Strava MCP Bridge prefix.
If multiple MCP clients run bridge processes at the same time, one process may rotate the Strava refresh token while another still has an older in-memory copy. When Strava rejects that older refresh token, the bridge re-reads its Keychain credential once and retries with the latest stored credential.
The one-time Claude Code import path still uses
/usr/bin/security find-generic-password -w to read Claude Code's existing
Keychain item because that item is owned by Claude Code's access control list.
This read is not a steady-state dependency and does not put secrets in
command-line arguments.
On server runs it reads only that bridge-owned item. It refreshes tokens through Strava MCP's OAuth token endpoint when the access token has one hour or less left before expiry, and also retries once after an upstream HTTP 401 by forcing a refresh.
Keychain helper calls time out after 120 seconds by default. OAuth refresh and upstream MCP requests time out after 30 seconds by default.
For emergency diagnosis only, the old /usr/bin/security CLI backend can be
selected explicitly. This fallback is read-only and cannot write refreshed
credentials:
STRAVA_MCP_KEYCHAIN_BACKEND=security-cli node bin/strava-mcp-bridge.js --auth bridge-keychain
Run with the direct Claude Code Keychain helper for migration or diagnosis:
strava-mcp-bridge --auth claude-code-keychain
Allow a specific tool call:
strava-mcp-bridge --auth bridge-keychain --allow-tool health
Allow full-resolution stream fetches while keeping the large result out of context:
strava-mcp-bridge \
--auth bridge-keychain \
--allow-tool get_activity_streams
For a project-specific pipeline, set an explicit stream sink:
strava-mcp-bridge \
--auth bridge-keychain \
--allow-tool get_activity_streams \
--stream-output-dir /absolute/path/to/strava-data/mcp-streams
The default mode is intentionally list-only. Do not enable tools until you have reviewed their schemas and privacy implications.
Removing The Bridge / Revoking Access
To delete the bridge-owned macOS Keychain credential:
strava-mcp-bridge auth remove # prints what would be removed, changes nothing
strava-mcp-bridge auth remove --yes # actually removes the Keychain item
Without --yes, the command only prints the target Keychain item and exits
without touching anything. auth remove deletes only the bridge-owned item
(Strava MCP Bridge Native-credentials); it never touches Claude Code's
credential, and removing when nothing is stored is a no-op.
This deletes the local credential only. To revoke the bridge's access on
Strava's side, deauthorize the connection from your Strava account settings
under the list of connected apps. After removal you can start over by re-running
strava-mcp-bridge bootstrap.
Why Does macOS Ask For Keychain Permission?
During bootstrap, and again after upgrades, macOS may show a dialog like
"strava-keychain-helper" wants to use your confidential information stored in
"Strava MCP Bridge Native-credentials". This is macOS Keychain access control
working as intended, not the bridge doing something unexpected. Two Keychain
items are involved:
Claude Code-credentials— read once by the macOSsecuritytool duringbootstrap/auth import. This is the explicit one-time import you requested; the bridge never touches this item outside those commands.Strava MCP Bridge Native-credentials— the bridge's own item, read and updated bystrava-keychain-helperduring normal operation.
What to click:
- Always Allow — macOS remembers the grant and future bridge sessions run without dialogs.
- Allow — grants a single read; the dialog returns on every access.
- Deny — the current command fails safely; nothing is read and nothing is lost.
Why it comes back after an upgrade: the helper is compiled from source on your machine at install time and is ad-hoc signed, so macOS ties "Always Allow" to that exact binary. A reinstall or upgrade produces a new binary, which macOS treats as a new program and asks about once more. One dialog per upgrade is expected.
The dialog is also a feature, not just friction: the audience for this bridge runs AI coding agents with shell access, and this dialog is the one visible signal a human gets when any new program tries to read the stored Strava credential. See THREAT_MODEL.md for the limits of this protection.
Codex Example
See examples/codex-config.toml.
Add the snippet to the target project's .codex/config.toml (project-scoped).
Avoid ~/.codex/config.toml unless you want every project to see the bridge.
This is the same TOML that strava-mcp-bridge config codex --profile minimal
prints (see Usage), so you can generate it instead of copying it.
For first-time setup from an AI coding tool, prefer the bundled skill at
skills/strava-mcp-bridge/SKILL.md. It tells
the agent to run doctor, then bootstrap, then use project-scoped config.
[mcp_servers.strava_bridge]
command = "node"
args = [
"/absolute/path/to/strava-mcp-bridge/bin/strava-mcp-bridge.js",
"--auth",
"bridge-keychain",
"--allow-tool",
"health,eligibility"
]
For training sync workflows, explicitly add the tools you need, for example:
"--allow-tool",
"health,eligibility,list_activities,get_activity_streams,get_activity_performance",
"--stream-output-dir",
"/absolute/path/to/strava-data/mcp-streams"
If you omit --stream-output-dir, stream files are written under the default
user-level data directory:
~/Library/Application Support/strava-mcp-bridge/streams/
After saving the config, restart Codex or start a new session, then verify the
link with the health tool before enabling any activity tools.
Development
Build the native Keychain helper:
npm run setup
Run tests:
npm test
The test suite uses only local mocks. It does not contact Strava and does not read Keychain.
Security
See SECURITY.md and THREAT_MODEL.md.
Research Status
Validated locally on 2026-07-08:
- Claude Code launched through Ollama can authenticate official Strava MCP.
- A non-Claude local client can complete
initializeandtools/listwith the user-authorized bearer token. - A stdio bridge can forward
initializeandtools/listto official Strava MCP while blockingtools/calllocally. - Strava MCP OAuth discovery identifies the token endpoint as
https://www.strava.com/oauth/mcp/token. - The bridge can import once from Claude Code, store its own credential, refresh with the official refresh token grant, and avoid writing anything back to Claude Code.
- Keychain operations use a Swift
Security.frameworkhelper by default, avoiding token-bearing command-line arguments.
The remaining deferred research question is truly independent first-time bootstrap:
How can a non-Claude-Code client obtain its first valid official Strava MCP
OAuth credential without Claude Code and without requiring a user-created Strava
developer app?
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.