oura-mcp-server
# Oura Ring MCP Server
[](https://opensource.org/licenses/MIT)
[](https://nodejs.org/)
[](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
Scored across 40 tools
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.
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.
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.
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.