Oura Ring MCP Server

Oura Ring MCP Server

Enables Claude to access and query personal Oura Ring health data, including activity, readiness, sleep, workouts, heart rate, stress, SpO2, sessions, and tags, via a self-hosted MCP server.

Category
访问服务器

README

Oura Ring MCP Server

Tests

A self-hosted MCP server that exposes your Oura Ring data — daily activity, readiness, sleep, workouts, heart rate, stress, SpO2, sessions, and tags — to Claude (claude.ai connectors or Claude Code) over HTTP.

Change type: Feature. This is a fork of camji55/oura-mcp at commit c8db34f (2026-08-12), MIT © 2026 Cameron Ingham (see LICENSE). Upstream's 494-line server exists only as a Python string embedded in docker-compose.yml (configs.oura_server_py.content) — nothing can import it, so it had zero tests and no CI. This fork extracts it into an importable package with a pytest suite, without changing behavior at the MCP tool boundary.

What changed vs. upstream

  • Package extraction. The inline server is now src/oura_mcp/config.py, client.py, auth.py, tools.py, server.py. All 11 tools keep identical names, signatures, field names, units, and docstrings. docker-compose.yml mounts ./src and a requirements.lock instead of embedding the server as a Compose config.
  • Test harness. 42 tests: unit tests for pagination, field-mapping fidelity, and the auth accept/reject matrix, plus a 13-scenario integration suite at the real MCP protocol boundary (real JSON-RPC over HTTP through the real ASGI app, including the auth middleware). See Testing.
  • What this fork did not do: the four deployment security gaps this fork set out to check (loopback-only bind, constant-time token compare, fail-closed on missing MCP_AUTH_TOKEN, pinned dependency lockfile) were already fixed upstream by commit c8db34f, two commits after the SHA originally targeted for this fork. This change verifies and test-locks that inherited hardening — see Security posture — it did not implement it from scratch.

Tools

Tool Description
get_daily_activity Steps, calories, MET minutes by intensity, sedentary/resting time, activity score
get_daily_readiness Readiness score, temperature deviation from baseline, contributor scores (HRV balance, resting HR, etc.)
get_daily_sleep Daily sleep scores and contributors
get_sleep_periods Detailed sleep periods: bedtimes, stage durations, efficiency, avg HR/HRV, lowest HR
get_workouts Logged workouts with type, intensity, calories, and start/end times
get_activity_summary Compact multi-day summary with per-day rows and period averages
get_heart_rate Per-day HR summaries from the intraday timeseries: min/avg/max bpm and averages by source
get_daily_stress Time in high-stress and high-recovery zones, plus Oura's day classification
get_daily_spo2 Nightly average SpO2 and breathing disturbance index
get_sessions Meditation, breathing, nap, and relaxation sessions with type, mood, and times
get_tags User-entered tags and notes (illness, travel, alcohol, custom)

Date-range tools default to the last 7 days when called without arguments. Fields are raw Oura values with explicit unit suffixes and full ISO 8601 timestamps — never human-formatted durations or times.

Requirements

  • Docker with Compose v2 (to run the server)
  • uv (to run tests / develop locally)
  • An Oura account with a personal access token

Quick start

  1. Clone the repo and create your .env:

    cp .env.example .env
    
  2. Edit .env:

    • OURA_ACCESS_TOKEN — your personal access token from cloud.ouraring.com/personal-access-tokens

    • MCP_AUTH_TOKEN — a long random secret that gates access to the server. Generate one:

      openssl rand -hex 32
      
  3. Start it:

    docker compose up -d
    

    First start takes ~30s while pip installs dependencies from requirements.lock (cached in a volume afterwards). The server listens on 127.0.0.1:8000; override with OURA_MCP_PORT / OURA_MCP_BIND in .env.

  4. Check health:

    curl http://localhost:8000/health
    

    {"status": "ok"} means the server is up. To also verify your Oura token works, authenticate the same endpoint:

    source .env && curl -H "Authorization: Bearer $MCP_AUTH_TOKEN" http://localhost:8000/health
    

    {"status": "ok", "oura_api": true} means the server can reach the Oura API with your token.

Connecting Claude

claude.ai (custom connector): add a connector with the URL

https://<your-host>/mcp/<MCP_AUTH_TOKEN>

Claude Code:

claude mcp add --transport http oura https://<your-host>/mcp --header "Authorization: Bearer <MCP_AUTH_TOKEN>"

Configuration

All configuration is via environment variables, loaded from .env by Docker Compose:

Variable Required Description
OURA_ACCESS_TOKEN yes Oura personal access token
MCP_AUTH_TOKEN yes Secret gating all /mcp requests (path segment or Bearer header)
OURA_TIMEOUT no Oura API request timeout in seconds (default 30)
OURA_MCP_PORT no Host port the server is published on (default 8000)
OURA_MCP_BIND no Host interface to bind (default 127.0.0.1; set 0.0.0.0 to expose beyond this machine)

