# Reported issues for strava-mcp

Pod holds 24 of 145 problems reported by people outside the maintainer team. Issues filed by the project's own owners, members and collaborators are excluded entirely — a maintainer's release checklist is not a warning to a prospective user.

Back to [strava-mcp](/mcp/strava-mcp-ljcl).

## Most discussed

### spike: external tile fetch through host CSP and free tile-source choice

## Summary
Determine whether an MCP App can fetch external map tiles through the host's sandbox/CSP, and choose the free tile source to standardize on.

## Area
mcp-app

## Motivation
MCP App iframes block external connections by default. The server can declare `_meta.ui.csp` (`connectDomains` / `resourceDomains`), but the host (Claude) must honor those allowlisted origins through its double-iframe sandbox. This is the gating unknown for any basemap. We also need to pick between OpenFreeMap's pu

[Read the thread](https://github.com/ljcl/strava-mcp/issues/60) · 2026-06-08 · closed · 2 comments

### feat: add Strava webhook (push subscription) support for real-time activity events

## Problem

The server can only poll. Strava's Webhook Events API can push create / update / delete events for activities and athlete deauthorizations, enabling real-time reactions (cache invalidation, notifications) without polling and reducing rate-limit pressure.

## Current behaviour

No subscription handling exists: no `/push_subscriptions` calls in `apps/server/src/stravaClient.ts`, and no webhook callback route in `apps/server/src/index.ts`.

## Proposed approach

1. Add a public callback

[Read the thread](https://github.com/ljcl/strava-mcp/issues/25) · 2026-06-07 · closed · 2 comments

### epic: Training analysis — prediction, splits, taper, and the fitness chart

Tracking issue. Four features over the same analysis primitives.

## Why these belong together

They share `fitnessTrend.ts`, `hillAnalysis.ts`'s grade-adjusted pace, and the `getBestEfforts` scan pattern. One of the pairs is explicit: #267's body already points at the companion view issue.

## Ordered batches

**1. Companion pair — order matters**
- #267 — extend `get-fitness-trend` with a planned-load taper projection (keep all math in `fitnessTrend.ts`)
- #262 — `view-fitness-trend` MCP App c

[Read the thread](https://github.com/ljcl/strava-mcp/issues/291) · 2026-07-26 · closed · 1 comment

### epic: Collapse the duplicated formatters

Tracking issue. Two issues filed as explicit companions — the same class of drift on either side of the server/app boundary.

## Why these belong together

Each body names the other. They are the same inventory exercise done twice, and the inventory is the expensive part.

## Members — either order

- #252 — hoist `formatClock`, the short-date helper, and one `formatDurationShort` into `@strava-mcp/data`, then delete the per-app copies. MCP App packages cannot import each other, so `packages/dat

[Read the thread](https://github.com/ljcl/strava-mcp/issues/287) · 2026-07-26 · closed · 1 comment

### epic: MCP protocol surface — schemas, capabilities, end-to-end coverage

Tracking issue. Groups everything the server advertises to a host — input schemas, output schemas, capabilities, notifications — plus the one test that would catch a break in any of them.

## Why these belong together

31 of 46 tools return text only, no capability beyond `tools`/`resources`/`prompts` is advertised, and the protocol layer is only partly tested — `tools/list` is exercised end-to-end but nothing else is. These issues share `server.ts`'s registration surface, `tools/outputs.ts`, an

[Read the thread](https://github.com/ljcl/strava-mcp/issues/284) · 2026-07-26 · closed · 1 comment

### chore: collapse the server formatDuration duplicates and the utils.ts split

## Context
`get-activity-zones` formats durations differently from every other tool because four `formatDuration` variants coexist server-side, one of them commented out with stale breadcrumbs. Nothing catches this class of drift: knip cannot see commented code or a genuinely-imported duplicate. A companion issue covers the same problem across the app packages (formatters into packages/data).

## Evidence
- Canonical null-safe version: `apps/server/src/formatters.ts:6` (`3725` renders `1:02:05` 

[Read the thread](https://github.com/ljcl/strava-mcp/issues/277) · 2026-07-25 · closed · 1 comment

### refactor: move duplicated clock, date, and duration formatters into @strava-mcp/data

## Context
Formatting helpers have drifted into per-app copies with incompatible output. `formatClock` is implemented twice; a `MONTHS` array plus UTC short-date helper is duplicated byte-for-byte in two a11y modules with a third near-copy; short durations render three different ways for the same idea (`1h 05m`, `1h5m`, hours). MCP App packages cannot import each other, so `packages/data` is the only shared home. Issue #216 fixed a pace-rollover bug in one copy only, which is the failure mode th

[Read the thread](https://github.com/ljcl/strava-mcp/issues/252) · 2026-07-25 · closed · 1 comment

### Reposition strava-mcp as a supplement to the official Strava MCP connector

## Summary

Strava has shipped an official MCP connector. It overlaps with our read-only tools but covers only a slice of what this server does. This epic repositions strava-mcp as a **supplement** to the official connector: we drop the duplicate reads and keep everything the official MCP cannot do.

Docs:
- FAQ: https://support.strava.com/en-us/articles/15401526-strava-api-and-mcp-faq
- Connector: https://support.strava.com/en-us/articles/15401531-strava-mcp-connector

## Official connector pro

[Read the thread](https://github.com/ljcl/strava-mcp/issues/81) · 2026-06-22 · closed · 1 comment

## Most recent

### epic: Release and container plumbing — gates that gate, manifests that match

Tracking issue. Groups five open issues across the release pipeline: what gates a merge, what builds the image, and what the registry is told afterwards.

## Why these belong together

Four of the five edit `.github/workflows/`, `scripts/setup-branch-protection.sh`, or the release manifests, and three of them are the same failure in different places — a step that was designed to gate something and does not.

- #358: `docker.yml` engineers its `changes` job specifically so a required Docker statu

[Read the thread](https://github.com/ljcl/strava-mcp/issues/368) · 2026-08-23 · open · 0 comments

### epic: Tool surface consistency — arg names, error handling, output honesty

Tracking issue. Groups four open issues that all rewrite the same tool handler bodies in `apps/server/src/tools/`.

## Why these belong together

The overlap is literal, not thematic. Seven of the eleven tools #354 migrates destructure an id argument that #352 renames:

| Tool | Today | #354 rewrites | #352 renames |
| --- | --- | --- | --- |
| `getSegment.ts` | `execute: async ({ segmentId }` | catch block :75-88 | `segmentId` → `segment_id` |
| `getActivityLaps.ts` | `execute: async ({ id }` |

[Read the thread](https://github.com/ljcl/strava-mcp/issues/367) · 2026-08-23 · open · 0 comments

### epic: Request path round two — coalescing, immutability, cached token reads

Tracking issue. Groups three open issues that all finish the same story: one read path that caches instead of re-fetching.

## Why these belong together

#355 and #357 rewrite the same five lines. `fetchClient.ts:462-466` is the cache-lookup block, ending in `return { data: cached as T }` — #355 wraps that site in an in-flight promise map so concurrent identical GETs share one upstream call, #357 changes what that same `return` hands back so a consumer cannot mutate the cached object. Landed sep

[Read the thread](https://github.com/ljcl/strava-mcp/issues/366) · 2026-08-23 · open · 0 comments

### perf: stop get-activity-photos and get-activity-zones dumping raw JSON into text content

## Summary

get-activity-photos and get-activity-zones append a pretty-printed dump of the entire raw Strava response to their text content, then also return the same data as `structuredContent`. Every successful call pays for both copies, and the raw one is the larger of the two.

## Area

server

## Motivation

`structuredContent` + `outputSchema` is the architecture invariant for tools returning data: the machine-readable copy is the structured payload, the text copy is for a human reading th

[Read the thread](https://github.com/ljcl/strava-mcp/issues/365) · 2026-08-23 · open · 0 comments

### chore: resolve the stale image identifier committed in server.json

## Summary

Committed `server.json` reads `"version": "2.13.2"` but `packages[0].identifier` is still `ghcr.io/ljcl/strava-mcp:2.3.0` — release-please only updates `$.version`, and the real tag is stamped transiently by publish-mcp.yml at publish time, so the committed file drifts ten minor versions behind.

## Area

ci-release

## Motivation

Anyone reading the repo (or copying the registry reference out of server.json) sees a long-stale image tag. It also makes release-PR diffs misleading — th

[Read the thread](https://github.com/ljcl/strava-mcp/issues/363) · 2026-08-22 · open · 0 comments

### feat: add a race-prep prompt chaining prediction and taper solving

## Summary

`prompts.ts` ships three prompts (weekly-review, annotate-last-run, segment-hunt). The toolset's most distinctive capability — answering "am I on form for my goal race?" via get-race-prediction plus get-fitness-trend's solved taper — has no guided prompt packaging the sequence.

## Area

server

## Motivation

Prompts are the discoverability layer for multi-step workflows in Claude Desktop/Code hosts. The race-prep sequence needs real ordering (best efforts → Riegel prediction → fitn

[Read the thread](https://github.com/ljcl/strava-mcp/issues/362) · 2026-08-22 · open · 0 comments

### docs: correct get-activity-photos' output prose and drop the Output Format sections

## Summary

get-activity-photos and get-activity-zones are the only two tools whose descriptions carry an `Output Format:` section, and both advertise a "complete raw JSON" text dump — get-activity-photos enumerating fields (`Source`, `uploaded_at`, "URLs for different sizes") that reach the text branch but are absent from its `structuredContent`. Their prose is also the wordiest on the surface: get-activity-photos spends 26 lines where comparable tools spend a paragraph.

## Area

server

## Mo

[Read the thread](https://github.com/ljcl/strava-mcp/issues/361) · 2026-08-22 · open · 0 comments

### chore: serve /health token status from the in-memory token cache

## Summary

`getTokenStatus()` calls `loadTokens()` directly instead of reading the in-memory `cachedTokens` that `getStravaToken` maintains, so every authed `/health` or `/auth/status` poll pays a filesystem read and re-emits the "[TokenManager] Loaded tokens …" stderr line.

## Area

server

## Motivation

`/health` backs the Docker HEALTHCHECK and operator dashboards, i.e. it is polled continuously — each poll currently costs a disk read plus log noise that buries real telemetry lines in `doc

[Read the thread](https://github.com/ljcl/strava-mcp/issues/360) · 2026-08-22 · open · 0 comments

### fix: pin the Docker Bun base image to the packageManager version

## Summary

Root `package.json` declares `packageManager: bun@1.3.6` (the single source of truth CI's setup action consumes), while `apps/server/Dockerfile` hardcodes `oven/bun:1.3.14` and `oven/bun:1.3.14-distroless` — two independent pins of the same runtime that Dependabot bumps separately.

## Area

docker

## Motivation

The lockfile is resolved by Bun 1.3.6 locally/in CI, but install and production execution happen on 1.3.14 inside the image; dev/test/prod can silently run different Bun ve

[Read the thread](https://github.com/ljcl/strava-mcp/issues/359) · 2026-08-22 · open · 0 comments

### fix: require the Docker status context branch protection already designs for

## Summary

`scripts/setup-branch-protection.sh` requires only the `check` status context, while `.github/workflows/docker.yml` explicitly engineers its changes/build jobs around being a required check on PRs — a required Docker status was clearly intended but never added to the enforced list.

## Area

ci-release

## Motivation

docker.yml's `changes` job exists "so that a required Docker status still reports on docs-only PRs — skipped jobs count as passing for required checks". With nothing ac

[Read the thread](https://github.com/ljcl/strava-mcp/issues/358) · 2026-08-22 · open · 0 comments

### fix: stop the response cache handing out shared mutable references

## Summary

On a hit, `FetchClient`'s `TtlLruCache` returns the exact cached object (`return { data: cached as T }`), so any consumer that mutates fetched data in place would poison the cache for every later reader within the TTL. No current caller does (the one candidate, get-best-efforts' `efforts.sort()`, operates on locally-built objects) — this is a latent hazard worth closing before it becomes live.

## Area

server

## Motivation

The cache exists precisely because activity details/stream

[Read the thread](https://github.com/ljcl/strava-mcp/issues/357) · 2026-08-22 · open · 0 comments

### fix: route legacy tools' catch blocks through typed error handling

## Summary

Eleven tools hand-roll catch-block formatting with fragile string matching (`errorMessage.includes("Record Not Found") || includes("404")`) instead of using the typed `HttpError.status` / `RateLimitError` that `handleApiError` already preserves, and their conventions have drifted (`❌ ${msg}`, bare `Error: ${msg}`, `❌ API Error:`).

## Area

server

## Motivation

Beyond maintenance cost this misreports rate limits to users: in these tools a `RateLimitError` falls into the generic bra

[Read the thread](https://github.com/ljcl/strava-mcp/issues/354) · 2026-08-22 · open · 0 comments

### perf: coalesce concurrent identical GETs in fetchClient

## Summary

The TTL+LRU cache dedupes sequential repeat reads only. Two concurrent identical GETs both miss and both hit Strava — including the exact view-/get-data pairs the URL-quantization work exists to dedupe, since MCP apps commonly fire both calls together on open.

## Area

server

## Motivation

`WINDOW_QUANTUM_SECONDS` quantization was built so an app's two-call pair shares one cache key; under concurrency the second call enters before the first populates the cache and pays full cost a

[Read the thread](https://github.com/ljcl/strava-mcp/issues/355) · 2026-08-22 · open · 0 comments

### fix: ignore .env.* variants in .gitignore

## Summary

`.gitignore` lists only `.env` under its environment section, while turbo.json treats `.env.local` as an expected local file and `.dockerignore` already excludes `.env.*` (keeping `!.env.example`). A contributor-created `.env.local` would sit untracked and committable with real secrets inside.

## Area

repo

## Motivation

`turbo.json:17` includes `.env.local` in `globalDependencies`, signalling it is an anticipated pattern; `git add .` would stage Strava OAuth client secrets into h

[Read the thread](https://github.com/ljcl/strava-mcp/issues/353) · 2026-08-22 · open · 0 comments

### refactor!: unify Strava id argument names across tool input schemas

## Summary

The same concept — a Strava activity id — is spelled three different ways across the tool surface: `get-activity-laps`/`get-activity-photos`/`get-activity-zones` take `id`, the analysis and write tools take `activityId`, and all 18 app-facing `view-*`/`get-*-data` tools take `activity_id`. Segment, route and effort args split the same way: `segmentId` in get-segment, get-segment-profile, list-segment-efforts and star-segment; `routeId` in get-route, get-route-preview, export-route-gp

[Read the thread](https://github.com/ljcl/strava-mcp/issues/352) · 2026-08-22 · open · 0 comments

### fix: escape reflected input in OAuth error pages (XSS in /auth/callback)

## Summary

`errorPage()` interpolates its `message` argument straight into HTML without escaping, and two call sites pass attacker-controllable text: the Strava `error` query parameter and caught exception messages.

## Area

server

## Motivation

The server is documented as publicly reachable (tunnel/reverse-proxy deployments). Anyone can craft `/auth/callback?error=<img src=x onerror=...>` and send the link to the athlete; the payload executes on the server's origin because the error branch 

[Read the thread](https://github.com/ljcl/strava-mcp/issues/351) · 2026-08-22 · open · 0 comments

The remaining reports are on [the project's issue tracker](https://github.com/ljcl/strava-mcp/issues).
