PluginWorld
Mi

Mirobody

MCP✓ SPEC VERIFIED

Self-hosted health data engine: lab reports, wearables and genetic files in one record, and an agent that cites its sources.

@thetahealth · v1.5.3 · Apache-2.0 · updated 2d ago

SECURITY

A

SCORE

91

STARS

▲ 1.4K

PLUG IN

git clone https://github.com/thetahealth/mirobody.git

See the README to configure this MCP server

README

Mirobody

Mirobody

Self-hosted AI health data engine: lab reports, wearables and genetic files in one record, and an agent that cites its sources.

English · 中文

PyPI Docker Hub PyPI Downloads License: Apache-2.0 GitHub stars

▶ Live demo, no sign-up · 📚 Documentation · 🐳 Docker Hub · ☁ Cloud API


Last year's checkup wrote A1c, this year's panel HbA1c, the new clinic Glycated Hemoglobin. One test, three names, nothing to compare. Mirobody reads any source, any format, any language, settles every value onto one standard, and answers questions over that record with each number traced to its file. It runs on your machine, on a model key you choose.

Asking how cholesterol has changed: the agent finds three files that name the test differently, resolves them to one code, and charts the trend

Three files, three names for the same test, one code. The agent finds all three, charts the trend, and names the file every number came from.

Run it in two commands

git clone --depth 1 https://github.com/thetahealth/mirobody.git && cd mirobody
OPENROUTER_API_KEY=sk-or-... ./deploy.sh     # Postgres, server and worker → http://localhost:18060

