Skip to main content
Glama
metacurb

google-health-mcp

by metacurb

Google Health MCP

Local, read-only access to recorded workouts through Google Health API v4. Node.js 22.14+ and TypeScript. Three runtime dependencies: the official MCP SDK, Zod and an XML parser.

Each user sets up their own Google Cloud project and OAuth client, then signs in with the Google account that holds their workout data.

Requirements

  • Node.js 22.14 or later, npm and Git.

  • A Google account with recorded workouts available through Google Health API.

  • Permission to create a Google Cloud project and configure its OAuth client.

  • A local desktop browser for sign-in and an MCP client that supports stdio, such as Codex.

The Google Cloud CLI (gcloud) is not required. There is no hosted service to deploy.

Related MCP server: google-health-mcp

1. Clone and build

git clone https://github.com/metacurb/google-health-mcp.git
cd google-health-mcp
npm ci
npm run build

Run the remaining terminal commands from this repository directory.

2. Create a Google Cloud project

  1. Open Google Cloud Console and sign in.

  2. Open the project selector, choose New project, enter a name such as Health MCP, select the available organization/location, and click Create.

  3. Select the new project. Keep it selected throughout setup.

  4. Open APIs & Services → Library, search for Google Health API, open it and click Enable.

These steps follow the Google Health project setup guide.

  1. Open Google Auth Platform. If prompted, click Get started.

  2. Enter an app name, a user support email and a developer contact email. Choose External for a personal Google account. Complete the required consent-screen fields and acknowledge Google's policy to create the configuration.

  3. Under Audience, keep the publishing status as Testing for initial setup.

  4. Under Test users → Add users, add the exact Google account email you will use for health-data sign-in, then Save. This is required even if you own the Cloud project.

See Google's consent-screen setup and test-user instructions.

For ongoing personal use, Audience → Publish app changes the OAuth app to In production. This removes the test-user allowlist and Testing's seven-day refresh-token expiry. Unverified-app warnings and a 100-user cap still apply. Personal use can qualify for a verification exception; distributing one shared OAuth app to the public has separate verification requirements. Publishing the OAuth configuration keeps this MCP local. See OAuth app states, token expiry and the personal-use exception.

4. Add the read-only scopes

Open Google Auth Platform → Data Access → Add or remove scopes. Find Google Health API and select exactly these three scopes, then click Update and Save:

https://www.googleapis.com/auth/googlehealth.activity_and_fitness.readonly
https://www.googleapis.com/auth/googlehealth.health_metrics_and_measurements.readonly
https://www.googleapis.com/auth/googlehealth.location.readonly

Activity access reads workouts; health-metrics access reads heart rate; location access permits optional TCX retrieval. GPS coordinates are excluded from tool output unless include_gps is true. The helper requests all three scopes and rejects incomplete or broader grants. See Google's scope configuration instructions.

5. Create a Desktop OAuth client

  1. Open Google Auth Platform → Clients → Create client.

  2. Choose Desktop app as the application type, enter a client name and click Create.

  3. Download the client's JSON file. Rename it to credentials.json and place it in the repository root, beside package.json.

This application requires a Desktop client with an installed section in its JSON file. The authentication helper checks the file type without printing its contents. Google Health's REST codelab uses a Web client; this project's local login uses the Desktop loopback flow. The helper creates its callback on a random local port, so no hosted redirect URL needs to be configured. Keep the downloaded file private; it is already in .gitignore.

6. Sign in and test API access

npm run auth
npm run smoke

auth opens the system browser. Select the account with your workout data and grant all three read-only permissions. Complete sign-in within five minutes. Valid saved credentials are reused. After changing the OAuth app's publishing status, use npm run auth -- --force to obtain a fresh grant.

smoke lists up to five workouts from the past 30 days, gets the newest workout, then retrieves up to five available heart-rate samples. Use npm run smoke -- --days=90 for a wider window. It prints health data to your terminal and saves no files. Exit code 2 means there were no workouts, so the heart-rate call could not be tested. Exit code 1 means a request or authentication failed. A successful request can still report missing heart-rate data.

7. Connect an MCP client

