# oura-mcp

> The Oura v2 API as an MCP server. All 19 collections, three tools, no
> dependencies beyond the MCP SDK.

It exists because Oura under-delivers without saying so, in four distinct ways,
and this server corrects all four. Oura never returns an error when it can't
give you what you asked for: it returns something different, shaped like a
correct response.

  1. Not following `next_token` returns a fraction. One local day of heartrate
     is 1,231 samples across 2 pages; a client that doesn't paginate gets 81%.
  2. `end_date` is inconsistent ACROSS collections —daily_activity, sleep and
     workout exclude the last day; the others don't— and workout filters by UTC
     date while reporting `day` in local time.
  3. `latest=true` on a collection that doesn't support it returns the entire
     collection, with no error.
  4. `fields=made_up` returns the complete record, without projecting.

## Tools

- `oura_collections` — all 19, what each carries and which parameters it takes.
- `oura_query` — one collection in full over a range, paginating to the end.
  Parameters: coleccion, dia, inicio, fin, campos, ultimo, formato.
  The range is INCLUSIVE on both ends.
- `oura_check` — self-check: auth mode, granted scopes, expiry. Returns
  neither the token nor any health value.

All three are read-only. There is no POST, PUT or DELETE in the package.
Parameter names are in Spanish because the codebase is.

## Warning keys in the response

- `truncated` + `continue_from` — data is missing; `continue_from` is the last
  day reached, so you ask again with `start` set to the day after. There is no
  cursor parameter on purpose: a cursor makes pagination the model's job.
- `pagination_cycle` — Oura repeated a token; what's there may be incomplete.
- `ignored_fields` — you asked for fields Oura didn't apply.
- `discarded_out_of_range` — records from the safety margin that were cut.
- `uneven_columns` — in CSV, not every record has the same keys.
- `empty` — the query came back empty; explains what's knowable about why.
- `large_response` — the payload is heavy; names the field that dominates it.

## Environment variables

- `OURA_SANDBOX=1` — Oura's official synthetic data, no credentials needed.
- `OURA_CLIENT_ID`, `OURA_CLIENT_SECRET` — for `oura-mcp --authorize` (OAuth2).
- `OURA_PAT`, `OURA_PAT_FILE` — personal token, if you already had one. Oura
  stopped issuing them in December 2025.
- `OURA_CREDENTIALS` — where the 0600 OAuth file lives.
- `OURA_API_BASE_URL` — point at another origin (testing).

## What it does NOT do

It doesn't analyze: no correlations, no anomalies, no period comparison. An
average computed inside reaches the model as a number without its method. The
data is delivered raw and the analysis belongs where the method can be cited.

## Links

- Repository: https://github.com/proscar87/oura-mcp
- PyPI: https://pypi.org/project/mcp-oura/
- MCP registry: io.github.proscar87/oura-mcp
