fitbit-mcp
by ArnavSankhe
README.md
# fitbit-mcp
Connect your **Fitbit / Pixel Watch** data to **Claude** (or any MCP client). Ask things like *"How did I sleep this week?"* or *"Is my resting heart rate trending down?"* and get answers from your real data.
- **Free.** Runs on Cloudflare Workers' free tier. No servers to babysit.
- **Open source.** MIT. Read every line.
- **Read-only.** It never writes to your Google Health data.
- **Works on your phone.** Add it once in Claude's settings; it syncs to the mobile apps.
- **Simple dashboard included.** 30-day sleep, steps, resting HR, HRV, zone minutes.
It talks to the [Google Health API](https://developers.google.com/health) (the replacement for the Fitbit Web API, which shut down in September 2026).
```
Claude ──MCP──▶ this Worker ──Google Health API──▶ your Fitbit data
(Cloudflare)
```
## Setup (about 20 minutes, one time)
You'll do three things: register an app with Google, deploy the Worker, add it to Claude.
### 1. Google Cloud (10 min)
1. Go to [console.cloud.google.com](https://console.cloud.google.com) and create a new project (call it anything, e.g. `fitbit-mcp`).
2. **Enable the API:** APIs & Services → Library → search **"Google Health API"** → Enable.
3. **Consent screen:** APIs & Services → OAuth consent screen (may be called "Google Auth Platform → Branding"):
- User type: **External**. App name: `Fitbit MCP`. Add your email as support and developer contact.
- **Audience → Test users:** add the Google account your Fitbit is linked to. *(While the app is in "Testing", only test users can sign in and Google expires sign-ins every 7 days. See [Going public](#going-public) to remove both limits.)*
- **Data access / Scopes:** add these three:
- `https://www.googleapis.com/auth/googlehealth.activity_and_fitness.readonly`
- `https://www.googleapis.com/auth/googlehealth.sleep.readonly`
- `https://www.googleapis.com/auth/googlehealth.health_metrics_and_measurements.readonly`
4. **Create credentials:** APIs & Services → Credentials → Create credentials → **OAuth client ID** → type **Web application**.
- Leave redirect URIs empty for now (you'll add one after deploying).
- Copy the **Client ID** and **Client secret**.
### 2. Deploy (5 min)
You need [Node.js](https://nodejs.org) 20+ and a free [Cloudflare](https://dash.cloudflare.com/sign-up) account.
```bash
git clone https://github.com/ArnavSankhe/fitbit-mcp.git
cd fitbit-mcp
npm install
npm run setup
```
`setup` logs you in to Cloudflare, creates the storage namespace, asks for your Google client ID and secret, and deploys. At the end it prints your URL, something like `https://fitbit-mcp.yourname.workers.dev`.
**Then go back to Google Cloud → Credentials → your OAuth client → Authorised redirect URIs** and add:
```
https://fitbit-mcp.yourname.workers.dev/callback
```
### 3. Add to Claude (1 min)
1. Claude → **Settings → Connectors → Add custom connector**.
2. Name: `Fitbit`. URL: `https://fitbit-mcp.yourname.workers.dev/mcp`
3. Click **Connect** → sign in with Google → **Allow**.
Now ask Claude: *"Use the Fitbit tool to check my devices"*. If it lists your watch, you're done.
Works the same in Claude Desktop, claude.ai, and the phone apps (custom connectors on mobile are in beta at the time of writing).
## What Claude can ask for
| Tool | What it returns |
|---|---|
| `get_daily_summary` | One row per day: steps, distance, floors, active calories, HR avg/min/max, resting HR, HRV, active zone minutes, weight, sleep hours |
| `get_sleep` | Each night: time asleep/awake, light/deep/REM minutes, efficiency |
| `get_heart` | Daily resting HR, HRV, and HR range |
| `get_activity` | Steps, distance, floors, calories, zone minutes |
| `get_workouts` | Logged exercises with duration, distance, calories, avg/max HR |
| `get_trends` | Last N days vs the N before, with % change |
| `get_devices` | Paired devices and unit settings (good connection test) |
| `get_raw` | Any raw data type, for debugging or things the summaries miss |
Dates are UTC. Data is fetched live from Google on every request and never stored.
## Dashboard
Open `https://fitbit-mcp.yourname.workers.dev/dashboard`, sign in with Google, and you get a 30-day view (7 to 90 days selectable) of sleep, steps, resting HR, HRV, zone minutes, calories, and workouts. Light and dark mode, works on phones.
## Sharing it with other people
Anyone can use **your** deployment: they add the same URL in Claude and sign in with their own Google account. Their tokens are stored separately; nobody can see anyone else's data.
Two Google limits apply while your app is in "Testing":
- **100 users max**, and you have to add each one as a test user.
- **Sign-ins expire after 7 days**, so people need to reconnect weekly.
### Going public
To lift both limits, publish the app: Google Cloud → OAuth consent screen → **Publish app**, then **Prepare for verification**. Google asks for:
- A homepage and privacy policy (this Worker serves both: `/` and `/privacy`).
- A short video showing the sign-in flow and what the app does.
- A justification for each scope (e.g. *"Reads sleep sessions to show the user their sleep summary in their AI assistant"*).
Because the Worker stores Google refresh tokens, Google's [Health API policy](https://developers.google.com/health/policies/health-api-developer-user-data-policy) may also require an annual **CASA security assessment**. Some authorised labs offer the basic tier free or cheaply. Verification itself is free but takes a few weeks.
## Costs
Cloudflare's free tier allows 100,000 requests a day. A typical user makes a few dozen. The dashboard's 90-day view is the heaviest thing here (about 20 Google calls). You'd need thousands of daily users before paying anything, and then it's about $5/month.
## Privacy
Stored in Cloudflare KV, per user: Google user ID, name, email, an encrypted refresh token, and a short-lived cached access token. **No health data is stored.** Full policy at `/privacy`. To delete everything: dashboard → Disconnect, or revoke at [myaccount.google.com/permissions](https://myaccount.google.com/permissions).
## Development
```bash
cp .dev.vars.example .dev.vars # fill in your Google credentials
npm run dev # http://localhost:8788
npm test # unit + tool tests (Google mocked)
npm run type-check
```
Test the MCP endpoint with the [MCP Inspector](https://github.com/modelcontextprotocol/inspector): `npx @modelcontextprotocol/inspector` → connect to `http://localhost:8788/mcp`.
For local OAuth, add `http://localhost:8788/callback` as a redirect URI in Google Cloud.
### Project layout
```
src/index.ts Worker entry: OAuth provider + MCP handler
src/google-handler.ts Sign-in screens, Google callback, dashboard routes
src/tools.ts The MCP tools
src/data.ts Fetch + aggregate daily rows, sleep, workouts, trends
src/health.ts Google Health API client and normalizers
src/tokens.ts Refresh-token storage and access-token refresh
src/pages.ts Home, privacy, dashboard HTML
scripts/setup.mjs One-command setup
tests/ Node test runner; Google API mocked
```
## Troubleshooting
- **"Google refused access (403)"** — the Health API isn't enabled in your project, or your account isn't listed as a test user.
- **"isn't connected yet, or the connection expired"** — Testing-mode sign-ins expire after 7 days. Remove and re-add the connector in Claude. Publishing the app fixes this permanently.
- **Empty numbers for a metric** — the data type name or response shape may differ from what the normalizers expect. Ask Claude to call `get_raw` with that type (e.g. `heart-rate`), then [open an issue](https://github.com/ArnavSankhe/fitbit-mcp/issues) with the output.
- **"redirect_uri_mismatch"** — the `/callback` URL isn't in your OAuth client's redirect URIs, or has a typo.
## Credits
Built on Cloudflare's [remote MCP Google OAuth template](https://github.com/cloudflare/ai/tree/main/demos/remote-mcp-google-oauth) and the [`agents`](https://github.com/cloudflare/agents) SDK. Not affiliated with Google or Fitbit.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues