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.
6.8 KiB
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) and that you know the capabilities and contract. 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, plusrestMode/cycleEnabledflags.
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:
- 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.
{ present: false }is the truth. It means not recorded — say so. Never substitute zero, never guess, never "estimate based on typical values".- 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.
- 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 (
presenton reads,writtenon 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
idvaluessearchreturns as the input tofetch; 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.