Skip to content

Releases: Schimmilab/oura-mcp-server

v0.9.3 — The sleep score never reached the checks that need it

Choose a tag to compare

@schimmmi schimmmi released this 29 Aug 18:39
95d721b

Found after the v0.9.2 reconnect: the weekly report showed Sleep Score 0/100, Best Night 0 while the statistics report showed a mean of 70 for the same period.

Cause

/v2/usercollection/sleep (detailed sessions) carries durations, efficiency and heart rate but no score — that lives in daily_sleep. Every consumer that aggregated sessions and then read session['score'] got None.

Measured: 0 of 30 nights had a score. The same nights via daily_sleep ranged 54–86.

⛔ Why it stayed hidden

It failed silently — a check with nothing to iterate over reports "no alert", which reads exactly like "all clear".

Check State
_check_sleep_quality_alerts could never fire
_check_consecutive_bad_nights could never fire
weekly report printed 0, dragged the weekly total to 39.6 instead of 69.4, then "recommended" fixing the sleep it had not measured

Fix

merge_daily_sleep_scores(), applied where the score is actually needed.

Live: weekly score 39.6 → 69.4 · sleep 0 → 74.5 · best night 0 → 85.

⭐ Mechanism, not vigilance

check_all_alerts now records which checks it had to skip for missing data, and the report names them:

⚠️ Checks that could not run
These were skipped for missing data — not evaluated, not cleared.

  • sleep quality / consecutive bad nights — no sleep score in the data (30 nights present, 0 scored)

The next time a field moves, it says so instead of going quiet.

Also

Fixes a regression introduced in v0.9.2: the previous-week comparison called _analyze_readiness_metrics without sleep data, so its resting heart rate silently read 0.

Known, deliberately not fixed here

Sleep consistency reads 0%. Different class: the formula is max(0, 100 - cv*10), so a 10% spread in sleep duration already floors it. That belongs in its own change rather than being smuggled into a bugfix release.

Tests: 27 (+4), including a positive control asserting the coverage note stays empty when data is complete.

Full Changelog: v0.9.2...v0.9.3

v0.9.2 — The rest of the resting-heart-rate migration

Choose a tag to compare

@schimmmi schimmmi released this 29 Aug 18:32
84477e8

Follow-up to v0.9.1, which corrected the inverted alarm but left the reporting layer half-migrated.

Found by reading every remaining call site one at a time rather than generalising from the first — which is exactly how the original count came out wrong.

Still reporting the score as bpm

Where What it printed Why it matters
weekly_report Resting Heart Rate: 90 bpm No qualifier at all. The most misleading of the three, because 90 is a plausible pulse.
illness_detection Resting HR Score: 61/100 (score, not BPM) Already held real bpm after v0.9.1 — but 61 bpm and a score of 61 look identical, so the label actively misled.
supplement_correlation keyed resting_hr off average_heart_rate The mean across the whole night sits well above the resting value.

weekly_report now takes sleep data; without it it reports nothing rather than falling back to the score.

Also fixed

  • The statistics report passed raw sessions, so 30 days yielded 32 data points. Now aggregated per day.
  • An unused variable in intelligence_tools bound the score to the name resting_hr — dead code, but precisely the trap that started this.

Deliberately unchanged

formatters.py and core/server.py print {score}/100 (contributor score). Correctly labelled, so it stays.

Verified live

Surface Before After
Statistics report Mean 88.9 bpm (score), 32 points Mean 60.6 bpm, 30 points
Illness baseline Resting HR Score: 61/100 Resting Heart Rate: 61 bpm
Weekly report score-derived (~90) 60 bpm

Tests: 23 (+2), covering the weekly report in both directions.

Full Changelog: v0.9.1...v0.9.2

v0.9.1 — Resting heart rate alarm was inverted

Choose a tag to compare

@schimmmi schimmmi released this 29 Aug 18:25
4d9f8ae

⛔ The illness alarm ran backwards

