Skip to main content
Glama
Quantified Self MCP Server

Quantified Self MCP Server

Quantified Self MCP

Your health data. Your AI. Your machine.

A privacy-first, local Model Context Protocol (MCP) server that connects AI assistants to your personal health history — with statistical evidence, provenance, and granular privacy built in.


⚡ Instant Client Setup

Connect Quantified Self MCP to your favorite AI assistant in seconds:

Claude Desktop

Add this to your Claude Desktop configuration:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json

  • Windows: %APPDATA%\Claude\claude_desktop_config.json

  • Linux: ~/.config/Claude/claude_desktop_config.json

{
  "mcpServers": {
    "quantified-self": {
      "command": "uvx",
      "args": ["quantified-self-mcp"]
    }
  }
}

(Or point "command" to your installed quantified-self-mcp binary if installed via pip).

Cursor

In Cursor, navigate to Settings → Features → MCP Servers → Add New MCP Server, or add to .cursor/mcp.json:

{
  "mcpServers": {
    "quantified-self": {
      "command": "uvx",
      "args": ["quantified-self-mcp"]
    }
  }
}

Windsurf

Add to your mcp_config.json:

{
  "mcpServers": {
    "quantified-self": {
      "command": "uvx",
      "args": ["quantified-self-mcp"]
    }
  }
}
TIP

Need guides for LM Studio, Open WebUI, or Android Health Connect? See our step-by-step Client Guides.


Related MCP server: apple-health-mcp

🚀 Quick Start

1. Install

pip install quantified-self-mcp

2. Initialize (Try in 10 Seconds with Demo Data)

Quantified Self MCP automatically creates and manages a local SQLite database (health.db). You can test it immediately with 60 days of realistic sample data, or import your personal health history:

# Instant demo mode (seeds 60 days of synthetic health data):
quantified-self-init-db --demo

# Or import your own wearable exports:
quantified-self-init-db export.xml                # Apple Health
quantified-self-init-db whoop.json --source whoop # WHOOP Developer API (v2)
quantified-self-init-db oura.json --source oura   # Oura Ring API (v2)
quantified-self-init-db health_connect_export.zip # Android Health Connect
quantified-self-init-db your-health-data.csv      # CSV

3. Verify Setup

Run the built-in diagnostic tool to verify Python dependencies, schema integrity, and database readiness:

quantified-self-doctor

4. Ask Your AI

Open your MCP-connected assistant and ask questions in plain English:

"How has my sleep and resting heart rate changed over the last 30 days?"
"Why did my HRV drop last week?"
"What patterns exist between my workout minutes and sleep quality?"
"Which of my health metrics changed the most this month?"

💡 Why Quantified Self MCP?

Most health apps either trap your data in a closed cloud dashboard or feed raw numbers blindly into an LLM. Quantified Self MCP provides a trustworthy, analytical bridge:

Feature

Standard Health Dashboards

Naive LLM Prompts

Quantified Self MCP

Data Ownership

❌ Locked in Cloud

⚠️ Uploaded to 3rd-party

✅ 100% Local SQLite

Privacy Redaction

❌ All or nothing

❌ Raw data dumped

✅ Granular Field Exclusion

Statistical Claims

⚠️ Uncalibrated percentages

❌ Hallucinated correlations

✅ ClaimEvidence & Caveats

Provenance Tracking

❌ Opaque

❌ None

✅ Multi-source origin stamps

Atomic Snapshots

⚠️ Manual exports

❌ None

✅ WAL-safe backup/restore

🔒 100% Local & Private

Your database lives on your machine in SQLite. No proprietary health cloud required. If you want a completely offline, air-gapped system, pair it with local models via LM Studio or Ollama.

You can also redact specific sensitive metrics from being exposed to the AI:

export HEALTH_PRIVATE_FIELDS=weight_kg,mood

Private fields remain safely stored in SQLite for your own records, but are blocked from all AI queries and analytics.


