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.

Available Tools

3 tools
get_workoutA
Read-onlyIdempotent

Read one workout summary, recorded activity type, available splits and data availability. Split output is paginated.

ParametersJSON Schema
NameRequiredDescriptionDefault
split_limitNo
split_offsetNo
workout_nameYesWorkout resource name returned by list_workouts; accepts a numeric user ID or the me alias.

TDQS

A3.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description adds pagination behavior ('Split output is paginated') and mentions data availability, which are not captured by the annotations (readOnly, idempotent, etc.). It does not contradict any annotation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two concise sentences with the main purpose front-loaded. No filler or redundancy; every word adds value.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a read-only tool with no output schema, the description gives a reasonable overview but omits details on response structure and how pagination parameters control output. It is adequate but leaves gaps for an agent to fully understand invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is only 33% (only workout_name is documented). The description mentions pagination but does not explain split_limit or split_offset, leaving these parameters unclear. It fails to compensate for the low schema coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's action ('Read one workout summary') and specifies the returned elements: recorded activity type, available splits, and data availability. It distinguishes itself from list_workouts (which lists) by focusing on a single workout, and from get_workout_series by its singular nature.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance on when to use this tool versus its siblings. It does not mention list_workouts or get_workout_series, nor does it provide conditions or exclusions. The agent must infer usage from the name and context.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_workout_seriesA
Read-onlyIdempotent

Read bounded detailed measurements in a workout time window. Per-series pagination. Optional TCX; GPS requires include_gps=true. No files are saved.

ParametersJSON Schema
NameRequiredDescriptionDefault
include_gpsNoSet true with include_tcx to return the original TCX, preserving GPS and extensions. Set false to remove them.
include_tcxNo
page_tokensNo
measurementsNo
workout_nameYesWorkout resource name returned by list_workouts; accepts a numeric user ID or the me alias.
max_tcx_bytesNoMaximum TCX bytes downloaded and returned. Default 1 MiB; maximum 10 MiB. Increase this if TCX reports RESPONSE_TOO_LARGE.
max_points_per_seriesNo

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover readOnly, idempotent, openWorld, and non-destructive behavior. The description adds genuinely useful behavioral context beyond that: per-series pagination, the GPS/include_gps requirement, and 'No files are saved.' It does not exhaustively cover edge cases, but it adds non-obvious facts without contradicting annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three concise sentences, front-loaded with the core read operation and followed by the most important behavioral details. There is no filler, no repetition of schema descriptions, and every sentence earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description plus schema is adequate for a default call, but with no output schema, a nested page_tokens object, and several defaulted parameters, an agent still lacks clarity on how pagination tokens are supplied and what the returned structure looks like. It is workable but has clear gaps for a 7-parameter tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is only 43%, leaving include_tcx, page_tokens, measurements, and max_points_per_series without schema descriptions. The description adds only 'Optional TCX; GPS requires include_gps=true' and 'per-series pagination,' which does not adequately compensate for the missing semantics of pagination tokens, measurement selection, or series size bounds.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb ('Read') and a clear resource ('bounded detailed measurements in a workout time window'). 'Per-series pagination' and optional TCX distinguish it from the sibling tools get_workout and list_workouts, which are single-workout and listing operations.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description clearly implies when to use the tool: when detailed measurements in a workout time window are needed, optionally with TCX. It gives concrete context but does not explicitly name alternatives or state when not to use it, so it stops short of full routing guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_workoutsA
Read-onlyIdempotent

List recorded workouts by local date range. Includes all activity types unless explicitly filtered. Paginated, newest first.

ParametersJSON Schema
NameRequiredDescriptionDefault
end_dateYesExclusive date in the recorded local time zone (YYYY-MM-DD).
page_sizeNo
page_tokenNo
start_dateYesInclusive date in the recorded local time zone (YYYY-MM-DD).
activity_typesNoOptional exact recorded types. Omit to include all activities, including HIKING.

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description transparently discloses the read-only behavior (consistent with readOnlyHint), includes pagination behavior, and states the filtering default. It does not contradict the annotations and adds useful behavioral context not already covered by readOnlyHint, idempotentHint, or openWorldHint.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences, with the main action front-loaded. Every sentence adds meaningful information (listing scope, filtering behavior, pagination, ordering). There is no redundant or vague wording.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool has 5 parameters and no output schema, the description covers the core purpose and pagination but does not explain how to page through results (page_token) or the maximum page size. It omits potential edge cases like empty dates or timezone handling, but is adequate for a basic list operation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema descriptions exist for start_date, end_date, and activity_types, but page_size and page_token lack any explanation. The tool description only says 'Paginated' without clarifying how page_token works or the meaning of page_size, leaving two parameters semantically under-specified relative to the 60% schema coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the primary action ('List recorded workouts') and specifies the scope with 'by local date range' and 'Includes all activity types unless explicitly filtered'. The mention of pagination and ordering ('newest first') adds clarity without ambiguity.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies when to use this tool (for broad listing by date range) and mentions filtering behavior, but it does not explicitly contrast with sibling tools like get_workout or get_workout_series. The guidance is implicit rather than explicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 3 tool updatesv0.1.0
    • First observedget_workout
    • First observedget_workout_series
    • First observedlist_workouts

TDQS

A4/5.0

Scored across 3 tools

Disambiguation5/5

Each tool has a distinct purpose: retrieving a single workout summary, listing multiple workouts, and fetching detailed measurement series. No overlap or ambiguity.

Naming Consistency5/5

All tool names follow the same verb_noun pattern (get_workout, list_workouts, get_workout_series), providing consistent and predictable naming.

Tool Count5/5

With only 3 tools, the server is well-scoped for its apparent read-only focus on workout data, staying within the typical 3-15 range.

Completeness4/5

The tools cover reading workouts comprehensively (single, list, and series), but lack any write or delete operations. If the server is intended as read-only, this is complete, though it may miss update/create actions for a full health API.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers