AgentStack
MCP verified MIT Self-run

Oura Mcp Server

mcp-loganmurphy-oura-mcp-server · by loganmurphy

Oura MCP Server — A Cloudflare Worker that connects your Oura Ring to any MCP-compatible AI assistant. Ask questions about your sleep, recovery, activity, and workouts in plain language. Deployed in minutes, runs on your own Cloudflare account, your data never leaves your control.

No reviews yet
0 installs
10 views
0.0% view→install

Install

$ agentstack add mcp-loganmurphy-oura-mcp-server

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No 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.

Are you the author of Oura Mcp Server? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

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

Bootstrap

pnpm install
pnpm bootstrap

The wizard handles everything:

  1. Sign in to Cloudflare via wrangler login
  2. Select your account and preview what will be created
  3. Create D1 (oura-cache) and KV (oura-oauth) — or reuse if they exist
  4. Prompt for your Oura PAT and a password for the MCP login page
  5. Deploy the Worker and set secrets
  6. 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 passwordnpx 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 uselsof -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.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.