Skip to main content
Glama
srwoodsaz

oura-mcp-server

by srwoodsaz
README.md
# Oura MCP Server

A small remote MCP server that lets **Claude and ChatGPT pull your Oura Ring data** (sleep, readiness, activity, heart rate, SpO2, stress) live, on demand. Your ring already syncs to Oura's cloud through the phone app — this reads from that cloud API, so there's nothing to install on your phone.

## Tools exposed

| Tool | What it returns |
|------|-----------------|
| `oura_get_daily_summary` | Sleep + readiness + activity + SpO2 + stress for a date range, in one call |
| `oura_get_daily_sleep` | Nightly sleep score + contributors |
| `oura_get_daily_readiness` | Readiness score + recovery contributors (best recovery signal) |
| `oura_get_daily_activity` | Activity score, steps, calories, MET minutes |
| `oura_get_sleep_periods` | Detailed sessions: stages, HR, HRV, breathing |
| `oura_get_heart_rate` | Time-series heart-rate samples |
| `oura_get_daily_spo2` | Blood oxygen % during sleep |
| `oura_get_daily_stress` | Daytime stress + recovery time |
| `oura_get_personal_info` | Profile (age, sex, height, weight) |

All accept `start_date` / `end_date` as `today`, `yesterday`, or `YYYY-MM-DD`.

---

## Setup (about 15 minutes, once)

### 1. Get Oura OAuth credentials
Oura phased out Personal Access Tokens (Dec 2025), so use OAuth2 — it also lets the server refresh itself so daily pulls keep working unattended.

1. Go to **https://cloud.ouraring.com/oauth/applications** and create an application.
2. Set the **Redirect URI** to exactly `http://localhost:8080/callback`.
3. Copy the **Client ID** and **Client Secret**.

### 2. Get your refresh token (one time)
On your computer (Node 20+):
```bash
npm install
OURA_CLIENT_ID=xxx OURA_CLIENT_SECRET=yyy node get-token.mjs
```
Approve in the browser window that opens. The terminal prints `OURA_REFRESH_TOKEN=...`. Save it.

> Just testing? Skip steps 1–2 and set `OURA_USE_SANDBOX=true` to use Oura's sample data.

### 3. Pick an MCP secret
Generate a long random string (e.g. `openssl rand -hex 24`). This protects your endpoint and becomes part of the URL.

### 4. Deploy (always-on cloud)
Push this folder to a GitHub repo, then use any host. Set these environment variables on the host: `MCP_SECRET`, `OURA_CLIENT_ID`, `OURA_CLIENT_SECRET`, `OURA_REFRESH_TOKEN`.

- **Railway** (no cold starts): New Project → Deploy from repo → add the env vars. Done.
- **Render**: New → Web Service → connect repo (a `render.yaml` is included) → add env vars.
- **Docker** (any host / Fly.io): a `Dockerfile` is included.

Your connector URL is:
```
https://<your-deployed-host>/mcp/<MCP_SECRET>
```
Confirm it's live: visiting `https://<your-deployed-host>/health` should return `{"status":"healthy"}`.

---

## Connect the AI apps

### Claude (Pro, Max, Team, or Enterprise)
Settings → **Connectors** → **Add custom connector** → paste the URL above → save. Start a chat and ask: *"What was my Oura readiness this week?"*

### ChatGPT (Plus, Pro, Business, Enterprise, or Edu)
Settings → **Connectors / Apps** → enable **Developer mode** → **Add custom connector** → paste the URL → save. (ChatGPT only supports remote HTTPS MCP servers, which this is.)

Both clients connect from the cloud, so the server must be publicly reachable over HTTPS (any of the hosts above provide that automatically).

---

## Daily use — example prompts
- "Give me my Oura daily summary for today."
- "How has my sleep score trended over the last 14 days?"
- "Compare my readiness on days I hit my activity target vs days I didn't."
- "What was my average resting heart rate and HRV this week?"

## Local run (for testing)
```bash
npm install && npm run build
MCP_SECRET=test OURA_USE_SANDBOX=true OURA_CLIENT_ID=x OURA_CLIENT_SECRET=x OURA_REFRESH_TOKEN=x npm start
# POST http://localhost:3000/mcp/test
```

## Notes
- Read-only: the server never writes to your Oura account.
- If pulls start failing with a 401, your refresh token was likely revoked — re-run `get-token.mjs` and update `OURA_REFRESH_TOKEN`.
- Keep `MCP_SECRET` private; anyone with the full URL can read your data.