🔎 Evidence & Provenance Engine

When an AI analyzes your health, a number without context is dangerous.

If an LLM sees a 24% drop in HRV, did it come from 30 continuous days of wearable data, or just 4 sparse readings with a week-long gap?

Quantified Self MCP solves this with an integrated ClaimEvidence Engine:

Analysis Result
     ↓
claim: ClaimEvidence
     ├── evidence   → Raw coverage observed (days logged, max gap, freshness)
     ├── profile    → Dimension assessment (sample size, temporal span, missingness)
     └── decision   → Authoritative claim tier:
                      [ insufficient | suggestive | detectable_not_meaningful | supported ]
                      + specific mandatory caveats (must_state)

Example: Naive vs. Evidence-Backed AI Response

  • Without Evidence Engine (Naive LLM):

    "Your HRV dropped 24% over the last month. You may be overtraining."

  • With Quantified Self MCP Evidence:

    "Your HRV decreased 24% over the last 30 days — but HRV was only logged on 71% of those days with a 5-day recording gap, so this is flagged as suggestive rather than a confirmed trend. (Source: Apple Watch)."

Every analytical tool (calculate_metric_trend, explain_metric_change, detect_metric_anomalies) returns explicit claim tiers alongside mandatory disclosures.


📊 Supported Data & Integrations

Quantified Self MCP ships with 20 specialized tools spanning raw measurements, longitudinal statistics, and data health:

                  AI Agent (Claude / Cursor / LM Studio)
                                   │
                              MCP Protocol
                                   │
                                   ▼
          ┌──────────────────────────────────────────────────┐
          │               Quantified Self MCP                │
          │                                                  │
          │  • Data Access         • Longitudinal Analytics  │
          │  • Workout Tracking    • ClaimEvidence Engine    │
          │  • Provenance Resolver • Data Freshness/Health   │
          └────────────────────────┬─────────────────────────┘
                                   │
                                   ▼
                         Local SQLite (health.db)

Metrics Supported Out of the Box

Metric

Category

Standard Unit

Notes

👟 Steps

Activity

count

Summed per day with multi-source deduplication

😴 Sleep

Recovery

hours

Provenance-checked interval handling

❤️ Resting Heart Rate

Cardiovascular

bpm

Daily baseline tracking & anomaly detection

📈 HRV (Heart Rate Variability)

Recovery

ms

RMSSD/SDNN normalized

⚖️ Weight & Body Fat

Measurements

kg / %

High-precision episodic measurement tracking

🏋️ Workouts

Activity

minutes / kcal

Individual workout sessions with sport metadata

🙂 Mood

Wellness

1-5 scale

Subjective well-being logs

💧 Water Intake

Nutrition

ml

Daily fluid tracking

The schema is fully extensible with custom metric definitions in metric_registry.py.


🔌 Client Compatibility

Client / Assistant

Compatibility

Documentation

Claude Desktop

✅ Official Support

Claude Desktop Setup Guide

Cursor

✅ Full Support

Cursor IDE Setup Guide

Windsurf

✅ Full Support

Configure via mcp_config.json

LM Studio

✅ Local AI

LM Studio Setup Guide

Open WebUI

✅ Self-Hosted

Open WebUI Setup Guide

Android Health Connect

✅ Export & Ingest

Health Connect Guide


🛠️ Operations & Data Safety

Atomic WAL Backups

Never copy health.db while your server or MCP client is running: SQLite runs in WAL mode, meaning uncommitted writes live in health.db-wal.

Use the built-in backup CLI, which takes a live, consistent snapshot:

# Create a verified snapshot:
quantified-self-backup create ~/backups/health-2026-11-04.db

# Safe restore (checks schema integrity before moving old DB aside):
quantified-self-backup restore ~/backups/health-2026-11-04.db

Diagnostics

Run anytime to check your Python runtime, database version, and environment configuration:

