Personal Health Data (community)
Read-only MCP server for retrieving your own Withings health data and completing one-time OAuth setup.
Get body measurements/weigh-ins over a date range: weight, body composition, BMR, metabolic/vascular age, standing heart rate.
Get blood pressure readings with systolic, diastolic, and pulse.
Get daily activity: steps, distance, floors, activity minutes, calories, heart rate, and HR zones.
Get nightly sleep summaries: sleep stages, efficiency, latency, wake-ups, score, heart/breathing rate, snoring.
List linked Withings devices with type, model, battery, firmware, and last sync.
Check setup/auth status without exposing secrets or tokens.
Start one-time authorization and exchange a redirect code for non-localhost callback flows.
Use date ranges up to 366 days; defaults are last 30 days for measurements/blood pressure and last 7 days for activity/sleep.
Read-only scopes only: no writing, deleting, or changing Withings data.
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., "@Personal Health Data (community) How did I sleep last night?"
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.
Personal Health Data (community)
Compatible with Withings.
An unofficial, community-built plugin. It is not affiliated with, endorsed or sponsored by Withings. For general wellness and informational use only. It is not a medical device and not medical advice (see Disclaimer). It reads only your own data, through Withings' Public API (https://developer.withings.com), using a Withings developer app that you create for yourself. It is read-only: it cannot change anything in your Withings account or on your devices.
Withings is a trademark of Withings. The name is used here only to say which service the plugin is compatible with. No Withings logo or artwork is used.
It is a small stdio MCP server (Node.js 18+, no dependencies, no install step) packaged as a Cursor plugin, plus one
skill that tells the agent when to use the tools. It works with any MCP client that can launch a local stdio server
(Cursor, Grok Bot, Claude Desktop and others). The entry point is server/index.mjs; the setup CLI is
server/cli.mjs.
New here? Start with the walkthrough
Setting this up is not super straightforward, but if you follow the steps exactly it should be fine. It is mostly copy-pasting. The first time takes about 10–15 minutes.
docs/SETUP-WITHINGS.md is the step-by-step for someone who has never used a developer portal. The short version of the callback: if you do not have a website, register exactly http://localhost:8765/callback (the plugin's default; it can answer that address on your own computer). An assistant should call setup_guide and give you one step at a time. Before anyone opens the Withings link, auth_status / start_authorization name the redirect the running process will actually send, and whether that value was configured or just the default.
Related MCP server: Withings MCP Server
Tools
Tool | What it returns | Withings endpoint (POST) | Scope |
| one row per weigh-in: weight, fat %, fat mass, fat-free mass, muscle, hydration, bone, visceral fat, extra/intracellular water, BMR, metabolic age, pulse wave velocity, vascular age, standing heart rate |
| user.metrics |
| systolic, diastolic, pulse |
| user.metrics |
| steps, distance, floors, activity minutes, calories, HR and HR zones per day |
| user.activity |
| per night: total sleep, time in bed, stages, efficiency, latency, wake-ups, sleep score, HR, breathing rate, snoring. Empty result = no sleep device in Withings; it then returns a |
| user.activity |
| linked devices: type, model, battery, firmware, last sync (no serial numbers or ids) |
| user.info |
| step-by-step setup script plus where this server is right now (no secrets) | (local) | |
| configured (env or | (local) | |
| one-time authorization (see Setup). |
|
Range tools take start_date / end_date (YYYY-MM-DD, your local dates; up to 366 days per call). Values are decoded
from Withings' value × 10^unit format into the units named in each field. Metrics your device didn't measure are
null, never 0. Every result carries data_source: "Withings".
The plugin requests only the read scopes user.info, user.metrics and user.activity. It never requests anything
that can write. Withings may still add user.sleepevents to the grant it returns; that scope only drives sleep-event
webhook notifications, which this plugin doesn't use. auth_status shows both lists: requested_scopes and
granted_scopes (with any extras explained under extra_granted_scopes).
Install
There is nothing to install: the server uses only Node.js built-ins, so there is no npm install / npm ci step
(a package-lock.json with no packages is included so npm ci also succeeds). Get the code and run it with Node:
git clone https://github.com/emilniclashofseth/personal-health-data-mcp.git
cd personal-health-data-mcp
node server/index.mjs # stdio MCP server (normally your MCP client starts it)In Cursor you can instead install it from the directory: https://cursor.directory/plugins/personal-health-data-community
Setup (about 10–15 minutes the first time)
Follow docs/SETUP-WITHINGS.md if this is your first time. The rest of this section is the same setup, shorter, for reference.
You need a Withings account with your devices synced in the Withings app. Withings' current terms say that sharing data with a third-party service may require an active Withings+ subscription (Terms of Use §6.4.1); check this if authorization works but data calls fail.
1. Create your own Withings developer app
Open the Withings Developer Dashboard: https://developer.withings.com/dashboard/ and sign in. You can use your normal Withings account. Withings' guide: https://developer.withings.com/developer-guide/v3/integration-guide/public-health-data-api/developer-account/create-your-accesses-no-medical-cloud
Create an application: Public API integration on the Public Cloud (free; endpoint
https://wbsapi.withings.net).Give it any name and description that is your own, e.g. "My health data". Don't call it "Withings …".
Callback URL — pick one, and register it exactly. The plugin's default, used whenever
WITHINGS_REDIRECT_URIis unset, ishttp://localhost:8765/callback.auth_statusshows the value the running process will send (redirect_uri), whether you set it or it was defaulted (redirect_uri_defaulted,redirect_uri_source), and a warning to compare it with the dashboard before you open Withings. If they differ, Withings showsredirect_uri_mismatchand does not give you a code.No website of your own:
http://localhost:8765/callback. LeaveWITHINGS_REDIRECT_URIempty. Withings acceptshttp/localhostcallbacks for apps in the integration (development) phase, which is limited to 10 users. That's fine because the only user is you. With this callback the plugin finishes the setup by itself when the browser is on the same computer: it listens on127.0.0.1:8765for up to 10 minutes after you start authorization. Use a different port by registering it and settingWITHINGS_REDIRECT_URIto the same value. Do not point the callback at a third-party redirect service; the browser is sent there with a one-time code.If the dashboard rejects localhost: register any HTTPS URL you control (e.g. a page on your own website). The page doesn't have to do anything. After you approve, your browser lands on
https://your-url?code=…&state=…. Copy that whole address within 30 seconds (Withings codes are valid for 30 s and work once) and give it to the setup CLI or theexchange_codetool. SetWITHINGS_REDIRECT_URIto exactly the URL you registered.
Save. The dashboard shows your Client ID and Secret (you can look them up there again later).
2. Give the client id and secret to the server
The server reads WITHINGS_CLIENT_ID, WITHINGS_CLIENT_SECRET and, if you didn't use the default callback,
WITHINGS_REDIRECT_URI. Pass them in one of two ways (never paste the secret into chat, a project file or a commit):
WITHINGS_ENV_FILE(recommended outside Cursor): the absolute path of a600file withKEY=valuelines:WITHINGS_CLIENT_ID=your-client-id WITHINGS_CLIENT_SECRET=your-secret # optional: # WITHINGS_REDIRECT_URI=http://localhost:8765/callback # WITHINGS_TOKEN_FILE=/absolute/path/tokens.jsonOnly those four names are read; blank lines,
#comments,exportand quotes are allowed; nothing is shell-expanded. Variables set directly in the server's environment win over the file.Directly as environment variables in your client's settings for the server.
The env file is re-read on every auth_status, setup_guide, start_authorization, and exchange_code call, so a
callback change in that file shows up on the next call. Values in the process environment (Cursor's Configure fields,
or variables you exported in the shell) were copied in when the process started. They do not change until a new
process starts, and they override the file. auth_status reports process_started_at, config_read_at,
redirect_uri_source (default, environment, or env_file), and config_stale_warning when the file and the
environment disagree.
Then register the server with your MCP client and restart/reload it when you set or change environment variables:
Cursor: install the plugin. When it asks for settings, paste the Client ID into
WITHINGS_CLIENT_IDand the Secret intoWITHINGS_CLIENT_SECRET(and the callback URL intoWITHINGS_REDIRECT_URIif you didn't use the default). To change them later: Plugins → Personal Health Data (community) → Configure, then reload the window.Any other client: see Use with other MCP clients.
3. Authorize once
Ask the agent to "set up Withings" (it calls start_authorization), or run it yourself:
WITHINGS_ENV_FILE=/absolute/path/to/withings.env node server/cli.mjs authorize(Putting the secret on the command line instead, WITHINGS_CLIENT_ID=... WITHINGS_CLIENT_SECRET=... node server/cli.mjs authorize, also works but leaves it in your shell history.) The CLI must use the same WITHINGS_TOKEN_FILE as your MCP
server (the default path, unless you set one). Open the printed URL, sign in to Withings and approve. With the localhost callback and the browser on the same computer, the page says "Connected" and
you're done. With your own HTTPS callback, paste the full redirect URL when the CLI asks (within 30 seconds). Check with
auth_status or node server/cli.mjs status.
Then ask something like "How has my weight and body fat changed this month?"
Browser and server on different computers
The localhost callback only completes by itself when the browser runs on the same machine as the server, because
Withings redirects your browser to http://localhost:8765/callback, i.e. to the browser's own machine.
The common case for a hosted assistant is exactly that: the plugin runs on the assistant's computer, and your browser
is on your own computer. The localhost page fails to load. That is expected. Copy the whole address from the address
bar (http://localhost:8765/callback?code=…&state=…) and paste it back to the assistant right away (within 30
seconds). The assistant passes it to exchange_code on the same server process that ran start_authorization.
The same paste works if the server is a remote box, a container, or an SSH session. At the CLI, paste it at the
Redirect URL (or wait): prompt of the same node server/cli.mjs authorize run.
Or forward the port before you approve: ssh -L 8765:127.0.0.1:8765 you@server, then the redirect reaches the
server's listener through the tunnel and finishes by itself.
Use with other MCP clients (Grok Bot, Claude Desktop, etc.)
The bundled mcp.json is for Cursor: ${CURSOR_PLUGIN_ROOT} and ${WITHINGS_…} are Cursor plugin variables, and
other clients will not expand them. Elsewhere, point the client at server/index.mjs with an absolute path (no
~, no ${...}) and pass the credentials by file:
{
"mcpServers": {
"personal-health-data": {
"command": "node",
"args": ["/absolute/path/to/personal-health-data-mcp/server/index.mjs"],
"env": { "WITHINGS_ENV_FILE": "/absolute/path/to/withings.env" }
}
}
}commandmay need the absolute path tonode(which node) if the client does not inherit your shell'sPATH.Put secrets in
env(better: in the env file), never inargs:argsshow up in process lists.On Windows use a path like
"C:\\Users\\you\\personal-health-data-mcp\\server\\index.mjs".
Keeping secrets out of client configs: launcher script
If your client can only run a command, or you want one place to manage credentials, use a launcher that points the
server at the 600 env file and execs it. The client config then names only the script. A copy is in
examples/run-health-data.sh:
#!/usr/bin/env bash
set -euo pipefail
ROOT=/absolute/path/to/personal-health-data-mcp
export WITHINGS_ENV_FILE="${WITHINGS_ENV_FILE:-$HOME/.config/personal-health-data-mcp/withings.env}"
[ -r "$WITHINGS_ENV_FILE" ] || { echo "run-health-data: env file missing or unreadable" >&2; exit 1; }
exec node "$ROOT/server/index.mjs"Client config: {"command": "/absolute/path/to/run-health-data.sh", "args": []}. Keep the script, the env file and
the token file outside any repository you commit.
Tokens, lifetime and re-authorizing
Tokens are stored in
~/.config/personal-health-data-mcp/tokens.json($XDG_CONFIG_HOMEif set; on Windows%APPDATA%\personal-health-data-mcp\tokens.json), outside any project, with the folder at mode700and the file at600. Your Withings user id is not stored.WITHINGS_TOKEN_FILE(env or env file) sets another absolute path, e.g. to keep it next to your other secrets or to give separate installs separate files. The server creates the folder (700) and file (600) when you authorize.Sharing one token file between installs (two MCP clients, two machines with a synced folder, a dev copy and an installed copy): the refresh token rotates on every refresh and the old one stops working soon after. Sharing is only safe when every process uses the same file on the same filesystem and the same lock: an exclusive
flockon the sidecar file<token file>.lock(for exampletokens.json.lock), re-reading the token file after taking the lock and saving by atomic rename. This plugin does that from v0.3.1 (through theflock,perlorpython3command; on Windows it only serialises within one process). Your own script can join in, e.g. Python:fcntl.flock(open(path + ".lock", "a"), fcntl.LOCK_EX). Never delete the lock file. Don't share the token file with tools that lock differently, copy it between machines, or put it on a synced/network folder where the lock doesn't work: one side will refresh, the other will keep an outdated refresh token, and you'll have to re-authorize. If in doubt, give each install its ownWITHINGS_TOKEN_FILEand authorize each once.Per Withings' docs (https://developer.withings.com/developer-guide/v3/integration-guide/public-health-data-api/get-access/access-and-refresh-tokens-no-recover): the access token expires after 3 hours; the refresh token expires after 1 year; and every refresh returns a new refresh token. The old one stops working 8 hours later or as soon as the new access token is used.
The server refreshes the access token automatically when it has less than 10 minutes left (or when Withings rejects it) and saves the new pair straight away, under a file lock, so two processes don't race on the rotating token.
Re-authorize (run step 3 again) if the refresh token expired (about a year without use), if you revoked access in the Withings app, if you changed the app's secret, or if a tool says the token was rejected. Running authorization again simply replaces the token file.
Revoke access at any time in the Withings app (connected apps/partners), or delete the app in the developer dashboard. Deleting the token file removes local access.
Privacy and security
The client secret is stored either by your MCP client (e.g. Cursor's plugin settings) and passed to the local server as an environment variable, or in a file you control (
WITHINGS_ENV_FILE) that the server reads at runtime. It is sent only to Withings (wbsapi.withings.net), and only when tokens are issued or refreshed.Errors are redacted. The secret, codes and tokens are never logged or returned by any tool.
No telemetry, no third-party servers, no server run by the author. The code sends requests only to Withings. Your data goes from Withings to the computer running the server and then to your MCP client and the AI model you use there, under those providers' terms. Make sure you are comfortable sharing health data with that model.
The author of this plugin does not receive, store or have access to your credentials, tokens or data. Tokens are kept only in the local token file described above.
This is for your own data. Don't use it to run a service for other people, and don't share your app's secret. Each person who installs the plugin creates their own Withings app, so no one else's app or server sits between you and Withings.
Troubleshooting
The same cases, with the wording to expect, are in docs/SETUP-WITHINGS.md ("If you get this error, do this").
Redirect URI mismatch. Withings shows
redirect_uri_mismatch/ "does not match partner callback url" instead of the approval page, orexchange_code/ the CLI says it rejected the callback and namesredirect_uri=…. The value this process sends (redirect_uriinauth_status) must equal a Callback URL in your developer app character for character: scheme (httpvshttps), host (localhostvs127.0.0.1), port, path, and trailing slash.redirect_uri_defaulted: truemeansWITHINGS_REDIRECT_URIis unset and the server is usinghttp://localhost:8765/callback— register that exact URL, or set the variable to the URL you already registered. Ifredirect_uri_sourceisenvironment, restart or reload so a new process starts. If it isenv_file, the next auth call re-reads the file; a set environment variable still wins (config_stale_warning). Then runstart_authorizationagain. The code from the failed attempt can't be reused.Changed the settings but the old value is still in use. Compare
process_started_atwith when you edited. Environment variables are frozen for the life of the process (a Cursor reload that does not actually respawn the server leaves the old value). The env file is not frozen.config_notesays which redirect this process used.config_stale_warningis set when the env file and the environment disagree. For the redirect it names both URLs. ForWITHINGS_CLIENT_IDorWITHINGS_CLIENT_SECRETit names the variable only and does not print the value.Code expired / invalid (status 304, "invalid code"). Codes last about 30 seconds, work once, and must be exchanged by the same server process that started the authorization.
exchange_codesays this separately from a callback mismatch. Runstart_authorizationagain and paste the new address immediately. If the pasted address's callback is not the one this server is using, the code is not sent (so it is not used up) and the error names both URLs."Not set up yet" although you set the values: your client probably passed the literal
${WITHINGS_CLIENT_ID}(only Cursor expands it). Use an absoluteWITHINGS_ENV_FILE.auth_statusshowsenv_file_problemif the file can't be read.State mismatch: the pasted redirect belongs to a different
start_authorization. Start again; don't reuse the code.The localhost page will not load: the browser is not on the same computer as the plugin. That includes a hosted assistant (plugin on the assistant's computer, browser on yours). Copy the full address bar and paste it back to the assistant within 30 seconds, or forward the port (
ssh -L 8765:127.0.0.1:8765 you@server).Port 8765 in use: free it, or register another localhost port and set
WITHINGS_REDIRECT_URIto match.Token rejected after it worked before: see "Sharing one token file" above; re-authorize. If you changed the callback since authorizing,
redirect_changed_warningsays so; data calls keep working until you re-authorize.get_sleep_summaryreturns nothing: no sleep device is linked in Withings. Checklist_devices.
Limits and caveats
Withings asks partners not to poll more than about once every 10 minutes per user and reports rate limiting as status
601. The server spaces calls, retries601/HTTP 5xx with backoff, and only calls the API when you ask.Body composition from a scale is an estimate. One step-on can be stored as several measurement groups a few seconds apart; the server merges groups from the same device that are at most 2 minutes apart into one row.
Not included: writing or deleting data, ECG waveforms, intraday streams, webhooks/notifications.
Field meanings follow Withings' API reference (https://developer.withings.com/api-reference) and may change when Withings updates it.
Development
npm test # offline tests: node --test test/*.test.mjs (mocked Withings HTTP; no credentials)
npm run test:live # a few real read-only calls against your own account (needs env + a token file)
npm run authorize # one-time authorization (CLI)No dependencies, no install step (npm ci works with the empty lockfile but isn't needed). The code: server/client.mjs (OAuth, token file, refresh, retries, redirect warnings), server/auth-flow.mjs (localhost
callback listener), server/setup-guide.mjs (the walkthrough setup_guide returns), server/tools.mjs (tools and output shapes), server/index.mjs (MCP stdio JSON-RPC),
server/cli.mjs (setup CLI). The human setup steps are docs/SETUP-WITHINGS.md.
Disclaimer
Not medical advice. This plugin retrieves data you already have in your Withings account, for general wellness and informational purposes only. It is not a medical device and is not intended to diagnose, treat, cure, monitor or prevent any disease or condition. Values (including blood pressure, heart rate and the device's own estimates such as body composition, metabolic or vascular age) come from consumer devices, may be missing or inaccurate, and are passed on as Withings reports them. Don't make medical decisions based on them; ask a doctor or other qualified health professional about any health concern. Any interpretation an AI assistant adds is the assistant's, not this plugin's.
Your data. The plugin runs on the computer where you start it and talks only to Withings, using your own developer app. It has no telemetry and no server of its own, and the author does not receive, store or have access to your credentials, tokens or data. Your MCP client and AI model provider handle the data under their own terms, which you choose.
Unofficial. Community project, compatible with Withings; not affiliated with, endorsed or sponsored by Withings. Withings is a trademark of Withings, used only to say what the plugin is compatible with.
Provided as is, use at your own risk. No warranty of any kind (see
LICENSE). You are responsible for following Withings' terms (Terms of Use and developer/API terms) and for your own data.
License
MIT (provided "as is", without warranty). See LICENSE. Not affiliated with Withings.
Available Tools
8 toolsauth_statusSetup statusARead-onlyIdempotent
Shows whether the Withings app credentials are configured and whether you have authorized access: token file location, granted scopes, minutes left on the current access token. Never shows the secret or tokens.
Inputs: no named parameters.
exchange_codeFinish authorization (non-localhost callback)A
Finishes the one-time authorization when the callback URL is not on localhost: pass the full URL the browser was redirected to (or just its code). Withings codes expire after 30 seconds and work once, so the CLI (node server/cli.mjs authorize) is easier. Saves the tokens to the local token file (chmod 600); never returns them.
Inputs: redirect_url_or_code (required).
get_blood_pressureBlood pressure and pulseARead-onlyIdempotent
Blood pressure readings (systolic, diastolic in mmHg) with pulse (bpm), from a Withings blood pressure monitor or device that measures it. Empty if you have none. Default: last 30 days; up to 366 days.
Inputs: end_date, start_date.
get_body_measurementsBody measurements (scale)ARead-onlyIdempotent
Weigh-ins in a date range, one row per step-on: weight, fat ratio, fat mass, fat-free mass, muscle, hydration (water), bone, visceral fat, extra/intracellular water, BMR, metabolic age, and on cardio scales pulse wave velocity, vascular age and standing heart rate. Units decoded; metrics the scale did not measure are null. Default: last 30 days; up to 366 days.
Inputs: end_date, start_date, include_manual.
get_daily_activityDaily activityARead-onlyIdempotent
One row per day: steps, distance, floors, soft/moderate/intense/active minutes, active and total calories, heart rate and HR-zone minutes (from a Withings watch or the app's step tracking). Default: last 7 days; up to 366 days.
Inputs: end_date, start_date.
get_sleep_summarySleep (nightly summary)ARead-onlyIdempotent
Nightly sleep from a Withings sleep mat or watch: total sleep, time in bed, deep/light/REM/awake minutes, efficiency, latency, wake-ups, sleep score, heart and breathing rate, snoring, breathing disturbances. A night is labelled by the day it ends. Default: last 7 days; up to 366 days.
Inputs: end_date, start_date.
list_devicesLinked devicesARead-onlyIdempotent
Withings devices linked to your account: type, model, battery level, firmware and the last time each synced (useful when data looks missing). No serial numbers or ids.
Inputs: no named parameters.
start_authorizationAuthorize (one time)A
One-time setup step. Returns the Withings authorization URL for the user to open in a browser (read-only scopes: user.info, user.metrics, user.activity). If the callback URL is on localhost, this server listens there for 10 minutes and finishes the setup by itself; otherwise the user runs exchange_code (or the CLI) with the redirect URL within 30 seconds. Writes only the local token file.
Inputs: no named parameters.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 8 tools
Each data tool targets a distinct health domain (body measurements, blood pressure, daily activity, sleep) and the auth tools clearly separate checking status, starting setup, and finishing setup. The descriptions helpfully clarify any edge overlaps, such as heart-rate data appearing in activity and sleep contexts.
Most tools use a consistent snake_case verb_noun pattern (get_body_measurements, get_blood_pressure, list_devices, start_authorization, exchange_code), but auth_status breaks the pattern as a noun-only name. The convention is still predictable overall.
Eight tools is well-scoped for a personal health data server: four data retrieval tools, one device listing tool, and three auth lifecycle tools. Each tool earns its place without obvious redundancy.
The surface covers core Withings health data domains and the full authorization lifecycle, with minor gaps such as no explicit weight goals or ECG/heart-specific retrieval beyond embedded fields. Agents can still accomplish the main workflows.
Maintenance
Related MCP Connectors
Read wearables and lab health data — sleep, activity, workouts, timeseries, lab tests and orders.
Read-only Apple Health data synced from your iPhone: sleep, heart rate, HRV, workouts and trends.
Your Garmin, Oura, WHOOP, Hevy and 35 more fitness apps, queryable from your own AI.
Connect your health, fitness, nutrition, sleep, and wearable data to your AI assistant.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables retrieval of health data from Withings smart scales including weight measurements and comprehensive body composition metrics like fat mass, muscle mass, and hydration levels. Supports multiple users, unit preferences, and OAuth authentication for secure access to personal health data.2ISC
- AlicenseAqualityCmaintenanceEnables access to Withings Health API data including body measurements, activity tracking, sleep analysis, workouts, and heart rate monitoring through OAuth2 authentication.81MIT
- AlicenseAqualityBmaintenanceProvides read-only access to Withings health metrics including body composition, sleep, workouts, and ECG data with local SQLite caching and trend analysis. Features incremental synchronization, automatic OAuth token refresh, and supports all 200+ Withings measurement types for comprehensive health tracking.8567 PyPI1GPL 3.0
- AlicenseAqualityCmaintenanceEnables AI clients to query Withings health data such as weight, body composition, heart rate, temperature, SpO2, activity, sleep, workouts, and ECG recordings.17MIT