Skip to main content
Glama
echocharlie

Oura MCP Server

by echocharlie
README.md
# Oura MCP server

[![CI](https://github.com/echocharlie/oura-mcp-server/actions/workflows/ci.yml/badge.svg)](https://github.com/echocharlie/oura-mcp-server/actions/workflows/ci.yml)

A Model Context Protocol (MCP) server that gives Claude **read-only** access to your
[Oura ring](https://ouraring.com) biometrics via the [Oura API v2](https://cloud.ouraring.com/v2/docs).

It's designed to sit **alongside a Strava connector**: every tool is namespaced `oura_*` and
keyed by ISO date (`YYYY-MM-DD`), so Claude can join Oura recovery data against Strava training
load on `date` and answer questions like *"how did last week's hard sessions affect my sleep,
stress, and next-day readiness?"* in one reasoning step.

![Claude running the Oura connector — daily summary with an overreach day highlighted](docs/demo.png)

<sub>Illustrative example using sample data, not real biometrics.</sub>

## Tools

| Tool | What it returns | Default window |
|------|-----------------|----------------|
| `oura_get_daily_summary` | **Start here.** One row/day: readiness, sleep & activity scores, total sleep, bedtime, resting HR, HRV, respiratory rate, temp deviation, breathing disturbance, steps, active calories, daytime stress. The cross-source join table. | 30 days |
| `oura_get_sleep_time` | Oura's **bedtime guidance**: optimal bedtime window + recommendation (what Oura thinks you *should* do, vs. `daily_summary`'s actual bedtime). | 30 days |
| `oura_get_sleep_detail` | Per-night architecture: bedtime, sleep stages (deep/REM/light), efficiency, latency, avg/lowest HR, HRV, respiratory rate. | 14 days |
| `oura_get_readiness_detail` | Readiness with every contributor (hrv_balance, resting HR, recovery index, body temp, previous-day activity, sleep balance…) — explains *why* readiness moved. | 30 days |
| `oura_get_stress_resilience` | Daytime stress vs. recovery minutes, day summary, and long-term resilience level + contributors. | 30 days |
| `oura_get_workouts` | Workouts as logged by Oura — reconcile against Strava, catch missed sessions, see intensity labels. | 30 days |
| `oura_get_baselines` | Slow-moving baselines: overnight SpO2, breathing disturbance, **pulse wave velocity** (arterial stiffness), vascular age, VO2 max. | 90 days |
| `oura_get_heart_rate` | Fine-grained HR timeseries, tagged by source (awake/rest/sleep/workout). Aggregated stats by default. | 24 hours |

All output is compact CSV with units stated in the column names.

## Setup

```bash
python -m venv .venv && source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install -e .
```

> **New here?** The [full setup & configuration guide](docs/SETUP.md) walks through install,
> token creation, the Claude Desktop config, common mistakes, and troubleshooting.

## Authentication

This server uses an Oura **Personal Access Token** (PAT):

1. Go to <https://cloud.ouraring.com/personal-access-tokens> and create a token.
2. Copy `.env.example` to `.env` and paste the token into `OURA_PERSONAL_ACCESS_TOKEN`
   (for local dev), or set it in the Claude Desktop `env` block below.

Never commit your real token — `.env` is gitignored.

## Run / test

```bash
fastmcp dev oura_mcp.py   # opens the MCP Inspector to exercise each tool manually
```

Run the tests (offline — no token or network needed):

```bash
pip install -e ".[dev]"
pytest -q
```

## Install in Claude Desktop

Edit `claude_desktop_config.json` (macOS: `~/Library/Application Support/Claude/`,
Windows: `%APPDATA%\Claude\`) and add:

```json
{
  "mcpServers": {
    "oura": {
      "command": "/ABSOLUTE/PATH/TO/oura-mcp-server/.venv/bin/python",
      "args": ["/ABSOLUTE/PATH/TO/oura-mcp-server/oura_mcp.py"],
      "env": {
        "OURA_PERSONAL_ACCESS_TOKEN": "your-token-here"
      }
    }
  }
}
```

Use the venv's Python interpreter and an absolute path to `oura_mcp.py` (older configs
pointing at `server.py` still work — it's kept as a shim). Fully quit and reopen
Claude Desktop after editing. The `oura_*` tools then appear in the tools/connectors menu,
ready to use alongside your Strava tools.

## Usage & examples

Once the server is connected, just ask Claude in plain language — it picks the right tool(s) and
date ranges. A few patterns that work well:

### Oura on its own

- *"Pull my Oura daily summary for the last two weeks."*
- *"Did my readiness drop after any night this month? Show me why."* → `oura_get_readiness_detail`
- *"Break down last night's sleep stages and compare them to my 14-day average."* → `oura_get_sleep_detail`
- *"Which days this month were most stressful, and how's my resilience trending?"* → `oura_get_stress_resilience`
- *"How's my VO2 max and SpO2 trending over the last 90 days?"* → `oura_get_baselines`
- *"What was my heart rate during yesterday's workout vs. overnight?"* → `oura_get_heart_rate`

### The headline use case — Oura + Strava together

Because both connectors are date-keyed, Claude can join them on `date` in one step:

- *"Compare last month's Strava training load against my Oura readiness and sleep. Flag any days
  I overreached (readiness or HRV dropped the morning after a hard session)."*
- *"After my long runs, how much does my resting heart rate rise the next morning, and how long
  until it recovers to baseline?"*
- *"Do my hardest training days hurt that night's deep sleep? Show the correlation."*
- *"Reconcile my Strava activities with Oura's logged workouts for the past month — did Oura catch
  anything Strava missed?"* → `oura_get_workouts`
- *"Build me a weekly table: Strava distance & suffer score next to Oura sleep score, avg HRV, and
  next-day readiness, so I can see if my training plan is sustainable."*

### What the data looks like

Tools return compact CSV with units in the column names — easy for Claude to reason over and join.
For example, `oura_get_daily_summary` returns rows like:

```csv
date,readiness_score,sleep_score,activity_score,total_sleep_h,bedtime,resting_hr_bpm,avg_hrv_ms,resp_rate_brpm,temp_deviation_c,breathing_disturbance_idx,steps,active_cal,stress_high_min,day_summary
2026-06-05,78,84,95,8.31,23:36,52,79,13.375,-0.16,0,8122,1211,360,stressful
2026-06-06,63,60,97,5.14,05:49,74,12,18.25,-0.19,6,7667,941,240,stressful
2026-06-07,85,87,96,7.95,01:01,60,37,17.125,0.13,1,7768,919,30,normal
```

Here the high-load, high-stress day (Jun 5: 1,211 active cal, 360 stressful min) is followed by a
readiness crash (84→63), a resting-HR spike (52→74 bpm), and an HRV collapse (79→12 ms) — exactly
the training-to-recovery signal this server is built to surface. Note the leading indicators moving
with it: respiratory rate jumps 13.4→18.3 brpm and breathing disturbance 0→6 on the same night.

> **Tip:** start broad with `oura_get_daily_summary`, then drill into a specific day or signal with
> the detail tools (`oura_get_readiness_detail`, `oura_get_sleep_detail`). Keep date ranges modest
> to stay fast and within Oura's rate limit.

## Notes & limits

- **Read-only.** No tool writes to your Oura account.
- Dates are ISO `YYYY-MM-DD` and the range is inclusive. Heart rate uses ISO 8601 datetimes.
- Some metrics are sparse or ring/firmware-dependent (VO2 max, cardiovascular age, resilience
  need enough history); rows show blanks where a metric wasn't measured.
- **VO2 max is a measurement *event*, not a daily value** — often only a few readings per
  quarter, so most rows are blank by design. `oura_get_baselines` appends a footer with the
  latest reading in the window so blanks aren't misread as "no data."
- **Overnight SpO2 must be enabled in the Oura app** (Settings → your ring) and needs sleep
  sessions longer than ~3 hours to register; it also increases battery drain.
- `pulse_wave_velocity_m_s` (meters/second) is the raw arterial-stiffness measure (lower = more elastic);
  `vascular_age_years` is a derived presentation of the same signal. Prefer PWV for tracking
  real change over time.
- Oura's rate limit is ~5000 requests/day; tools default to modest windows and paginate
  automatically.

TDQS

A4.4/5.0

Scored across 8 tools

Disambiguation5/5

Each tool targets a distinct data domain (stress, sleep detail, bedtime guidance, readiness breakdown, workouts, baselines, HR timeseries) with no overlap. The descriptions explicitly clarify differences, like separating actual bedtime from recommended bedtime.

Naming Consistency5/5

All tools follow the consistent pattern 'oura_get_<noun_phrase>' in snake_case. The verb 'get' is used uniformly, and the noun phrases clearly indicate the data type (e.g., 'stress_resilience', 'daily_summary', 'sleep_detail').

Tool Count5/5

With 8 tools, the server covers the major categories of Oura health data without being overly granular or sparse. Each tool earns its place, offering focused access to key metrics while avoiding unnecessary fragmentation.

Completeness4/5

The tool set covers most primary Oura data types (sleep, readiness, activity, HR, stress, baselines, workouts). Minor gaps exist, such as missing dedicated tools for user tags or activity detail beyond the summary, but the daily summary serves as a central join table that mitigates this.

Maintenance

ActivitySlowing
ResponsivenessNo issues