quantified-self-doctor

📚 Documentation

Deep-dive documentation and technical specs:


🤝 Contributing

Contributions are warmly welcomed! We are actively looking for contributions in:

  • New wearable & app adapters (Garmin, Oura, Fitbit)

  • Specialized analytical & correlation tools

  • Evidence engine calibration

  • New MCP client integrations

Please review CONTRIBUTING.md and CODE_OF_CONDUCT.md before opening a pull request.


📄 License

Licensed under the MIT License.

Available Tools

20 tools
aggregate_measurementsPreview a source-priority resolution of a day's measurementsA
Read-onlyIdempotent

Preview what one day's raw measurements would roll up to if a conflicting metric were resolved using source_priority, alongside that day's actual current daily_metrics values.

daily_metrics is a database-maintained projection (see db/schema.sql): every log_measurement/import automatically keeps it in sync the moment it's written, using each metric's fixed aggregation method: summed for steps, sleep_hours, workout_minutes and water_ml; averaged for resting_heart_rate, mood, heart_rate and hrv_ms; the latest reading for weight_kg (see aggregation_rules). When a metric has measurements from more than one source on a day, the projection uses only one source's observations — never a blend of devices. It takes the highest-ranked present source in the stored source priority list (manual log_daily_metric entries rank first by default); if none is ranked, the source that observed the most hours of that day, then the one with the latest observation, then by name. This tool writes nothing: "aggregated" previews what the source_priority you pass would produce; "row" is the real, currently-stored value, which differs from "aggregated" whenever the stored priority differs from the one you pass.

If a metric has measurements from more than one source that day (e.g. an Apple Watch and a Garmin both logging resting_heart_rate), use get_metric_provenance first to see whether they actually disagree.

Privacy note: this server and its SQLite file are entirely local, but the data returned by this tool becomes part of the conversation sent to whatever model the calling client is configured with. If that model runs in the cloud rather than on your machine, treat this the same as pasting the data into a chat with that provider.

Inputs: date (required), source_priority. Returns: row, date, aggregated

calculate_metric_trendCalculate metric trendA
Read-onlyIdempotent

Fit a simple straight-line trend to one metric over a window and report its direction, slope (change per day), and r_squared (how well a straight line actually fits — low r_squared means "noisy," not "flat").

Use this tool when:

  • the user asks whether one metric is trending up/down/flat over a continuous window (e.g. "is my weight trending down?", "how has my HRV changed?").

Do not use this tool when:

  • the user wants two specific, separately-defined ranges compared (e.g. "this month vs last month") rather than a single continuous slope -> use compare_metric_periods instead.

  • the user asks what's typical/normal rather than which direction it's moving -> use get_baseline instead.

  • the user asks about isolated unusual days rather than an overall direction -> use detect_metric_anomalies instead.

Privacy note: this server and its SQLite file are entirely local, but the data returned by this tool becomes part of the conversation sent to whatever model the calling client is configured with. If that model runs in the cloud rather than on your machine, treat this the same as pasting the data into a chat with that provider.

Inputs: metric (required), end_date, start_date. Returns: claim, range, trend, metric, +2 more

clear_metricClear a single metricA
DestructiveIdempotent

Blank out (set to null) a single metric for a single day, without touching that day's other metrics. The counterpart to log_daily_metric for undoing a bad value — e.g. a mood logged for the wrong day, or a weight entered with the wrong units. Clears everything recorded for that metric/day — including individual log_measurement readings or imported rows, not just a value log_daily_metric wrote directly — so the metric genuinely goes back to "nothing recorded" rather than falling back to a blended value from whatever else is left.

Privacy note: this server and its SQLite file are entirely local, but the data returned by this tool becomes part of the conversation sent to whatever model the calling client is configured with. If that model runs in the cloud rather than on your machine, treat this the same as pasting the data into a chat with that provider.

Inputs: date (required), field (required). Returns: row, note, cleared