The server starts with node dist/index.js and waits for MCP messages on stdin. It does not open a web page. The MCP client normally starts and stops this process. npm run --silent start also works; avoid plain npm start in client configuration because its script banner would enter the protocol.

For Codex, run this from the repository directory in a macOS/Linux shell:

codex mcp add google-health -- "$(node -p 'process.execPath')" "$(pwd)/dist/index.js"

This records absolute paths to Node and the built server. To configure paths manually, run node -p 'process.execPath' and node -p 'require("node:path").resolve("dist/index.js")' from the repository. Copy the results into your client's stdio command and argument fields.

For example, in Codex's config.toml, replace both placeholder paths:

[mcp_servers.google-health]
command = '/absolute/path/to/node'
args = ['/absolute/path/to/google-health-mcp/dist/index.js']
tool_timeout_sec = 90

On Windows, use the paths printed by Node, including node.exe. The single-quoted TOML strings accept Windows backslashes. Other MCP clients need the same executable and server argument. No credentials or token environment variables belong in the MCP configuration.

Restart the MCP connection after registration or rebuilding. Try asking: “List my workouts from last month, including hikes.” See Codex MCP configuration for client setup details.

Credential storage and privacy

Credentials are read explicitly from ~/.config/gcloud-health/application_default_credentials.json. Valid credentials for this project's client are reused. Run npm run auth when sign-in is needed; npm run auth -- --force forces a new grant. The MCP server does not open a browser itself. On POSIX systems, the directory uses mode 0700 and new credential files use 0600. Access tokens are held in memory and refreshed on demand. The application rejects grants with missing or extra scopes.

Here, ~ means the current operating-system user's home directory; the .config/gcloud-health directory is also used on Windows. There is one saved OAuth client/account per OS user. Signing in with another client replaces the credentials in that isolated directory.

Authentication uses PKCE and a temporary callback bound to 127.0.0.1. The callback stops when login ends. No gcloud command runs during authentication or MCP use. Existing gcloud configuration, default application credentials, shell startup files, GOOGLE_APPLICATION_CREDENTIALS and metadata-server credentials are not used or changed. Tool results are sent to the MCP client that requested them; that client's data-handling policy also applies.

No health write endpoints exist in this client. Health requests use GET only. The OAuth token exchange uses POST to Google's token endpoint. HTTP redirects are refused. Errors expose HTTP status and known Google error codes, never raw response bodies, OAuth URLs, client credentials or tokens. Diagnostics go to stderr.

Setup troubleshooting

Problem

Action

Google allows only developer-approved testers

In the OAuth client's project, add the exact sign-in email under Audience → Test users, or use the personal-use publishing option above. Start a fresh login.

CLIENT_FILE_MISSING or INVALID_CLIENT_FILE

Download a Desktop OAuth client JSON file and save it as credentials.json beside package.json.

AUTH_REQUIRED, invalid_grant or expired consent

Run npm run auth -- --force and complete sign-in again.

SCOPE_MISMATCH

Check the three Data Access scopes above, then sign in again and grant all three.

HTTP 403 or SERVICE_DISABLED

Confirm Google Health API is enabled in the same project as the downloaded client, and check account/organization access restrictions.

LOGIN_TIMEOUT or BROWSER_FAILED

Run authentication in a local desktop terminal with a working browser and complete the new sign-in within five minutes.

No workouts returned

Check the selected Google account and date range; try npm run smoke -- --days=90.

INVALID_PAGE_TOKEN

See the known workout-pagination limitation below.

TCX RESPONSE_TOO_LARGE

Retry with a larger max_tcx_bytes, up to 10485760 (10 MiB). If the client still rejects this value, rebuild and restart the MCP connection. Larger exports remain unsupported.

Tools

Tool

Inputs

Result

list_workouts

start_date, end_date; optional activity_types, page_size (1–25), page_token

Recorded summaries and next page token

get_workout

workout_name; optional split_offset, split_limit (1–100)

Summary, both split collections and data availability

get_workout_series

workout_name; optional measurements, max_points_per_series (1–500), page_tokens, include_tcx, include_gps, max_tcx_bytes (1024–10485760; default 1048576)

Detailed samples, a page token for each measurement, optional TCX

Example list arguments:

{"start_date":"2026-08-01","end_date":"2026-09-01","page_size":25}

Use the exact name returned by that tool as workout_name. Supported measurements are heart_rate (default), distance, steps, active_energy_burned, active_zone_minutes and altitude (elevation gain). To get multiple types, pass them in measurements. To retrieve TCX, set include_tcx: true; add include_gps: true only when you want coordinates. Default TCX output removes coordinates, vendor extensions and metadata while retaining standard measurements. Oversized TCX produces an explicit error rather than broken, truncated XML. No exports are saved.

To read the original TCX with GPS and vendor extensions preserved, use:

{
  "workout_name": "<name returned by list_workouts>",
  "include_tcx": true,
  "include_gps": true,
  "max_tcx_bytes": 10485760,
  "max_points_per_series": 1
}

TCX defaults to a 1 MiB limit; max_tcx_bytes can raise it to 10 MiB per call. The limit applies to both the downloaded XML and returned XML. With include_gps: true, valid TCX is returned unchanged (transformation: none), including any recorded coordinates. This cannot add GPS data that Google did not supply. No scope change or new sign-in is needed if the three required read-only scopes were already granted. GPS remains opt-in for other calls.

The source limits are defined in src/limits.ts. After changing them, run npm test and restart the MCP connection so the client loads the new schema and server. A currently running process keeps its old limits until restarted.

Data rules:

  • Resource names can contain Google's numeric user ID or me. The client preserves returned names and uses users/me for outgoing requests. A different workout ID or conflicting numeric user ID in a response is rejected.

  • List dates use the workout's recorded local time: inclusive start, exclusive end, at most 366 days. All activity types, including HIKING, are included by default. An exact activity_types filter runs within each API page; an empty page can still have a next page. Keep the same filters and page size when paging.

  • Activity labels are never reclassified. splitSummaries and default splits remain separate named collections; they can describe overlapping segments.

  • Metrics include units and timestamps. Google integer strings remain strings to avoid precision loss. Missing metrics are null; recorded zero stays zero. source: recorded means Google supplied the value, which may itself be calculated by Google. Local calculations appear under derivedMetrics with their formulas. Derived pace uses recorded active time, never elapsed time.

  • get_workout marks telemetry and TCX as not_checked; a summary metric does not prove detailed samples exist. Series results distinguish available, missing, unknown_more_pages and error.

  • Detailed streams are selected by the workout's time window, not an activity foreign key. They may contain overlapping device sources or paused periods. Samples and interval starts use [startTime, endTime). Interval measurements are not clipped, prorated, deduplicated or summed. A returned page is not a whole-workout total.

  • Each selected series has its own page token. Continue with that series in measurements and its token in page_tokens. Up to six series are read per call. JSON responses are capped at 4 MiB per request and TCX at the requested byte limit. Each serialized MCP result is capped at 21 MiB before its text copy (up to about 42 MiB combined), allowing room for escaped XML and measurements. A request times out after 20 seconds; only a 401 gets one token refresh and retry.

Verification

Run npm test to build and execute the tests. Google credentials and live API access are not required for these tests.

See TESTING.md for the exact live-access status. Tests use invented TypeScript fixtures and injected HTTP responses. They never load personal health exports.

Known Google limitation observed on 2026-09-07: workout listing returned a next-page token that Google rejected with HTTP 400, Invalid page token. The client reports INVALID_PAGE_TOKEN; it does not treat a failed page as complete. Use narrower date ranges and inspect nextPageToken for possible incomplete results. Heart-rate pagination passed live checks. This limitation can still prevent a complete workout history from being retrieved.

Credentials, tokens, .env files, health export formats and data directories are ignored by Git. JSON files are ignored by default except the package files and TypeScript config. Keep any manual health downloads in data/, downloads/ or exports/.

Official references

Checked on 2026-09-07:

The workout guide's simple heart-rate example uses startTime/endTime query parameters. This client follows the endpoint reference's documented filter expression instead. Exercise listing uses the explicitly documented civil-time filter. TCX uses alt=media and partialData=true, which permits available non-GPS data. The REST reference does not document an activity-type filter, so that filter is local.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

No tool schema history has been recorded yet.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

Latest Blog Posts

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/metacurb/google-health-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server