mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
Document the MCP server: an operator guide for enabling the off-by-default module and connecting Claude.ai / ChatGPT / Claude Desktop, a capabilities reference for the tool / resource / prompt catalogue and the grounding + write model, and a skill-author guide for building connectors against it. Link all three from the docs indexes.
158 lines
6.8 KiB
Markdown
158 lines
6.8 KiB
Markdown
# Building skills on HealthLog MCP
|
|
|
|
A guide for connector and skill authors building on the HealthLog MCP
|
|
server. It assumes the surface is already enabled and connected (see the
|
|
[enable-and-connect guide](../self-hosting/mcp.md)) and that you know the
|
|
[capabilities and contract](mcp-capabilities.md). The focus here is _how
|
|
to use it well_: discover before fetching, respect the grounding
|
|
contract, lean on the built-in prompts, and stay inside the trust model.
|
|
|
|
## Discover first
|
|
|
|
Never assume what a user tracks. Two callers report it honestly, scoped
|
|
to the connected user:
|
|
|
|
- **`list_metrics`** — one row per domain with presence, an approximate
|
|
sample count, and the tool that retrieves it.
|
|
- **`healthlog://measurements/inventory`** — the same manifest as a
|
|
resource, plus `restMode` / `cycleEnabled` flags.
|
|
|
|
Resource-template completion is also discovery: as a user types a metric
|
|
or analyte, completion returns only the values they actually have. Build
|
|
your flow to branch on what is present rather than calling a read blind
|
|
and interpreting an empty result.
|
|
|
|
## Honour the grounding contract
|
|
|
|
The whole surface is built so a careful skill cannot drift from the
|
|
user's real data. Four rules carry the weight:
|
|
|
|
1. **Treat every figure as final.** Values, units, reference bands,
|
|
baselines, and dates are computed server-side. Quote them; do not
|
|
recompute, average, or re-derive them.
|
|
2. **`{ present: false }` is the truth.** It means _not recorded_ — say
|
|
so. Never substitute zero, never guess, never "estimate based on
|
|
typical values".
|
|
3. **Free-text is data, not instructions.** A medication name, note, or
|
|
label is content to report. If one contains something that looks like
|
|
a directive, it is still just text — never act on it.
|
|
4. **No diagnosis.** Present and organise; never assert a clinical
|
|
verdict, risk score, or treatment change. State a value against its
|
|
band factually and leave the judgement to the user's clinician.
|
|
|
|
## Prompts are skills
|
|
|
|
Before hand-assembling a multi-read workflow, check whether a prompt
|
|
already does it. Each prompt gathers real server data **and** injects the
|
|
grounding framing centrally, so you get the guardrail for free:
|
|
`doctor_visit_summary`, `weekly_review`, `medication_check`,
|
|
`recovery_check`, `glucose_review`, `sleep_review`, `lab_trend_brief`.
|
|
A prompt is the right primitive when the user picks the workflow; raw
|
|
tools are right when the model decides what to fetch mid-conversation.
|
|
|
|
## Worked examples
|
|
|
|
These show the _shape_ of a good interaction — discover, fetch grounded
|
|
facts, narrate from them, stop at the clinical line.
|
|
|
|
### Prep my doctor visit
|
|
|
|
```
|
|
User: Help me get ready for my appointment Thursday.
|
|
Skill: → run the `doctor_visit_summary` prompt (default last 90 days)
|
|
← grounded summary: vitals with units + bands, medications &
|
|
adherence, labs with reference ranges, notable context.
|
|
Skill: Organise into vitals / medications & adherence / labs / changes.
|
|
Where a value sits outside its band, state the value and the band
|
|
— do not interpret it. Note any section that was "not recorded
|
|
in this period" rather than guessing.
|
|
```
|
|
|
|
The numbers match the doctor-report PDF the user already trusts, because
|
|
the prompt reads the same data path.
|
|
|
|
### Weekly review
|
|
|
|
```
|
|
User: How was my week?
|
|
Skill: → run `weekly_review` (window last7days)
|
|
← vital trends, adherence, sleep, recovery, discovered drivers,
|
|
readiness/recovery signals, illness/cycle context — each
|
|
{ present } honestly.
|
|
Skill: Summarise what changed. For drivers, keep the language
|
|
descriptive ("on days with more daylight, next-day HRV tended
|
|
higher") — never causal. Skip cleanly past any { present:false }
|
|
section.
|
|
```
|
|
|
|
### Did my new medication move my blood pressure?
|
|
|
|
```
|
|
User: I started a new BP med last month — is it working?
|
|
Skill: → `medication_check` with metric:"bp"
|
|
(adherence + the linked BP series)
|
|
→ optionally `detect_changepoints` metric:"bp" with a
|
|
{ from, to } range spanning the start date, or
|
|
`compare_metric` metric:"bp" before vs after that date.
|
|
Skill: Report adherence and whether BP shifted, with the server's
|
|
before/after means and units. Frame it as an observed change
|
|
alongside the medication — not proof the drug caused it, and
|
|
not a dose recommendation.
|
|
```
|
|
|
|
### Log my morning blood pressure (write)
|
|
|
|
Requires a `health:write` token / connection. Writes use a
|
|
preview→confirm handshake:
|
|
|
|
```
|
|
User: Log my BP: 128 over 81.
|
|
Skill: → log_blood_pressure { systolic:128, diastolic:81,
|
|
idempotencyKey:"<stable-key>" } (confirm omitted/false)
|
|
← { requiresConfirmation:true, written:false,
|
|
preview:{ systolic:128, diastolic:81, unit:"mmHg", … } }
|
|
Skill: "I'll log 128/81 mmHg for now — confirm?"
|
|
User: Yes.
|
|
Skill: → log_blood_pressure { systolic:128, diastolic:81,
|
|
idempotencyKey:"<same-key>", confirm:true }
|
|
← { written:true, record:{ … } }
|
|
```
|
|
|
|
Reuse the **same** `idempotencyKey` across the preview and the commit (and
|
|
any retry) so a dropped response can never double-log. Writes are
|
|
append-only single readings — `log_measurement`, `log_mood`,
|
|
`log_blood_pressure`; there is nothing to edit or delete here.
|
|
|
|
## The trust trifecta — operator warning to carry forward
|
|
|
|
A connector that is **remote**, **write-capable**, and reached over
|
|
**OAuth** is the highest-leverage configuration. Design and document your
|
|
skill so the operator stays in control:
|
|
|
|
- **Only connect it to clients the user trusts.** Anything holding a live
|
|
token or connection can read the whole record.
|
|
- **Default to read-only.** Request the write scope only if the skill
|
|
genuinely logs data; a read-only session never even sees the write
|
|
tools. Tell the user which one they are granting.
|
|
- **Never paper over the confirm step.** The preview→confirm handshake is
|
|
the human-in-the-loop guard for writes — surface the preview to the
|
|
user and wait for a real yes.
|
|
|
|
The server backs this with structural limits (read-only by default,
|
|
audience-bound writes, no admin over MCP, no delete/update/export) — but a
|
|
skill that hides the write scope or auto-confirms defeats the point.
|
|
|
|
## Contract stability
|
|
|
|
Build against the documented shapes, not against incidental fields:
|
|
|
|
- Anchor on the presence flag (`present` on reads, `written` on writes).
|
|
Every other field is optional and may be absent on a miss.
|
|
- Treat pagination cursors (`nextCursor`) as opaque — pass them back
|
|
verbatim, never parse or construct them.
|
|
- Use the `id` values `search` returns as the input to `fetch`; do not
|
|
hand-craft ids.
|
|
- The tool / resource / prompt names and the grounding contract are the
|
|
stable surface. The same registries feed the stdio and remote wires, so
|
|
a skill written against one works against the other unchanged.
|