catence
MCP server for local fitness-data extraction and analysis from Garmin Connect, Intervals.icu, and Strava. Provides read-only analytical tools over DuckDB and targeted Strava enrichment.
README
Catence
Local fitness-data extraction and analysis for Garmin Connect, Intervals.icu, and targeted Strava enrichment.
Catence keeps raw provider artifacts, a DuckDB catalogue, a Parquet activity-stream lake, and a local read-only MCP server for analytical access. The distributable npm package is catence: its catence command runs the server, while catence-data manages the local data store.
MVP: install once, connect from any local agent
After publishing catence to npm, create a data store at an absolute path. Keeping it outside any individual repository means Codex, OpenCode, and other clients read the same private snapshot.
npx --yes --package=catence catence-data --data-dir /absolute/path/to/catence-data init
Import data with the management CLI, not the MCP server:
npx --yes --package=catence catence-data --data-dir /absolute/path/to/catence-data sync --provider intervals
npx --yes --package=catence catence-data --data-dir /absolute/path/to/catence-data build-retrieval-index
The MCP command is intentionally just one process invocation:
npx --yes catence --data-dir /absolute/path/to/catence-data
It writes protocol traffic only to stdout; diagnostics go to stderr. --data-dir takes precedence over CATENCE_DATA_DIR. If neither is supplied, the legacy default remains .catence/ under the process working directory, which is useful for source development but not recommended for agent configuration.
See agent installation for copy-paste Codex and OpenCode configuration, a global-install alternative, the future APM producer ramp, and the HTTP-hosting boundary.
Requirements
- Node.js 22+
- Python 3.12+ with uv
Node is enough to run ordinary MCP reads against an existing data store. uv and Python are required for Garmin imports and targeted Strava enrichment; the published package carries the Garmin worker and writes its virtual environment inside the selected data directory.
Setup
npm install
uv sync
cp .env.example .env
Populate the provider credentials you want to use. Runtime data defaults to .catence/, which is ignored by Git.
Commands
npm run catence-data -- status
npm run catence-data -- sync --provider intervals
npm run catence-data -- sync --provider garmin --from 2025-07-29
npm run catence-data -- sync --provider strava
npm run catence-data -- sync --provider all
npm run catence-data -- retry --run <run-id>
npm run catence-data -- backfill --from 2020-01-01
npm run catence-data -- backfill --provider garmin --from 2026-07-01 --refresh
npm run catence-data -- build-retrieval-index
npm run mcp
All provider calls are protected by a read-only endpoint allowlist. Garmin supplies canonical activity and health data; Intervals syncs only its activity-summary analysis values, not duplicate health, files, streams, or unrelated account data. A sync continues after individual extraction failures and records them for retry.
The first ordinary sync uses the historical fallback only when no normalized
coverage exists. After that, sync is incremental: Garmin daily facts use a
three-day overlap, activities use a fourteen-day overlap, and unchanged
activities do not re-download their detail endpoints, original files, or
streams. --from and backfill are explicit ranges and do not move the
incremental cursor. catence-data status reports each provider cursor and its next
effective start date. backfill --provider garmin --from YYYY-MM-DD --refresh
re-fetches every Garmin activity's detail, files, and streams in the selected
range and upserts the resulting facts through today. It also fetches Garmin's
historical cycling FTP setting range, retaining each daily setting separately
from activity-level FTP and the current-only setting. It preserves archived raw
artifacts and does not delete data merely absent from a later provider payload.
Build the retrieval index after a completed sync. It contains compact, derived activity/plan/nutrition context only—not GPS tracks, sample streams, raw JSON, or original files. A later sync marks it stale again.
MCP
npm run mcp launches a local stdio server from this checkout. The package entry point is catence --data-dir /absolute/path/to/catence-data. Ordinary analytical tools and resources open catence.duckdb with DuckDB READ_ONLY mode for each request. The only MCP writes are hydrate_strava_activity, serial hydrate_recent_strava_activities, and hydrate_strava_segment_history; they take the shared data-directory write lock, call only the Strava GET allowlist, archive before normalization, commit, and release the lock. If any writer owns the lock, the enrichment call returns retryable data_sync_in_progress; ordinary reads remain read-only snapshots.
For development from this repository, invoke it with an explicit data path:
npm run mcp -- --data-dir /absolute/path/to/catence-data
For an npm-installed agent, use the npx configuration in agent installation instead. Do not put Intervals, Garmin, or Strava credentials in an MCP client configuration: those are needed by the management process only.
The ordinary tool set is analytical; the only mutation-capable additions are hydrate_strava_activity, serial hydrate_recent_strava_activities, and hydrate_strava_segment_history:
catence_statusanddescribe_dataexpose coverage, provenance, units, and catalog constraints.describe_datasetis the compact schema browser for one cataloged dataset, avoiding exploratory SQL just to find field names.training_metric_observationsincludes observed sports, metric names, units, and source types.read_series,aggregate_data,analyze_series, andfit_series_modelcover bounded time-series work.read_seriesautomatically aggregates dense streams and returns deterministic cursor pagination withreturnedRows,totalRows, andtruncated.get_ftp_history,get_vo2max_history,power_curve_trend,latest_cycling_activities, andcycling_progress_reportexpose source-aware fitness projections without JSON-path or array-index SQL. VO₂max requires an explicit sport for observations; an omitted sport returns the available-sport summary, because Garmin often stores a running estimate asgeneric.find_activitiesis the authoritative, paginated way to discover activities or likely races by sport, distance, name, and date.search_contextfinds generated context and prominently identifies a stale index and its direct-query alternative.query_read_only_datais the escape hatch for novel questions. It permits one parameterizedSELECT/WITH … SELECTagainst cataloged views only, with a 500-row per-page, safe response-size, and four-second limit. It returns deterministic pagination plusreturnedRows,totalRows,truncated, andnextCursor, and rejects comments, multiple statements, mutations/DDL, extension commands,COPY/ATTACH, and filesystem table functions.
Examples:
analyze_series({ dataset: "daily_health", metrics: ["hrv_ms"],
startDate: "2026-01-01", endDate: "2026-03-31", resolution: "day",
analysis: "theil_sen_trend" })
fit_series_model({ dataset: "daily_health", metrics: ["hrv_ms"],
startDate: "2026-01-01", endDate: "2026-03-31", resolution: "day",
model: "ols_linear" })
Targeted Strava enrichment
Strava is intentionally sparse and on demand. It is not an activity backfill or a canonical activity source. Garmin remains the original/canonical activity detail source; Intervals contributes separately named analysis values such as training load, RPE, feel, and weighted power.
Connect it from a terminal that has the registered Strava application credentials. The first command prints an approval URL; after approving it, pass the returned authorization code to the second command.
export STRAVA_CLIENT_ID=...
export STRAVA_CLIENT_SECRET=...
npx --yes --package=catence catence-data --data-dir /absolute/path/to/catence-data auth strava
npx --yes --package=catence catence-data --data-dir /absolute/path/to/catence-data auth strava --code <authorization-code>
Catence requests read, activity:read_all, and read_all. Credentials and the rotating OAuth token are stored only in <data-dir>/secrets/strava.json, mode 0600; they are never stored in DuckDB, raw artifacts, normal configuration, logs, or MCP output. Remove the local connection with:
catence-data --data-dir /absolute/path/to/catence-data disconnect strava
For local, let's go with
(knead-garmin) rifusaki@rifuAir catence % npm run catence-data auth strava
> catence@0.1.0 catence-data
> tsx src/interfaces/cli/main.ts auth strava
{
"authorizationUrl": [go in here],
"scopes": [
"read",
"activity:read_all",
"read_all"
],
"status": "authorization_required"
}
(knead-garmin) rifusaki@rifuAir catence % npm run catence-data -- auth strava --code [code]
sync --provider strava refreshes only the current athlete bike and shoe records. An MCP agent can then use hydrate_strava_activity for one existing Catence activity/source ID, or hydrate_recent_strava_activities for an explicit list or bounded date/sport window. The batch tool processes writes serially and reports every match, miss, ambiguity, and skip. Catence retrieves an already-linked Strava source, or an explicitly Strava-qualified ID, directly; otherwise it searches a narrow time window and applies the audited matcher. Garmin multisport parents hydrate their non-transition child activities individually. The matcher uses sport families, start time, elapsed duration, and distance; it deliberately does not reject on Strava's indoor/virtual classification. A direct lookup that is no longer available falls back to that safe matcher. Unmatched results include the search window, returned/qualified candidate counts, rejection reasons, and practical next checks. It then persists the activity detail, assigned gear, segment efforts, and embedded segment facts. hydrate_strava_segment_history accepts one persisted segment ID and retrieves only that segment detail plus the authenticated athlete historic efforts. Completed enrichment is cached unless refresh is set. A throttle or interruption returns saved partial state with its continuation page, so the next request resumes rather than discarding existing rows.
Segment facts include name, length, grades, climb category, elevation, privacy, hazard, and raw starred state. Strava public segment data does not expose verification, so Catence always reports it as unavailable and never guesses missing power, speed, or verification data.
Identity, writes, and local limits
Provider external IDs are strong activity links. The general cross-provider activity linker auto-links only one compatible candidate: the sport family must match, starts must be within 90 seconds, moving duration must differ by at most max(120 seconds, 5%), and distance by at most max(200 meters, 2.5%). Its virtual/indoor safeguard is separate from Strava hydration, which uses elapsed duration and does not reject on that classification. Corrections are explicit and auditable:
catence-data --data-dir /absolute/path/to/catence-data activity link --source intervals:123 --activity garmin:456
catence-data --data-dir /absolute/path/to/catence-data activity unlink --source intervals:123
The optional <data-dir>/config.json configures in-process MCP limits. Missing, blank, {}, omitted values, and null mean unlimited; existing SQL/result/time safety limits still apply. Every MCP data-bearing tool and resource is guarded. Remote Strava headers and HTTP 429 outcomes always take priority; provider budgets only lower local use.
{
"mcp": {
"rateLimits": {
"server": null,
"tools": { "*": null },
"resources": { "*": null }
}
},
"providers": {
"strava": {
"budget": {
"maxConcurrentRequests": null,
"readRequestsPer15Minutes": null,
"readRequestsPerDay": null
}
}
}
}
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。