compare_metric_periodsCompare two periodsA
Read-onlyIdempotent

Compare one metric's average between two date ranges — e.g. "this month vs. last month" or "since starting a new medication vs. before." The two ranges may be any length and need not be adjacent or equal in size; each is summarized with its own baseline first.

Use this tool when:

  • the user names or implies two distinct date ranges to weigh against each other (e.g. "compare my average steps this month to last month", "since starting a new medication vs. before").

Do not use this tool when:

  • there's only one continuous window and the question is about direction over time, not two discrete ranges -> use calculate_metric_trend instead.

  • the user wants "what's normal" for a single window, not a before/after comparison -> use get_baseline instead.

Privacy note: this server and its SQLite file are entirely local, but the data returned by this tool becomes part of the conversation sent to whatever model the calling client is configured with. If that model runs in the cloud rather than on your machine, treat this the same as pasting the data into a chat with that provider.

Inputs: metric (required), period_a_end (required), period_b_end (required), period_a_start (required), +1 more. Returns: claim, delta, metric, period_a, +7 more

detect_metric_anomaliesDetect metric anomaliesA
Read-onlyIdempotent

Flag days where one metric deviated sharply from its own baseline over the window, using a median/MAD-based modified z-score rather than a mean/stdev z-score — more robust for short, noisy personal-health series, where the mean/stdev version is easily dragged around by the very outliers it's supposed to catch.

Use this tool when:

  • the user asks whether anything looked unusual/off/weird on particular days for one metric (e.g. "was there anything unusual about my sleep last month?").

Do not use this tool when:

  • the user wants a general sense of what's typical, with no interest in flagging specific days -> use get_baseline instead.

  • the user asks about a steady increase/decrease over time rather than isolated spikes/dips -> use calculate_metric_trend instead.

  • the user already has one specific date in mind and wants the full "why" behind it (baseline, anomaly, trend, and correlations together) -> use explain_metric_change instead.

Privacy note: this server and its SQLite file are entirely local, but the data returned by this tool becomes part of the conversation sent to whatever model the calling client is configured with. If that model runs in the cloud rather than on your machine, treat this the same as pasting the data into a chat with that provider.

Inputs: metric (required), end_date, threshold, start_date. Returns: claim, range, metric, evidence, +3 more

explain_metric_changeExplain a metric changeA
Read-onlyIdempotent

Build an evidence bundle for "why did my look like that on ?": that day's value against a 90-day baseline, whether it qualifies as an anomaly, the trend leading into it, and any other metric that correlates with it strongly enough to be worth mentioning. Returns facts, not an explanation — turning "sleep was 2.6 stdev below baseline and resting heart rate correlates at r=0.71" into an actual answer for the person is what the calling model should do with these facts, not something this tool guesses at itself.

Do not use this tool when:

  • scanning across many metrics for what changed lately, without a specific metric/date in mind -> use get_recent_changes instead.

  • you only need one piece of this bundle (just the baseline, just the trend, just anomalies, or just a correlation) rather than the full why-explanation -> use get_baseline, calculate_metric_trend, detect_metric_anomalies, or find_metric_correlation directly instead.

Privacy note: this server and its SQLite file are entirely local, but the data returned by this tool becomes part of the conversation sent to whatever model the calling client is configured with. If that model runs in the cloud rather than on your machine, treat this the same as pasting the data into a chat with that provider.

Inputs: date (required), metric (required). Returns: date, trend, value, metric, +13 more

export_health_data_csvExport health data to CSVA
DestructiveIdempotent

Write daily health metrics for a date range to a CSV file on disk, next to the database, instead of returning every row through this tool's own result.

Privacy note: this server and its SQLite file are entirely local, but the data returned by this tool becomes part of the conversation sent to whatever model the calling client is configured with. If that model runs in the cloud rather than on your machine, treat this the same as pasting the data into a chat with that provider.

Unlike read_health_data, this is not capped at MAX_ROWS_RETURNED and the row values themselves are not included in this tool's response — only the resulting file's path and a row count are. That means a long-range export doesn't have to pass through a cloud LLM's context just to produce a file you can open yourself (in a spreadsheet, a notebook, another tool, etc.). Any metric listed in HEALTH_PRIVATE_FIELDS is still written as an empty cell in the file, since those fields shouldn't leave the database at all, not just stay out of the model's context.

Inputs: end_date, start_date. Returns: path, range, rows_exported

find_metric_correlationFind correlation between two metricsA
Read-onlyIdempotent

Compute the Pearson correlation between two metrics over the same window, joined by date. Correlation, not causation: a strong r just means the two moved together, not that one caused the other. A question phrased causally ("does my sleep affect my mood?") still gets only this association. The result does not show that one metric affects or causes the other, so report r, n and the wording the claim allows, and say nothing stronger.

Use this tool when:

  • the user names or implies two different metrics and asks whether they move together (e.g. "does my sleep affect my mood?", "do days with more workout minutes tend to have a higher HRV?").

Do not use this tool when:

  • only one metric is in question -> use get_baseline, calculate_metric_trend, or detect_metric_anomalies instead, depending on what's being asked about that one metric.

  • the question is why one metric looked the way it did on a specific date -> use explain_metric_change, which already reports the other metrics that correlate with it.

  • the question is whether one metric differs between two time periods (e.g. before vs. after a date) -> use compare_metric_periods.

Reading the result: report what was observed (r, n, and how many days of each metric were present) separately from what may be claimed about it. Read claim.decision before reporting r. Its tier (insufficient, suggestive, detectable_not_meaningful or supported) is the strongest wording allowed, and a high r from a thin or gappy sample is capped there, not in r itself, so never judge a correlation from r alone. claim.decision.template is a sentence frame, not finished text: replace "..." with the finding, and {limiting_factors} with the must_state entries put into plain words. Those entries are codes, not sentences (practical_not_evaluated, for example, means that whether the difference matters in practice was not checked), so do not quote a code to the user. When data_health is not VALID, say why.

Privacy note: this server and its SQLite file are entirely local, but the data returned by this tool becomes part of the conversation sent to whatever model the calling client is configured with. If that model runs in the cloud rather than on your machine, treat this the same as pasting the data into a chat with that provider.

Inputs: metric_a (required), metric_b (required), end_date, lag_days, +1 more. Returns: n, r, note, claim, +6 more

get_baselineGet metric baselineA
Read-onlyIdempotent

Compute "what's normal" for one metric over a window: mean, median, and standard deviation. This is the number every other analytics tool below measures against, so a wider window (60-90+ days) gives a more stable baseline than the 30-day default read_health_data uses.

Use this tool when:

  • the user asks what's normal/typical/usual for one metric (e.g. "what's normal for my resting heart rate?"), with no particular day or direction of change in mind.

Do not use this tool when:

  • the user asks whether a metric is going up/down over time -> use calculate_metric_trend instead.

  • the user asks whether specific days looked unusual -> use detect_metric_anomalies instead.

  • the user wants two ranges compared against each other -> use compare_metric_periods instead.

Privacy note: this server and its SQLite file are entirely local, but the data returned by this tool becomes part of the conversation sent to whatever model the calling client is configured with. If that model runs in the cloud rather than on your machine, treat this the same as pasting the data into a chat with that provider.

Inputs: metric (required), end_date, start_date. Returns: claim, range, metric, baseline, +2 more

get_data_statusCheck how current the data isA
Read-onlyIdempotent

Report how current and complete the database is: latest data date, coverage window, gaps, last successful import, and an overall status of CURRENT, STALE, INCOMPLETE, NO_DATA or IMPORT_FAILED. Check this before drawing conclusions from any analysis, and say so when the status is anything other than CURRENT. The status and latest_data describe the database as a whole, so one metric that is still arriving keeps them current while another has stopped: read stale_metrics too, and do not present a listed metric as current.