Compose fails fast with a clear error if either required variable is missing; the server itself also refuses to start (RuntimeError) if MCP_AUTH_TOKEN is unset, whether run under Compose or via uvicorn oura_mcp.server:app directly.

Testing

uv sync
uv run pytest

Runs in CI on every push and PR to main via .github/workflows/test.yml (uv sync --locked && uv run pytest, Python 3.12 — matching the container's python:3.12-slim). The docker-marked container smoke test is excluded from the default run (see below) and does not run in CI.

42 tests, ~99% coverage on client.py/tools.py (gate held at 98%). Every Oura API call is stubbed from synthetic fixtures in tests/fixtures/ (generated against Oura's public OpenAPI spec, vendored as openapi-1.37.json — no real health data, no live PAT). An autouse fixture wraps every test in respx's network guard, which raises immediately on any unmocked HTTP call rather than letting it reach the network.

  • tests/test_client.pyOuraClient pagination, including the two-page concatenation test (the single highest-value test in the suite).
  • tests/test_tools.py — per-tool field-mapping fidelity, including a golden-record check for get_sleep_periods.
  • tests/test_auth.py — the auth middleware accept/reject matrix.
  • tests/test_fixtures_validate.py — fixtures validated against the vendored OpenAPI spec (scripts/validate_fixtures.py).
  • tests/test_integration.py — 13 end-to-end scenarios at the real MCP protocol boundary (JSON-RPC over HTTP through the real ASGI app and auth middleware): handshake, single- and multi-page tool calls, unit/timestamp fidelity, heart-rate summarization, the full auth matrix including fail-closed-on-unset-token, health endpoint behavior, and explicit error propagation on upstream 401/429/timeout (no silent failures).

The container smoke test (test_container_smoke_docker_compose_up: real docker compose up, poll /health, one real initialize against 127.0.0.1:8000, and a check that the published port is loopback-only) is marked @pytest.mark.docker + @pytest.mark.slow and skipped by default — run it explicitly with uv run pytest -m docker. Docker isn't installed on the machine this fork is normally developed on, so this test can't run there; it instead runs on demand in CI via the docker-smoke job in .github/workflows/test.yml (workflow_dispatch, since GitHub-hosted runners ship Docker) — verified passing.

Security posture

All of the following were already present in upstream at the c8db34f fork point — this fork verifies them with tests and preserves them through the package extraction, it did not introduce them:

  • Loopback-only by default. docker-compose.yml binds 127.0.0.1:8000:8000 unless you set OURA_MCP_BIND=0.0.0.0.
  • Fail-closed auth. The server refuses to start if MCP_AUTH_TOKEN is unset — RuntimeError at import time, so this holds under uvicorn directly, not just under Compose's ${VAR:?} guard.
  • Constant-time token comparison (hmac.compare_digest), so response timing leaks nothing about the token.
  • Pinned dependencies. requirements.lock is fully pinned (via uv pip compile); the container installs from it at start rather than resolving version ranges fresh each time.
  • Auth-gated health detail. /health answers anonymous callers with bare liveness ({"status": "ok"}) only. Oura connectivity detail — which would reveal whether your token is currently valid — requires the auth token, and the upstream check behind it is cached 60s so it can't be used to burn your Oura API quota.
  • Container hardening carried over unchanged: no-new-privileges, 256 MB memory limit, log rotation.

Still the operator's responsibility: TLS termination. The auth token travels in the URL path (for claude.ai connectors) or a header — put the server behind a TLS-terminating reverse proxy (Caddy, nginx, Cloudflare Tunnel, Tailscale) before exposing it beyond localhost. Path-based tokens can end up in proxy access logs — treat those logs as sensitive. Keep .env out of version control (already covered by .gitignore); if a token leaks, revoke it at cloud.ouraring.com and generate a new MCP_AUTH_TOKEN.

Known gaps / follow-ups

  • health() blocks the event loop. The handler is async def but calls synchronous httpx under the hood via OuraClient — a pre-existing upstream characteristic, carried over unchanged rather than fixed inline, per this fork's behavior-preserving-extraction scope. Worth a dedicated follow-up if /health latency ever matters under load.

How it works

docker-compose.yml starts a stock python:3.12-slim container, bind-mounts src/oura_mcp and requirements.lock, installs dependencies from the lockfile at boot, and runs uvicorn oura_mcp.server:app. Auth is a small Starlette middleware (oura_mcp.auth.TokenPathAuthMiddleware) that accepts either POST /mcp/<token> (claude.ai) or POST /mcp with a Bearer header (Claude Code), and OuraClient transparently follows next_token pagination to exhaustion on every collection endpoint.

License

MIT © 2026 Cameron Ingham (upstream). Fork point: c8db34f.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选