Files
HealthLog/docs/api/mcp-skills.md
T
Marc-André Bombeck 1387cb56cc docs(mcp): add enable, connect, and skill-building guides
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.
2026-06-28 01:37:18 +02:00

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.