Withings MCP Server
Withings MCP Server
A Model Context Protocol server that wraps the Withings Health API — including the full OAuth2 handshake and token refresh — so an MCP client (Claude, etc.) can read your smart-scale data: weight, body fat, muscle mass, bone mass, water, and more.
It runs as a remote, hosted server on AWS Lambda (Streamable-HTTP transport), with:
Withings credentials in SSM Parameter Store SecureString parameters (KMS-encrypted).
Withings tokens in DynamoDB (encrypted at rest), refreshed automatically.
A server-side
/callbackroute that completes the Withings OAuth exchange for you.Its own minimal OAuth 2.0 server guarding the MCP endpoint, so you connect it to Claude by pasting a client ID + secret — no custom headers required.
It also runs locally (single process, JSON-file token cache) for development.
Two OAuth flows, don't confuse them. (1) Claude → this server: the built-in OAuth authorization server (
/authorize,/token) that lets Claude obtain an access token. (2) This server → Withings: the flow that links your Withings account (withings_start_authorization+/callback). You set up (1) once when adding the connector, and (2) once to link your scale.
Architecture
Claude ──OAuth (id+secret)──▶ Lambda Function URL
│
┌──────────────┴────────────────────┐
│ Starlette ASGI app (Mangum) │
│ /.well-known/oauth-* discovery │
│ /authorize /token OAuth AS │ ← Claude authenticates
│ /mcp Streamable-HTTP │──▶ tools (Bearer-protected)
│ /callback Withings code→token │
│ /health │
└───────┬────────────────────┬───────┘
│ │
SSM SecureString DynamoDB
(withings + oauth client creds) (withings tokens + oauth codes)
│
└──▶ Withings API (account.withings.com, wbsapi.withings.net)Why Lambda + Streamable-HTTP: the transport runs stateless (one self-contained JSON request/response per call), which maps cleanly onto Lambda — no long-lived SSE connection to hold open. Tokens live in DynamoDB precisely because Lambda containers are ephemeral.
How the MCP OAuth works (Claude → this server)
The /mcp endpoint is protected by an access token, but you never paste a token
anywhere. This server implements just enough of OAuth 2.0 for Claude's built-in
client to obtain one:
Claude hits
/mcp, gets401with aWWW-Authenticateheader pointing at the discovery metadata.Claude reads
/.well-known/oauth-protected-resourceand/.well-known/oauth-authorization-server.Claude runs the authorization-code + PKCE flow using the client ID/secret you configured, and receives an access token (+ refresh token) it stores and sends automatically on every call.
Security with minimal machinery: token issuance at /token requires the
client secret (a confidential client), PKCE prevents code interception, and
redirect_uri hosts are allow-listed so the server can't be abused as an open
redirector. Access tokens are HMAC-signed with the client secret, so validating a
request needs no database lookup.
Tools
Tool | Description |
| Whether an account is linked and when the token expires. |
| Returns an |
| Weight + body-composition groups, filterable by |
| The most recent weigh-in with full body composition. |
| All Withings measurement type codes with names/units. |
| Delete the stored tokens. |
Measurement values are returned already decoded (Withings sends value + unit
exponent; the server computes value × 10^unit), e.g.
{"weight": {"value": 70.5, "unit": "kg"}}.
Prerequisites
A Withings developer account and application: developer.withings.com → Dashboard → create an app. Note the Client ID and Client Secret. You will add a Callback URL after the first deploy (step 5).
AWS account with credentials configured (
aws configure).AWS SAM CLI (
brew install aws-sam-clior install docs) and Python 3.12.
Deploy to AWS (Lambda)
1. Store secrets in SSM Parameter Store
These are KMS-encrypted SecureStrings (default aws/ssm key — no extra cost) and
are never placed in the CloudFormation template or Lambda env vars.
WITHINGS_CLIENT_ID=xxxxxxxx \
WITHINGS_CLIENT_SECRET=yyyyyyyy \
./scripts/setup-ssm.shWITHINGS_CLIENT_ID/SECRET are from your Withings developer app. The script
also generates an OAuth Client ID + Client Secret (for Claude → this server)
and prints them — save these, you'll paste them into Claude in the last step.
(Override with OAUTH_CLIENT_ID=... OAUTH_CLIENT_SECRET=..., or change the SSM
prefix with SSM_PREFIX=....)
2. Build
sam build3. First deploy (redirect URL not known yet)
cp samconfig.toml.example samconfig.toml # edit region if needed
sam deploy --guided # accept defaults; leave PublicBaseUrl emptyWhen it finishes, note the stack Outputs:
FunctionUrl— e.g.https://abc123.lambda-url.eu-west-1.on.aws/McpEndpoint— that URL +mcpRedirectUri— that URL +callback
4. Register the callback in Withings
In your Withings app settings, add the RedirectUri from the outputs
(exactly, including https:// and /callback) to the app's Callback URLs.
5. Redeploy with the public base URL
The server needs to know its own address to build the OAuth redirect_uri. Pass
the FunctionUrl without a trailing slash:
sam deploy --parameter-overrides \
"SsmPrefix=/withings-mcp PublicBaseUrl=https://abc123.lambda-url.eu-west-1.on.aws"That's it — the server is live.
Connect an MCP client (Claude)
Add a custom / remote MCP connector pointing at the McpEndpoint
(.../mcp). In the connector's Advanced / OAuth settings, paste the OAuth
Client ID and OAuth Client Secret that setup-ssm.sh printed.
That's all the configuration — no headers, no tokens. When you enable the connector, Claude discovers the server's OAuth metadata, opens a browser to complete the authorization-code flow, and stores the resulting access token itself. It refreshes the token automatically when it expires.
If a client insists on Dynamic Client Registration and has no field for a client ID/secret, tell me — this server can add a minimal
/registerendpoint. The current design targets the "paste client ID + secret" path you asked for.
First-run authorization (link your Withings account)
Once the connector is authenticated:
Ask Claude to call
withings_start_authorization.Open the returned
authorization_url, sign in to Withings, approve.You're redirected to
/callback, which stores your Withings tokens and shows a success page.Call
withings_connection_statusto confirm, thenwithings_get_latest_weightorwithings_get_measurements.
Withings access tokens are refreshed automatically using the stored refresh token, so this is a one-time step.
Local development
python -m venv .venv && source .venv/bin/activate
pip install -r requirements-dev.txt
cp .env.example .env # fill in Withings client id/secret
python scripts/run_local.py # serves http://localhost:8000Register http://localhost:8000/callback as a callback URL in your Withings app
and set PUBLIC_BASE_URL=http://localhost:8000 in .env. Tokens are cached to
~/.withings-mcp/tokens.json. The MCP endpoint is http://localhost:8000/mcp.
To skip the MCP OAuth handshake while poking at the server locally, set
OAUTH_DISABLE=1 in .env (the /mcp endpoint is then unauthenticated — local
use only).
Run the tests (no network/AWS needed):
pytestSecurity notes
Secrets never leave SSM except as decrypted values inside the Lambda execution environment. The IAM role grants
ssm:GetParameter*scoped to<prefix>/*andkms:DecryptonlyViaService ssm.The MCP endpoint is OAuth-protected. The Function URL is
AuthType: NONE(so Withings and Claude's browser flow can reach the public routes), but/mcprequires a valid access token this server issued. Tokens are HMAC-signed with the OAuth client secret and checked in constant time; rotate access by re-runningsetup-ssm.shwith a newOAUTH_CLIENT_SECRET(this also invalidates all previously issued tokens).The OAuth authorization server is deliberately minimal but not loose: confidential client (secret required at
/token), PKCE S256 enforced, andredirect_urirestricted to an allow-listed set of hosts (OAUTH_ALLOWED_REDIRECT_HOSTS). Authorization codes are single-use with a 5-minute TTL./callback(Withings) is public but CSRF-protected: it only accepts an OAuthstatethat this server issued (stored in DynamoDB with a 10-minute TTL) and consumes it once.Tokens sit in DynamoDB with encryption at rest (AWS-owned KMS key by default). For a customer-managed key, add an
SSESpecificationwithSSEType: KMSand your key ARN intemplate.yaml.This is a single-user server ("my scale data"). To support multiple Withings accounts, key the token store by the Withings
useridreturned in the token response instead of the fixedtokenspartition key.
Measurement type reference (common scale metrics)
Code | Metric | Unit |
1 | Weight | kg |
5 | Fat-free mass | kg |
6 | Fat ratio | % |
8 | Fat mass weight | kg |
76 | Muscle mass | kg |
77 | Hydration | kg |
88 | Bone mass | kg |
170 | Visceral fat | — |
Call withings_list_measure_types for the full set (blood pressure, heart rate,
SpO₂, temperature, pulse wave velocity, etc.).
License
MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/owenobyrne/mcp-withings'
If you have feedback or need assistance with the MCP directory API, please join our Discord server