Use this tool when:

  • the user asks how up to date their data is, or whether an import is needed.

  • you are about to interpret recent data and want to know whether it is fresh enough to trust.

Privacy note: this server and its SQLite file are entirely local, but the data returned by this tool becomes part of the conversation sent to whatever model the calling client is configured with. If that model runs in the cloud rather than on your machine, treat this the same as pasting the data into a chat with that provider.

Inputs: stale_after_days. Returns: gaps, as_of, action, reason, +12 more

get_import_statusList recent importsA
Read-onlyIdempotent

List the most recent import runs, newest first: which importer, the source file's name and SHA-256, whether the run succeeded, failed or was interrupted, and how many rows were loaded, skipped and written.

Use this tool when:

  • the user asks what was imported and when, or why an import did not go through.

Privacy note: this server and its SQLite file are entirely local, but the data returned by this tool becomes part of the conversation sent to whatever model the calling client is configured with. If that model runs in the cloud rather than on your machine, treat this the same as pasting the data into a chat with that provider.

Inputs: limit. Returns: imports

get_metric_historyGet metric historyA
Read-onlyIdempotent

Read one metric's day-by-day values, without the other six metrics read_health_data always includes. Use this when you only care about a single metric (e.g. before calling get_baseline or calculate_metric_trend yourself) and don't need the full multi-metric payload.

Use this tool when:

  • the user wants one specific metric's history/trend over time (e.g. "show my weight over the last 30 days", "how has resting heart rate changed this month").

Do not use this tool when:

  • the request is broad/general, across multiple metrics at once -> use read_health_data instead.

Privacy note: this server and its SQLite file are entirely local, but the data returned by this tool becomes part of the conversation sent to whatever model the calling client is configured with. If that model runs in the cloud rather than on your machine, treat this the same as pasting the data into a chat with that provider.

Inputs: metric (required), end_date, start_date. Returns: range, metric, points, evidence, +1 more

get_metric_provenanceBreak a metric down by sourceA
Read-onlyIdempotent

Show one metric's raw measurements for one day, broken down by which source reported them — answers "which one is correct?" when e.g. an Apple Watch and a Garmin disagree on resting heart rate, instead of silently averaging two different devices into one number.

Do not use this tool when:

  • the user just wants a plain day-by-day history for the metric, with no need to see the per-source breakdown -> use get_metric_history instead.

Privacy note: this server and its SQLite file are entirely local, but the data returned by this tool becomes part of the conversation sent to whatever model the calling client is configured with. If that model runs in the cloud rather than on your machine, treat this the same as pasting the data into a chat with that provider.

Inputs: date (required), metric (required). Returns: date, metric, sources, conflict

get_recent_changesGet recent changesA
Read-onlyIdempotent

Scan every (non-private) metric for what's changed lately: a recent period vs. the four-times-as-long period before it (period-over-period shift), any anomalies inside the recent period, and a trend over it. The single best tool to start a "how have I been doing?" conversation with — it does the scanning across all metrics that would otherwise take one get_baseline/detect_metric_anomalies/calculate_metric_trend call per metric.

Do not use this tool when:

  • the user already named a specific metric and wants the full why-bundle for it (value, baseline, anomaly flag, trend, correlated metrics) -> use explain_metric_change instead.

Privacy note: this server and its SQLite file are entirely local, but the data returned by this tool becomes part of the conversation sent to whatever model the calling client is configured with. If that model runs in the cloud rather than on your machine, treat this the same as pasting the data into a chat with that provider.

Inputs: days. Returns: changes, data_health, recent_range, baseline_range

log_daily_metricLog a daily metricA
DestructiveIdempotent

Record one or more health metrics for a single day, creating that day's row if it doesn't already have one.

