google-health-mcp-server
by lukendatigh
README.md
# google-health-mcp-server
A complete, open-source single tenant [MCP](https://modelcontextprotocol.io) server for the
[Google Health API](https://developers.google.com/health) v4 -- the OAuth-based successor to the
Fitbit Web API. Every method reachable with just a user's own OAuth grant is exposed as a tool
below, including profile/settings, paired devices, raw data points, source reconciliation, and
both physical- and civil-time rollups.
Runs two ways:
- **Locally over stdio** -- for Claude Desktop / Claude Code, zero hosting required.
- **Remotely over HTTP** -- for [claude.ai](https://claude.ai) / Cowork connectors, deployable to Fly.io with the included Dockerfile.
View the full product and technical design [here](PRD-TDD.md).
## Tools
17 tools cover every non-Cloud-IAM method in the API. Full list with exact endpoint coverage: [docs/tools.md](docs/tools.md).
| Read | Write |
|---|---|
| `get_identity` | `update_profile` |
| `get_profile`, `get_irn_profile` | `update_settings` |
| `get_settings` | `create_data_point`, `update_data_point` |
| `list_paired_devices`, `get_paired_device` | `batch_delete_data_points` |
| `list_data_points`, `get_data_point` | |
| `reconcile_data_points` | |
| `roll_up_data_points`, `daily_roll_up_data_points` | |
| `export_exercise_tcx` | |
## One-time setup: getting a refresh token
This server acts on behalf of one Google account (yours). Before running it, you need an OAuth
client and a refresh token for that account:
1. Follow [Set up Google Cloud and OAuth](https://developers.google.com/health/setup) to enable
the **Google Health API** on a Google Cloud project and create a **Web application** OAuth 2.0
Client ID. Use `https://developers.google.com/oauthplayground` as an authorized redirect URI if
you plan to use the OAuth Playground for the next step (or run your own short local script).
2. Add yourself as a **test user** for the OAuth consent screen, and add the
[scopes](https://developers.google.com/health/scopes) you want this instance to use (e.g.
`.../auth/googlehealth.activity_and_fitness.readonly`, `.../auth/googlehealth.sleep.readonly`).
3. Using the [OAuth 2.0 Playground](https://developers.google.com/oauthplayground) (gear icon →
"Use your own OAuth credentials" → paste your client ID/secret), authorize the scopes from step
2 against your Google account, then exchange the authorization code for tokens. Copy the
**refresh token** it returns.
4. You now have the three values this server needs: `GOOGLE_HEALTH_CLIENT_ID`,
`GOOGLE_HEALTH_CLIENT_SECRET`, `GOOGLE_HEALTH_REFRESH_TOKEN`.
Note: while your OAuth consent screen is in **Testing** status, refresh tokens expire after 7
days and need to be re-minted. Publish the consent screen (or request verification) for a
long-lived refresh token -- see [Token behavior during testing](https://developers.google.com/health/setup#token_behavior_during_testing).
## Quick start (local, stdio)
Requires Node.js 20+.
```bash
npx google-health-mcp-server
```
Point your MCP client (Claude Desktop, Claude Code, etc.) at it. For Claude Desktop, add to `claude_desktop_config.json`:
```json
{
"mcpServers": {
"google-health": {
"command": "npx",
"args": ["google-health-mcp-server"],
"env": {
"GOOGLE_HEALTH_CLIENT_ID": "your-client-id",
"GOOGLE_HEALTH_CLIENT_SECRET": "your-client-secret",
"GOOGLE_HEALTH_REFRESH_TOKEN": "your-refresh-token",
"GOOGLE_HEALTH_READ_ONLY": "false"
}
}
}
}
```
## Configuration
| Env var | Required | Default | Description |
|---|---|---|---|
| `GOOGLE_HEALTH_CLIENT_ID` | yes | -- | OAuth 2.0 client ID from your Google Cloud project. |
| `GOOGLE_HEALTH_CLIENT_SECRET` | yes | -- | OAuth 2.0 client secret paired with the client ID. Never logged, never echoed in tool output. |
| `GOOGLE_HEALTH_REFRESH_TOKEN` | yes | -- | Refresh token for the one Google account this instance acts on behalf of. Never logged, never echoed in tool output. |
| `GOOGLE_HEALTH_READ_ONLY` | no | `false` | When `true`, every mutation tool is hidden from `tools/list` entirely. Recommended for any instance you don't fully trust the client of. |
| `GOOGLE_HEALTH_API_BASE_URL` | no | `https://health.googleapis.com/v4` | Override for testing against a mock server. |
| `GOOGLE_HEALTH_TOKEN_URL` | no | `https://oauth2.googleapis.com/token` | Override for testing against a mock token endpoint. |
HTTP mode (below) needs three more: `PORT`, `PUBLIC_URL`, `MCP_HTTP_PASSWORD`. See [.env.example](.env.example) for all of them with descriptions.
## Remote (HTTP) deployment on Fly.io
The HTTP transport is gated by OAuth (required for claude.ai/Cowork connector approval). This is
**single-tenant OAuth**, and it is a separate concern from the Google OAuth grant above: it just
gates access to *your instance* behind one shared password, not per-user Google accounts. See
[docs/architecture.md](docs/architecture.md#two-independent-oauth-layers) for why and how.
1. Install [flyctl](https://fly.io/docs/flyctl/install/) and `fly auth login`.
2. `fly launch --no-deploy` from this directory (it will read `fly.toml`; rename the `app` there first if you want a specific subdomain).
3. Set secrets (never put these in `fly.toml`, which is committed to git):
```bash
fly secrets set GOOGLE_HEALTH_CLIENT_ID=your-client-id
fly secrets set GOOGLE_HEALTH_CLIENT_SECRET=your-client-secret
fly secrets set GOOGLE_HEALTH_REFRESH_TOKEN=your-refresh-token
fly secrets set MCP_HTTP_PASSWORD=choose-a-strong-password
```
4. Edit `fly.toml`'s `PUBLIC_URL` to match your actual `*.fly.dev` hostname (or custom domain), then:
```bash
fly deploy
```
5. In claude.ai / Cowork, add a custom connector pointing at `https://<your-app>.fly.dev/mcp`. You'll be redirected to a login page on your own instance -- enter `MCP_HTTP_PASSWORD` to approve the connection.
Notes:
- Single `shared-cpu-1x` machine, no volume. Both the cached Google access token and the
downstream OAuth session state (registered clients, tokens) live in memory and are lost on
redeploy or restart -- you'll just need to reconnect the connector afterwards (the Google refresh
token itself is unaffected; it's re-exchanged for a fresh access token automatically). Acceptable
trade-off for a low-traffic personal instance; see the TDD.
- `fly.toml`'s `[http_service]` is configured to stay always-on (not scaled to zero) for exactly that reason -- a stop/start cycle would otherwise force reconnection too.
## Development
```bash
npm install
npm run dev:stdio # run src/stdio.ts directly with tsx
npm run dev:http # run src/http.ts directly with tsx
npm run typecheck
npm run lint # biome check
npm run lint:fix
npm test
npm run build # bundles dist/stdio.js and dist/http.js with tsup
```
See [docs/architecture.md](docs/architecture.md) for the repo layout and request-flow details.
## License
MIT -- see [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues