Shaughv Health
FreeNot checkedA remote MCP server that allows trusted LLM assistants to read Google Health/Fitbit data and log nutrition, hydration, and body measurements via the Google Heal
About
A remote MCP server that allows trusted LLM assistants to read Google Health/Fitbit data and log nutrition, hydration, and body measurements via the Google Health API.
README
A private, allowlist-only remote MCP server on Vercel for Emmett and Christian. Their trusted LLM assistants — Claude Code / Claude Desktop, claude.ai (web + mobile connectors), and ChatGPT connectors — can read each approved user's own Google Health data ("How many steps today?", "How did I sleep?", "Why am I tired?", "What did I eat yesterday?") and write the things a wearable can't track itself: nutrition, hydration, and body measurements.
It is a thin, typed, authenticated data adapter over the
Google Health API (health.googleapis.com/v4).
The LLM does the reasoning; this server returns accurate data with timestamps, units, and
freshness metadata on every response. It is not a health warehouse, an analytics
product, or anything that diagnoses — there are no medical claims anywhere in the surface.
docs/PLAN.mdis the build plan and the source of truth for architecture, the four auth layers, the database schema, the tool surface, the watchouts, and the E2E verification bar. Accepted architecture decisions underdocs/adr/govern their specific scope; ADR-0001 fixes the private audience and Google verification posture. This README is the human front door. Live phase/task status is on the.tasks/board — see Task board.
Table of contents
- What it is / what it is not
- Who it's for and how it's used
- Architecture
- The four auth layers (never conflate)
- Tech stack
- Current status
- Repository structure
- Setup guide
- Connecting an MCP client
- MCP surface
- Security posture
- Troubleshooting
- Task board
- Further reading
What it is / what it is not
| It is | It is not |
|---|---|
| A remote MCP server (streamable HTTP) on Vercel | A local/stdio MCP server |
A thin, typed adapter over health.googleapis.com/v4 |
A data warehouse or ETL pipeline |
| Read access to activity, sleep, heart, nutrition | An analytics or dashboard product |
| Write access for nutrition, hydration, measurements | A writer of sleep / exercise / settings (absent by design) |
| Private and allowlist-only (Emmett and Christian; isolated per user) | Public or self-service multi-tenant |
| A returner of data + freshness metadata + units | A source of diagnoses, advice, or medical claims |
The device data path is never live — a Fitbit Air syncs to the Fitbit app, which
pushes to the Google Health cloud, which this server reads on demand. Every response
therefore carries freshness metadata (retrievedAt, latestDataTime?, isPossiblyStale,
note) so the LLM can distinguish "no data synced yet" from "nothing happened."
Who it's for and how it's used
Audience: Emmett and Christian, and the LLM assistants each authorizes. Each person can
reach only their own Google Health connection. Nobody else can sign in: the exact approved
identities and the decision not to pursue public verification are fixed by
ADR-0001, and enforced through
ALLOWED_GOOGLE_EMAILS.
Intent — how Emmett uses it. He asks his assistants natural questions and expects grounded answers with real numbers, timestamps, and freshness notes:
- "How many steps do I have today? What's left to hit my goal?"
- "How did I sleep last night?" / "Why am I tired?"
- "Why is my heart rate so high?" (latest synced HR, explicitly not live)
- "What did I eat yesterday?" / "What's my exercise looking like this week?"
- "Log a snack: Greek yogurt, 150 cal, 15 g protein." / "Edit that to 180 cal." / "Delete it."
- "Log 16 oz of water." / "Update my weight to X."
Writes are the reason the server exists at all beyond read-only: a Fitbit Air does not track food, water, or manual measurements, so the LLM logs them through validated, audit-logged write tools. Every mutation is explicit-input-only (never inferred) and leaves an audit row.
Future directions (parked on the board, not part of v1 — see Task board):
#api— a plain REST API surface (bearer/PAT-authenticated) over the same health services, so Emmett's own scripts/apps can call the data without an OAuth dance. Feasibility analyzed: feasible and additive on the current stack.#rlw— a possible future migration from Vercel serverless to Railway (long-lived container); this is the moment the FastMCP question reopens.#w11— v1.1 webhooks: learn when new data lands (subscriber registration, signature verification, a freshness ledger). Deferred by design; tables already exist but stay dormant in v1.
Architecture
┌─────────────┐ Bluetooth sync ┌──────────────┐
Fitbit Air ─────▶│ Fitbit app │ ─────────────────▶│ Google Health │
(wearable) │ (phone) │ │ cloud │
└─────────────┘ └──────┬───────┘
│ Google Health API
│ health.googleapis.com/v4
│ ▲ user OAuth token
│ │ (AES-256-GCM
│ │ encrypted in Neon)
▼ │
┌───────────────────────────┐ OAuth 2.1 + DCR ┌────────┴──────────────────────┐
│ LLM client │ ───────────────────▶ │ Next.js on Vercel │
│ Claude Code / Desktop │ │ (health.emmetts.dev) │
│ claude.ai web + mobile │ ◀─────────────────── │ │
│ ChatGPT connector │ MCP tools/resources │ ┌─────────────────────────┐ │
└───────────────────────────┘ │ │ mcp-handler → /api/mcp │ │
│ │ better-auth → OAuth AS │ │
│ │ Google Health client │ │
│ └───────────┬─────────────┘ │
└──────────────┼────────────────┘
│ Drizzle ORM
▼
┌──────────────────┐
│ Neon Postgres │
│ encrypted tokens │
│ audit log │
│ webhook tables │
│ (dormant, v1.1) │
└──────────────────┘
Request path in one line: an LLM client completes an OAuth 2.1 flow (with Dynamic Client Registration) against this app's own authorization server, gets a token, calls the MCP endpoint; a tool handler resolves the app user, fetches fresh data from Google Health using the decrypted-on-demand health token, bounds/normalizes the payload, and returns it with freshness metadata.
The four auth layers (never conflate)
These are four separate things. Confusing them is the number-one source of bugs here.
| # | Layer | What it is | Status |
|---|---|---|---|
| 1 | Vercel account login | Emmett's Vercel dashboard access | Irrelevant to runtime |
| 2 | Neon Auth | Neon's own auth product | Disabled — Neon is only a database |
| 3 | Google Health consent | Health-scope OAuth; tokens AES-256-GCM encrypted in Neon; done once per (re)connect | Custom routes under /api/auth/google-health/* |
| 4 | MCP client auth | This app is an OAuth 2.1 authorization server (better-auth built-in mcp plugin, DCR on); the human login step is Google Sign-In restricted to ALLOWED_GOOGLE_EMAILS |
Endpoints under /api/auth/mcp/* + /.well-known/* |
Layers 3 and 4 use the same Google OAuth client ID but separate flows and scopes. Layer
4's login uses basic openid email profile scopes only; layer 3 requests the nine
googlehealth.* scopes. The health-flow refresh token is the one that gates data access.
Tech stack
| Concern | Choice | Notes |
|---|---|---|
| Framework | Next.js 16 (App Router) | Server-centric; all API routes on the Node runtime (never edge — needs node:crypto + the DB driver) |
| Language | TypeScript 5.9 | Pinned to ^5: Next 16's build-time type checker cannot load the TS 7 native compiler (see Troubleshooting) |
| MCP transport | mcp-handler + @modelcontextprotocol/sdk |
Streamable HTTP, per-request. Deliberately not FastMCP (a decision — see CLAUDE.md / .tasks/tasks/rlw.md) |
| Auth server | better-auth 1.6.23, built-in mcp plugin |
The built-in better-auth/plugins mcp, not @better-auth/mcp (that package targets the unreleased 1.7) |
| Database | Neon Postgres + Drizzle ORM (@neondatabase/serverless) |
Pooled URL at runtime, unpooled URL for migrations |
| Time | Luxon | Timezone-correct ranges (default America/Chicago), DST-safe, sleep-crosses-midnight logic |
| Validation | Zod 4 | Tool input schemas; write-tool validation |
| Tests | Vitest (+ MSW / undici mocks) | Unit + mocked-API integration |
| Host | Vercel | Canonical domain health.emmetts.dev; Deployment Protection is preview-only (production must stay open — the app brings its own auth) |
Current status
Milestone #v1 is complete and live. The .tasks/ board remains the source of truth for
future work.
- Production is live at
health.emmetts.dev; Vercel, Neon, Google OAuth, migrations, and all auth layers are wired. - The MCP endpoint, 10 read/diagnostic tools, 5 write tools, and 5 resources are deployed.
- Google Health consent, encrypted token storage/refresh, identity mapping, reads, writes, audit logging, and freshness behavior have been verified against real data.
- The full DCR → PKCE authorize → token → bearer MCP chain is covered by the E2E harness; the core prompt battery also passed through the real claude.ai connector.
- The current suite contains 74 unit/integration tests. Google Health webhooks remain deferred
by design to v1.1 (
#w11).
Repository structure
.
├── docs/PLAN.md ← SOURCE OF TRUTH (architecture, decisions, phases, E2E bar)
├── docs/adr/ ← accepted architecture decision records
├── README.md ← this file (human front door)
├── CLAUDE.md ← agent instructions for Claude sessions
├── AGENTS.md ← agent instructions, tool-neutral (Codex/Cursor/etc.)
├── .env.example ← every env var, documented by name
├── package.json ← scripts + pinned deps
├── next.config.ts / tsconfig.json / drizzle.config.ts / vitest.config.ts
│
├── app/ ← Next.js App Router
│ ├── layout.tsx · page.tsx ← landing / status dashboard
│ ├── icon.svg ← local Google Health MCP favicon
│ ├── sign-in/page.tsx ← Google sign-in (resumes interrupted OAuth flows)
│ ├── components/sign-out-button.tsx
│ ├── api/
│ │ ├── [transport]/route.ts ← live MCP endpoint (`/api/mcp`)
│ │ ├── auth/[...all]/route.ts ← better-auth handler (sign-in, /mcp/authorize, /token, DCR /register)
│ │ ├── auth/google-health/start/route.ts ← health-consent redirect (session-gated)
│ │ ├── auth/google-health/callback/route.ts ← code exchange → encrypt+store → identity map
│ │ └── health/status/route.ts ← healthcheck (no secrets)
│ └── .well-known/
│ ├── oauth-authorization-server/route.ts
│ └── oauth-protected-resource/route.ts
│
├── src/
│ ├── auth/ ← auth.ts (better-auth config), allowlist, app-user resolution,
│ │ state (health-consent CSRF state), token-service (single-flight
│ │ refresh), token-store, google-health-oauth, auth-client
│ ├── db/ ← schema.ts (8 domain tables), auth-schema.ts (7 better-auth tables), client.ts
│ ├── security/ ← encryption.ts (AES-256-GCM), redact.ts
│ ├── audit/ ← mutation-audit.ts (insert-only audit writer)
│ ├── google-health/ ← typed API client, data-type registry, scopes, and errors
│ ├── time/ ← timezone-safe physical/civil range helpers
│ ├── health-services/ ← shared orchestration layer (also enables future #api)
│ └── mcp/ ← thin tool and resource registration
│
├── drizzle/ ← generated SQL migrations + meta
├── scripts/db-inspect.mjs ← utility: list/inspect Neon tables
├── tests/unit/ ← Vitest: encryption, redact, allowlist, state, token-service, google-health-oauth
└── .tasks/ ← the task board (see "Task board")
Database tables (15 total): domain — app_users, oauth_connections, oauth_tokens,
oauth_states, mutation_audit_log, webhook_events (dormant), data_freshness (dormant),
health_cache; better-auth — user, session, account, verification,
oauthApplication, oauthAccessToken, oauthConsent.
Setup guide
1. Accounts and services
- Google Cloud — enable the Google Health API; configure the OAuth consent screen with
the
googlehealth.*scopes; create an OAuth web client; add the redirect URIs below; publish the app to "In production". Publishing is essential: in Testing status Google expires refresh tokens after 7 days (refresh_token_expires_in: 604799). The "unverified app" badge that appears after publishing is expected and stays — do not submit for verification. Public verification and CASA were evaluated and rejected in ADR-0001. - Vercel — import this repo as a project. Framework auto-detects as Next.js. Keep Deployment Protection = preview-only (production must stay open so programmatic MCP clients can reach it; the app enforces its own auth).
- Neon — the Postgres database was provisioned and connected from the Vercel dashboard
through its Storage/Marketplace integration; it is not a separately operated application
service. The integration injects
DATABASE_URL(pooled) andDATABASE_URL_UNPOOLEDautomatically. Neon Auth is disabled.
2. Google OAuth redirect URIs
One web client serves both the better-auth sign-in flow (/api/auth/callback/google)
and the Google Health consent flow (/api/auth/google-health/callback). Canonical domain:
health.emmetts.dev; the Vercel-assigned google-health-mcp-realemmetts.vercel.app also
serves. Register all six:
https://health.emmetts.dev/api/auth/callback/google
https://health.emmetts.dev/api/auth/google-health/callback
https://google-health-mcp-realemmetts.vercel.app/api/auth/callback/google
https://google-health-mcp-realemmetts.vercel.app/api/auth/google-health/callback
http://localhost:3000/api/auth/callback/google
http://localhost:3000/api/auth/google-health/callback
Requested health scopes (nine, minimum-necessary — location/ecg/irn are configured on
the consent screen but not requested; there is no settings.writeonly):
Read : googlehealth.activity_and_fitness.readonly
googlehealth.health_metrics_and_measurements.readonly
googlehealth.sleep.readonly
googlehealth.nutrition.readonly
googlehealth.profile.readonly
googlehealth.settings.readonly
Write: googlehealth.nutrition.writeonly
googlehealth.health_metrics_and_measurements.writeonly
googlehealth.profile.writeonly
3. Environment variables
Every variable is documented by name in .env.example. Values are never committed —
locally they live in .env.development.local (gitignored); in production they live in the
Vercel env store. Never put secret values in code, logs, the task board, or any
markdown.
| Variable | Purpose | Notes |
|---|---|---|
DATABASE_URL |
Pooled Neon connection (runtime) | Injected by the Neon integration |
DATABASE_URL_UNPOOLED |
Direct Neon connection | Used only by drizzle-kit migrations |
GOOGLE_CLIENT_ID |
Google OAuth client id | One client serves both flows |
GOOGLE_CLIENT_SECRET |
Google OAuth client secret | |
TOKEN_ENCRYPTION_KEY |
32-byte base64 AES-256-GCM key | Environments sharing this Neon database must share this key. Losing or splitting it orphans stored tokens (reconnect required) |
BETTER_AUTH_SECRET |
better-auth signing secret | |
BETTER_AUTH_URL |
Auth issuer / base URL | http://localhost:3000 local · https://health.emmetts.dev prod |
NEXT_PUBLIC_APP_URL |
Public app URL | Matches BETTER_AUTH_URL per environment |
ALLOWED_GOOGLE_EMAILS |
Comma-separated allowlist | Fail-closed sign-in perimeter; exact approved identities are governed by ADR-0001 |
GOOGLE_CLOUD_PROJECT_NUMBER |
v1.1 webhooks | Leave empty until v1.1 |
GOOGLE_HEALTH_SUBSCRIBER_ID |
v1.1 webhooks | Leave empty until v1.1 |
WEBHOOK_AUTH_SECRET |
v1.1 webhooks | Leave empty until v1.1 |
Generate a key: node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
4. Local development
npm install
cp .env.example .env.development.local # then fill in values
npm run dev # http://localhost:3000
npm test # Vitest
npm run typecheck # tsc --noEmit
npm run db:generate # generate a Drizzle migration from schema changes
npm run db:migrate # apply migrations (uses DATABASE_URL_UNPOOLED)
node scripts/db-inspect.mjs # list/inspect Neon tables
Local sign-in needs GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET populated and the
localhost:3000 redirect URIs registered (both are in place). The dev box is Windows —
all npm scripts must be cross-platform.
5. Deploy
Push to main; Vercel builds and deploys. Migrations are run from a dev machine against the
unpooled Neon URL (npm run db:migrate), not in the build. Production env-var changes only
take runtime effect on the next deploy. Note: an auto-deploy webhook has occasionally not
fired on push — if a deploy doesn't appear, deploy manually via the Vercel CLI.
Connecting an MCP client
The production endpoint is live. Each client walks the OAuth flow and an approved person signs in with their allowlisted Google account.
- Claude Code:
claude mcp add --transport http health https://health.emmetts.dev/api/mcp - claude.ai (web + mobile): Settings → Connectors → Add custom connector →
https://health.emmetts.dev/api/mcp→ complete OAuth. - ChatGPT: Add a custom connector — it registers itself via Dynamic Client Registration.
MCP surface
Defined in docs/PLAN.md §"MCP surface (v1)"; input schemas per the handoff spec §11. Every
read response carries freshness + units and is payload-bounded (default pageSize ≤ 100; HR
series summarized via rollups; truncation notes when capped).
Read tools (9, + ping): get_today_steps, get_sleep_summary, get_latest_heart_rate,
get_exercise_week, get_nutrition_log, get_health_context (bundle: sleep + latest HR +
resting HR/HRV + recent activity + nutrition — data only, no conclusions), query_health_data
(generic list/reconcile, registry-allowlisted), rollup_health_data, get_sync_status.
Notable behaviors: query_health_data auto-builds the right filter per record type — including
the civil date field for daily-* aggregates, which carry no physical timestamp (a
sample-time filter would silently return everything); use mode: "reconcile" for Google's
merged/deduped stream when multiple sources log the same metric. get_sleep_summary
distinguishes STAGES (deep/light/REM) from CLASSIC sessions (a single asleep block — a
device capture condition, surfaced via stagesStatus, e.g. REJECTED_COVERAGE, not poor
sleep). Staleness is cadence-aware: once-per-night metrics (sleep, daily-*) don't flag
isPossiblyStale for a same-day value; live-ish samples use a 3-hour threshold.
Write tools (5): create_nutrition_log, update_nutrition_log (replace semantics — the
live PATCH endpoint 500s; a new data-point name is returned), delete_nutrition_log,
create_hydration_log, update_measurement (weight | body-fat | height). update_profile
was dropped: the live endpoint 403s despite the granted scope (documented server-side
bug); the service layer is kept for re-enablement.
Resources (5): health://profile, health://settings, health://connected-user,
health://data-types, health://freshness.
Absent by design: sleep, exercise, and settings writes; bulk historical writes.
Security posture
- Four auth layers, never conflated (table above). The MCP endpoint requires a valid
OAuth token; sign-in is allowlisted to
ALLOWED_GOOGLE_EMAILS; DCR is open by design (any client may register — safety comes from the allowlisted login, not registration). The approved people and the prohibition on public signup are fixed by ADR-0001. - Google tokens are AES-256-GCM encrypted at rest (
TOKEN_ENCRYPTION_KEY, withkey_versionfor rotation). No plaintext tokens in the DB, logs, or error paths — aredact()helper strips token patterns (ya29.,1//,GOCSPX-, JWTs,Bearer/Basic, Neonnpg_) before anything is logged. - Token refresh is single-flight through an atomic, expiring database claim on the token
row (compatible with the stateless Neon HTTP driver); on refresh failure the connection is
marked
reauth_required. - Allowlist removal blocks new sign-ins, not already-issued MCP tokens by itself. Immediate offboarding also requires revoking the person's better-auth sessions and MCP tokens plus their stored Google Health connection; see ADR-0001's revocation section.
- Writes (nutrition / hydration / measurements only) are Zod-validated, explicit-input
only, and every mutation is audit-logged in
mutation_audit_log. There are no sleep / exercise / settings write tools at all. - No medical diagnosis language anywhere; freshness/limitation notes on every response.
- Production stays open (no Vercel SSO wall) on purpose — the application's own auth is the perimeter.
Troubleshooting
| Symptom | Cause / fix |
|---|---|
| Refresh tokens die after ~7 days | The Google OAuth app is in Testing status. Publish it to In production (Audience page). Testing caps refresh tokens at 7 days (refresh_token_expires_in: 604799). |
| "Google hasn't verified this app" warning | Expected for this private, unverified, in-production app. Approved users may proceed (Advanced → continue). Do not submit for verification; see ADR-0001. |
| Redirect URI mismatch / new domain 404s | Every domain that serves the app needs its two redirect URIs registered on the OAuth client. Redirect changes and DNS/CNAME can take minutes to propagate. |
reauth_required on the dashboard or in tool errors |
The health refresh token failed or expired (often the 7-day Testing cap). Re-run the Google Health consent flow (Connect/Reconnect). |
| Data looks stale / a workout is missing | The device path is not live: Fitbit Air → Fitbit app → Google Health has real sync latency. freshness.isPossiblyStale + latestDataTime flag this; missing data ≠ zero activity. |
| MCP endpoint returns 401 | No/invalid OAuth token. The WWW-Authenticate header points at the protected-resource metadata; the client should walk the OAuth flow. |
| New env var isn't taking effect | Production env changes apply on the next deploy. Redeploy. |
| Build fails on TypeScript | Keep typescript pinned to ^5. Next 16's build-time type checker cannot load the TS 7 native compiler. |
No deploy after git push |
The auto-deploy webhook has occasionally not fired; deploy manually via the Vercel CLI. |
| Non-allowlisted Google account can't sign in | Working as intended — sign-in is rejected server-side for any email not in ALLOWED_GOOGLE_EMAILS (fails closed on an empty allowlist). |
| Claude returns from the browser immediately, then the connector fails | An existing Google session can make Google sign-in silent, so the missing account-chooser page is not itself an error. Use the work-computer OAuth runbook to locate the stage that failed. |
Claude work-computer OAuth: browser returns, connector still fails
This is the MCP client-auth flow, not Google Health consent. If the browser already has a valid Google session, clicking Sign in with Google can complete without showing Google's account chooser. The saved MCP authorization request then resumes and returns to Claude immediately. That silent SSO path is normal; the useful question is what happened after the return.
Anthropic's remote-connector guide explains that remote connectors are configured and brokered through the Claude account, with the MCP connection originating from Anthropic's servers rather than the workstation. They can be shared across Claude on the web, Desktop, and Claude Code when the same account and workspace are selected; see Anthropic's connector guidance. On Team and Enterprise plans, an Owner or Primary Owner must add/enable the connector for the organization, and each user normally authenticates it individually.
Record the last confirmed stage before changing anything:
| Last confirmed stage | What it means |
|---|---|
| The Health MCP sign-in page never opens | Check Claude's connector URL, selected account/workspace, and organization connector configuration. |
| The sign-in page opens; no Google chooser appears; the browser returns to Claude | This can be normal silent Google SSO. Continue tracing the callback/token stages. |
| Google or the Health MCP shows an error before returning | Investigate Google sign-in, the email allowlist, redirect URI, or server-side authorization error. |
| Claude receives the callback, but the server records no access-token issuance | Investigate the callback handoff and token exchange. |
The server issues an access token, but no authenticated /api/mcp request follows |
On versions before 0.1.2, the client may have rejected the malformed legacy ID token after access-token persistence. On 0.1.2+, first confirm JWKS/ID-token verification, then investigate Claude's connector state, credential persistence, workspace, or duplicate configuration. |
An authenticated MCP initialize or tool request reaches /api/mcp |
OAuth succeeded; troubleshoot MCP transport, reconnection, or the tool call separately. |
For the work-computer incident that prompted this runbook, production evidence showed Claude
access tokens being persisted after the browser returned. A regression replay then found that
better-auth's deprecated MCP path issued an ephemeral HS256 ID token while advertising
RS256, omitted the issuer, wrote auth_time in milliseconds, and advertised JWKS/UserInfo
endpoints that returned 404. A strict client can reject that response before its first Bearer
request even though the server has already stored the access token. Version 0.1.2 repairs that
boundary with an encrypted, persisted RS256 signing key and live JWKS/UserInfo endpoints;
the work-computer retry remains the final acceptance check. This is MCP client auth, not evidence
that Google Health consent failed.
A fresh 2026-07-14 revalidation also completed native DCR, S256 PKCE, browser handoff, loopback
callback, token exchange, MCP initialize/discovery, and a read-only tool call through both
Claude Code and Codex against production. Claude used a localhost callback; Codex used its
127.0.0.1:<ephemeral>/callback/<server-id> shape. The account-level Claude connector and the
separately authenticated direct Claude Code entry were both connected, and the correlated server
window contained no relevant OAuth/MCP 5xx or unexpected 4xx responses. A new failure that occurs
before any server request is therefore on the client, work-network, or workspace-policy side, not
an MCP callback failure demonstrated at the Health server.
Do not rotate shared auth/encryption secrets, revoke unrelated users' or clients' tokens, disconnect Google Health, or clear every credential as a first response. Scope any reset to the one affected connector.
Recovery sequence:
- Capture the exact Claude error (including any
ofid_...identifier), local timestamp with timezone, Claude Code or Desktop version, selected Claude account/workspace, and whether the connector came from Claude's account settings or a direct Claude Code MCP entry. - Update Claude Code/Desktop, fully quit the affected clients (including the Desktop tray process), reopen them, and confirm the same account and workspace are selected. Use the latest available release; see Anthropic's Claude Code changelog.
- Check the account-level connector in Claude's connector settings. On Team/Enterprise, have
an Owner or Primary Owner confirm its organization configuration. Disconnect/reconnect the
affected user, or remove and re-add that remote connector once; avoid repeated retries
that create more client registrations without isolating the failing stage. Re-add it only at
https://health.emmetts.dev/api/mcp, authenticate once, and test it on Claude web before checking Desktop and Code. - In Claude Code, check
claude mcp listand/mcpfor a separately configured direct entry pointing to the same URL. Direct local/project/user entries take precedence over a duplicate claude.ai connector. For that duplicate direct entry only, use Clear authentication in/mcp, remove the duplicate, and retry with the account connector (or re-add the direct entry once if it is intentionally preferred). Anthropic documents this precedence and the clear-auth control in the Claude Code MCP guide. - Use Claude Code's “paste the full callback URL” fallback only when the browser ends on a
literal connection error at a local
http://localhost:<port>/callbackURL and Claude Code is prompting for that URL. It does not apply to a normal remote return to Claude Desktop or to a callback that already completed. - If the failure persists on 0.1.2 after the ID token verifies against the advertised JWKS,
preserve the captured
ofid_..., timestamp, versions, workspace type, and sanitized/mcpstatus for Anthropic support, together with the server-side timeline. A valid token response followed by no authenticated MCP request is then a post-token client/connector failure.
For Codex, codex mcp list should show the Health entry as OAuth; use
codex mcp login shaughv-health for a connector-scoped retry. Codex registers a public client and
owns a randomized 127.0.0.1 callback, so no fixed Codex callback belongs in Google Cloud. If a
Codex run fails before the server receives DCR or an unauthenticated MCP discovery probe, update
Codex and correct the local model/client configuration first. See OpenAI's
Codex MCP guide.
Task board
Work is tracked on a self-contained board under .tasks/ (the SHAUGHV tasks system).
.tasks/TASKS.md— the board (Backlog / To-Do / Active / Done). Source of truth for what's next..tasks/MILESTONES.md— dated epics; tasks join one with an(ms #id)tag. Current milestone:#v1..tasks/tasks/<id>.md— a rich detail file per task (TT;DR-led, with## Verification,## Status,## Activity). The decision history lives here — e.g.rlw.md(Railway/FastMCP),api.md(REST surface feasibility),inf.md(the infra session),w11.md(webhooks v1.1)..tasks/CLAUDE.md— working memory (people, terms, projects, preferences).
The live dashboard is a zero-dependency Node server; resolve its port from
.tasks/.board-server.json or run node .tasks/board-server.mjs status. See
CLAUDE.md / AGENTS.md for how agents should use the board.
Further reading
docs/adr/0001-private-allowlist-only.md— accepted private-audience, OAuth verification, and access-control decision.docs/PLAN.md— the authoritative build plan (read first).CLAUDE.md— instructions for Claude coding sessions.AGENTS.md— the same operational guidance, tool-neutral, for any agent (Codex, Cursor, etc.)..env.example— the env-var reference.
Installing Shaughv Health
This server has no published package — it is built from source. Open the repository and follow its README.
▸ github.com/RealEmmettS/Google-Health-MCPFAQ
Is Shaughv Health MCP free?
Yes, Shaughv Health MCP is free — one-click install via Unyly at no cost.
Does Shaughv Health need an API key?
No, Shaughv Health runs without API keys or environment variables.
Is Shaughv Health hosted or self-hosted?
A hosted option is available: Unyly runs the server in the cloud, no local setup required.
How do I install Shaughv Health in Claude Desktop, Claude Code or Cursor?
Open Shaughv Health on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
Fetch
Web content fetching and conversion for efficient LLM usage.
AWS KB Retrieval
Retrieval from AWS Knowledge Base using Bedrock Agent Runtime.
by modelcontextprotocolSpring AI MCP Server
Provides auto-configuration for setting up an MCP server in Spring Boot applications.
llm-analysis-assistant
A very streamlined mcp client that supports calling and monitoring stdio/sse/streamableHttp, and can also view request responses through the /logs page. It also
by xuzexin-hzCompare Shaughv Health with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All ai MCPs