Privacy note: this server and its SQLite file are entirely local, but the data returned by this tool becomes part of the conversation sent to whatever model the calling client is configured with. If that model runs in the cloud rather than on your machine, treat this the same as pasting the data into a chat with that provider.

Only the metrics you pass are written — anything left as null is not touched, so logging just today's mood doesn't erase today's steps if they were set earlier. To undo a value logged by mistake, use clear_metric rather than trying to overwrite it with a placeholder.

Use this tool when:

  • the user is recording a simple day-level value for one of the nine fixed metrics below (e.g. "log my weight as 82 kg", "I walked 8,000 steps today").

Do not use this tool when:

  • the observation needs its own timestamp/source, or the day may have more than one reading of the same metric -> use log_measurement instead.

  • it's a workout/exercise session -> use log_workout_session instead (workout_minutes here is just the daily total, not the session itself).

Inputs: date (required), mood, steps, hrv_ms, +6 more. Returns: row, logged

log_measurementLog a raw measurementA

Record a single raw observation — one metric, one value, one point in time — rather than a whole day's summary. Use this instead of log_daily_metric when the source, exact time, or the fact that there were multiple readings that day matters (e.g. three separate workouts, or a wearable's periodic heart-rate samples).

Use this tool when:

  • recording one timestamped observation where the exact time, source, or possibility of multiple same-day readings matters (e.g. "record my blood pressure reading from my cuff at 7am").

Do not use this tool when:

  • it's just a single end-of-day value for a fixed metric -> use log_daily_metric instead.

  • it's a workout/exercise session -> use log_workout_session instead.

Privacy note: this server and its SQLite file are entirely local, but the data returned by this tool becomes part of the conversation sent to whatever model the calling client is configured with. If that model runs in the cloud rather than on your machine, treat this the same as pasting the data into a chat with that provider.

Inputs: value (required), metric (required), timestamp (required), unit, +2 more. Returns: measurement

log_workout_sessionLog a workout sessionA

Record one workout as a structured event — activity, timing, intensity, and heart-rate response — rather than folding it into the day's workout_minutes total. Use this alongside (not instead of) log_daily_metric/log_measurement for workout_minutes: this is what lets explain_metric_change say what the workout was, not just how long it ran. A day can have more than one session; each call adds a new row.

Use this tool when:

  • the user describes an actual workout/exercise session (e.g. "I went running for 40 minutes", "log today's strength workout").

Do not use this tool when:

  • the user only wants to record the day's total exercise minutes as a single number, with no activity type/timing/intensity -> use log_daily_metric (workout_minutes) or log_measurement instead.

Privacy note: this server and its SQLite file are entirely local, but the data returned by this tool becomes part of the conversation sent to whatever model the calling client is configured with. If that model runs in the cloud rather than on your machine, treat this the same as pasting the data into a chat with that provider.

Inputs: date (required), activity_type (required), duration_minutes (required), notes, +5 more. Returns: session

read_health_dataRead health dataA
Read-onlyIdempotent

Read daily health metrics from the local database: steps, sleep hours, resting heart rate, weight (kg), workout minutes, mood, and water intake (ml).

Use this tool when:

  • the request is broad/general, across multiple metrics at once (e.g. "what health data do I have?", "overview of this week").

Do not use this tool when:

  • the user wants one specific metric's history/trend over time -> use get_metric_history instead.

  • the user wants raw/individual measurement rows (timestamp, source) -> use read_measurements instead.

  • the user wants workout sessions specifically -> use read_workout_sessions instead.

  • the user is asking why something changed, or wants a trend, anomaly, comparison, or correlation -> use explain_metric_change (one metric, one date) or get_recent_changes (scan across all metrics) instead.

Privacy note: this server and its SQLite file are entirely local, but the data returned by this tool becomes part of the conversation sent to whatever model the calling client is configured with. If that model runs in the cloud rather than on your machine, treat this the same as pasting the data into a chat with that provider.

