# Runspector — AI running coach for Garmin (pypi · runspector)

AI running coach for Garmin: HRV, readiness and VO2max explained, workouts on your watch

- Trust score: 67/100 (medium)
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-11-04

## Components

- pypi · `runspector`: 67/100 (this document), [markdown](https://verifymcp.io/servers/edxleon-runspector/runspector.md), [page](https://verifymcp.io/servers/edxleon-runspector/runspector)

## Channel facts

- Registry: `pypi`
- Package: `runspector`
- Version: `0.10.0`
- Transport: `stdio`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-11-04.

- **Supply Chain Security**: 50/100
  - Malware scan not yet available for this package.
  - No known CVEs affecting this package version or its production dependencies.
  - Runs hatchling.build at install time, a recognised build step with no custom scripting around it.
  - 0 of 37 dependencies flagged as unhealthy.
- **Provenance & Transparency**: 100/100
  - Source repository is publicly reachable at the declared URL.
  - Cryptographically verified build provenance (signed, bound to edxleon/runspector).
  - Clear OSI-approved license (MIT).
  - Actively maintained (last published 1 days ago).
  - Publishes a security disclosure policy (SECURITY.md).
- **Schema Quality & AI Usability**: 67/100
  - AI-judged instruction clarity (excellent).
  - Context-footprint check failed: tool/resource definitions use about 3462 tokens (~192/item across 18 items; 18 tools + 0 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 17/100
  - Stability observed for 5 of 30 days with no destabilising changes; credit accrues until the full window elapses.
- **Tool Coverage**: 98/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 93% of tool parameters carry a description.
  - Structured output schemas are declared (100% of tools); any adoption earns full credit.
- **Tool Safety**: 100/100
  - No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.
  - We read all 18 captured tool definition(s), and no name or description among them implies an irreversible operation.
  - An AI judge read all 19 captured unit(s) of tool text and found none that tries to manipulate the model reading it.
- **Capabilities**: 100/100
  - Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.

## Install

### How do I install the Runspector — AI running coach for Garmin MCP server?

Runspector — AI running coach for Garmin runs locally as a PyPI package, launched with uvx runspector. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.

### Claude

```bash
claude mcp add edxleon-runspector -- uvx runspector
```

### Cursor

```json
{
  "mcpServers": {
    "edxleon-runspector": {
      "command": "uvx",
      "args": [
        "runspector"
      ]
    }
  }
}
```

### VS Code

```json
{
  "servers": {
    "edxleon-runspector": {
      "command": "uvx",
      "args": [
        "runspector"
      ]
    }
  }
}
```

### Codex

```bash
codex mcp add edxleon-runspector -- uvx runspector
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "edxleon-runspector": {
      "type": "local",
      "command": [
        "uvx",
        "runspector"
      ],
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add edxleon-runspector --command uvx --arg runspector
```

### Hermes

```yaml
mcp_servers:
  edxleon-runspector:
    command: "uvx"
    args: ["runspector"]
```

### Netclaw

```json
{
  "McpServers": {
    "edxleon-runspector": {
      "Transport": "stdio",
      "Command": "uvx",
      "Arguments": [
        "runspector"
      ]
    }
  }
}
```

### Vellum

```bash
assistant mcp add edxleon-runspector -t stdio -c uvx -a runspector
```

### Other

```json
{
  "mcpServers": {
    "edxleon-runspector": {
      "command": "uvx",
      "args": [
        "runspector"
      ]
    }
  }
}
```

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-11-04 (score 67, −14)

- [security regression] Malware scan: pass → unverified
- [functional improvement] Security disclosure: fail → pass

### 2026-11-03 (score 81, +15)

- [security improvement] Malware scan: unverified → pass

### 2026-11-02 (score 66, +1)

- [security regression] Stability: 0.07 → unverified
- [security regression] Tool safety: pass → unverified
- [security improvement] Malware scan: unverified → pass
- [functional regression] Capabilities: pass → unverified
- [functional regression] Tool coverage: 100 → unverified
- [functional] First check of Schema quality: unverified
- [functional] Package version: 0.7.0 → 0.10.0
- [functional] Package version: 0.7.0 → 0.9.0
- [functional] Package version: 0.7.0 → 0.8.0

### 2026-11-01 (score 65, 0)

- [functional regression] Schema quality: 2775 → 3217
- [functional] Package version: 0.6.0 → 0.7.0

### 2026-10-31 (score 65, +1)

- [functional improvement] Stability: unverified → 0.03
- [functional] Package version: 0.5.1 → 0.6.0

### 2026-10-30 (score 64)

First indexed and scored.

## MCP tools (18)

### `get_athlete_profile` (~158 tokens)

What the app has learned about THIS athlete from their own data, each
with its evidence: how many days they run and on which weekdays, their
long-run day, a weekly other sport, how consistent they are, their
intensity pattern over 28 days, the VO2max trend and whether a measurement
is due, Garmin's load focus, resting-HR baseline and zones - and, where a
value moved and held, since when. Call it once per conversation after
\`get_coaching_reference`, and use it to make the advice theirs. It is
context, not today's numbers: those come from the other tools, and the
decision stays the app's. Read-only; no Garmin call.

Output parameters:

- `result` (string)

### `get_coaching_reference` (~91 tokens)

The coach's full rule set: how to read the data, today's decision, the
planning rules, zones and intensity distribution, the energy boundary and
the sources behind each threshold - the same text the app's own coach works
from. Call it ONCE per conversation, before the first training advice, and
follow it. No arguments, no Garmin call; it reads nothing of the athlete.

Output parameters:

- `result` (string)

### `get_training_readiness` (~195 tokens)

Today's readiness verdict GO / EASY / REST with the signals behind it (HRV
status, sleep score, Body Battery, resting HR vs 27-day baseline, ACWR, days
since the last hard workout). Rule-based and conservative. START HERE for
"should I train today?". Carries the app's own decision for today and, on a
hard day, WHICH hard session the history calls for (VO2max intervals,
threshold reps, or the steady block Garmin measures VO2max from) - explain
that, do not pick another; the athlete's goal from their profile when set;
the Garmin calendar for the next 14 days, each entry with its weekday, date
and schedule id (what propose_change names). Flags stale data explicitly. When
the last Garmin sync is older than 30 minutes it syncs first (10-40 s) and
says so on top.

Output parameters:

- `result` (string)

### `get_recovery_summary` (~74 tokens)

Averages of sleep, HRV, resting HR, stress, Body Battery and steps over the
last N days plus a snapshot of the latest day. Use for "how has my recovery
been?". Aggregates only — never a day-by-day list.

Input parameters:

- `period_days` (integer): Window length in days.

Output parameters:

- `result` (string)

### `get_daily_metrics` (~54 tokens)

Every recorded recovery value for ONE day (default: latest day with data).
Use when a single night/day is in question, not for trends.

Input parameters:

- `day`: ISO date YYYY-MM-DD. Omit for the latest.

Output parameters:

- `result` (string)

### `get_trend` (~58 tokens)

Weekly averages of ONE recovery metric over the last N days — for "is my
HRV / resting HR / sleep trending up or down?".

Input parameters:

- `metric` (string, required)
- `period_days` (integer): Window length in days.

Output parameters:

- `result` (string)

### `get_training_load` (~88 tokens)

Training-load picture: ACWR with its SOURCE (Garmin's EWMA ratio, or a
self-computed fallback that is less reliable), Garmin training status, VO2max
with change, and weekly load buckets. VO2max change is only reported when the
value actually varied (Garmin carries the last value forward).

Input parameters:

- `period_days` (integer): Window length in days.

Output parameters:

- `result` (string)

### `get_recent_activities` (~64 tokens)

Workout digest: totals per sport plus the latest workouts, each run
classified Quality / Long Run / Easy (from training effect and duration). Use
to see what was actually trained before recommending the next session.

Input parameters:

- `period_days` (integer): Window length in days.

Output parameters:

- `result` (string)

### `get_intensity_distribution` (~81 tokens)

Time in heart-rate zones across all runs (easy Z1-2 / moderate Z3 / hard
Z4-5 / Z5) with percentages — the basis for the 80/20 polarisation question.
States how many runs have zone detail, so an incomplete picture is visible.

Input parameters:

- `period_days` (integer): Window length in days.

Output parameters:

- `result` (string)

### `analyze_workout` (~108 tokens)

Deep dive into ONE run: time in each HR zone, interval structure (rep count,
rep length, work vs recovery HR), weather, performance condition, plus
rule-based notes. Default: the latest run with detail. The interval structure
is Garmin's auto-detection — if the athlete states a different structure,
the athlete is right.

Input parameters:

- `activity_id`: Garmin activity id; wins over `day`.
- `day`: ISO date YYYY-MM-DD. Omit for the latest.

Output parameters:

- `result` (string)

### `get_vo2max_history` (~85 tokens)

VO2max over the last 8 weeks as STEPS (only the days the value really
changed — Garmin carries it forward in between) plus a descriptive comparison
of the last 28 days with the 28 before (distance, Z5 minutes, easy share,
temperature). Use for "why is my VO2max moving?". Descriptive, not causal.

Output parameters:

- `result` (string)

### `sync_garmin` (~70 tokens)

Pull the latest days and workouts from Garmin Connect NOW. Call this first
when today's run or last night's sleep is missing. Takes 10-40 s. Read-only
towards Garmin; writes only to the local database.

Input parameters:

- `days` (integer): How many days back to re-fetch.

Output parameters:

- `result` (string)

### `propose_workout` (~394 tokens)

Build a structured session for the athlete's route or time budget and file
it as a PROPOSAL - warm-up, work reps with targets, recovery jogs, cool-down -
sized so the whole thing adds up to the route. Targets come from the
athlete's own Garmin zones; anything not measured is listed as an assumption.
Use when the athlete asks for a workout ("plan me intervals for my 10 km",
"an easy 45 minutes", "a threshold session") or when the readiness verdict
calls for a different session than the one on the calendar.
Kinds: easy/long (one capped step), threshold (Z4 reps), vo2max (Z5 reps),
steady (>= 12 min even effort at threshold - the only shape Garmin measures
VO2max from). `day` defaults to today.
Returns the preview and a proposal id. NOTHING is written to Garmin: show
the preview, and only if the athlete says yes call apply_workout with the
id. On an impossible request (route too short) returns why, not a session.

Input parameters:

- `day`: ISO date YYYY-MM-DD. Omit for the latest.
- `distance_km`: Route length in km, e.g. 10 for a fixed 10 km loop. Give this OR duration_min.
- `duration_min`: Session length in minutes. Give this OR distance_km.
- `kind` (string, required)
- `name`: Workout name on the watch. Default: kind and structure, e.g. 'VO2max 4x4 min'.
- `replaces_schedule_id`: A calendar entry this session REPLACES (the number after 'schedule' in get_training_readiness). Applying unschedules it first; the workout stays in the library. Use for the readiness swap: an easy ru…

Output parameters:

- `result` (string)

### `propose_week` (~432 tokens)

Build a polarised training week and file it as ONE proposal: a VO2max
session and a threshold session 48 h apart, the long run on its weekday,
easy runs between, nothing hard the day before or after the long run, at
least one rest day. The sessions go on the days the athlete usually runs,
and the quality sessions keep clear of a weekly other sport - read from
their history, each an `assumption:` line. Sized from the athlete's own running weeks and read
against their history, in code: +10 % by default, held or unloaded on the
load ratio, a down week after three weeks up, a return week after a pause,
and a measurement week (a steady block in the threshold slot) when VO2max
has had no new value for 28 days - each one an `assumption:` line in the
preview; explain the line, do not override it. Targets from their own
zones, pace from Garmin's race predictions when no threshold is measured.
Use when the athlete asks for their week ("plan my week", "what should
next week look like"). The calendar
wins: a day that already has an entry gets nothing, and a quality session
too close to a hard calendar day becomes an easy run - each change is a
\`calendar:` line. Returns every session with its day and one proposal id;
apply_workout with that id puts the package on Garmin after the athlete's
yes. Nothing is written here.

Input parameters:

- `days_per_week`: Running days. Default: the athlete's profile, else their history (the days they usually run), else 4 - the last two listed as an assumption.
- `long_run_day`: Weekday of the long run. Default: the profile, else the weekday of their usual long run, else sun - the last two listed as an assumption.
- `start_day`: First day of the week to plan, YYYY-MM-DD, today or up to 8 days ahead. Default: the coming Monday (today, if today is Monday).

Output parameters:

- `result` (string)

### `propose_change` (~222 tokens)

File the athlete's OWN change to their planned week as a proposal:
"Friday instead of Saturday", "Tuesday shorter", "no intervals this week",
"add an easy run on Thursday". The athlete decides: the change is kept as
asked even where the rules disagree - more than the plan's step, two hard
days in a row, a hard session in a return week, a run on a rest day or
while they said they are in pain. What the rules think of it comes back as
\`coach:` lines; say them to the athlete in your own words, and suggest a
better change in your answer instead of making one they did not ask for.
A workout this app did not make can be removed or replaced, not moved.
Returns the change with its days and a proposal id. NOTHING is written to
Garmin: apply_workout with the id after the athlete's yes; undo_workout
takes it back.

Input parameters:

- `changes` (array, required): Exactly the changes the athlete asked for - nothing they did not ask for.

Output parameters:

- `result` (string)

### `apply_workout` (~169 tokens)

WRITE a proposed session to the athlete's Garmin account: upload the
workout, schedule it on the proposal's day, push it to the watch, then read
it back and verify structure and targets. Call ONLY after the athlete has
seen the preview and explicitly agreed - a request ("plan me 10 km") is not
agreement, "yes, put it on the watch" is. Not available to the app's own
card runs; there the athlete clicks. Returns what is now on Garmin, with
any warning (not pushed because the watch is offline; a mismatch found on
read-back). A proposal can be applied once; an unknown or used id says so
and lists the open ones.

Input parameters:

- `proposal_id` (string, required): The id propose_workout returned.

Output parameters:

- `result` (string)

### `undo_workout` (~165 tokens)

Take a session (or a whole applied week) back OFF the athlete's Garmin
calendar. The counterpart of apply_workout: use it when the athlete says
the session should not be there after all, or after trying the write path
out. It unschedules - the workout stays in their Garmin library, so
applying the same proposal again puts it back on a day without building or
uploading anything a second time. Only sessions this app uploaded and
scheduled are touched; a calendar entry that has changed hands since is
left alone and reported. Like apply_workout it needs the athlete's explicit
yes, and it is not available to the app's own card runs.

Input parameters:

- `proposal_id` (string, required): The id of a proposal that was applied.

Output parameters:

- `result` (string)

### `check_in` (~387 tokens)

Record what the athlete SAYS about themselves today: ill, in pain, sore
or stressed - or that it is over. Call it when they tell you, in their own
words ("I have a cold", "my calf hurts", "legs are heavy from Sunday",
"rough week at work"); never from the data alone - a low HRV is not a cold.
It changes today's decision in code: ill or pain rest the day whatever the
watch says, and after they end it, as many easy days follow as it lasted;
sore or stressed take the hard session away. It holds until they say it is
over, or runs out after a few days without a new word; saying it again keeps
it open. Returns what was recorded and the app's decision for today now.
Local only, nothing goes to Garmin; not available to the app's own card runs.

Input parameters:

- `kind` (string, required): ill: a cold, fever, flu, stomach bug. pain: something hurts (a calf, a knee, a foot) - not muscle soreness. sore: muscle soreness, heavy legs. stressed: work or life stress.
- `note`: Where or what, in the athlete's words: "left calf", "sore throat". Optional.
- `over` (boolean): True when the athlete says it is OVER ("the cold is gone", "no more pain"). "Better" is not over - ask.
- `since`: The day it began, YYYY-MM-DD, when the athlete says so ("since yesterday", "since Monday") - at most 7 days back. Default: today.
- `undo` (boolean): True only when the athlete says today's check-in was recorded by mistake ("no, my friend is ill"): it is taken back as if never said. Not for "it is gone" - that is over.

Output parameters:

- `result` (string)

## Diagnostics

Captured diagnostic sections: Provenance, Install scripts, Dependencies. The full working is on the page: https://verifymcp.io/servers/edxleon-runspector/runspector#diagnostics

## Score history

- 2026-11-04: 67
- 2026-11-03: 81
- 2026-11-02: 66
- 2026-11-01: 65
- 2026-10-31: 65
- 2026-10-30: 64

## Common questions

### What is the Runspector — AI running coach for Garmin MCP server?

Runspector — AI running coach for Garmin is an MCP server listed in the public MCP registry as io.github.edxleon/runspector. AI running coach for Garmin: HRV, readiness and VO2max explained, workouts on your watch. This page covers its PyPI package (runspector).

### Is the Runspector — AI running coach for Garmin MCP server safe to use?

Runspector — AI running coach for Garmin scores 67 out of 100 on VerifyMCP. We found no known CVEs affecting it as of 4 November 2026. Its build provenance is signed and verified. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.

### What tools does the Runspector — AI running coach for Garmin MCP server expose?

Runspector — AI running coach for Garmin exposes 18 tools: get_athlete_profile, get_coaching_reference, get_training_readiness, get_recovery_summary, get_daily_metrics, and 13 more. Their descriptions and schemas cost roughly 2,895 tokens of context every time the server is loaded.

### Is the Runspector — AI running coach for Garmin MCP server still maintained?

Runspector — AI running coach for Garmin is still listed as active in the MCP registry. We last reached this channel on 4 November 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.

### What licence is the Runspector — AI running coach for Garmin MCP server under?

Runspector — AI running coach for Garmin declares the MIT licence, which is OSI-approved. That covers the source only, and says nothing about the cost of any service it calls.

## Links

- PyPI project: https://pypi.org/project/runspector/
- Socket report: https://socket.dev/pypi/package/runspector
- Repository: https://github.com/edxleon/runspector
- Changelog RSS feed: https://verifymcp.io/servers/edxleon-runspector/runspector.xml
- Changelog JSON feed: https://verifymcp.io/servers/edxleon-runspector/runspector.json
- HTML version of this page: https://verifymcp.io/servers/edxleon-runspector/runspector
