OpenStride MCP Server
Connects to a user's Strava account to import bounded recent running activities (Run, TrailRun, and VirtualRun) with metadata such as name, date, distance, and moving time. Supports syncing recent runs and disconnecting the local Strava connection; imported activities do not automatically complete planned workouts.
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., "@OpenStride MCP Servercreate a 12-week half marathon plan, 4 runs a week"
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.
OpenStride
A personal running planner you can host on Cloudflare Workers and D1. Original, transparent training rules, optional Worker LLM recommendations, and calendar sync. Manual planning does not require AI or a Runna account. MIT licensed. Not affiliated with Runna or Strava.
Latest Changes
September 11, 2026
Worker AI recommendations use recent runs and connected calendar availability.
Google Calendar bridge refreshes availability and reconciles opted-in workouts.
Miles lead distances and paces; kilometers follow. Forms support both units.
Calendar and CSV exports include miles before kilometers. API storage remains metric.
Related MCP server: garmin-mcp
Features
Build 4–24 week plans for a running habit, 5K, 10K, half marathon, or marathon preparation.
Choose running days, a long-run day, your recent mileage, and training approach.
Get AI recommendations from recent running and calendar availability, then review before saving.
Check Google Calendar availability and opt individual plans into scheduled event sync.
Track completion, actual distance/time, effort, and notes. Move sessions to another date.
Explicitly reduce future training by 20%; completed sessions stay intact.
Download iCalendar and CSV files, and access your plans through a JSON API or MCP.
Optionally import recent runs from your own Strava account.
Server-rendered interface with a small script for distance-unit conversion. Cloudflare may inject its analytics beacon according to the zone configuration.
This is a single-person installation. Cloudflare Access can restrict browser login to one email address using emailed one-time codes. API and MCP clients use a separate bearer token with access to all installation data.
Run locally
Install Node.js 22 or newer and npm. From this directory:
npm ci
node scripts/local-secret.mjs
npm run db:local
npm run devOpen the local URL printed by Wrangler. Your running space uses HTTP Basic auth:
username runner, password the APP_TOKEN value in .dev.vars.
The home page is public. Keep .dev.vars private; it is ignored by Git.
npm run check
npm run buildbuild bundles the Worker in dry-run mode; it does not deploy.
Tests cover the engine, exports, and integration behavior. Strava API tests use
fixtures; connecting a real account is an optional separate verification.
Deploy
The app needs one Worker and one D1 database. Manual planning can run within
Workers Free limits. The checked-in configuration enables Workers AI, which adds
model usage subject to your account's allowances and pricing. Remove the ai
binding to disable recommendations. Calendar sync uses a separate online host.
npx wrangler login
npx wrangler d1 create openstride --config wrangler.jsoncReplace the deployment's database_id in wrangler.jsonc with the ID printed by
that command. If you change the database name, update database_name too. Choose
an available Worker name if openstride is already used in your account.
Generate a different production password and store it in your password manager:
node -e "console.log(require('node:crypto').randomBytes(32).toString('hex'))"
npx wrangler secret put APP_TOKEN --config wrangler.jsonc
npm run db:remote
npm run check
npm run build
npm run deployPaste the generated password at the secret prompt. Never put it in wrangler.jsonc
or source code. Set your own custom domain in routes; preview and workers.dev
hostnames are disabled. A token shorter than 32 characters fails closed.
For email-code login, create a Cloudflare Access self-hosted application for
YOUR-HOST/app, select One-time PIN, and allow only your email. Store
CF_ACCESS_TEAM_DOMAIN, CF_ACCESS_AUD, and OWNER_EMAIL as Worker secrets.
The Worker verifies the assertion signature, issuer, audience, expiry, and email.
Partial configuration fails closed; Basic authentication is disabled in Access mode.
Leave /api/* and /mcp reachable for bearer-authenticated clients. Browser exports
use /app/* and remain protected by Access. With no Access configuration, local
Basic authentication uses username runner and APP_TOKEN.
Cloudflare's documented free limits, checked September 10, 2026:
Resource | Free allowance |
Worker requests | 100,000/day across the account |
Worker CPU | 10 ms per invocation |
D1 rows read | 5 million/day |
D1 rows written | 100,000/day |
D1 storage | 5 GB total across the account |
Sources: Workers limits and D1 pricing. These are account limits, shared with your other apps. This is intended for personal use, not unlimited public traffic. On Free, exhausted limits can make requests fail. On a paid account, paid pricing applies. Check your account plan and usage before deploying. A local test does not prove production CPU usage; inspect Cloudflare's CPU metrics after deployment.
Training rules
Read src/engine.ts for the full algorithm. It is original code, not a Runna
algorithm reproduction or a clinically validated coaching system.
The engine starts from recent weekly and longest-run distances. A zero baseline starts with a small run-walk schedule. Gentle, balanced, and challenging approaches use 4%, 6%, and 8% peak-volume increments, respectively; every fourth non-taper week reduces volume. Race-oriented plans finish with a taper. Long runs and total volume have caps. Low longest-run baselines can reduce the weekly total further. Distances are allocated in tenths of a kilometer. A recent 5K result optionally provides rough pace ranges. Quality sessions begin after the first two weeks, with at most one per week; recovery and taper weeks use easy work.
These are software heuristics, not evidence that any progression is safe for you. Race goals mean preparation: the app does not automatically insert a race-distance workout, assess injury, establish race readiness, or replace a coach. Review each plan, adjust for your circumstances, and stop a session that is unsuitable.
Weeks are seven-day windows starting on your chosen date. Dates are calendar strings; exports use all-day events. "Today" in the interface uses UTC. Moving a workout preserves its original training-week grouping.
Easing is explicit, never inferred from a missed run or imported activity. Repeating an ease request with the same start date is idempotent. A different start date can reduce overlapping future sessions again. Imported activities do not automatically complete workouts.
Worker AI recommendations
The checked-in configuration binds Workers AI as AI with remote inference.
The model is @cf/meta/llama-3.3-70b-instruct-fp8-fast. Remote inference, including
local development with that binding, consumes account usage. Review
Workers AI pricing
before enabling it; no zero-cost guarantee is implied.
Open New plan, review the starting distances, choose your constraints, and add optional notes. Get AI recommendations returns a preview without saving a plan or writing calendar events. The model receives your configuration and notes, a summary of imported runs from the latest 28 calendar days, and daily calendar availability totals. Activity names, IDs, GPS routes, appointment titles, and appointment descriptions are excluded from its prompt. Notes are sent as entered.
Recommendations preserve your goal, start date, plan length, long-run day, and provided 5K result. Recommended days must fit your selected days; training volume and intensity cannot exceed your submitted baseline. The training engine builds workouts after you review and save. The browser retains a model rationale only when the saved configuration matches its server-side draft. Drafts expire after 24 hours. Manual settings remain available if AI fails or is disabled.
The Worker allows 20 attempts per installation per UTC day, including failures, with 1,200 output tokens and a 25-second application timeout per attempt. The timeout is not a billing guarantee. Invalid output is rejected. Check the returned recommendation and rationale when verifying model behavior.
Google Calendar connection and sync
The bridge uses an existing authenticated gog installation on an online host.
Google credentials remain there; the browser has no Google token entry form.
The account needs Calendar read access and write access to a chosen destination
if you enable plan sync. Confirm stored authorization with gog auth doctor.
Create a private ~/.config/openstride/calendar-bridge.json:
{
"url": "https://YOUR-HOST",
"token": "YOUR_APP_TOKEN",
"account": "YOUR_GOOGLE_ACCOUNT",
"gog": "/opt/homebrew/bin/gog"
}Use your actual installation token/account, set file permissions to 600, and
never commit it. The URL must be an HTTPS origin without a path or credentials.
Run the first import on the host where gog is authorized:
node scripts/calendar-bridge.mjs ~/.config/openstride/calendar-bridge.jsonIn Calendar, select Personal or the other calendars whose busy time should inform planning. Initial setup is read-only: no destination is selected and no plan is opted into event sync. A newly discovered primary calendar is the default availability calendar. Run another sync after selecting calendars to refresh their busy intervals. Choose a writable destination and time zone/running window, then explicitly Enable calendar sync inside each desired plan. Saving calendar settings alone does not enable plan sync.
The bridge imports a bounded 180-day window (one day back and 179 days ahead), with at most 2,000 busy intervals and 512 KiB per upload. It uploads calendar names/IDs/time zones, busy intervals, and sync receipts. Existing appointment titles, descriptions, attendees, and locations are not uploaded. Stale or incomplete availability is not presented as connected. Changing read calendars requires a new snapshot before their availability is used.
Timed runs are placed within free intervals in your daily running window; sessions without enough room are flagged as conflicts. Stable event IDs and private ownership markers restrict changes to OpenStride events. Receipt fingerprints skip unchanged writes and let failed changes retry. Concurrent or older snapshot writes are rejected instead of silently overwriting newer state. Past and completed sessions are preserved when sync is disabled. Google Calendar edits do not update workout records; edit the plan in OpenStride. This is not general two-way editing.
Optional five-minute host schedule
scripts/calendar-sync.zsh acquires a local lock, loads the existing private
~/.config/gogcli/agent-env.zsh environment, and runs the bridge. It expects
calendar-sync.zsh and calendar-bridge.mjs in ~/tools/openstride/ and Node at
/opt/homebrew/bin/node. Adapt these paths for another host.
scripts/com.barkleesanders.openstride-calendar.plist.template runs every 300
seconds and at load. Replace every __HOME__ with the host's absolute home path
before installing it in ~/Library/LaunchAgents/. The repository provides the
template; it does not install or activate a LaunchAgent. Installation and bootstrap
belong to the deployment step, after checking the wrapper's prerequisites.
Logs use ~/tools/openstride/calendar-sync.log and calendar-sync-error.log.
The host must remain online; stopping the agent pauses sync. If a stale lock is
reported, verify the earlier process has stopped before removing the lock.
Optional Strava import
Create your own API application at Strava API settings. Strava currently requires a subscription to create an API application; existing applications can be reused. See Strava getting started. Configure its authorization callback domain to match your deployment hostname. Set these additional secrets:
npx wrangler secret put STRAVA_CLIENT_ID --config wrangler.jsonc
npx wrangler secret put STRAVA_CLIENT_SECRET --config wrangler.jsonc
npx wrangler secret put STRAVA_REDIRECT_URI --config wrangler.jsoncUse https://YOUR-HOST/app/strava/callback for the
redirect URI. For local development, add the same variables to .dev.vars and
use a localhost callback accepted by your Strava app settings.
Open My running → Connect Strava, grant activity-read access, and then sync. The adapter fetches one page of at most 100 recent activities and retains Run, TrailRun, and VirtualRun metadata. It stores name, date, distance, and moving time; it does not store GPS routes, heart rate, or activity photos. This is a bounded recent import, not a complete historical backup. Strava rate limits and developer application restrictions still apply. See Strava authentication.
Tokens are encrypted with AES-GCM using a key derived from APP_TOKEN. Changing
that password makes existing tokens unreadable: reconnect Strava afterward.
Existing Strava CLI bridge
If an existing strava-pp-cli installation owns your Strava tokens, keep refreshes
there and run node scripts/strava-bridge.mjs /path/to/private/bridge.json hourly.
The private config contains url (your HTTPS origin), token (APP_TOKEN), and
optionally cli (absolute CLI path). Never commit this file.
The first successful run imports history, capped at 4,000 source activities; later runs refresh the last 90 days. Reaching the cap fails visibly without claiming a complete import. Only Run, TrailRun, and VirtualRun records are sent. Stable Strava IDs prevent duplicates. Imports update metadata but do not remove older records or mark plan sessions complete. The dashboard shows the last receipt time. The bridge requires the host to be online; stop its scheduler to pause sync.
POST /api/activities/import and MCP import_activities accept batches of up to
100 validated run records. Apple Watch workouts can reach this bridge through
Strava's Health integration on iPhone; this does not import sleep, HRV, or the
entire Apple Health database.
Disconnect removes the local connection; revoke application access in Strava to
revoke its authorization there. Previously imported activity records remain.
Apple Health and Android Health Connect are not directly supported by this Worker; they require a native companion with device permissions. Watch guidance, live GPS recording, and push notifications are also outside this web app.
Data and maintenance
Plans, activities, calendar snapshots, sync receipts, and AI drafts live in D1. Enabled recommendations call Workers AI, Strava adapters call Strava, and the host calendar bridge calls Google Calendar. Cloudflare processes requests and may inject its analytics beacon or retain operational logs according to your settings. Do not share your installation token with an untrusted MCP client.
The dashboard displays the 10 most recent plans and 100 most recent activities. Keep plan IDs from the API or your links to access older plans directly. Plan changes use optimistic concurrency checks so simultaneous edits cannot silently overwrite each other. Conflicting updates require reloading and retrying.
Back up the complete database before changing migrations:
npx wrangler d1 export DB --remote --output openstride-backup.sql --config wrangler.jsoncTreat that backup as private: it includes your training data and encrypted connection records. Keep the corresponding installation token separately if restoring a Strava connection. Do not commit backups. Update dependencies deliberately, run the checks, then deploy. Deleting a Worker does not automatically delete its D1 database.
JSON API
Send Authorization: Bearer YOUR_APP_TOKEN. Browser exports use the dashboard's
configured Access or Basic login. JSON requests use Content-Type: application/json.
Browser mutations require a matching Origin; cross-origin requests are rejected.
Method | Path | Input / result |
GET |
| Latest ten plans |
POST |
| Plan configuration; returns saved plan |
POST |
|
|
GET / PUT |
| Snapshot/settings / save read calendars, destination, time zone, and window |
GET |
| Desired event changes for the host bridge |
POST |
| Calendar snapshot and event receipts from the bridge |
PUT |
|
|
GET |
| Complete plan |
PATCH |
| Status, date, actualKm, actualMinutes, effort, notes |
POST |
|
|
GET |
| All-day calendar export |
GET |
| Spreadsheet export |
GET / POST |
| List / record an activity |
POST |
|
|
GET |
| Strava configuration and connection status |
POST |
| Import bounded recent runs |
DELETE |
| Forget local Strava connection |
Example plan body:
{
"name": "Autumn running",
"goal": "10k",
"startDate": "2026-10-01",
"weeks": 12,
"currentWeeklyKm": 20,
"currentLongestKm": 8,
"days": [2, 4, 7],
"longRunDay": 7,
"intensity": "balanced",
"recent5kMinutes": 30
}Weekdays use ISO numbering: Monday 1 through Sunday 7. Choose 2–6 distinct days, including the long-run day. Distances must be 0–100 km in tenths, with longest run no greater than weekly distance. Optional 5K time must be 12–90 minutes. Unknown configuration fields are rejected. See Zod schemas for the complete input contract.
Manual activity body: date, name, distanceKm, durationMinutes.
Workout status is planned, completed, or skipped; effort is 1–10.
Imported runs and workout completion logs are distinct, avoiding automatic matching
or accidental double attribution. Endpoints return 400 for invalid inputs, 404 for
missing resources, and 409 for conflicting edits. No delete-plan endpoint is
provided; export data before performing deliberate database maintenance.
MCP
Point a client that supports custom bearer headers at
https://YOUR-HOST/mcp using the same installation token.
This is a stateless HTTP JSON-RPC endpoint; POST returns JSON, GET streaming is not
implemented. It negotiates MCP protocol 2025-03-26 and requires no session ID.
It does not implement OAuth discovery, so clients that require OAuth rather than
static bearer credentials cannot connect directly.
Tools: list_plans, get_plan, create_plan, update_workout, ease_plan,
list_activities, log_activity, export_calendar, export_csv, strava_status,
sync_strava, disconnect_strava, import_activities, propose_plan,
calendar_status, configure_calendar, and sync_plan_calendar. tools/list
returns each input schema. propose_plan returns a recommendation without saving;
after review, pass its configuration to create_plan. Calendar sync is opt-in via
sync_plan_calendar, after choosing a destination with configure_calendar.
Connect Strava in your browser before invoking its import tools.
Example initialization body:
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {},
"clientInfo": { "name": "my-client", "version": "1.0" }
}
}Then send a notifications/initialized notification and tools/list or
tools/call requests. Tools can change your data; approve mutations according to
your MCP client's controls. The shared token gives full access to this installation.
Contributing
Use npm ci, keep changes focused, and run npm run check plus npm run build.
Include regression tests for behavior changes. Do not submit credentials, personal
training records, proprietary app assets, or decompiled third-party source.
The CI workflow checks pull requests without deploying anything.
To clear logged numeric values, PATCH their fields to null; omitting a field preserves it.
Blank numeric fields in the browser form clear the saved value. Notes accept up to 1,000 characters.
This server cannot be deployed
Maintenance
Related MCP Connectors
Your AI writes training plans that arrive as structured workouts on iPhone and Apple Watch.
AI running coach. Reads Strava, Suunto, Polar, Apple Health or Health Connect. Adapts to your feel.
AI cycling coach — training plans and beginner guidance via the Etapa API.
- JotiOAuthcom.kompetic
Read your workouts, history, and stats; create and schedule new workouts. Writes are additive only.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables AI assistants to read Garmin activities and create/schedule structured workouts and multi-week training plans on Garmin Connect, syncing to the user's watch.1MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI assistants to read Garmin activities and create/schedule structured workouts and multi-week training plans directly on Garmin Connect, syncing to your watch with guided prompts.1MIT
- FlicenseNot gradedqualityCmaintenanceAI-powered coaching for runners, cyclists, swimmers, and triathletes, enabling personalized workouts, training plan adaptation, performance analytics, and AI coaching via Claude, MCP clients, or HTTP.-
- AlicenseBqualityCmaintenanceTurns any AI assistant into a physiological AI running coach by abstracting fitness APIs and exposing 70+ structured analytical MCP tools based on endurance models.21MIT