Make Claude your personal health AI — locally. An MCP server that exposes Apple Health data via DuckDB queries and tools.
Two tools with strong naming (verb_noun pattern) and comprehensive parameter schemas. get_workout_route has exceptional documentation with detailed descriptions of pagination, downsampling, and heart rate integration. get_server_info provides diagnostic output. However, output schemas are described in prose rather than formal JSON Schema, limiting LLM parsing precision. No tool annotations (readOnlyHint) present despite both being read-only operations. Parameter descriptions are thorough but could be more concise for LLM token efficiency.
Return the server's runtime state for self-diagnosis. Fields: db_path (absolute path of the DuckDB file the server has open), version (server version string), record_count (rows in the records table, 0 on a fresh DB without an imports table), config_source (which tier of resolve_db_path() supplied the path: 'env:APPLE_HEALTH_DB', 'env:APPLE_HEALTH_DATA_DIR', or 'platform_default'). Use this when troubleshooting a 'no data' response to confirm the server opened the same DB file the importer wrote to — the canonical symptom of the Windows MSIX AppContainer %LOCALAPPDATA% sandbox redirect.
Get GPS route data for a workout. Returns ``{items, total, next_offset, truncated_by_size, size_budget_bytes}``; ``next_offset`` is ``null`` on the last page, ``truncated_by_size`` is ``true`` when the server clipped the items list to stay under the host's 1 MB transport ceiling. Each item carries {latitude, longitude, elevation (m, rounded to 0.1 m), timestamp, speed (m/s, rounded to 0.001), course (deg, rounded to 0.1)}. Lat/lon are rounded to 6 decimals (~0.1 m, below GPS error). ``items`` is capped at ``limit`` rows (default 2000, max 50000); for long-form workouts page through with ``offset``. Call get_workout_details first to check has_route. ``every_nth=N`` (issue #161): server-side equispaced downsampling — returns every N-th point ordered by timestamp (N=5 cuts row count to ~20%; capped at 1000). ``total`` reports the downsampled count, not the underlying route_points row count. Use this to stay under the size budget on long workouts where polyline fidelity matters more than per-second resolution. ``with_heart_rate=True`` (issue #162): adds {heart_rate, heart_rate_offset_secs} to each item — the nearest HKQuantityTypeIdentifierHeartRate sample within ±30 s of the route point; heart_rate is null when no HR sample is in range. ``heart_rate_offset_secs`` is the absolute time delta in seconds (rounded to 0.01 s) — use it to down-weight matches whose offset is large.
Output schemas documented in prose descriptions rather than formal JSON Schema. LLMs cannot reliably parse unstructured text to extract field types and structure. get_workout_route describes output as '{items, total, next_offset, truncated_by_size, size_budget_bytes}' but no JSON Schema definition is visible.
Tool annotations missing. Both tools are read-only operations but lack readOnlyHint annotation in schema. This prevents clients from optimizing caching, retry logic, and safety checks.
get_workout_route parameter descriptions are verbose (some >200 chars). While comprehensive, they exceed LLM-optimal length (50-200 chars per param). Issue references (#161, #162) are helpful for context but add token overhead.
Inferred effective spec: <=2025-11-25.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 63 | <=2025-11-25 | v2 |
No error handling guidance in tool descriptions. What happens if workout_hash is invalid? If offset exceeds total? If heart rate data is unavailable? LLMs need recovery hints.
get_server_info output fields (db_path, version, record_count, config_source) are described in prose but no formal schema defines their types. Is version a string? Is record_count an integer? LLMs must infer.