Quantified Self MCP Server
A privacy-first, local MCP server that lets AI assistants query and analyze personal health data stored in SQLite, with evidence tiers, provenance, and privacy controls.
Read daily health metrics across dates, plus summaries and coverage.
Export health data to CSV without returning every row through the model.
Log or clear daily metrics such as steps, sleep, heart rate, HRV, weight, mood, water, and workout minutes.
Log and read raw timestamped measurements with source metadata.
Preview source-priority aggregation and inspect per-source metric provenance/conflicts.
Log and read structured workout sessions with activity, duration, intensity, and heart-rate data.
Check database freshness, coverage, gaps, stale metrics, and recent import history.
Retrieve one metric’s day-by-day history with data-quality evidence.
Compute baselines, detect anomalies, calculate trends, compare periods, and find correlations.
Scan recent changes across all metrics and explain a specific metric change on a date.
Return claim tiers, caveats, evidence profiles, and mandatory wording guidance for analytical results.
Keep data local and support private-field redaction from AI queries.
Allows importing health data from Apple Health export files (XML) into the local database, as well as reading and logging daily health metrics.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Quantified Self MCP Server What was my average sleep and steps last week?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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.jsonWindows:
%APPDATA%\Claude\claude_desktop_config.jsonLinux:
~/.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"]
}
}
}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-mcp2. 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 # CSV3. Verify Setup
Run the built-in diagnostic tool to verify Python dependencies, schema integrity, and database readiness:
quantified-self-doctor4. 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,moodPrivate 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 | |
Cursor | ✅ Full Support | |
Windsurf | ✅ Full Support | Configure via |
LM Studio | ✅ Local AI | |
Open WebUI | ✅ Self-Hosted | |
Android Health Connect | ✅ Export & Ingest |
🛠️ 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.dbDiagnostics
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 toolsaggregate_measurementsPreview a source-priority resolution of a day's measurementsARead-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 trendARead-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_periodsinstead.the user asks what's typical/normal rather than which direction it's moving -> use
get_baselineinstead.the user asks about isolated unusual days rather than an overall direction -> use
detect_metric_anomaliesinstead.
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 metricADestructiveIdempotent
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 periodsARead-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_trendinstead.the user wants "what's normal" for a single window, not a before/after comparison -> use
get_baselineinstead.
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 anomaliesARead-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_baselineinstead.the user asks about a steady increase/decrease over time rather than isolated spikes/dips -> use
calculate_metric_trendinstead.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_changeinstead.
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 changeARead-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_changesinstead.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, orfind_metric_correlationdirectly 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 CSVADestructiveIdempotent
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 metricsARead-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, ordetect_metric_anomaliesinstead, 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 baselineARead-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_trendinstead.the user asks whether specific days looked unusual -> use
detect_metric_anomaliesinstead.the user wants two ranges compared against each other -> use
compare_metric_periodsinstead.
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 isARead-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 importsARead-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 historyARead-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_datainstead.
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 sourceARead-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_historyinstead.
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 changesARead-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_changeinstead.
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 metricADestructiveIdempotent
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_measurementinstead.it's a workout/exercise session -> use
log_workout_sessioninstead (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_metricinstead.it's a workout/exercise session -> use
log_workout_sessioninstead.
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) orlog_measurementinstead.
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 dataARead-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_historyinstead.the user wants raw/individual measurement rows (timestamp, source) -> use
read_measurementsinstead.the user wants workout sessions specifically -> use
read_workout_sessionsinstead.the user is asking why something changed, or wants a trend, anomaly, comparison, or correlation -> use
explain_metric_change(one metric, one date) orget_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 measurementsARead-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_datainstead.the user wants one metric's day-by-day history -> use
get_metric_historyinstead.the user wants workout sessions -> use
read_workout_sessionsinstead.
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 sessionsARead-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_dataorget_metric_historyinstead.
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
Scored across 20 tools
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.
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.
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.
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
Related MCP Connectors
Personal finance, bank account, and shared memory connector for Claude, ChatGPT, Gemini Spark & more
Query, explore, and manage your databases through natural conversation.
Query 40 databases from Claude, ChatGPT, or Cursor — on any device. Read-only, encrypted, audited.
Your personal data for AI — Telegram, bank, courses, Zoom & more, scoped to you.
Related MCP Servers
- AlicenseAqualityAmaintenanceAn MCP server that allows users to query and analyze their Apple Health data using SQL and natural language, utilizing DuckDB for fast and efficient health data analysis.391 npm574MIT
- AlicenseNot gradedqualityDmaintenanceLoads Apple Health export data into a local SQLite database and exposes tools to query health metrics and workout records via natural language.35 PyPI3MIT
- AlicenseNot gradedqualityDmaintenanceEnables querying personal data synced from services like Lunch Money and Strava using SQL via Claude.11 npm1MIT
- AlicenseBqualityDmaintenanceEnables 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.2614 npmMIT