mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 06:06:10 +00:00
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.
This commit is contained in:
1 parent
391c78674e
commit
1387cb56cc
5 files changed
+537
-2
No files matched your search
@@ -0,0 +1,168 @@
|
||||
# MCP connector — enable and connect
|
||||
|
||||
HealthLog can expose **one user's own health record to an external
|
||||
assistant** (Claude.ai, ChatGPT, Claude Desktop) through a Model Context
|
||||
Protocol (MCP) server. The assistant reads your figures — vitals, sleep,
|
||||
labs, medications, discovered drivers — and, if you opt in, can log a
|
||||
self-reported reading back. Everything stays on your instance; the
|
||||
assistant only sees what it asks for, one record at a time.
|
||||
|
||||
The surface is **off by default**. It does nothing until you (1) pin a
|
||||
canonical origin, (2) turn the `mcp` module on for your account, and
|
||||
(3) mint a token. A self-hoster who never opts in gets zero new exposure
|
||||
— the `/mcp` endpoint answers `404` until all three are in place.
|
||||
|
||||
This guide covers turning it on and pointing a real client at it. For
|
||||
what the server can do, see [the capabilities
|
||||
reference](../api/mcp-capabilities.md); for building a connector or skill
|
||||
on top of it, see [building skills](../api/mcp-skills.md).
|
||||
|
||||
> **Read this before enabling writes.** A remote, write-capable connector
|
||||
> reached over OAuth is a powerful combination. Read the
|
||||
> [trust model](#trust-model-read-this-before-enabling-writes) at the end
|
||||
> of this page first.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Three things must be true before `/mcp` will answer:
|
||||
|
||||
| Requirement | Why |
|
||||
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`APP_URL` is set** | The endpoint fails closed without a pinned canonical origin. `APP_URL` (or `NEXT_PUBLIC_APP_URL`) is the origin every OAuth document and audience check is anchored to — `Host` is never trusted. Without it the surface stays invisible (`404`). |
|
||||
| **The global API is on** | The operator's instance-wide API switch must be enabled (it is by default). With it off, every token path — including `/mcp` — returns `404`. |
|
||||
| **The `mcp` module is on** | A per-account opt-in. It is the only module that ships dark; you turn it on in Settings. Until then `/mcp` answers `404` for that account. |
|
||||
|
||||
`APP_URL` is the same origin you already use for passkeys and invite
|
||||
links — for example `https://health.example`. It must be reachable by the
|
||||
client you are connecting (Claude.ai and ChatGPT connect from their own
|
||||
servers, so the origin has to be a real public hostname for those; a
|
||||
LAN-only origin works for Claude Desktop over stdio).
|
||||
|
||||
## 1. Enable the module
|
||||
|
||||
In the app: **Settings → MCP**. The card has three parts:
|
||||
|
||||
- **Enable** — the opt-in switch. Toggle it on; the `/mcp` endpoint goes
|
||||
live for your account and the card shows your endpoint URL
|
||||
(`<origin>/mcp`).
|
||||
- **Connections** — cloud connectors that completed the OAuth flow appear
|
||||
here; revoke any of them.
|
||||
- **Tokens** — manual connector tokens (see below).
|
||||
|
||||
## 2. Mint a connector token
|
||||
|
||||
Still in **Settings → MCP → Tokens**:
|
||||
|
||||
1. Choose the scope with the **write** toggle:
|
||||
- **off** → a read-only token (`health:read`). This is the default and
|
||||
the right choice unless you actually want the assistant to log
|
||||
readings.
|
||||
- **on** → a read **and** write token (`health:read health:write`).
|
||||
The write scope only ever admits the three confirmed, append-only
|
||||
write tools (below) and is **bound to `/mcp`** — it can never write
|
||||
over the regular REST API.
|
||||
2. Name the token and create it.
|
||||
3. **Copy the `hlk_…` value now** — it is shown exactly once and cannot
|
||||
be retrieved again. Tokens default to a 90-day lifetime.
|
||||
|
||||
The token never carries the `*` wildcard and never the
|
||||
`medication:ingest` scope the generic `/api/tokens` mints — it is the
|
||||
least-privilege MCP scope and nothing more. The same list shows
|
||||
last-used time and lets you revoke any token.
|
||||
|
||||
Cloud connectors that use OAuth (next section) mint the same
|
||||
`health:read` scope automatically, so for Claude.ai / ChatGPT you do not
|
||||
need to mint a token by hand — the manual token is for the stdio / power-
|
||||
user path and for clients you wire up with a static Bearer.
|
||||
|
||||
## 3. Connect a client
|
||||
|
||||
The endpoint is `<APP_URL>/mcp` for every remote client. A non-browser
|
||||
client (Claude.ai / ChatGPT server-side) sends no `Origin`, so it
|
||||
connects directly; a browser `Origin` that does not match `APP_URL` is
|
||||
refused (`403`) as a DNS-rebinding defence.
|
||||
|
||||
### Claude.ai (remote + OAuth)
|
||||
|
||||
1. In Claude.ai, add a custom connector and paste `<APP_URL>/mcp`.
|
||||
2. Claude.ai discovers the OAuth flow automatically: the `401` from
|
||||
`/mcp` points at `/.well-known/oauth-protected-resource`, which names
|
||||
the authorization server, and the client walks `/authorize` →
|
||||
`/token`. PKCE (S256) is mandatory; the built-in bridge accepts
|
||||
client-id-metadata-document clients with dynamic registration as a
|
||||
fallback.
|
||||
3. Approve the consent screen. The connection then appears under
|
||||
**Settings → MCP → Connections**, where you can revoke it.
|
||||
|
||||
No token to paste — OAuth mints and rotates a short-lived `health:read`
|
||||
access token (60 minutes, with a refresh token) behind the scenes.
|
||||
|
||||
### ChatGPT (remote)
|
||||
|
||||
1. Add `<APP_URL>/mcp` as a connector. The same OAuth discovery applies.
|
||||
2. In ChatGPT's **default** (non-Developer) mode, the only tools it will
|
||||
call are **`search`** and **`fetch`** — these are HealthLog's two-tool
|
||||
retrieval façade over the same server-authoritative reads. So in
|
||||
default ChatGPT you ask in natural language ("what's my recent LDL?")
|
||||
and it searches your record and fetches the matching item with a
|
||||
citation deep-link back into the app. Developer mode exposes the full
|
||||
tool catalogue.
|
||||
|
||||
### Claude Desktop (stdio)
|
||||
|
||||
Claude Desktop runs a local process and speaks the protocol over stdio —
|
||||
no OAuth, no public hostname. Mint a token (step 2), then add an entry to
|
||||
`claude_desktop_config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"healthlog": {
|
||||
"command": "pnpm",
|
||||
"args": ["dlx", "tsx", "src/cli/mcp-stdio.ts"],
|
||||
"cwd": "/path/to/HealthLog",
|
||||
"env": {
|
||||
"HEALTHLOG_MCP_TOKEN": "hlk_…",
|
||||
"DATABASE_URL": "postgres://…",
|
||||
"API_TOKEN_HMAC_KEY": "…",
|
||||
"ENCRYPTION_KEYS": "…"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The stdio entrypoint reads the token from `HEALTHLOG_MCP_TOKEN`
|
||||
(preferred — keeps the secret out of the process argument list) or as the
|
||||
first CLI argument. With no token it refuses to start; running it with a
|
||||
minted token **is** the opt-in. It needs database access, so it runs from
|
||||
a checkout with the same `DATABASE_URL` / `API_TOKEN_HMAC_KEY` /
|
||||
`ENCRYPTION_KEYS` your instance uses. Use `tsx` via `pnpm dlx` — the
|
||||
production standalone image strips `tsx`, so this is a local-only tool.
|
||||
stdout is the JSON-RPC channel; diagnostics go to stderr.
|
||||
|
||||
## Trust model — read this before enabling writes
|
||||
|
||||
The combination of a **remote** endpoint, **write** capability, and
|
||||
**OAuth** access is exactly the high-leverage surface to be careful with.
|
||||
HealthLog ships several structural limits, but the operator's choices
|
||||
still matter:
|
||||
|
||||
- **Only enable the connector for clients you trust.** Anything that
|
||||
holds a valid token or a live OAuth connection reads your health
|
||||
record. Revoke connections and tokens you no longer use (Settings →
|
||||
MCP).
|
||||
- **Use a read-only token unless you need writes.** The default. A
|
||||
read-only session never even sees the write tools advertised.
|
||||
- **Writes are narrow by construction.** The only writes are
|
||||
append-only, confirmed inserts of a single self-reported reading
|
||||
(`log_measurement`, `log_mood`, `log_blood_pressure`). There is no
|
||||
medication, lab, schedule, delete, update, or data-export tool, and a
|
||||
`health:write` MCP token is refused on every REST write — it can never
|
||||
become a general write credential.
|
||||
- **Admin is unreachable over MCP.** `requireAdmin()` is cookie-only and
|
||||
the MCP wire carries no cookie, so no token — not even a `*` wildcard —
|
||||
can reach an admin endpoint over this surface.
|
||||
|
||||
The structural guarantees are detailed in [the capabilities
|
||||
reference](../api/mcp-capabilities.md#security-and-write-model).
|
||||
Reference in new issue
Block a user