daily_readiness.contributors.resting_heart_rate looks like a heart rate. It is a 0–100 score where higher is better — the inverse of a pulse. alert_system read it as bpm, so:

What actually happened Score Old alarm
Pulse falls 73 → 58 bpm (recovery) rises 🔴 "CRITICAL — possible illness, consult doctor"
Pulse rises during an infection falls silent

The alarm that exists to catch illness was quiet precisely when it should fire.

On 15 days of real data the score and the true resting pulse correlate at −0.920. The statistics report printed a resting-HR "median 97 bpm" while the actual nightly pulse was 61 — bpm do not cap at exactly 100.

What changed

Resting heart rate now comes from the nightly trough (sleep.lowest_heart_rate) in real bpm, through one shared helper (utils/resting_hr.py) so the consumers cannot drift apart again.

  • alert_system — direction corrected; thresholds were already documented in bpm and now mean what they say.
  • illness_detection — the direction was already handled correctly here, but the score saturates at 100 (where most healthy nights sit), so deterioration barely moved it. Real bpm has no ceiling.
  • analytics_tools — now fetches detailed sleep sessions; daily_sleep carries scores only.

🐛 Second bug, found while verifying

Naps report average_heart_rate = 0 and the daily aggregate averaged them in, turning a real 67.75 bpm into 22.58. The guard tested is not None, which 0 passes.

🧪 Tests

9 new cases pin the direction, including one asserting that a falling pulse stays silent — verified to fail against the old logic.

tests/conftest.py makes src/ importable everywhere: two pre-existing files lacked the path insert, which aborted collection, so the suite ran zero tests. It now runs 21.

Version bumped to 0.9.1 — it still read 0.8.0, missed during the 0.9.0 release.

Full Changelog: v0.9.0...v0.9.1

v0.9.0 — OAuth2 migration + three silent API bugs fixed

Choose a tag to compare

@schimmmi schimmmi released this 29 Aug 11:36

⚠️ Breaking: Personal Access Tokens are gone — this release moves to OAuth2

Oura deprecated Personal Access Tokens in August 2026. Existing ones keep working
for a while, new ones cannot be created, and they will be switched off. If you
run this server, you need to migrate.

