Skip to main content
Glama
trenerok

oura-mcp-server

by trenerok
README.md
# Oura Ring MCP Server

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Node.js](https://img.shields.io/badge/Node.js-18%2B-green.svg)](https://nodejs.org/)
[![MCP](https://img.shields.io/badge/MCP-1.0-blue.svg)](https://modelcontextprotocol.io/)

A comprehensive Model Context Protocol (MCP) server for the [Oura Ring](https://ouraring.com/) API V2. Access sleep, activity, readiness, heart rate, stress, and other health data from your Oura Ring through AI assistants like Claude, ChatGPT, or Cursor IDE.

> **Security First**: This server implements **read-only operations only** for user data. Your health information cannot be modified.

---

## What is This?

This MCP server allows AI assistants to access your Oura Ring health data. When connected, you can ask questions like:
- "How did I sleep last night?"
- "What's my readiness score this week?"
- "Show me my heart rate trends"
- "Analyze my stress patterns"

**Keywords**: Oura Ring, MCP Server, Model Context Protocol, Sleep Tracking, Health Data, Wearable API, AI Integration, Claude, ChatGPT, Cursor

---

## Table of Contents

- [What is Oura Ring?](#what-is-oura-ring)
- [Features](#features)
- [Quick Start](#quick-start)
- [Getting an Access Token](#getting-an-access-token)
- [Configuration](#configuration)
- [MCP Client Setup](#mcp-client-setup)
- [Available Tools (37 Total)](#available-tools-37-total)
- [Tool Reference](#tool-reference)
- [For AI Agents](#for-ai-agents)
- [Rate Limiting](#rate-limiting)
- [Project Structure](#project-structure)
- [Security](#security)
- [Troubleshooting](#troubleshooting)
- [Contributing](#contributing)
- [License](#license)

---

## What is Oura Ring?

[Oura Ring](https://ouraring.com/) is a smart ring that tracks:
- **Sleep** - Sleep stages (deep, light, REM), sleep quality, timing
- **Activity** - Steps, calories, movement throughout the day
- **Readiness** - Recovery score based on sleep, activity, and body signals
- **Heart Rate** - Continuous heart rate and HRV (heart rate variability)
- **Body Temperature** - Skin temperature deviations
- **Stress** - Daytime stress and recovery patterns
- **SpO2** - Blood oxygen saturation levels

This MCP server provides access to all this data through the [Oura API V2](https://cloud.ouraring.com/v2/docs).

---

## Features

| Feature | Description |
|---------|-------------|
| **37 MCP Tools** | Complete coverage of all Oura API V2 endpoints |
| **Read-only by design** | Cannot modify user data |
| **OAuth2 Authentication** | Secure token-based authentication with refresh token support |
| **Automatic Token Refresh** | Automatically refreshes expired access tokens |
| **Sandbox Support** | Test with mock data before using real data |
| **Rate Limiting** | Built-in rate limiting with exponential backoff |
| **Token Sanitization** | Access tokens are never exposed in logs or errors |

---

## Quick Start

### 1. Clone and Install

```bash
git clone https://github.com/trenerok/oura-mcp-server.git
cd oura-mcp-server
npm install
```

### 2. Get OAuth2 Credentials

See [Getting an Access Token](#getting-an-access-token) below.

### 3. Build

```bash
npm run build
```

### 4. Configure MCP Client

See [MCP Client Setup](#mcp-client-setup) for Claude Desktop, Cursor IDE, or other clients.

---

## Getting an Access Token

Oura uses OAuth2 for authentication. Personal access tokens were deprecated in December 2025.

### Step 1: Create an OAuth2 Application

1. Go to [Oura Cloud Applications](https://cloud.ouraring.com/oauth/applications)
2. Click "New Application"
3. Fill in the required fields:
   - **Display Name**: Your app name (e.g., "My MCP Server")
   - **Description**: Brief description
   - **Website**: Your website or GitHub repo URL
   - **Privacy Policy**: URL (can be your GitHub repo for personal use)
   - **Terms of Service**: URL (can be your GitHub repo for personal use)
   - **Redirect URI**: `http://localhost:8080/callback`
4. Select all the **Scopes** you want to access
5. Click "Create Application"
6. Copy your **Client ID** and **Client Secret**

### Step 2: Get Access Token

Use the included helper script:

```bash
node scripts/get-token.js YOUR_CLIENT_ID YOUR_CLIENT_SECRET
```

This will:
1. Open your browser to authorize the application
2. Exchange the authorization code for tokens
3. Display your access token and refresh token

### Step 3: Save Your Tokens

Add the tokens to your MCP configuration (see [MCP Client Setup](#mcp-client-setup)).

---

## Configuration

### Environment Variables

| Variable | Required | Description |
|----------|----------|-------------|
| `OURA_ACCESS_TOKEN` | Yes* | OAuth2 access token for user data |
| `OURA_REFRESH_TOKEN` | No | Refresh token for automatic token renewal |
| `OURA_CLIENT_ID` | No** | OAuth2 application client ID |
| `OURA_CLIENT_SECRET` | No** | OAuth2 application client secret |
| `OURA_USE_SANDBOX` | No | Set to `true` for sandbox/mock data |

\* Required for user data endpoints
\** Required for token refresh and webhook management

### Automatic Token Refresh

If you provide `OURA_REFRESH_TOKEN`, `OURA_CLIENT_ID`, and `OURA_CLIENT_SECRET`, the server will automatically refresh your access token when it expires (typically after 24 hours).

---

## MCP Client Setup

### Claude Desktop

Add to your `claude_desktop_config.json`:

**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`

```json
{
  "mcpServers": {
    "oura": {
      "command": "node",
      "args": ["/absolute/path/to/oura-mcp-server/dist/index.js"],
      "env": {
        "OURA_ACCESS_TOKEN": "your_access_token",
        "OURA_REFRESH_TOKEN": "your_refresh_token",
        "OURA_CLIENT_ID": "your_client_id",
        "OURA_CLIENT_SECRET": "your_client_secret"
      }
    }
  }
}
```

### Cursor IDE

Add to `.cursor/mcp.json` in your project directory:

```json
{
  "mcpServers": {
    "oura": {
      "command": "node",
      "args": ["./dist/index.js"],
      "env": {
        "OURA_ACCESS_TOKEN": "your_access_token",
        "OURA_REFRESH_TOKEN": "your_refresh_token",
        "OURA_CLIENT_ID": "your_client_id",
        "OURA_CLIENT_SECRET": "your_client_secret"
      }
    }
  }
}
```

### Other MCP Clients

Use similar configuration with the appropriate config file format for your client.

---

## Available Tools (37 Total)

### Overview by Category

| Category | Count | Tools |
|----------|-------|-------|
| **Personal Info** | 1 | User profile (age, weight, height) |
| **Sleep** | 6 | Sleep sessions, daily scores, bedtime recommendations |
| **Activity** | 2 | Daily activity, steps, calories burned |
| **Health Metrics** | 14 | Heart rate, readiness, SpO2, stress, resilience, cardiovascular age, VO2 max |
| **Workouts** | 4 | Workout sessions, meditation/breathing sessions |
| **Tags** | 4 | User-created tags and enhanced tags |
| **Device** | 4 | Ring configuration, rest mode periods |
| **Webhooks** | 6 | Webhook subscription management |

### Complete Tool List

#### Personal Info (1 tool)
| Tool | Description |
|------|-------------|
| `get_personal_info` | Get user profile: age, weight, height, biological sex, email |

#### Sleep (6 tools)
| Tool | Description |
|------|-------------|
| `get_sleep` | Detailed sleep sessions with stages (deep, light, REM), HRV, heart rate |
| `get_sleep_by_id` | Single sleep session by document ID |
| `get_daily_sleep` | Daily sleep scores with contributor breakdown |
| `get_daily_sleep_by_id` | Single daily sleep document by ID |
| `get_sleep_time` | Optimal bedtime recommendations |
| `get_sleep_time_by_id` | Single sleep time document by ID |

#### Activity (2 tools)
| Tool | Description |
|------|-------------|
| `get_daily_activity` | Daily steps, calories, active time, MET minutes |
| `get_daily_activity_by_id` | Single daily activity document by ID |

#### Health Metrics (14 tools)
| Tool | Description |
|------|-------------|
| `get_heart_rate` | Heart rate time series (BPM with timestamps) |
| `get_daily_readiness` | Daily readiness scores with contributors |
| `get_daily_readiness_by_id` | Single readiness document by ID |
| `get_daily_spo2` | Blood oxygen saturation (SpO2) data |
| `get_daily_spo2_by_id` | Single SpO2 document by ID |
| `get_daily_stress` | Daily stress levels and recovery time |
| `get_daily_stress_by_id` | Single stress document by ID |
| `get_daily_resilience` | Daily resilience scores |
| `get_daily_resilience_by_id` | Single resilience document by ID |
| `get_daily_cardiovascular_age` | Vascular age estimates |
| `get_daily_cardiovascular_age_by_id` | Single cardiovascular age document by ID |
| `get_vo2_max` | VO2 max estimates (cardiovascular fitness) |
| `get_vo2_max_by_id` | Single VO2 max document by ID |

#### Workouts (4 tools)
| Tool | Description |
|------|-------------|
| `get_workouts` | Workout sessions (activity type, calories, distance, duration) |
| `get_workout_by_id` | Single workout document by ID |
| `get_sessions` | Meditation and breathing sessions with HRV data |
| `get_session_by_id` | Single session document by ID |

#### Tags (4 tools)
| Tool | Description |
|------|-------------|
| `get_tags` | User-created tags for tracking behaviors |
| `get_tag_by_id` | Single tag document by ID |
| `get_enhanced_tags` | Enhanced tags with metadata and time ranges |
| `get_enhanced_tag_by_id` | Single enhanced tag document by ID |

#### Device (4 tools)
| Tool | Description |
|------|-------------|
| `get_ring_configuration` | Ring details: color, size, firmware, hardware type |
| `get_ring_configuration_by_id` | Single ring configuration by ID |
| `get_rest_mode_periods` | Rest mode activation history |
| `get_rest_mode_period_by_id` | Single rest mode period by ID |

#### Webhooks (6 tools) - Requires Client Credentials
| Tool | Description |
|------|-------------|
| `list_webhook_subscriptions` | List all webhook subscriptions |
| `get_webhook_subscription` | Get a specific webhook by ID |
| `create_webhook_subscription` | Create new webhook subscription |
| `update_webhook_subscription` | Update existing webhook |
| `delete_webhook_subscription` | Delete a webhook subscription |
| `renew_webhook_subscription` | Renew webhook before expiration |

---

## Tool Reference

### Common Parameters

#### Date Range Parameters (most tools)

| Parameter | Format | Required | Description |
|-----------|--------|----------|-------------|
| `start_date` | `YYYY-MM-DD` | No | Start of date range |
| `end_date` | `YYYY-MM-DD` | No | End of date range |
| `next_token` | string | No | Pagination token for next page |

#### DateTime Parameters (heart rate only)

| Parameter | Format | Required | Description |
|-----------|--------|----------|-------------|
| `start_datetime` | ISO 8601 | No | Start datetime (e.g., `2024-01-01T00:00:00Z`) |
| `end_datetime` | ISO 8601 | No | End datetime |
| `next_token` | string | No | Pagination token |

### Example Responses

#### Personal Info
```json
{
  "id": "abc123",
  "age": 35,
  "weight": 75.5,
  "height": 1.80,
  "biological_sex": "male",
  "email": "user@example.com"
}
```

#### Daily Sleep
```json
{
  "data": [
    {
      "id": "sleep123",
      "day": "2024-01-15",
      "score": 85,
      "contributors": {
        "deep_sleep": 80,
        "efficiency": 90,
        "latency": 85,
        "rem_sleep": 75,
        "restfulness": 82,
        "timing": 95,
        "total_sleep": 88
      },
      "timestamp": "2024-01-15T00:00:00.000+00:00"
    }
  ],
  "next_token": null
}
```

#### Daily Readiness
```json
{
  "data": [
    {
      "id": "ready123",
      "day": "2024-01-15",
      "score": 78,
      "contributors": {
        "activity_balance": 85,
        "body_temperature": 100,
        "hrv_balance": 72,
        "previous_day_activity": 80,
        "previous_night": 75,
        "recovery_index": 90,
        "resting_heart_rate": 82,
        "sleep_balance": 70
      },
      "temperature_deviation": 0.1,
      "timestamp": "2024-01-15T00:00:00.000+00:00"
    }
  ],
  "next_token": null
}
```

#### Heart Rate
```json
{
  "data": [
    {
      "bpm": 62,
      "source": "awake",
      "timestamp": "2024-01-15T08:30:00+00:00"
    },
    {
      "bpm": 58,
      "source": "rest",
      "timestamp": "2024-01-15T08:35:00+00:00"
    }
  ],
  "next_token": null
}
```

---

## For AI Agents

This section provides structured guidance for AI agents using this MCP server.

### Server Capabilities

- **Data Access**: Read-only access to all Oura Ring health data
- **Authentication**: OAuth2 Bearer token with automatic refresh
- **Real-time Data**: Data is fetched live from Oura API
- **Pagination**: Large datasets return `next_token` for pagination

### Quick Start for AI Agents

1. **Verify connectivity**: Call `get_personal_info` first
2. **Use date ranges**: Always specify `start_date` to limit data
3. **Handle empty data**: Empty arrays mean no data for that period
4. **Check null values**: Many fields may be null if unavailable

### Recommended Workflows

#### Health Dashboard
```
1. get_personal_info() → Get user profile
2. get_daily_readiness(start_date: "<7 days ago>") → Weekly readiness trend
3. get_daily_sleep(start_date: "<7 days ago>") → Weekly sleep scores
4. get_daily_activity(start_date: "<7 days ago>") → Weekly activity
5. get_daily_stress(start_date: "<7 days ago>") → Weekly stress patterns
```

#### Sleep Analysis
```
1. get_daily_sleep(start_date: "<30 days ago>") → Monthly sleep scores
2. get_sleep(start_date: "<7 days ago>") → Detailed sleep sessions with stages
3. get_sleep_time(start_date: "<7 days ago>") → Bedtime recommendations
```

#### Fitness Assessment
```
1. get_daily_activity(start_date: "<30 days ago>") → Activity trends
2. get_workouts(start_date: "<30 days ago>") → Workout history
3. get_vo2_max(start_date: "<30 days ago>") → Cardiovascular fitness
4. get_heart_rate(start_datetime: "<today>T00:00:00Z") → Today's heart rate
```

#### Stress & Recovery
```
1. get_daily_stress(start_date: "<7 days ago>") → Stress levels
2. get_daily_resilience(start_date: "<7 days ago>") → Resilience scores
3. get_daily_readiness(start_date: "<7 days ago>") → Recovery status
```

### Key Metrics Explained

| Metric | Range | Meaning |
|--------|-------|---------|
| **Sleep Score** | 0-100 | Overall sleep quality (>85 = optimal) |
| **Readiness Score** | 0-100 | Recovery level (>85 = ready for activity) |
| **Activity Score** | 0-100 | Daily movement goal progress |
| **Stress (high)** | seconds | Time in high stress state |
| **Recovery (high)** | seconds | Time in recovery/rest state |
| **HRV** | ms | Heart rate variability (higher = better recovery) |
| **Resting HR** | bpm | Resting heart rate (lower = better fitness) |

### Error Handling

| Error | Cause | Solution |
|-------|-------|----------|
| `Access token not configured` | Missing OURA_ACCESS_TOKEN | Add token to config |
| `401 Unauthorized` | Token expired | Server auto-refreshes if refresh token provided |
| `403 Forbidden` | Scope not authorized | Re-authorize with required scopes |
| `429 Rate Limit` | Too many requests | Server auto-retries with backoff |
| Empty `data: []` | No data for date range | Try different date range |

### Important Notes

- **Date Format**: Use `YYYY-MM-DD` for dates
- **Datetime Format**: Use ISO 8601 for `get_heart_rate` (e.g., `2024-01-15T00:00:00Z`)
- **Timezone**: Data is returned in user's timezone
- **Pagination**: If `next_token` is not null, more data is available
- **Null Values**: Fields may be null if ring wasn't worn or data unavailable

---

## Rate Limiting

The server implements automatic rate limiting per [Oura API documentation](https://cloud.ouraring.com/v2/docs).

| Limit | Value |
|-------|-------|
| **Requests** | 5000 per 5 minutes |
| **Algorithm** | Token bucket with refill |
| **Retry** | Exponential backoff (1s → 2s → 4s, max 30s) |
| **Max Retries** | 3 attempts |

---

## Project Structure

```
oura-mcp-server/
├── src/
│   ├── index.ts           # MCP server entry point
│   ├── client.ts          # Oura API client with auth & rate limiting
│   ├── rate-limiter.ts    # Token bucket rate limiter
│   ├── types.ts           # TypeScript type definitions
│   └── tools/
│       ├── index.ts       # Tool aggregator
│       ├── personal-info.ts
│       ├── sleep.ts
│       ├── activity.ts
│       ├── health.ts
│       ├── workout.ts
│       ├── tags.ts
│       ├── device.ts
│       └── webhook.ts
├── scripts/
│   └── get-token.js       # OAuth2 token helper
├── dist/                  # Compiled JavaScript
├── package.json
├── tsconfig.json
├── .env.example
├── .gitignore
├── LICENSE
├── CHANGELOG.md
└── README.md
```

---

## Security

### Best Practices

1. **Never commit tokens** — Use environment variables
2. **Use refresh tokens** — Enable automatic token renewal
3. **Test with sandbox** — Set `OURA_USE_SANDBOX=true` for development
4. **Limit scopes** — Only request OAuth2 scopes you need

### Security Features

- Access tokens sanitized from all error messages
- Read-only API access (cannot modify data)
- Automatic token refresh (no manual token management)
- Rate limiting prevents API abuse

---

## Troubleshooting

### Common Issues

| Issue | Solution |
|-------|----------|
| `Cannot find module` | Run `npm run build` |
| `Access token not configured` | Add `OURA_ACCESS_TOKEN` to env |
| `401 Unauthorized` | Token expired; add refresh token or get new token |
| `403 Forbidden` | Missing OAuth2 scope; re-authorize app |
| Empty data returned | Check date range; ensure ring synced data |
| `Token not authorized for scope` | Re-create OAuth2 app with required scopes |

### Getting Help

1. Check [Oura API Documentation](https://cloud.ouraring.com/v2/docs)
2. Open an issue on GitHub
3. Check [MCP Protocol Documentation](https://modelcontextprotocol.io/)

---

## Development

```bash
# Install dependencies
npm install

# Build TypeScript
npm run build

# Watch mode (auto-rebuild)
npm run dev

# Run server
npm start

# Clean build
npm run clean
```

---

## Contributing

1. Fork the repository
2. Create a feature branch: `git checkout -b feature/my-feature`
3. Make changes and test: `npm run build`
4. Commit: `git commit -m "Add my feature"`
5. Push: `git push origin feature/my-feature`
6. Create a Pull Request

---

## API Reference

- [Oura API V2 Documentation](https://cloud.ouraring.com/v2/docs)
- [Oura Authentication Guide](https://cloud.ouraring.com/docs/authentication)
- [Model Context Protocol](https://modelcontextprotocol.io/)

---

## License

MIT License — see [LICENSE](LICENSE) file.

---

## Disclaimer

This software is provided "as is" without warranty. This project is not affiliated with or endorsed by Oura Health Oy.

**Not medical advice.** Oura Ring data is for informational purposes only and should not be used for medical diagnosis or treatment.

TDQS

B3.1/5.0

Scored across 40 tools

Disambiguation4/5

Tools are mostly distinct: each data type typically has a list and a by_id variant, and descriptions clarify the difference. However, some pairs like get_sleep vs get_daily_sleep or get_tags vs get_enhanced_tags require careful reading, which could cause occasional misselection.

Naming Consistency4/5

Consistent snake_case with a get_ / get_..._by_id pattern for most data tools, and list_/get_/create_/update_/delete_ for webhooks. Minor inconsistency: list operations sometimes use singular nouns (get_sleep) and sometimes plural (get_workouts), and get_rest_mode_periods pairs with a singular by_id tool.

Tool Count2/5

40 tools is heavy for the apparent scope; many are thin list/by_id pairs that could be merged or parameterized. While each maps to an API endpoint, the count feels excessive and increases cognitive load.

Completeness5/5

Covers the full Oura v2 API surface: personal info, all health metrics with list and by_id, workouts, sessions, tags, enhanced tags, ring config, and full webhook CRUD plus renew. No obvious missing endpoints for the domain.

Maintenance

ActivityInactive
ResponsivenessNo issues