Install
$ agentstack add mcp-loganmurphy-oura-mcp-server ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
oura-mcp-server
[](https://github.com/loganmurphy/oura-mcp-server/actions/workflows/ci.yml) [](LICENSE) [](https://www.buymeacoffee.com/loganmurphc)
A lightweight Model Context Protocol server that exposes your Oura Ring data as tools for Claude. Runs on Cloudflare Workers with a D1 cache layer for fast repeated queries.
Tested with Claude.ai (web), Claude Desktop, and Claude mobile via claude.ai/settings/connectors. Any MCP client that supports OAuth 2.1 remote servers should work — though only Claude is officially tested and the bootstrap wizard targets Claude exclusively.
Also available: strava-mcp-server — Strava activities, stats, and gear as MCP tools.
Architecture
Claude (web / desktop / mobile)
│ OAuth 2.1 (PKCE)
Cloudflare Worker (@cloudflare/workers-oauth-provider)
├─ KV OAuth tokens (30-day access, non-expiring refresh)
├─ D1 per-day cache (5m today / 6h yesterday / 24h older)
└─ Oura API fetched only on cache miss
All tools share a single /mcp endpoint and one OAuth login. On a partial cache hit the worker fetches only the missing date range and merges it with cached data. Empty responses are never cached (ring not yet synced).
Requirements
- Cloudflare account (free tier)
- Oura Personal Access Token
- Node.js 24, pnpm 10 — Volta recommended (versions pinned in
package.json)
Bootstrap
pnpm install
pnpm bootstrap
The wizard handles everything:
- Sign in to Cloudflare via
wrangler login - Select your account and preview what will be created
- Create D1 (
oura-cache) and KV (oura-oauth) — or reuse if they exist - Prompt for your Oura PAT and a password for the MCP login page
- Deploy the Worker and set secrets
- Copy the MCP URL to your clipboard and open
claude.ai/settings/connectors(first run only)
Re-running is fully idempotent.
Local dev first?
pnpm connect-local # prompts for tokens, applies local D1 schema, guides ngrok tunnel setup
pnpm dev # keep running in a separate terminal
Run pnpm bootstrap when ready to deploy.
Connect
All clients connect to the same MCP endpoint. pnpm bootstrap copies this URL to your clipboard on completion:
https://oura-mcp-server..workers.dev/mcp
Settings → Connectors → Add custom connector → paste URL → Connect → enter password → Authorize.
After connecting, click Configure on the Oura connector and set each tool to Allow — otherwise Claude may ask for permission on every use.
> Setup must be done on claude.ai (web) — the mobile app doesn't support adding connectors. Once added via web, it's available across all Claude clients.
Local development
cp wrangler.example.jsonc wrangler.jsonc # fill in YOUR_KV_NAMESPACE_ID + YOUR_DATABASE_ID
pnpm install && pnpm cf-typegen
printf 'OURA_API_TOKEN=your_token\nMCP_AUTH_PASSWORD=your_password\n' > .dev.vars
npx wrangler d1 execute oura-cache --local --file=./migrations/001_init.sql
pnpm dev # http://localhost:8787
Testing with ngrok
Claude.ai (web/mobile) requires HTTPS. Use ngrok to expose the local dev server:
brew install ngrok/ngrok/ngrok
ngrok config add-authtoken YOUR_TOKEN # free at ngrok.com
pnpm dev # terminal 1
ngrok http 8787 # terminal 2 → https://xxxx.ngrok-free.app
Add /mcp as a custom connector at claude.ai/settings/connectors.
> Free tier URLs change on restart — re-add the connector in Claude when that happens.
Smoke testing
BASE=https://oura-mcp-server..workers.dev # or http://localhost:8787
# Server reachable
curl -s $BASE/.well-known/oauth-authorization-server | jq .
curl -s $BASE/health
# Unauthenticated call returns 401 — expected
curl -s -X POST $BASE/mcp -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | jq .
Full OAuth flow (cURL PKCE)
No browser required — curl handles the full flow end-to-end. Run this as one block:
# Prompt for password — avoids storing it in shell history
printf "MCP password: " && read -s MCP_PASSWORD && echo
# 1. Register a client
CLIENT=$(curl -s -X POST $BASE/oauth/register -H "Content-Type: application/json" \
-d '{"client_name":"curl-test","redirect_uris":["http://localhost:9999/callback"],
"grant_types":["authorization_code"],"response_types":["code"],
"token_endpoint_auth_method":"none"}')
CLIENT_ID=$(echo $CLIENT | jq -r .client_id)
# 2. PKCE challenge
CODE_VERIFIER=$(openssl rand -base64 32 | tr -d '=+/' | cut -c1-43)
CODE_CHALLENGE=$(printf '%s' "$CODE_VERIFIER" | openssl dgst -sha256 -binary | base64 | tr '+/' '-_' | tr -d '=')
# 3. GET the login form, then POST the password to complete authorization
FORM_HTML=$(curl -s "$BASE/authorize?client_id=$CLIENT_ID&response_type=code&redirect_uri=http://localhost:9999/callback&code_challenge=$CODE_CHALLENGE&code_challenge_method=S256&state=test")
OAUTH_PARAMS=$(echo "$FORM_HTML" | grep -o 'name="oauth_params" value="[^"]*"' | sed 's/name="oauth_params" value="//;s/"$//' | sed 's/&/\&/g')
AUTH_PAGE=$(curl -s -X POST $BASE/authorize \
--data-urlencode "oauth_params=$OAUTH_PARAMS" \
--data-urlencode "password=$MCP_PASSWORD")
# Extract and decode the auth code from the success page iframe
RAW_CODE=$(echo "$AUTH_PAGE" | grep -o 'code=[^&"]*' | head -1 | sed 's/code=//')
AUTH_CODE=$(node -e "process.stdout.write(decodeURIComponent(process.argv[1]))" "$RAW_CODE")
# 4. Exchange code for token
TOKEN=$(curl -s -X POST $BASE/oauth/token \
--data-urlencode "grant_type=authorization_code" \
--data-urlencode "code=$AUTH_CODE" \
--data-urlencode "redirect_uri=http://localhost:9999/callback" \
--data-urlencode "client_id=$CLIENT_ID" \
--data-urlencode "code_verifier=$CODE_VERIFIER" \
| jq -r .access_token)
echo "Token: ${TOKEN:0:30}..."
# 5. Call a tool
curl -s -X POST $BASE/mcp -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"oura_daily_sleep","arguments":{"start_date":"2026-04-18","end_date":"2026-04-25"}}}' | jq .
Manual deploy
If you prefer not to use pnpm bootstrap:
npx wrangler login
npx wrangler d1 create oura-cache
npx wrangler kv namespace create OAUTH_KV
# paste both IDs into wrangler.jsonc
npx wrangler d1 execute oura-cache --remote --file=./migrations/001_init.sql
npx wrangler secret put OURA_API_TOKEN
npx wrangler secret put MCP_AUTH_PASSWORD
pnpm deploy
Tool reference
All date params optional, default to last 7 days (YYYY-MM-DD). end_date is always inclusive from the caller's perspective.
| Tool | Returns | | ---------------------- | -------------------------------------- | | oura_daily_sleep | Sleep score + contributors | | oura_sleep_sessions | Sleep stages, HRV, HR, breathing, temp | | oura_daily_readiness | Readiness score + contributors | | oura_daily_spo2 | Blood oxygen saturation | | oura_daily_activity | Steps, calories, activity minutes | | oura_workouts | Session type, duration, calories, HR | | oura_daily_stress | Stress, recovery, ruggedness scores |
All tools accept start_date, end_date, and skip_cache (bool).
day field convention:
- Sleep, readiness, SpO2 → wake-up date (session ending morning of Apr 24 →
day: "2026-04-24") - Activity, workouts, stress → calendar date
Women's health tools (opt-in)
Three additional tools are available for Oura accounts with cycle tracking, reproductive health, or perimenopause features enabled. They are off by default.
| Tool | Returns | | --------------------------- | --------------------------------------------------- | | oura_cycle_insights | Cycle phase, predicted period start, fertile window | | oura_reproductive_health | Basal body temperature deviation, cycle metrics | | oura_perimenopause_health | Vasomotor symptoms, sleep disruption patterns |
Enable via pnpm bootstrap — the wizard prompts during setup. To enable manually:
# Production (Cloudflare Worker)
npx wrangler secret put ENABLE_WOMENS_HEALTH # enter: true
# Local dev — add to .dev.vars
echo "ENABLE_WOMENS_HEALTH=true" >> .dev.vars
> These tools return empty data if the corresponding feature isn't active in the Oura app.
Third-party integrations
Workouts synced to Oura from external apps (e.g. Strava) do not always appear in the Oura API — this is an upstream sync limitation, not a server issue. For richer Strava data, see strava-mcp-server.
Troubleshooting
Tools not appearing — remove and re-add the connector at claude.ai/settings/connectors; tool list refreshes on reconnect.
OURA_API_TOKEN not configured — local: check .dev.vars and restart pnpm dev; production: npx wrangler secret put OURA_API_TOKEN.
Today's data empty — open the Oura app to trigger a sync, then ask Claude to use skip_cache: true.
Oura 401 / token rejected — PATs expire ~every 3 months. Rotate: npx wrangler secret put OURA_API_TOKEN (no redeploy needed).
Rotate MCP password — npx wrangler secret put MCP_AUTH_PASSWORD, then pnpm revoke (invalidates existing sessions so Claude re-auths with the new password).
pnpm bootstrap fails at Cloudflare login — run npx wrangler login manually first.
Port 8787 in use — lsof -ti :8787 | xargs kill -9 && pnpm dev
Project structure
src/
index.ts Worker entry — OAuth wrapper, /mcp dispatch, auth UI
cache.ts D1 cache (per-day TTL, partial-hit merging)
oura.ts Oura API client + response noise stripping
tools.ts MCP tool definitions
ui.ts Login and success page HTML
scripts/
bootstrap.ts Setup wizard (D1, KV, Worker deploy, secrets)
connect-local.ts Credentials + D1 schema setup for local dev
revoke.ts Purge all OAuth KV tokens to force re-auth
migrations/
001_init.sql D1 schema
If this saved you some time, a coffee is always appreciated!
[](https://www.buymeacoffee.com/loganmurphc)
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: loganmurphy
- Source: loganmurphy/oura-mcp-server
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.