Building a DHIS2 assistant (worked example)¶
A complete, reproducible walkthrough: scaffold a portable DHIS2 project whose model lives next to it, attach the DHIS2 CLI bridge as an MCP tool, point it at a named DHIS2 profile, and confirm the local model actually calls the tool.
The result is a self-contained project directory you can copy to another machine — the model weights, the assistant definition, and the tool wiring all travel together (the only per-machine bit is the DHIS2 profile, see below).
See Tools (MCP) for choosing between the three DHIS2 MCP sizes; this
guide uses the bridge (one dhis2_cli tool) because it pairs well with a
smaller local model.
One command (the reproducible shortcut)
Everything below is scaffolded by a single template:
That presets the model (Ornith-1.0-9B, the benchmark winner), a
DHIS2 system prompt, the read-only bridge, and example files — then prints the profile
setup. The rest of this guide walks through what that command sets up, so you can build
or adapt it by hand.
Prerequisites¶
HEIM_LIBRARY_ROOTset to your library (so the model can be copied from it).llama-serveronPATH(brew install llama.cpp) — the GGUF runtime.uv/uvx— used to run the DHIS2 bridge (uvx dhis2w-mcp-bridge).-
A DHIS2 profile in
~/.config/dhis2/profiles.toml. The bridge selects the target server by profile name; credentials/tokens stay on the machine, never in the committed project. Example:Confirm the profile reaches the server before wiring heim:
1. Scaffold the project with a local model copy¶
heim project new <dir> runs an interactive wizard. --model skips the model
picker; --copy (alias --local) copies the chosen model into the project's own
library/ instead of relying on the shared library — that is what makes the
project portable.
--git initializes a repo and writes a .gitignore that excludes the local
library/ (the weights) and .env — so the project is committable out of the box.
Drop it if you don't want version control.
The wizard asks three things:
- Kind —
1(Chat). - Tools — leave blank. The DHIS2 bridge is not a heim plugin, so it does not appear in the plugin picker; we add it by hand in the next step.
- System prompt — e.g. "You are a DHIS2 assistant. Use the dhis2 tools to query the connected DHIS2 instance instead of guessing…".
heim copies the model (a fast local-disk copy if it is already in your shared
library) and writes dhis2/heim.toml:
# This project ships its own "library/" store (the model was copied there);
# @shared is the machine default library (HEIM_LIBRARY_ROOT) if you set one.
libraries = ["library", "@shared"]
[project]
model = "lmstudio-community/gemma-4-12B-it-QAT-GGUF"
system_prompt = "You are a DHIS2 assistant. …"
chat_voice = "kokoro:af_heart"
The weights land under dhis2/library/gguf/lmstudio-community/gemma-4-12B-it-QAT-GGUF/.
Don't commit the weights
The project-local library/ holds multi-GB model files. --git (above) already
gitignores it; if you version the project some other way, exclude library/
(or at least the weight files) yourself.
2. Attach the DHIS2 bridge¶
Add a server entry to dhis2/.mcp.json (standard mcpServers JSON, so it has a
first-class env object). DHIS2_MCP_READONLY=1 keeps it query-only:
{
"mcpServers": {
"dhis2": {
"command": "uvx",
"args": ["dhis2w-mcp-bridge"],
"env": { "DHIS2_PROFILE": "play42", "DHIS2_MCP_READONLY": "1" }
}
}
}
The DHIS2 CLI bridge is a single dhis2_cli tool that runs d2 CLI calls against the
connected instance; DHIS2_PROFILE selects the target (from profiles.toml). To
retarget later, swap play42 for another profile name (e.g. play43) — no other change.
3. Verify the tool wiring¶
heim project show (run inside dhis2/) prints the resolved model and spawns the
MCP servers to list their real tools — so it confirms the bridge connects before
you ever load the model:
Tools
connecting to 1 MCP server(s) …
dhis2 (env DHIS2_PROFILE=play42 DHIS2_MCP_READONLY=1 uvx dhis2w-mcp-bridge) — 1 tool(s)
dhis2_cli Run a `d2w` CLI command against a DHIS2 server.
You can also list installed plugin servers (the bridge is a project MCP, not a plugin, so it won't appear here) with either spelling:
4. Try it — the model calls the tool¶
Run a one-shot chat (-p) that forces a lookup. heim loads gemma via llama-server,
connects the bridge, and runs the agent loop: the model emits a dhis2_cli tool
call, heim executes it against play42, and feeds the result back:
heim chat -p "What DHIS2 version is the server running, and what is the system name? Use your dhis2 tool to check, don't guess."
⚙ dhis2__dhis2_cli({"args":["system","info"]})
↳ Root(exit_code=0, stdout='{"version":"2.42.6-SNAPSHOT", …, "systemName":"DHIS 2 Demo - Sierra Leone", …}')
The DHIS2 server is running version 2.42.6-SNAPSHOT, and the system name is
DHIS 2 Demo - Sierra Leone.
For the interactive TUI (or the browser), run heim chat or heim serve --ui in the
same directory — both bind to the project's model with the DHIS2 tool available.
Next to a live DHIS2 tab
Scaffold from the dhis2 template (heim project new mydhis2 --template dhis2) to also
get an [assistant] block, then serve it on a pinned port and drive it from the
Chrome side panel: a target banner that confirms your active tab matches the
instance, Verify, Who am I here?, and Use my login to let the tools act as you
(a read-only PAT minted in the tab, never a copied password).
Recap¶
| Step | Command |
|---|---|
| Scaffold + copy model | heim project new dhis2 --model …gemma-4-12B-it-QAT-GGUF --copy |
| Attach bridge | add a .mcp.json entry: uvx dhis2w-mcp-bridge with env.DHIS2_PROFILE=play42 |
| Verify tools | heim project show |
| Try it | heim chat -p "…" (or heim serve --ui) |
| Retarget server | change the profile name in the env prefix |