Inputs: end_date, start_date. Returns: rows, range, summary, coverage, +1 more

read_measurementsRead raw measurementsA
Read-onlyIdempotent

Read individual measurement rows (not the daily_metrics aggregate), most recent first. Use this to see exactly when and where each reading came from, rather than just a day's summarized value.

Use this tool when:

  • the user wants raw/individual observations (e.g. "what measurements have I recorded?"), including their timestamp or source.

Do not use this tool when:

  • the user wants a broad, multi-metric overview -> use read_health_data instead.

  • the user wants one metric's day-by-day history -> use get_metric_history instead.

  • the user wants workout sessions -> use read_workout_sessions instead.

Privacy note: this server and its SQLite file are entirely local, but the data returned by this tool becomes part of the conversation sent to whatever model the calling client is configured with. If that model runs in the cloud rather than on your machine, treat this the same as pasting the data into a chat with that provider.

Inputs: limit, metric, source, end_date, +1 more. Returns: count, measurements

read_workout_sessionsRead workout sessionsA
Read-onlyIdempotent

Read individual workout sessions (not the daily_metrics workout_minutes total), most recent day first. Use this to see what each workout actually was — activity, timing, intensity, heart rate — rather than just a day's summed minutes.

Use this tool when:

  • the user asks about workouts/exercise sessions specifically (e.g. "what workouts did I do this week?", "show my recent gym sessions").

Do not use this tool when:

  • the user just wants the daily workout_minutes total, not individual sessions -> use read_health_data or get_metric_history instead.

Privacy note: this server and its SQLite file are entirely local, but the data returned by this tool becomes part of the conversation sent to whatever model the calling client is configured with. If that model runs in the cloud rather than on your machine, treat this the same as pasting the data into a chat with that provider.

Inputs: limit, end_date, start_date, activity_type. Returns: count, sessions

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.2/5.0

    Scored across 20 tools

    Disambiguation4/5

    Nearly every tool carries explicit 'Use this tool when / Do not use this tool when' routing rules, so an agent can separate logging (daily vs. measurement vs. workout) and the analytics family (baseline/trend/anomaly/correlation/compare/recent-changes/explain) reliably. The remaining friction is in the raw-row readers — read_measurements, get_metric_provenance and aggregate_measurements all surface per-source day-level data and their boundary is only implied by prose, not by name.

    Naming Consistency5/5

    All 20 names are snake_case verb_noun pairs with a stable verb vocabulary: log_/clear_ for writes, read_/get_ for reads, and detect_/find_/calculate_/compare_/explain_/aggregate_ for analysis. No camelCase, no bare nouns, and the prefix reliably predicts whether a tool mutates data.

    Tool Count4/5

    Twenty tools is at the heavy end of the range, but they divide into coherent groups (4 writes, 4 raw readers, 7 analytics, 3 status/export) and each covers an operation the others do not. A couple of the deeper analytics tools (explain_metric_change, get_recent_changes) could arguably be compositions of simpler ones, which keeps this just shy of a 5.

    Completeness4/5

    The surface is strong: logging, correction via clear_metric, aggregate and raw reads, CSV export, freshness/import status, and a full analytics ladder from baseline through correlation to a why-bundle. Gaps remain in lifecycle coverage — there is no way to edit or delete an individual measurement row or a workout session (only clear_metric at day granularity), and imports can be inspected but not triggered.

    Maintenance

    ActivityActive
    ResponsivenessLimited data

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      Enables querying personal data synced from services like Lunch Money and Strava using SQL via Claude.
      11 npm
      1
      MIT
    • A
      license
      B
      quality
      D
      maintenance
      Enables AI agents to interact with local SQLite databases with full CRUD, schema introspection, foreign key relations, generated columns, and multi-format import/export (CSV, JSON, XLSX) through natural language.
      26
      14 npm
      MIT