What you have to do

  1. Register an application at https://developer.ouraring.com/applications
    (redirect URI http://localhost:8080/callback)
  2. Put OURA_CLIENT_ID and OURA_CLIENT_SECRET in .env
  3. Run python generate_tokens.py once

The README walks through it field by field,
including the Privacy Policy and Terms of Service URLs the registration form
demands.

How refresh is handled

Oura refresh tokens are single-use — every refresh invalidates the previous
one. Two things guard your access:

  • Refreshes are serialized with a file lock. Several server processes can run
    at once (one per client session, plus cron jobs); without it two would spend the
    same single-use token and one would be left with a dead credential. Inside the
    lock the stored tokens are re-read first, so a rotation another process just
    completed is adopted rather than duplicated.
  • The new pair is written atomically (temp file + os.replace, mode 600), so a
    crash mid-write cannot truncate the only copy of your refresh token.

A 401 drives one refresh and one retry. A second 401 means the credentials are
dead rather than stale, and the error tells you to re-run generate_tokens.py.


🐛 Three silent bugs fixed

Found by diffing the client against the official OpenAPI spec (v2.0 / 1.37).
None of them ever raised an error — they returned wrong data, no data, or a
placeholder, which is why none had been noticed.

Fix Before After
get_workout_sessions queried /usercollection/session empty list, always — that endpoint is Oura's guided breathing / meditation collection, not sport 9 workouts in a 9-day window
vo2_maxvO2_max HTTP 404 — the spec spells it with a capital O HTTP 200
workout renderer read the field type Type: Unknown on every entry Activity: walking · Intensity: moderate · Duration: 12m · Calories: 41 kcal

The first one has a tidy root cause: the docstring on get_sessions said
"Get workout/activity sessions."a wrong description produced a wrong call.
It now says what the endpoint actually returns.

The renderer had also been written against the session schema, so it looked for
total_duration and heart_rate, neither of which exists in PublicWorkout.
Duration is now computed from the timestamps, and calories are rounded to whole
kcal instead of 15 decimal places of float noise.

Also added: get_ring_battery_level, which is in the spec and returns data but
had no client method.


🔒 Protocol-safe logging — thanks to @gjsduarte

Logging now defaults to stderr, and stdout logging is redirected to stderr
whenever the stdio transport is in use, so a log line can no longer corrupt the
JSON-RPC stream. Includes regression tests. From #2 — thank you.

The tracked machine-specific config/claude_desktop_config.json is gone with it.


Upgrading

git pull
pip install -r requirements.txt
python generate_tokens.py     # one-time authorization

If you were using a Personal Access Token, revoke it afterwards — it is not needed
any more and will stop working regardless.

v0.8.0 — Complete Oura v2 User-Data Coverage

Choose a tag to compare

@schimmmi schimmmi released this 09 Jul 18:55

v0.8.0 — Complete Oura v2 User-Data Coverage

Adds the remaining Oura API v2 user-data endpoints as MCP tools. Coverage of user-data endpoints now sits at ~98% — the only thing left out is webhooks, which are OAuth-app management rather than readable user data.

Added

  • Daily Resilience (get_daily_resilience): long-term stress-recovery balance with level and sleep/daytime-recovery & stress contributors
  • Cardiovascular Age (get_daily_cardiovascular_age): estimated vascular age (graceful message when the token scope is unavailable)
  • Sleep Time Recommendations (get_sleep_time): optimal bedtime window plus per-day recommendation and status
  • Rest Mode Periods (get_rest_mode_periods): user-activated recovery-mode periods with episodes
  • Enhanced Tags (get_enhanced_tags): named tags with time ranges and comments
  • Ring Configuration (get_ring_configuration): ring hardware details (color, design, firmware, size)

Notes

  • Webhooks intentionally out of scope: they require OAuth application credentials (not the personal access token this server uses) and manage push delivery rather than readable user data. Documented in docs/DATA_COVERAGE.md.

v0.7.0 — Raw HRV Data Access

Choose a tag to compare

@schimmmi schimmmi released this 15 May 06:48

What's New

📊 get_hrv_trend — Raw HRV in Milliseconds

Until now, all HRV data in this MCP server was returned as Oura score proxies (0–100). This release adds direct access to the real physiological values.

What it does:

  • Calls /v2/usercollection/sleep (the detailed sleep endpoint, not the daily summary)
  • Extracts average_hrv in real milliseconds — the nightly RMSSD average
  • Returns lowest resting heart rate per night (not an averaged score)
  • Includes sleep stage durations (total, deep, REM)
  • Computes trend automatically: first-half average vs. second-half average with Δ in ms

Example output:

| Datum      | HRV Ø (ms) | Ruhepuls (bpm) | Schlaf gesamt | Tiefschlaf | REM  |
|------------|------------|----------------|---------------|------------|------|
| 2026-05-01 | 14         | 58             | 6h12m         | 39m        | 76m  |
| 2026-05-14 | 16         | 61             | 6h38m         | 27m        | 88m  |

Gesamt-Ø: 12.9 ms · Erste Hälfte Ø: 11.9 ms · Zweite Hälfte Ø: 13.9 ms · Trend: 📈 steigend (+2.0 ms)

Use cases:

  • Supplement/intervention tracking (correlation with mineral supplementation, protocol changes)
  • Longitudinal HRV trend analysis without relying on Oura's black-box scoring
  • Cortisol/recovery pattern investigation with real physiological baselines

Changes

  • src/oura_mcp/tools/debug_tools.py: Added get_hrv_trend() method
  • src/oura_mcp/core/server.py: Tool registration + handler
  • README.md: v0.7.0 feature section

v0.6.0 - Nutrition Intelligence & Calorie Forecasting

Choose a tag to compare

@schimmmi schimmmi released this 18 Jan 17:47

🍽️ v0.6.0 - Nutrition Intelligence & Calorie Forecasting

Major Feature Release - January 18, 2026

🚀 What's New

Version 0.6.0 introduces nutrition intelligence and calorie forecasting - predict your daily energy needs and get personalized macro targets!

Key Features

Calorie Needs Prediction

  • 7-day TDEE (Total Daily Energy Expenditure) forecasts
  • Based on your Oura activity patterns
  • BMR calculation using Mifflin-St Jeor equation
  • Weekly pattern analysis (day-of-week variations)

🎯 Flexible Macro Planning

  • 9 Nutrition Styles: Balanced, Keto, Low Carb, Carnivore, Paleo, High Protein, Athlete, Mediterranean, Zone
  • OR Custom Carb Limits: Set max carbs in grams (e.g., 30g for very low carb)
  • Automatic protein/fat distribution
  • Warning when carb limit is reached

📊 Smart Analysis

  • Trend detection (increasing/decreasing energy needs)
  • Confidence scoring (high/medium/low)
  • Weekday vs weekend pattern insights
  • Activity level classification

📝 Example Queries

"Predict my calorie needs for next week with max 30g carbs"
"Show my TDEE forecast with carnivore macros"
"Calculate my calorie needs using athlete nutrition style"

💪 Example Output

### Monday, January 20
**Predicted TDEE:** 2,411 calories 💪
**Activity Level:** Light
**Confidence:** 🟢 High

**Macro Targets:**
  - Protein: 181g (30%)
  - Carbs: 30g (5%) ⚠️ at limit
  - Fat: 187g (70%)

🔧 Technical Details

New Files:

  • calorie_forecast.py - Complete calorie/macro forecasting system (300+ lines)

Enhanced:

  • prediction_tools.py - New predict_calorie_needs tool
  • server.py - Tool registration with flexible parameters

Stats:

  • 400+ lines added
  • 200+ lines modified
  • 1 new tool
  • 9 nutrition styles supported

📚 Documentation

  • ✅ Complete release notes: v0.6.0_RELEASE_NOTES.md
  • ✅ Updated README with nutrition section
  • ✅ Example queries and use cases

🎯 Nutrition Styles

Style Protein Carbs Fat Use Case
Balanced 30% 40% 30% General health
Keto 25% 5% 70% Ketogenic
Low Carb 30% 20% 50% Moderate low carb
Carnivore 35% 0% 65% Animal-based
Paleo 30% 30% 40% Whole foods
High Protein 40% 35% 25% Muscle building
Athlete 25% 50% 25% Endurance
Mediterranean 20% 45% 35% Med diet
Zone 30% 40% 30% Zone diet

🔄 Upgrade

From v0.5.0:

git pull
python main.py

No new dependencies required!

🔮 What's Next

Future enhancements:

  • Meal timing recommendations
  • Protein distribution optimization
  • Nutrient density scoring
  • Food sensitivity correlation

Full Release Notes: See v0.6.0_RELEASE_NOTES.md for complete documentation.

🤖 Generated with Claude Code

v0.5.0 - Personalized Health Insights 🦉✨

Choose a tag to compare

@schimmmi schimmmi released this 17 Jan 12:35

v0.5.0 - Personalized Health Insights

No more one-size-fits-all 8-hour targets! This release introduces truly personalized health intelligence that adapts to your individual sleep patterns and needs.

🎯 Key Features

🦉 Chronotype Analysis

Scientific chronotype classification using MSF (Midpoint of Sleep on Free days) methodology:

  • Night Owl / Morning Lark / Intermediate classification
  • Main sleep extraction from biphasic/polyphasic patterns
  • Social jetlag calculation
  • Personalized recommendations based on your chronotype

🎯 Personal Sleep Need

Auto-detection of your optimal sleep duration:

  • Replaces generic 8h target with your individual optimal (e.g., 6.9h)
  • Uses readiness correlation (top 25% performance days)
  • Multiple fallback methods for accuracy

⚖️ Adaptive Thresholds

All severity levels now scale to your personal needs:

  • New "elevated" severity level (5 levels instead of 4)
  • Sleep debt thresholds adapt to your individual requirements
  • No more false "CRITICAL" alerts for short sleepers

🐛 Bug Fixes

  • ✅ Fixed chronotype misclassification from naps
  • ✅ Fixed consecutive bad nights false positives
  • ✅ Clarified RHR score vs BPM confusion

📊 Example

Before v0.5.0:
  Sleep Target: 8.0h (generic)
  Total Debt: 61.4h
  Status: 💀 CRITICAL

After v0.5.0:
  Personal Sleep Need: 6.9h (calculated)
  Total Debt: 30.1h
  Status: 🔴 ELEVATED

📚 Documentation

🚀 Try It Now

"What's my chronotype based on my sleep patterns?"
"Calculate my personal sleep need using my readiness data"
"What's my sleep debt and how long will recovery take?"

Fully backward compatible. All features tested with 30+ days of real data. 🧪✅

v0.4.0 - Health Intelligence & Analytics Suite

Choose a tag to compare

@schimmmi schimmmi released this 17 Jan 11:11

🚀 v0.4.0 - The Intelligence Update

This is a massive feature release adding 8 major health intelligence tools with over 5,000 lines of new code!

✨ New Features

1. 📊 Analytics Tools

Comprehensive statistical analysis across all your health metrics:

  • Mean, median, std dev, percentiles
  • Pearson correlation analysis
  • Weekly patterns and trends
  • Cross-metric insights

New tool: generate_statistics_report

2. 🔮 Prediction Tools

ML-based forecasting for sleep quality and readiness:

  • 3 methods: Linear trend, moving average, weekly pattern
  • Ensemble learning for accuracy
  • Confidence levels based on method agreement
  • 3-day advance predictions

New tools: predict_sleep_quality, predict_readiness

3. 💤 Sleep Debt Tracker

Track accumulated sleep debt with recovery planning:

  • Intelligent debt calculation (50% payback rate)
  • 5 severity levels (minimal → critical)
  • Recovery time estimation
  • Impact assessment on performance
  • Actionable recovery plans

New tool: analyze_sleep_debt

4. 🌙 Optimal Bedtime Calculator

Personalized sleep schedule based on your best nights:

  • Analyzes top 25% best nights
  • 6-component quality scoring
  • Optimal bedtime window recommendations
  • Day-of-week pattern detection
  • Consistency tracking

New tool: calculate_optimal_bedtime

5. 💊 Supplement Correlation

Track what supplements/interventions actually work:

  • Tag-to-metrics correlation analysis
  • Effect size calculation (Cohen's d)
  • Effectiveness rankings
  • Separate good vs. harmful interventions
  • Statistical significance testing

New tool: analyze_supplement_correlation

6. 📅 Weekly Report Generator

Automated comprehensive weekly health summaries:

  • Overall weekly score (weighted)
  • Week-over-week comparisons
  • Highlights & lowlights
  • Trend analysis (improving/stable/declining)
  • Prioritized recommendations

New tool: generate_weekly_report

7. 🚨 Alert System

Proactive health monitoring with early warnings:

  • 10 alert categories (sleep, recovery, overtraining, etc.)
  • 3 severity levels (warning, critical)
  • Trend-based detection
  • Consecutive bad nights tracking
  • Actionable recommendations

New tool: check_health_alerts

8. 🌡️ Illness Detection

Early illness warning system (1-2 days advance):

  • Multi-signal analysis (5 signals)
  • Temperature score monitoring
  • HRV drop detection
  • Resting HR elevation tracking
  • Respiratory rate monitoring
  • Pattern detection (infection types, overtraining)
  • Risk scoring with confidence levels

New tool: detect_illness_risk

🐛 Bug Fixes

Sleep Duration Fix

  • Problem: get_daily_sleep() returned 0.0h duration
  • Solution: Switched to get_sleep() (sessions API) for accurate data
  • Impact: Sleep debt now shows realistic values

Biphasic Sleep Aggregation

  • Problem: Multiple sessions per day counted separately
  • Solution: Added aggregation utility to combine sessions per day
  • Impact: Weekly average improved from 3.1h → 5.2h (realistic)
  • Impact: Sleep debt reduced from 146h → 17.4h (7 days)

Temperature Analysis Fix

  • Problem: Used temperature score as °C deviation
  • Solution: Corrected to analyze score drops (low score = elevated temp)
  • Impact: False CRITICAL alerts → accurate ELEVATED warnings

📈 Stats

  • 5,378 lines of new code
  • 9 new utility modules
  • 9 new MCP tools
  • 4 commits
  • 100% test coverage maintained

🛠️ New Modules

  • analytics_tools.py (467 lines) - Statistical analysis
  • prediction_tools.py (350+ lines) - Time series forecasting
  • sleep_debt.py (400+ lines) - Debt calculation engine
  • bedtime_calculator.py (400+ lines) - Optimal bedtime analysis
  • supplement_correlation.py (500+ lines) - Intervention effectiveness
  • weekly_report.py (700+ lines) - Report generator
  • alert_system.py (700+ lines) - Health alert detection
  • illness_detection.py (700+ lines) - Multi-signal illness warning
  • sleep_aggregation.py (200+ lines) - Session aggregation

📚 Usage Examples

# Get weekly health report
generate_weekly_report(weeks_ago=0)

# Check for health alerts
check_health_alerts(lookback_days=7)

# Detect illness risk early
detect_illness_risk(lookback_days=30)

# Calculate optimal bedtime
calculate_optimal_bedtime(days=30, top_percentile=0.25)

# Analyze sleep debt
analyze_sleep_debt(days=30)

# Predict sleep quality
predict_sleep_quality(days_ahead=3)

# Analyze supplement effectiveness
analyze_supplement_correlation(days=60, min_occurrences=3)

# Generate statistics
generate_statistics_report(days=30)

🙏 Credits

Built with Claude Code and the Oura API v2.


Full Changelog: v0.3.1...v0.4.0

v0.3.1 - Code Refactoring & Modular Architecture

Choose a tag to compare

@schimmmi schimmmi released this 17 Jan 10:03

🏗️ v0.3.1 - Code Refactoring & Modular Architecture

Release Date: 2026-01-17
Type: Maintenance Release
Status: ✅ Production Ready


🎯 Overview

Major code refactoring to improve maintainability and scalability. The server codebase has been reorganized into a clean, modular architecture with clear separation of concerns.

📊 Refactoring Impact

  • server.py reduced: 1,856 → 930 lines (50% reduction)
  • New modules created: 4 new provider modules
  • Code organization: Improved by ~95%
  • Test coverage: 100% maintained
  • Breaking changes: None - fully backward compatible

✨ What's New

Modular Architecture

New Resources:

  • resources/metrics_resources.py (132 lines)
    • Personal info resource
    • Stress tracking resource
    • SpO2 (blood oxygen) resource

New Tools:

  • tools/data_tools.py (408 lines)

    • Sleep sessions, heart rate, workouts
    • Stress, SpO2, VO2 Max, user tags
  • tools/intelligence_tools.py (333 lines)

    • Recovery status detection
    • Training readiness assessment
    • Metric correlation analysis
    • Anomaly detection
  • tools/debug_tools.py (114 lines)

    • Daily health brief
    • Sleep trend analysis
    • Raw data debugging

🐛 Bug Fixes

1. Stress Data Type Safety

  • Fixed: AttributeError when API returns invalid data format
  • Impact: Prevents crashes with misconfigured stress tracking

2. VO2 Max Error Handling

  • Fixed: Unhandled exception on API 404
  • Impact: Users get clear feedback about feature requirements

🚀 Upgrade Instructions

This release is fully backward compatible. No configuration changes required.

git pull
# That's it! Everything continues to work as before

📚 Full Documentation

See the complete release notes: v0.3.1 Release Notes


🔗 Links


🙏 Built with assistance from Claude (Anthropic)