Docker is the only requirement: no Python, Node.js, GPU or Git LFS, and not even Git (curl -L https://github.com/thetahealth/mirobody/archive/refs/heads/main.tar.gz | tar xz && cd mirobody-main is the same checkout). deploy.sh writes the secrets and your key into .env and pulls the prebuilt image, building it from the checkout when the pull fails. It stops and names the fix when a port is taken or another Mirobody stack already runs under this folder's name. The image runs beside its own Postgres, so docker run alone is not a way in. A key added later goes in .env, then docker compose up -d: a restart does not read .env again.

  1. Sign in. The sign-in page offers the demo account, you@mirobody.ai with code 111111 on the Email code tab. SEED_DEMO_DATA is on by default, so two accounts already hold 2,019 readings: you, and mom@mirobody.ai, who shares her record with you view-only.
  2. Drop a file on the Data page. demo/upload/ holds four files the seed leaves out. The lab PDF and the other lab's CSV are yours; the report photo and the spreadsheet are mom's, so drop those signed in as her. Each analyte comes out with a value, a unit and a code, linked to the page it was read from.
  3. Ask. "How has my cholesterol moved?" finds every file that carries it, whatever the lab called it, charts the trend, and names the file behind each number. The same question on the shared record answers from data you can only view.
  4. Say how you feel. Type headache since last night, BP 150/95, no fever, metformin 500 mg morning and evening under Data › Records. One sentence becomes a coded complaint, two coded readings and a medication on your list, and "no fever" is kept out of the record rather than logged as a fever.

Dropping a lab-report PDF on the Data page; its analytes are extracted and appear in the indicators table, each with a LOINC code The same question asked on the shared record; the agent answers from a different person's files One typed sentence becomes a headache coded NS01, blood pressure coded 8480-6 and 8462-4, and metformin on the medication list; 'no fever' is not logged

Which key. Any one of these runs every surface: OpenRouter (OPENROUTER_API_KEY), OpenAI (OPENAI_API_KEY), Gemini (GOOGLE_API_KEY), Anthropic (ANTHROPIC_API_KEY), DeepSeek, DashScope, or any OpenAI-compatible gateway through <PROVIDER>_BASE_URL. config.llm.yaml names the variable (api_key: OPENROUTER_API_KEY), never the secret; mirobody doctor prints what each surface selected.

→ Self-host guide · Configuration · Deploy on a server

What you get

  • Every source, one record. Garmin, Oura and Whoop connect directly; anything a band, ring or scale writes into Apple Health comes with it; PDFs, phone photos, spreadsheets and exports, 23 file types in all, are read by kind.
  • One record for the whole family. Invite a partner or a parent, or add a child who never signs in. Sharing is a care circle: invite-only, off by default, and one function, resolve_subject, is the only way an account reaches a record that is not its own.
  • Say how you feel, in your own words. Type headache since last night, BP 150/95, no fever, metformin 500 mg morning and evening into the journal. Your model splits the sentence and types each part; the codes come from the vocabulary, never from the model.
  • No invented codes. Every reading lands in one settled system, or the engine says it could not place it and keeps your words. Built and tested against real reports in English, Chinese, Japanese and Russian.
  • Genotypes as facts, not verdicts. Upload a 23andMe, AncestryDNA, MyHeritage, FTDNA or WeGene export, or a VCF, and ask by rsID, gene or region. A drug question gets CPIC coverage, never a phenotype or a change of medication. How genetics works.
  • The agent reasons only over coded data, and it need not be ours. Trends from minutes to months, drawn as a chart; comparisons across labs, files and devices, because one code sits under all of them. Every tool is also served at /mcp, gated per user.

How one person reaches another's health record: a request passes resolve_subject, which requires both memberships accepted and the subject's own health_access switch, and either returns access trimmed to the request or raises a 403

→ The four-minute walkthrough · Device setup for Garmin, Oura and Whoop

Collect · Translate · Agent

Collect, Translate, Agent: three stages, left to right

Stage What it does Where
① Collect Lab reports, wearables, phone photos, genetic files, a sentence in the journal, all pulled in. The source is kept as it was, so every reading points back to the page it was read from. collect/
② Translate One name to one code, one unit to UCUM, offline and deterministic. A1c, HbA1c and Glycated Hemoglobin become the same test here (LOINC), and 头疼 and headache the same complaint (ICPC-3). engine/ · translate/
③ Agent Ask over the coded record. Trend a value by minute, hour, day, week or month; compare across labs and devices, because they share one code. It charts the result in its reply and names the file every number came from. agent/

Try the engine alone

One command, five spellings, no key, no network, and with uvx no install either.

uvx --python 3.12 mirobody resolve "LDL cholesterol" 血红蛋白 ヘモグロビン "空腹血糖(GLU)" 血脂

--python 3.12 lets uv fetch the Python the package needs: on an older default interpreter it would pick a release from before 1.2 instead.

mirobody resolve: 血红蛋白 and ヘモグロビン landing on the same LOINC code, and one deliberate abstention

血红蛋白 and ヘモグロビン: two languages, one code, 718-7. 血脂 (lipids) names a category, not one observation, so it resolves to nothing rather than a guess.

from mirobody.engine import resolve, resolve_reading, standardize_reading

resolve("血红蛋白").loinc                                     # '718-7'   any language, one code
resolve("total cholesterol").loinc                          # '2093-3'  [Mass/volume]
resolve_reading("total cholesterol", "5.0", "mmol/L").loinc  # '14647-2' [Moles/volume]: the unit picks the code
resolve_reading("total cholesterol", "193", "mg/dL").loinc   # '2093-3'
resolve("中性粒细胞百分比").loinc                               # '26511-6' Neutrophils/Leukocytes
resolve_reading("中性粒细胞", "62 %", None).loinc              # '26511-6' a percentage...
resolve_reading("中性粒细胞", "4.2", "10*9/L").loinc           # '26499-4' ...and a count are two codes
resolve("血脂").resolved                                     # False    a category, not an observation
standardize_reading("血红蛋白", "13.5", "g/dL")["code"]["coding"][0]["code"]  # '718-7'  the same answer as a FHIR Observation

Complaints and diagnoses have their own axis, ICPC-3, and the same rule: a code or a stated refusal, never a guess.

from mirobody.translate import resolve_symptom, resolve_condition

resolve_symptom("头疼").code           # 'NS01'         Headache; 'headache', '頭痛' and 'головная боль' answer the same
resolve_symptom("疼").outcome         # 'refused'      too broad to code; the words are kept, the code is not invented
resolve_condition("2型糖尿病").code    # 'TD72'         Type 2 diabetes mellitus
resolve_condition("糖尿病").outcome    # 'needs-input'  which type? asked, not assumed

→ Standardization in depth · The library · examples/

Or hand it to your agent

Two skills teach Claude Code, Codex, Cursor or Gemini CLI to use it. One turns raw health files, exports and symptoms into coded rows and reads a lab report against the resolver instead of memory, with no key; the other runs the stack and connects it over MCP:

npx skills add thetahealth/mirobody --skill translate-health-data
npx skills add thetahealth/mirobody --skill mirobody

Or, with no Node, from the plugin marketplace that Claude Code and Codex both read:

claude plugin marketplace add thetahealth/mirobody && claude plugin install mirobody@mirobody
codex plugin marketplace add thetahealth/mirobody && codex plugin add mirobody@mirobody

→ skills/

Or connect it to your agent over MCP

Every tool the built-in agent has is also served at /mcp, one link per person: Settings → MCP link makes one that opens your record and nothing else. On the same computer:

Client Setup
Claude Code claude mcp add --transport http mirobody <link>
Codex codex mcp add mirobody --url <link>
Cursor ~/.cursor/mcp.json: {"mcpServers": {"mirobody": {"url": "<link>"}}}
Gemini CLI gemini mcp add --transport http mirobody <link>
Claude Desktop claude_desktop_config.json: {"mcpServers": {"mirobody": {"command": "npx", "args": ["-y", "mcp-remote", "<link>"]}}}. Its "Add custom connector" connects from Anthropic's cloud, which cannot reach localhost.
ChatGPT, claude.ai They connect from the cloud as well, so the stack needs an HTTPS address they can reach: deploy it on a server, and read SECURITY.md first.

Without the stack, uvx --python 3.12 mirobody mcp serves the vocabulary over stdio, offline and with no key: names to LOINC, units, and a reading or a complaint to FHIR. Its one tool that reads a whole document, standardize_report, also needs the [parse] extra and a model key: uvx --python 3.12 --from 'mirobody[parse]' mirobody mcp.

Privacy

Nothing leaves your machine except calls to the model you chose, and to a device vendor once you link one. ② Translate stays local entirely: a name to a code, a unit to UCUM, looked up in a bundle that ships inside the package, with no key and no network. Your record lives in your own Postgres, in containers you run, and nothing here reports usage anywhere. Encryption at rest does not yet cover every field; before this reaches a network you do not control, read SECURITY.md.

Numbers you can check

Claim Check it
296/296 on the tests an ordinary checkup prints, written the way a report prints them, in English, Chinese (Simplified and Traditional), Japanese, Russian and Estonian test_engine_coverage.py prints the score when you run it
328 UCUM units with dimensional analysis, a molar-mass bridge, and an explicit refusal to convert a percentage into a count Standardization in depth
316 standard device indicators Device crosswalk
The package names the vocabulary that answered you: mirobody.BUNDLE_VERSION is loinc-2.83+2026.09.17-aacb2c715b56 python -c "import mirobody; print(mirobody.BUNDLE_VERSION)"
Three public benchmarks for health agents: ESL-Bench (longitudinal virtual users; paper arXiv 2604.02834), MedHall-Bench (field-level hallucination: dose, unit, reference range, code) and MedHarm-Bench (red-team safety) thetahealth/mirobody-eval runs them under one scoring discipline

The engine powers Theta Wellness, a live consumer health product with 12,000+ registered users and 1,700+ daily active.

Use it, extend it

You want Do this
Offline resolution and units in your code pip install mirobody on Python 3.12+: no key, no network, two packages
A document turned into readings pip install 'mirobody[parse]': PDF, image, Excel, Word, PowerPoint, text; only a scanned page reaches a vision model
These tools in Claude Code, Codex, Cursor, Claude Desktop or your own loop Settings → MCP link, then one line per client
A new tool or device provider Drop a file into mirobody/agent/tools/ or mirobody/collect/providers/ and restart, or pip install a package declaring a mirobody.providers / mirobody.tools / mirobody.agents entry point
Your coding agent taught to use it npx skills add thetahealth/mirobody --skill translate-health-data for the library, --skill mirobody for the stack; see skills/
Your own agent harness pip install 'mirobody[agent]' for the middleware and virtual-filesystem backends, or point AGENT_DIRS at your directory to replace the shipped agent outright

→ MCP integration · Adding tools · Bringing your own agent

Contributing

The highest-leverage contribution is a term the resolver gets wrong. Run mirobody resolve "<term>", or resolve_symptom for a complaint; if the answer is wrong or empty, report it or add a row to resolver_overrides.tsv plus a case to test_engine_coverage.py. The coverage score is the review.

pip install -e '.[app,test]'
pytest -q                                                            # the gates a clone ships
python -m unittest benchmarks.health_records.test_cases              # LOINC/UCUM and ICPC-3 decisions, five languages
python -m unittest discover -s benchmarks/genomics -p 'test_*.py'    # one genotype truth, ten file shapes
lint-imports && ruff check mirobody examples

→ CONTRIBUTING.md · benchmarks/ · AGENTS.md for coding agents · Repository layout · Roadmap · CHANGELOG · SECURITY

Documentation, and what shaped this

docs.mirobody.ai has the guides, in English and Chinese. This repository's docs/ holds the design notes the code is checked against: the pipeline, the answer surface, standardization.

Mirobody's design draws on the following standards and projects, with thanks: HL7 FHIR, Regenstrief Institute (LOINC), UCUM, ICPC-3 (WONCA), CPIC, OHDSI OMOP, Open Wearables, Open mHealth / IEEE 1752, wearipedia, dlt / Airbyte / Singer, deepagents and LangChain. Terminology licences: LICENSE-3RD-PARTY.

Star History Chart

If it read a report for you, a star helps the next person find it. Releases land most weeks; Watch for them.

Apache 2.0 · © 2026 Theta Health

SIMILAR PLUGINS