npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

resmed-data-mcp

v1.0.2

Published

Mac Mini indexer + MCP server: archives raw ResMed AirSense CPAP data from a FlashAir-enabled SD card into SQLite, queryable over the LAN by any MCP client

Readme

ResMed-Data-MCP

Archives raw data from a ResMed AirSense CPAP's SD card — read over Wi-Fi via a Toshiba FlashAir card — into a local SQLite database on a Mac Mini, and serves it read-only to any MCP client (Claude, Codex, Grok, etc.) over the LAN via Streamable HTTP.

This is a personal quantified-self data plane, not a clinical tool: it archives every field the device reports (AHI, leak rate, pressure, usage hours, and everything else in the nightly summary, plus raw signal detail as it becomes available) without computing or interpreting anything. Synthesis — trends, "how did I sleep", correlations — is left to whatever AI client queries it.

How it works

The AirSense writes to its SD card in the EDF format: STR.edf is a rolling ~365-day nightly summary (one data record per night, one signal per column — AHI, leak, pressure, usage minutes, etc.), and DATALOG/<YYYYMMDD>/ holds per-night detail files at higher resolution once the device has flushed them. A generic EDF/EDF+ parser (src/edf/parse.ts) reads both without any ResMed-specific field list — it just reads whatever signals the header declares, so it also works on future firmware and other ResMed models without changes.

Architecture

| Process | Role | | --- | --- | | Indexer (resmed-data-mcp indexer) | Always-on host. Polls the FlashAir on an interval (default 5m), downloads STR.edf and any new/changed DATALOG files, parses them, and writes to SQLite. | | MCP server (resmed-data-mcp serve, default) | Always-on read-only MCP server over Streamable HTTP. Opens the same SQLite file read-only (WAL mode lets it read while the indexer writes). |

Both run as separate LaunchAgents (see examples/) so a slow FlashAir sync never blocks queries and a client bug never touches the data. The MCP server listens on 0.0.0.0:8420 by default, so it's reachable identically whether a client is on the same Mac or elsewhere on the LAN — every client just points at http://<host>:8420/mcp.

Data model

  • nightly_summary — one row per calendar night, one column per signal STR.edf reports (columns are discovered from the device automatically, not hardcoded — so AHI, Leak_95, Duration, S_C_Press, etc. all show up as real, queryable REAL columns). This is the ergonomic table for "how did I sleep" questions.
  • edf_signals / edf_signal_records — the fully generic archive: every signal, from every file ever seen (STR.edf and every DATALOG detail file), with raw digital samples preserved as-is plus enough calibration metadata to decode them. This is what "as much raw data as possible" means in practice — nightly_summary is a convenience view over a subset of it.
  • source_files, sync_runs, devices — provenance and operational history: what file came from where, when, and whether syncing is healthy.

EDF+ annotation signals (event timestamps, if the device ever writes them) are stored as raw undecoded bytes for now — see Known limitations.

No vector database: this data is fully structured and numeric (nightly metrics, calibrated signal samples), which SQL aggregation handles better than similarity search. If a future signal turns out to carry free text worth searching semantically, that's a reason to reconsider, not a default.

MCP tools

| Tool | Purpose | | --- | --- | | list_nights | Nightly summary rows in a date range, newest first. | | get_night | Full summary row for one date. | | get_trend | Time series + min/max/avg for one nightly_summary column. | | list_signals | Discover every raw signal label across all ingested files. | | get_signal_samples | Decoded raw samples for one signal in one file. | | query_raw | Arbitrary read-only SELECT against the full schema. | | sync_status | Last sync run, device info, row counts — how fresh is this. |

The connection backing the MCP server is opened SQLite-read-only, so query_raw can't write regardless of what SQL text it's given — that's enforced by the database, not just by string validation.

Install

npm install -g resmed-data-mcp

Requires Node.js 22.5+ (uses the built-in node:sqlite module — no native dependencies, no npm rebuild across machines/architectures).

Develop from source

git clone https://github.com/sfls1397/ResMed-Data-MCP.git
cd ResMed-Data-MCP
npm install   # runs `tsc` via the prepare script

Configure

Copy examples/config.json.example to ~/.resmed-data-mcp/config.json and set flashAirBaseUrl to your FlashAir's address (find it with your router's DHCP client list, or the FlashAir's own config page). Everything else has a sane default:

| Key | Default | Notes | | --- | --- | --- | | flashAirBaseUrl | http://192.168.68.50 | The FlashAir's HTTP address | | pollInterval | 5m | Clamped to 30s–1h | | serverHost | 0.0.0.0 | Bind address for the MCP server | | serverPort | 8420 | |

Env vars override the file: RESMED_DATA_FLASHAIR_URL, RESMED_DATA_POLL_INTERVAL, RESMED_DATA_SERVER_HOST, RESMED_DATA_SERVER_PORT, RESMED_DATA_DB_PATH. RESMED_DATA_HOME relocates the whole ~/.resmed-data-mcp/ directory (test/dev only).

Backfill once, then install the LaunchAgents

resmed-data-mcp backfill     # one-shot: pulls everything currently on the card

Copy examples/com.resmed-data-mcp.indexer.plist and examples/com.resmed-data-mcp.server.plist into ~/Library/LaunchAgents/, replacing REPLACE_ME_WITH_NPM_BIN_DIR and REPLACE_ME (your username) with real values, then:

launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.resmed-data-mcp.indexer.plist
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.resmed-data-mcp.server.plist

Point clients at it

See examples/mcp-client.json. Any MCP client that supports an HTTP server entry (Claude, Codex, Grok Bot, ...) connects the same way whether it runs on the Mac Mini itself or another machine on the same LAN:

{ "mcpServers": { "resmed-data": { "url": "http://<host>:8420/mcp" } } }

Paths

All under ~/.resmed-data-mcp/ (config cannot override this directory):

| Path | Purpose | | --- | --- | | ~/.resmed-data-mcp/config.json | Settings (see above) | | ~/.resmed-data-mcp/data.sqlite | The database (WAL mode) | | ~/.resmed-data-mcp/indexer.lock | Sync-cycle lock, so an overlapping poll never runs concurrently with itself |

Security

No authentication — this is meant for a trusted home LAN only. Don't expose port 8420 to the internet (no port forwarding, no tunnel). The MCP server's database connection is read-only at the SQLite level, so even a buggy or malicious client can't write through it.

Known limitations

  • EDF+ annotation signals (the "EDF Annotations" channel, used for event timestamps in some DATALOG detail files) are archived as raw bytes but not decoded into structured events yet — there was no real annotation-bearing file available to validate a decoder against when this was built. Decoding can be added once DATALOG detail files with real annotations exist on a card.

License

MIT