apeiron-mcp
apeiron-mcp is a read-only MCP server that exposes a client's Apeiron health data through 11 consolidated tools, each returning a standard envelope (client_id, domain, period, data, unit_system, last_synced_at, source, notes).
Sleep (
get_sleep_data) — sleep stages, duration, efficiency, sleep HRV, sleep score; defaults to the last 7 days, uses the single-day endpoint when start == end.Exercise (
get_exercise_data) — workout logs (type, duration, calories, HR zones, RPE) with an optionalactivity_typefilter applied locally.Nutrition (
get_nutrition_data) — nutrition score plus per-question detail scores over a date range.Cardio (
get_cardio_metrics) — resting HR, HRV, VO2max, blood pressure, aerobic capacity; optionalmetricfilter (matched locally).Fitness assessments (
get_fitness_assessment) — periodic body composition, bone density, balance, movement, muscle strength; optionalassessment_type.Cognitive (
get_cognitive_data) — memory, reaction time and processing-speed scores with trends.Healthspan rollup (
get_healthspan_domain_summary) — cross-domain healthy-signal scores, optionally as of a date.Lifestyle rollup (
get_lifestyle_summary) — sleep/activity/nutrition adherence for a week, month or quarter.Trends (
get_trends) — the client's most significant trend changes;domain/metric/date bounds/granularityare accepted but not sent to the API.Notes (
get_notes) — clinician/client/system free-text notes with an optionalauthorfilter (local).Chat history (
get_chat_history) — coaching messages newest-first with alimit;conversation_idis ignored (single thread per client).Behaviour — requires an Apeiron bearer token or email+password, otherwise returns labelled stub data; runs over stdio, streamable HTTP or SSE, and can be deployed via Docker.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@apeiron-mcp how's my sleep trend been this month?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
apeiron-mcp
An MCP server exposing client health data from the Apeiron backend
(https://api.apeiron.life). Tool surface is intentionally consolidated by
data shape (time-series vitals, periodic assessments, unstructured text,
derived aggregates) rather than by raw domain, to keep LLM tool-selection
unambiguous.
Tools
# | Tool | Purpose | Apeiron API endpoint |
1 |
| Sleep analytics for a date window. |
|
2 |
| Workout analytics for a date window. |
|
3 |
| Nutrition score + per-question scores. |
|
4 |
| Cardio + aerobic (resting HR, HRV, VO2max, BP, aerobic capacity). |
|
5 |
| Body comp, bone density, balance, movement, muscle strength. |
|
6 |
| Cognitive assessment datasets and trends. |
|
7 |
| Cross-domain rolled-up healthy-signal scores. |
|
8 |
| Lifestyle/adherence rollup for a period. |
|
9 |
| Most significant trend changes for a client (top 5). |
|
10 |
| Clinician/client/system free-text notes. |
|
11 |
| Coaching chat messages (most recent first). |
|
¹ Uses the …-day variant (GET /activity_feed/sleep-analytics-day,
GET /activity_feed/exercise-analytics-day) when the requested window is a
single day.
See Endpoint mapping for the parameters sent to each endpoint and the filters that are applied server-side vs. locally.
All tools return the envelope:
{
"client_id": "...",
"domain": "...",
"period": {"start": "...", "end": "..."},
"data": [],
"unit_system": "metric",
"last_synced_at": "...",
"source": "apeiron-api",
"notes": ["..."]
}sourceis"apeiron-api"for live data and"stub"when no credentials are configured.notesis only present when there is something to explain: a filter the server could not apply, a retried request, or an argument the API ignores.datacarries the API payload as returned by the backend — the server does not reshape it into an invented schema, so field names and units (weight_lbs,height_inch, …) are the backend's.client_idis the Apeironperson_idand must be numeric whenever credentials are configured.
Related MCP server: carechronicle-mcp
Install & run
pip install -e .
apeiron-mcp # stdio (default)
MCP_TRANSPORT=streamable-http apeiron-mcp # HTTP on http://127.0.0.1:8000/mcpThe transport is selected with MCP_TRANSPORT; see
Configuration for the bind address, port and other options.
Register with Claude Desktop
Add to claude_desktop_config.json:
{
"mcpServers": {
"apeiron": {
"command": "apeiron-mcp"
}
}
}Run with Docker
The image is host-agnostic: it defaults to the network-friendly
streamable-http transport so it can run as a long-lived service, and it can
also be driven over stdio by a local MCP client.
Build
docker build -t apeiron-mcp:0.1.0 .Run as an HTTP service (recommended for deployment)
# 8000 is taken by apeiron-ml's research-api on the shared EC2 -> publish on 8001.
docker run -d --name apeiron-mcp \
-p 8001:8000 \
-e APEIRON_ACCESS_TOKEN="$APEIRON_ACCESS_TOKEN" \
--restart unless-stopped \
apeiron-mcp:0.1.0The endpoint is then http://localhost:8001/mcp. Verify it:
curl -i -X POST http://localhost:8001/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'Prefer a plain-JSON reply (easier to script against)? Add
-e MCP_JSON_RESPONSE=true.
Run over stdio (for a local MCP client)
-i keeps stdin attached and the image writes all logs to stderr, so
stdout stays a clean JSON-RPC stream:
docker run -i --rm -e MCP_TRANSPORT=stdio apeiron-mcp:0.1.0Point a client at it with:
{
"mcpServers": {
"apeiron": {
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT=stdio", "apeiron-mcp:0.1.0"],
"env": { "APEIRON_ACCESS_TOKEN": "..." }
}
}
}With Docker Compose
cp .env.example .env # then edit APEIRON_ACCESS_TOKEN
docker compose up -d --build
docker compose logs -fThe apeiron-mcp service is started by default; a stdio service lives behind a
profile (add -T when running from a script or CI, where no TTY is attached):
docker compose --profile stdio run --rm apeiron-mcp-stdioThe container runs as a non-root user (uid/gid 10001) with a read-only
root filesystem, all Linux capabilities dropped and a built-in HEALTHCHECK
(TCP probe on the configured port). Host port mapping is controlled by
MCP_HOST_PORT (host side, default 8001) and MCP_PORT (container side,
default 8000). MCP publishes on 8001 because apeiron-ml's research-api
already binds 8000 on the shared EC2 instance.
Configuration
Backend credentials:
Variable | Default | Purpose |
|
| Backend base URL. |
| (unset) | Bearer token for the API. |
| (unset) | Login email — used to mint a token when no bearer token is set. |
| (unset) | Login password. |
|
| Per-request timeout in seconds. |
With none of APEIRON_ACCESS_TOKEN / APEIRON_EMAIL+APEIRON_PASSWORD set, the
tools return labelled stub data ("source": "stub") instead of API results, so
the demos keep working offline.
Server behaviour:
Variable | Default | Purpose |
|
|
|
|
| HTTP bind address. |
|
| HTTP bind port. |
|
| Run without HTTP sessions (use behind a load balancer without sticky sessions). |
|
| Reply with plain JSON instead of SSE streams. |
| (unset) | Comma-separated |
| (unset) | Comma-separated |
|
| Compose only — host port published by |
Security note: binding to a non-loopback address (
0.0.0.0, required in a container) disables the SDK'sHost/Originvalidation so the service is reachable — the server logs a warning when this happens. When you expose the service beyond a trusted network, setMCP_ALLOWED_HOSTS(e.g.apeiron.example.com,apeiron.example.com:*— list both forms, because entries are matched against theHostheader including its port) and terminate TLS at a reverse proxy. The server itself ships no authentication layer, so do not publish the port directly to the internet.
CI/CD
Two GitHub Actions workflows mirror the apeiron-ml pipeline: they build the image in CI, push it to AWS ECR, and deploy a chosen tag to the staging/production host.
Workflow | Trigger | What it does |
push to any branch (or manual) | Builds | |
manual ( | Pulls the requested tag onto the target host and (re)starts the |
Image tags
Source branch | Image tag | Git tag |
|
| — |
| next semantic version, e.g. |
|
|
| — |
Tags are immutable, so each build pushes a fresh versioned tag rather than
overwriting a moving latest. Deploy references an explicit tag.
Required repository secrets
Secret | Used by | Purpose |
| build | AWS credentials for the ECR login. |
| build, deploy | Registry host, e.g. |
| deploy | SSH key for the target host. |
| deploy | EC2 hostname to deploy to. |
Deploying
Provide a
.env.dockerin the home directory of the target host (the workflow runsdocker run --env-file .env.docker). Start from the committed template and fill in the real values:cp .env.example ~/.env.docker # then edit APEIRON_ACCESS_TOKEN etc.See Configuration for every variable.
Run the Deploy MCP workflow and pick the image tag and the environment. MCP is published on host port
8001and the container always listens on8000;8000is left alone because apeiron-ml'sresearch-apiuses it.The workflow restarts the container and fails loudly (with the last 50 log lines) if it exits during startup.
The ECR_REPOSITORY (phs/apeiron-mcp), AWS_REGION (us-west-2),
CONTAINER_NAME (apeiron-mcp) and HOST_PORT (8001) values live in the
env: block at the top of each workflow, so a different registry, region,
container name or host port is a one-line change.
Exposing the service: the apeiron-ml EC2 security group only opens ports
8000and22, so8001is reachable on the host/VPC but not from the internet until an ingress rule for8001is added to the terraform security group (apeiron-ml/terraform_utils/research{,_staging}_env/ec2.tf). In the meantime reach it over an SSH/SSM tunnel or front it with a reverse proxy.
Authentication
Every read endpoint needs a bearer token; without one the API answers
401 {"message": "No authorization token found"}. Two ways to supply it:
Static token — set
APEIRON_ACCESS_TOKEN. Mint one with the login endpoint:curl -s https://api.apeiron.life/people/login \ -H 'Content-Type: application/json' \ -d '{"email":"you@example.com","password":"secret","app":"dashboard"}' # -> {"token": "...", "person": {...}}Email + password — set
APEIRON_EMAILandAPEIRON_PASSWORD; the client callsPOST /people/loginon first use, caches the token, and re-authenticates once if a call comes back401. To print a token forAPEIRON_ACCESS_TOKEN:python -c "from apeiron_mcp.apeiron_client import fetch_token; \ print(fetch_token('you@example.com', 'secret'))"
Endpoint mapping
Swagger spec: https://api.apeiron.life/_api.json (v13.0). The server only uses
read (GET) endpoints; the validated dataset types are bio_marker,
body_comp, cardiorespiratory, cognitive_health, lifestyle_assessment and
physical_assessment.
Tool | Request | Date handling |
|
| native (defaults to the last 7 days) |
| native, when | |
|
| native; |
|
| native |
|
| filtered locally on |
|
| filtered locally on |
|
| filtered locally on |
|
| native for healthy signals |
|
| native for the summary card |
|
| native; the endpoint returns the top significant changes and accepts only |
|
| native; |
|
| filtered and trimmed locally ( |
Filters applied locally
Some tool arguments have no counterpart in the API, so they are applied to the
response instead. When a filter matches nothing, the full response is returned
and an entry is added to the envelope's notes field, so results are never
silently emptied:
get_exercise_data.activity_type— matched against activity-name fields (activity_type_shown,name,workout, …).get_cardio_metrics.metric—resting_hr,hrv,vo2max,blood_pressureandaerobic_capacityare matched against metric/group names (sohrvalso matches "heart rate variability").get_notes.author— matched against author/role/source fields.Date bounds on
/datasets/{type}(which takes no date parameter) and on/message/.get_chat_history.limit— the most recent N messages are returned.get_trendsno longer filters by metric:GET /trend_statistics/summarytakes onlyperson_idand returns the person's most significant trend changes.get_chat_history.conversation_idis ignored: Apeiron keeps a single message thread per client.
Everything else (person_id, group, dates on range-aware endpoints) is sent to
the API as query parameters.
Development
pip install -e .
python examples/poc_walkthrough.py # all 11 tools, stub data
APEIRON_PERSON_ID=123 python examples/poc_walkthrough.py # same calls, live dataThe walkthrough prints the envelope's source and notes for every call, so it
doubles as a wiring check: without credentials everything reports
"source": "stub"; with credentials the same calls hit api.apeiron.life. Note
that the MCP stdio client only forwards a safe subset of the environment
(HOME, PATH, …), which is why the examples pass the APEIRON_* variables to
the server process explicitly.
LLM agent chat UI
examples/agent_demo.py puts a small browser chat UI in front of the server: an
LLM picks the MCP tools, every tool call is shown above the answer, and the reply
streams in as it is written. Start the server over streamable HTTP, set one LLM
key, then open http://127.0.0.1:8080:
MCP_TRANSPORT=streamable-http apeiron-mcp # terminal 1
.venv/bin/pip install anthropic openai
OPENAI_API_KEY=... .venv/bin/python examples/agent_demo.py # terminal 2 (or ANTHROPIC_API_KEY)Failures (a bad API key, the server not running) are reported inline in the page
instead of as a stack trace. MCP_URL overrides the MCP endpoint and
AGENT_HOST / AGENT_PORT the UI bind address.
API failures (bad token, unknown person_id, transport error) surface as MCP
tool errors carrying the failing method, path and HTTP status.
Available Tools
11 toolsget_cardio_metricsC
Cardio + aerobic metrics (same physiological domain).
Args: metric: If omitted, returns all supported metrics.
Inputs: client_id (required), metric, end_date, start_date. Returns: object
get_chat_historyC
Paginated coaching/chat conversation logs.
Args: conversation_id: Restrict to one conversation thread. limit: Max messages to return (default 50).
Inputs: client_id (required), limit, end_date, start_date, +1 more. Returns: object
get_cognitive_dataC
Cognitive test scores (memory, reaction time, processing speed) with trends.
Inputs: client_id (required), end_date, start_date. Returns: object
get_exercise_dataB
Workout logs — type, duration, calories, heart-rate zones, RPE.
Args:
client_id: Client identifier.
start_date: ISO date, inclusive.
end_date: ISO date, inclusive.
activity_type: Optional filter (e.g. "run", "strength").
Inputs: client_id (required), end_date, start_date, activity_type. Returns: object
get_fitness_assessmentB
Periodic clinical/assessment-style readings.
Covers bone density, body composition, balance, movement quality, and muscle strength — periodic (not continuous) measurements.
Inputs: client_id (required), end_date, start_date, assessment_type. Returns: object
get_healthspan_domain_summaryC
Cross-domain rolled-up healthspan scores.
Inputs: client_id (required), as_of_date. Returns: object
get_lifestyle_summaryB
Sleep/activity/nutrition adherence rollup for a period.
Inputs: client_id (required), period. Returns: object
get_notesD
Free-text clinical/coach/client notes.
Inputs: client_id (required), author, end_date, start_date. Returns: object
get_nutrition_dataC
Meals logged, macros, calories, hydration, supplements.
Inputs: client_id (required), end_date, start_date. Returns: object
get_sleep_dataB
Sleep stages, duration, efficiency, HRV during sleep, sleep score.
Args: client_id: Client identifier. start_date: ISO date (YYYY-MM-DD), inclusive. Defaults to 7 days ago. end_date: ISO date (YYYY-MM-DD), inclusive. Defaults to today.
Inputs: client_id (required), end_date, start_date. Returns: object
get_trendsC
Generic trend tool — time series with deltas/direction for any metric.
Args:
domain: e.g. "sleep", "cardio", "nutrition".
metric: metric name within that domain (e.g. "hrv", "sleep_score").
granularity: bucket size for the returned series.
Inputs: domain (required), metric (required), end_date (required), client_id (required), +2 more. Returns: object
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 11 tools
Most tools target distinct data domains (cognitive, sleep, exercise, nutrition, cardio, fitness assessment). Potential confusion exists between the two summary tools and between domain-specific get_*_data and get_trends, but descriptions clarify raw versus derived data.
All 11 tools use consistent snake_case with the get_ verb prefix followed by a descriptive noun phrase. The pattern is predictable and readable throughout, with only minor length variations.
11 tools is well-scoped for a read-only health data aggregation server. Each tool maps to a distinct data source or summary type, with no obviously redundant tools.
The surface covers core health domains (cognitive, sleep, exercise, nutrition, cardio, fitness assessments) plus summaries, trends, notes, and chat. Minor gaps include no client listing or metadata tool, but for a read-only aggregation API the coverage is strong.
Related MCP Connectors
Read wearables and lab health data — sleep, activity, workouts, timeseries, lab tests and orders.
Your labs, wearables, DNA and notes as one health record your AI agent can read (OAuth 2.1).
Your Garmin, Oura, WHOOP, Hevy and 35 more fitness apps, queryable from your own AI.
Connect your wearables, rings and training apps, then ask your AI about your own health data.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceExposes personal health data (recovery, sleep, strain, etc.) as MCP tools for AI agents to query and analyze.5MIT
- FlicenseNot gradedqualityDmaintenanceEnables LLMs to interact with clinical patient records using tools for document ingestion, structured conversion, patient profiling, record listing, search, and secure Q&A over patient documentation.-
- FlicenseNot gradedqualityCmaintenanceEnables AI assistants to retrieve wellness metrics such as activity, blood pressure, glucose, heart rate, sleep, and SpO2 from a PostgreSQL-backed health database via MCP tools.-
- AlicenseAqualityCmaintenanceEnables AI clients to query Withings health data such as weight, body composition, heart rate, temperature, SpO2, activity, sleep, workouts, and ECG recordings.17MIT