Heart Rate MCP
Connects directly to Bluetooth LE heart-rate straps and watches that broadcast bpm, enabling live heart-rate readings without an external account or intermediary app.
Posts heart-rate events such as connections, disconnections, changes, zone crossings, spikes, and summaries to Discord webhooks, with optional wake markers and mentions for bots.
Supports Garmin HRM straps and watches as Bluetooth LE heart-rate sources when they broadcast heart rate, allowing live bpm readings from Garmin devices.
Sends heart-rate events as JSON webhooks to Home Assistant, enabling automations and dashboards based on live bpm data.
Accepts heart-rate pushes from iOS Shortcuts via the HTTP API, so phone automations can feed live bpm into the bridge.
Sends heart-rate events as JSON webhooks to n8n, allowing workflows to react to live bpm changes and events.
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
Heart Rate MCP
Let your AI see your heart rate, live. Works with Claude and any other MCP client, and with Discord bots through webhooks. No VRChat or other app needs to sit in between.
Pulsoid ─┐ ┌─► MCP server ──► your AI asks: "what's my heart rate?"
HypeRate ┤ │ (Claude Desktop, Claude Code, Cursor, Discord bots…)
Bluetooth strap / watch ─┼─► bridge ┤
OSC (VRChat-style apps) ─┤ ├─► Discord webhook ──► posts in a channel on its own
phone / watch HTTP push ─┤ │ (bots in the channel just see it)
text file ───────────────┘ ├─► any JSON webhook (Home Assistant, n8n, …)
└─► local API + live page / OBS overlayTwo ways for your AI to see it, and you can use both at once:
MCP server (pull). The AI calls
heart_rate_nowwhenever it wants to know.Discord webhook (push). The bridge posts on its own when something happens: the sensor connects, a big change, crossing 100/120/140, a sudden spike, a periodic recap. A bot reading the channel sees it without calling anything.
Supported heart rate sources
Source | What you need |
| A Pulsoid account + access token. Covers everything Pulsoid supports (most watches and straps, via the Pulsoid app). |
| A HypeRate API key + your session ID. |
| A Bluetooth LE heart rate strap or a watch that broadcasts HR (Polar, Garmin HRM, Wahoo, Coospo, Magene, …) and Bluetooth on the PC. No account needed. |
| Any app that sends heart rate over OSC (most VRChat HR apps can). |
| Any app that can send an HTTP request: phone automations (iOS Shortcuts, Tasker), watch apps like HeartRateToWeb. |
| An app that keeps a text file updated with the current bpm. |
| Nothing; fake data for trying it out. |
List several and they become fallbacks: sources = ["ble", "pulsoid"] uses the strap directly and falls back to Pulsoid when the strap goes quiet.
Related MCP server: WHOOP MCP Server
Setup
Needs Python 3.10 or newer.
git clone https://github.com/digi-the-robot/heart-rate-mcp
cd heart-rate-mcp
pip install -r requirements.txt
python run.py init # creates config.toml from config.example.tomlOpen config.toml, set sources, and fill in what that source needs (see Source setup). Then check it:
python run.py checkcheck sends a test post to every webhook and then prints live readings for 20 seconds. If you see numbers, you're done.
Prefer a command?
pip install -e .gives youhrbridge run,hrbridge check, etc. Everypython run.py Xbelow is the same ashrbridge X.
Connect your AI (MCP)
The MCP server is python run.py mcp. Use the full path to run.py.
Claude Desktop: Settings → Developer → Edit Config, then add:
{
"mcpServers": {
"heart-rate": {
"command": "python",
"args": ["C:/path/to/heart-rate-mcp/run.py", "mcp"]
}
}
}Claude Code:
claude mcp add heart-rate -- python /path/to/heart-rate-mcp/run.py mcpAny other MCP client (Cursor, Windsurf, Discord bots like llmcord…): add a stdio MCP server with command python and args ["/path/to/run.py", "mcp"].
You can skip config.toml entirely for a simple setup and pass settings as environment variables in the MCP config, e.g. "env": {"PULSOID_TOKEN": "..."}. See Environment variables.
Tools the AI gets
Tool | Returns |
| Current bpm; status ( |
| Min/avg/max for the period plus an evenly spaced, averaged series. |
| Recent spikes, zone crossings, big changes, and connects/disconnects, each with readable text. |
Example heart_rate_now:
{
"status": "live",
"bpm": 94,
"measured_at": "2026-11-02T21:14:03-08:00",
"age_seconds": 0.6,
"source": "pulsoid",
"trend": "rising",
"trend_bpm_per_min": 7.5,
"last_1_min": {"avg": 90, "min": 86, "max": 94, "samples": 58},
"last_10_min": {"avg": 78, "min": 66, "max": 94, "samples": 590},
"sensor_battery_percent": 80,
"summary": "94 bpm, rising (+7.5 bpm/min), live via pulsoid. Last 10 min: avg 78, range 66-94."
}How it runs
One small background process (the daemon) holds the sensor connection and sends the webhooks. Every MCP server, one per AI client or bot, just asks the daemon. So five bots can share one chest strap, and Discord gets one post per event, not five.
You don't have to start the daemon yourself. The first MCP server to launch starts it, and it keeps running after that client closes. If you change the code or config.toml, the next MCP launch restarts it with the new settings. To run it by hand (e.g. for webhooks with no AI client open):
python run.py start # in the background (keeps running after you close the window)
python run.py run # foreground, Ctrl+C to stop
python run.py status # what's it doing?
python run.py stopDon't want a background process? Set [mcp] daemon = "embedded". The MCP server then reads the sensor itself, but sends no webhooks, and each client opens its own connection (not good for Bluetooth).
Discord webhooks
In Discord: channel settings → Integrations → Webhooks → New Webhook → Copy Webhook URL.
In
config.toml:
[[discord]]
url = "https://discord.com/api/webhooks/..."
events = ["connected", "disconnected", "change", "zone", "spike", "summary"]
cooldown_seconds = 60What gets posted is controlled in [events]:
Event | Posted when | Default |
| data starts arriving (or comes back) | |
| silent for | 60 s |
| bpm moved | 10 bpm |
| crossed one of | 100, 120, 140 |
| rose | +20 in 30 s |
| every | off |
cooldown_seconds is the minimum gap between change/zone posts. If your heart rate keeps moving during the gap, the bridge posts where it ended up once the gap is over, so the channel never sits on an old number.
Posts look like 💓 **HR:** 104 bpm, now above 100 or ⚡ **HR spike:** 72 → 98 bpm in 12s. You can reword any of them with [discord.templates] (see config.example.toml).
Add more [[discord]] blocks for more channels, each with its own events.
"My bot doesn't react to the posts"
Most Discord bot frameworks ignore messages from other bots and webhooks by default. Either:
let the posts sit in the channel as context the bot reads when it next replies (that's often all you want), or
have the bot allow webhook posts that carry a marker, and tell the bridge which events should wake it:
[[discord]]
url = "..."
wake_events = ["spike"] # only spikes wake the bot
wake_prefix = "[HEART]" # the marker your bot checks for
mention = "<@123456789012345678>" # optional: ping the botMentions are only ever sent on wake events, and @everyone/@here never ping.
Source setup
Pulsoid
You need an access token with heart rate read access:
Manual token: go to https://pulsoid.net/ui/keys → Create new token. Pulsoid's docs list this as a BRO feature, but free accounts have been able to create one too, so try it first.
Browser login:
python run.py pulsoid-loginuses Pulsoid's device login and saves the token intoconfig.toml. It needs an OAuth client ID from Pulsoid, set aspulsoid.client_idin the config orPULSOID_CLIENT_IDinhrbridge/cli.py.
sources = ["pulsoid"]
[pulsoid]
token = "..."Keep the Pulsoid app measuring (phone, watch, or PC app). The bridge reads Pulsoid's real-time feed directly, so VRChat doesn't need to be open.
HypeRate
Request an API key at https://www.hyperate.io/api. The session ID is the one your HypeRate app shows.
sources = ["hyperate"]
[hyperate]
api_key = "..."
session_id = "..."Bluetooth (strap or watch)
python run.py scan # wear the strap so it wakes upsources = ["ble"]
[ble]
device = "Polar H10" # part of the name, or the address; empty = first one foundMost straps allow only one or two Bluetooth connections at a time. If your phone app is connected, the PC may not get in. Watches usually need their "broadcast heart rate" mode turned on.
OSC
Point your HR app's OSC output at the bridge's port (default 9100), not VRChat's 9000/9001. The bridge accepts integer bpm on /avatar/parameters/HR, HeartRate, HeartRateInt, and a few others (osc.addresses to change). It ignores 0-1 "percent" floats.
Many apps only send a value when it changes. If your heart rate sits perfectly still for longer than stale_after_seconds, it'll briefly show as stale.
HTTP push (phones, watches, automations)
sources = ["http"]
[api]
host = "0.0.0.0" # so your phone can reach this PC
push_token = "make-up-a-secret"Then send any of these to http://<your-PC-ip>:47820/api/push:
GET /api/push?bpm=72&token=make-up-a-secret
POST /api/push {"bpm": 72, "token": "make-up-a-secret"}
POST /api/push rate=72 (form; also with Authorization: Bearer <token>)The bpm can be called bpm, hr, heartrate, heart_rate, rate or value. Windows will ask whether to allow Python through the firewall; allow it on private networks.
Text file
sources = ["file"]
[file]
path = "C:/path/to/heartrate.txt"The bridge reads the first number in the file each time the file changes.
Local API and live page
While the daemon runs, it serves on http://127.0.0.1:47820:
| a live heart rate page; works as an OBS browser source |
| same JSON as the |
| history |
| recent events |
| daemon, sources, and webhook stats |
| input for the |
Environment variables
Override the config file (handy inside an MCP client's config):
Variable | Same as |
| path to the config file |
|
|
|
|
|
|
|
|
| adds a Discord webhook with default settings |
|
|
|
|
Config values can also reference variables: token = "${PULSOID_TOKEN}".
The config file is looked up in this order: --config, HRBRIDGE_CONFIG, config.toml next to run.py, then %APPDATA%\hrbridge\config.toml (Windows) or ~/.config/hrbridge/config.toml.
Troubleshooting
python run.py statusshows each source's state, e.g.error: Pulsoid rejected the token (HTTP 401).Log file:
hrbridge.lognext to your config.The AI says "no_data": the
sourcesline inheart_rate_nowsays why (waiting for the app, bad token, device not found…).Port 47820 is taken: set
[api] portto something else. Every client uses the same config, so they'll all follow.
Privacy
Heart rate is health data. Webhook posts are visible to everyone in that Discord channel, so pick the channel accordingly. The local API listens only on this PC unless you change api.host. Nothing is stored on disk except the log.
License
MIT
This server cannot be deployed
Maintenance
Related MCP Connectors
Your Garmin, Oura, WHOOP, Hevy and 35 more fitness apps, queryable from your own AI.
Read-only Apple Health data synced from your iPhone: sleep, heart rate, HRV, workouts and trends.
Connect your health, fitness, nutrition, sleep, and wearable data to your AI assistant.
Connect your wearables, rings and training apps, then ask your AI about your own health data.
Related MCP Servers
- AlicenseAqualityCmaintenanceReal-time human state awareness for AI agents. Fuses cardiovascular, vocal, visual, and textual signals into a unified stress score (0-100). Streamable HTTP transport with 4 tools: ingest_signal, get_human_state, get_history, delete_subject.6MIT
- AlicenseAqualityDmaintenanceEnables querying WHOOP biometric data (recovery, strain, sleep, workouts, heart rate) from any MCP-compatible AI client, supporting remote and direct modes.1622 npmMIT
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to query personal health data collected by any wearable or phone fitness app through Android Health Connect or Apple Health, exposing read-only tools for metrics like heart rate, sleep stages, VO2 max, and body composition. It can run locally over USB with nothing stored, or be self-hosted on Cloudflare Workers with D1 to retain history beyond Health Connect's 30-day limit.14 npmMIT
- FlicenseNot gradedqualityAmaintenanceEnables AI assistants to read a user's WHOOP recovery, sleep, HRV, strain, and workout history through a local, read-only server with tools for today's status, personal baselines, sleep debt, trends, and period comparisons. It supports Claude Desktop, Claude Code, Codex, and GitHub Copilot, letting users ask natural-language questions about their health data without exporting it themselves.16 npm-