| name | krill |
| version | 0.0.11 |
| description | Use when working with a Krill swarm โ the home-automation/IoT system whose nodes are reachable via the krill-mcp Model Context Protocol server (typically http://<host>:50052/mcp, see https://krillswarm.com). Invoke for discovering or inspecting Krill servers, nodes, DataPoints, Triggers, Filters, Executors, Pins, or peers; reading time-series sensor data; reasoning about which node type to use for a given automation; authoring, uploading, downloading, and improving SVG dashboards (Diagram nodes) that overlay live node state on a custom layout; and dispatching distributed LLM work across swarm-enabled Server.LLM nodes. Triggers on keywords like krill, swarm, krill server, krill node, krill-mcp, DataPoint, Trigger threshold, SVG dashboard, k_ anchor, swarm sensor, pi-krill, create project, create diagram, improve diagram, swarm work, swarm fleet, submit LLM work, swarm batch. |
Krill skill
Krill is a peer-to-peer home-automation / IoT platform built around a tree of typed nodes. Each Krill server runs as a peer in a swarm; nodes describe sensors, computations, triggers, executors, and visualizations. This skill lets Claude discover and reason about a live Krill swarm via the krill-mcp Model Context Protocol server, and author SVG dashboards bound to live node state.
How nodes work: observers, sources, verbs
Internalize this model before wiring anything โ it drives every authoring decision:
- Nodes are independent observers. Every node lists the nodes it watches in
meta.sources. When a source completes its work, every node observing it is invoked (woken) โ there is no "parent executes its children" push path. The observed node knows nothing about its observers; wiring always lives on the observer side.
- Invocation is a pull. A woken node reads what it needs at that moment from its
meta.inputs โ the nodes whose last result (meta.snapshot) it consumes. sources decide when a node runs; inputs decide what values it reads. A node consuming another's output usually lists it in both.
- Every node publishes its result in its own
meta.snapshot. A Calculation stores its computed value there; a webhook stores the HTTP response; a DataPoint stores its current reading. Downstream observers pull from there.
meta.invocationTriggers gates wake-up: SOURCE_INVOKED (a listed source completed) and/or ON_CLICK (manual tap). Empty list = the node never auto-fires.
- Verbs cascade. When a node fires it sends its
meta.nodeAction verb downstream โ EXECUTE (run your normal action) or RESET (stand down / clear). Observers apply the source's verb, so one RESET upstream clears a whole chain. Nodes with no sensible response to a verb safely ignore it (best effort).
- Parent/child is visual organization only. The tree groups nodes in the UI; it carries no execution semantics. As a convenience, the server wires a newly created node's parent into its
sources (+ SOURCE_INVOKED) when sources is empty and neither side is a Project/Server container โ so a child observes its parent by default. Rewire freely with set_node_wiring.
Example โ a counter that increments every 5 seconds: a CronTimer fires on schedule; a Calculation lists the cron in sources (wakes on fire), the counter DataPoint in inputs, and formula: "[<hostId>:<counterId>]+1"; the counter DataPoint lists the calc in both sources and inputs, so each computed value is pulled, filtered, and stored as a time-series point.
Use KrillApp.Trigger.Timer for a one-shot countdown instead of a recurring schedule. Send EXECUTE via set_node_action to start the countdown; RESET cancels it. Configure the duration with update_node meta={delay: <ms>}.
When to invoke
- The user mentions a Krill swarm, a specific Krill server (e.g.
kraken.local), or any Krill node type by name, or uses the word "Krill" in a sentence.
- The user wants to see what sensors / nodes exist, read a value, or understand a trigger or executor's current state.
- The user wants a visual dashboard built on top of their swarm โ almost always means a
KrillApp.Project.Diagram SVG.
- The user asks "what kind of node should I use to ..." โ answer from the bundled node-type catalog instead of guessing.
Do not invoke for generic IoT/home-automation questions that don't reference Krill โ answer those directly.
Check for MCP tools before telling the user to install anything
Before assuming the session needs a Custom Connector, verify what's actually available:
- Probe for MCP tools in the current session (e.g. search for
list_servers, create_node, list_node_types, read_series). If they're present, use them.
- If they're absent but
krill-mcp is running on the LAN, use the Direct-to-MCP JSON-RPC fallback โ POST JSON-RPC envelopes to http://<host>:50052/mcp with the same bearer. This gives you every MCP tool including writes (create_node, record_snapshot, update_diagram). This is the preferred fallback for anything that mutates state. See references/mcp-tools.md โ "Direct-to-MCP JSON-RPC fallback" for the exact envelope and headers.
- If
krill-mcp itself is down but a Krill server on :8442 is reachable, use the Direct-to-Krill REST fallback โ but note the hard limits: no endpoint exists for snapshot writes (all attempts at /data, /snapshot, /data/record, etc. 404). REST fallback covers reads and generic POST /node/{id} upserts. See references/mcp-tools.md โ "Direct-to-Krill fallback".
- Only ask the user to install / register the MCP connector when none of the above work (e.g. neither
:50052/mcp nor :8442 is reachable).
Bearer-token locations checked in order: ~/.krill/pin_token, then sudo krill-mcp-token on the krill-mcp host, then /etc/krill/credentials/pin_derived_key on a Krill server.
Bundled references
Read these on demand โ they're not auto-loaded:
references/mcp-tools.md โ the MCP tools (read: list_servers, list_nodes, get_node, read_series, server_health, reseed_servers; write: list_projects, create_project, create_diagram, update_diagram, get_diagram, upload_diagram_file, download_diagram_file; swarm dispatch: submit_swarm_work, submit_swarm_batch, get_swarm_fleet, get_swarm_work_status), their JSON shapes, the standard discovery + diagram + swarm-dispatch flows, auth setup. Read this first whenever you need to query or mutate a swarm.
references/node-types/INDEX.md โ table of all 37 Krill node types grouped by role (state / trigger / action / filter / display / container / infra) with one-liners and side-effect levels. Read this when the user's request needs a node type and you don't already know which one applies.
references/node-types/KrillApp.<Type>.json โ full spec for one node type: purpose, behavior, inputs, outputs, valid parents/children, side-effect level, examples. Read after narrowing via the index, before recommending the type to the user.
references/dashboard-conventions.md โ the k_* anchor convention, DiagramMetaData shape, authoring guidance, reference example. Read before generating any SVG dashboard.
Standard workflow
For a discovery / inspection request
- If MCP tools are available in this Claude session, call
list_servers โ server_health โ list_nodes (with a type filter when the user's intent narrows it). Use get_node for specific nodes the user names. Use read_series only when historical values matter.
- If
list_servers returns {"servers": []} โ call reseed_servers first. v0.0.6 mitigates the startup seed race with After=krill.service, bootstrap retries, and lazy re-probe on miss, but if the registry is still empty, reseed_servers forces a full re-probe without shell access. Only ask the user to systemctl restart krill-mcp after reseed_servers returns 0 servers AND the Krill server appears to be up (check via the direct-to-krill fallback below).
- If the user names a Krill server (e.g. "check pi-krill-05") and it's not in
list_servers โ before giving up, try list_nodes with server: "localhost" (or look in list_servers for a localhost-hosted entry). The default seed is localhost:8442, so a krill-mcp co-installed with a krill server registers the host under that alias โ the user may have named the box thinking of it as the Krill server while the MCP only knows it as localhost. If that succeeds, tell the user you resolved their hostname to the MCP's local entry so they're not surprised.
- If MCP tools are not configured in the session, use the Direct-to-MCP JSON-RPC fallback (
references/mcp-tools.md โ "Direct-to-MCP JSON-RPC fallback") โ POST JSON-RPC envelopes to http://<host>:50052/mcp with Accept: application/json, text/event-stream and the bearer. This gives you every MCP tool including writes, not just reads. Then tell the user how to add the Custom Connector so it works natively next time.
- If
krill-mcp itself is wedged (service stopped, or unreachable) but the underlying Krill server on :8442 is up, fall back to hitting the Krill REST API directly โ curl -sk -H "Authorization: Bearer $TOKEN" https://<host>:8442/nodes. Reads + generic node upserts work; snapshot writes do NOT (no /data, /snapshot, /data/record route exists โ they 404). Details in references/mcp-tools.md โ "Direct-to-Krill fallback".
- Translate type strings like
krill.zone.shared.KrillApp.DataPoint into the human catalog entry from references/node-types/INDEX.md when explaining results.
For SVG dashboard requests (create / improve)
Key invariant: DiagramMetaData.source is a URL, not inline SVG. Krill stores the SVG as a file on the server and renders it by fetching that URL. The create_diagram and update_diagram tools handle the full two-step (upload file โ post node with URL) โ don't try to cram SVG markup into source yourself.
- Call
list_nodes to enumerate every node the user might want on the dashboard (DataPoints, Graphs, Triggers, Executors โ whatever lives under the target project). Capture each one's id, name, and type.
- Read
references/dashboard-conventions.md โ the anchor contract (<rect id="k_<node-uuid>" fill="none"/>, no inner content, Krill overlays live UI), DiagramMetaData.anchorBindings: Map<String,String>, the URL-not-inline contract, runtime behavior.
- Pick the parent project. Diagrams must be children of a
KrillApp.Project container.
- Call
list_projects first. If exactly one project exists, use it (announce the choice). If several, ask the user which one. If none exist, offer to create one and call create_project with a sensible name.
- Ask the user (or infer) the layout: floor plan, equipment diagram, grid of tiles, themed illustration. Remember: you only decide where each node sits and how big โ the client renders everything inside the anchor. Default to a clean dark-background tile grid if no preference.
- Stage the SVG in
/tmp/<slug>.svg where <slug> is the lowercase_snake_case of the diagram name. Writing a real file first makes it easy to re-read, diff across iterations, and hand-inspect before shipping โ nothing is committed until you call create_diagram. Use the same tmp path across edits so intermediate work isn't lost.
- Emit one bare
<rect id="k_<node-uuid>" x=".." y=".." width=".." height=".." fill="none"/> per bound node โ no inner text, no placeholder graph, no tile chrome on the rect. Build anchorBindings as {"k_<uuid>": "<uuid>", ...}.
- Call
create_diagram with {projectId, name, source: <contents of /tmp/<slug>.svg>, anchorBindings}. The tool always uploads the file and constructs the URL; filename defaults to slug(name)+".svg". Pass uploadFileName only to override the default filename.
- For improving an existing diagram: call
get_diagram, save the current svg to /tmp/<slug>_before.svg, edit into /tmp/<slug>.svg, reason about what to change (layout tweak, new anchors for newly-created DataPoints, visual cleanup), then call update_diagram with {diagramId, source: <new markup>, anchorBindings?}. Passing source re-uploads to the same filename the current URL references. Preserve anchor ids that are still in use โ changing them without rewriting anchorBindings breaks the live overlay.
- Round-trip verify every write. After
create_diagram or update_diagram, call get_diagram and diff the returned svg + anchorBindings against what you sent. The tool response alone is not authoritative โ a v0.0.5 bug had update_diagram reporting success while silently dropping anchorBindings. v0.0.6 fixed that occurrence, but keep the round-trip as defense-in-depth. If the diff shows a mismatch, use the direct PUT / direct POST recovery recipes in references/mcp-tools.md โ "Recovery: write didn't land".
- For large SVGs (>~50 KB) โ e.g. after Inkscape text-to-path flattening โ skip the
source parameter in update_diagram and direct-PUT the file via curl -k -X PUT https://<host>:8442/project/<id>/diagram/<file>.svg instead, then call update_diagram only with the metadata you want changed (e.g. anchorBindings). Passing 200 KB of SVG through a tool call burns conversation tokens for no benefit. See references/dashboard-conventions.md โ "Small facts worth knowing" for the exact recipe.
For "which node type should I use" / "how do I set up X" (non-diagram)
- Skim
references/node-types/INDEX.md, narrow to 1โ3 candidates by role and one-liner.
- Read the full JSON spec for each candidate โ pay attention to
llmConnectionHints (what parents/children are valid), llmSideEffectLevel, llmInputs/llmOutputs, and the llmExamples.
- Recommend a single best-fit type with a short rationale. Mention valid parent/child wiring so the user knows where it slots into their tree.
- Call
create_node to stand up the node on a server, passing {server?, type, parent, name?, meta?}. The type accepts either the short name (KrillApp.DataPoint) or the FQN. Use list_node_types (or the bundled references/node-types/ specs) to see valid parent/child relationships and the default meta skeleton for each type. For specialized flows โ Projects and Diagrams โ keep using create_project / create_diagram (the diagram tool handles the SVG upload + URL computation that create_node doesn't).
For "build this tree on my server" (multi-node authoring)
- Discover:
list_servers โ list_nodes on the chosen server to find the root/parents that already exist.
- Consult
list_node_types (or references/node-types/INDEX.md) and resolve the user's description to concrete KrillApp.<Type> values. Validate each pair against the validParentTypes / validChildTypes in the registry before building.
- Resolve the parent. First, mirror the existing tree โ
list_nodes and look at where DataPoints, Triggers, etc. already sit on this swarm. The server is permissive about parent types (create_node warns but doesn't refuse a parent outside validParentTypes), so projects routinely organise DataPoints under a KrillApp.Project or nest DataPoints under other DataPoints to express a topical hierarchy (pi-krill-05 does both). When no precedent exists, fall back to the catalog: pick the first type from the target's validParentTypes for which the server has exactly one matching node. If none match, walk to the next valid parent type. If multiple match, ask the user which one. Catalog defaults worth memorizing โ these are the typical placement, not server-enforced rules:
KrillApp.DataPoint โ typically parented by KrillApp.Server (or KrillApp.Server.SerialDevice for a sensor wired to a serial device). The server also accepts KrillApp.Project (topical grouping) and KrillApp.DataPoint (composite readings, e.g. nitrate โ nitrate ppm); mirror the existing tree's pattern when adding new DataPoints rather than "correcting" it.
- Everything under a Diagram/TaskList/Journal/Camera โ parent is the containing
KrillApp.Project.
- Triggers โ parent is the
KrillApp.DataPoint they watch (or the shared KrillApp.Trigger container under that DataPoint, when one already exists).
- Executors โ parent is the Trigger (or the shared
KrillApp.Executor container under that Trigger) that fires them.
- Filters โ parent is the
KrillApp.DataPoint.Filter container under the DataPoint.
- Build top-down, parent-first. Each
create_node call returns the new nodeId; use that as the parent for its children. Example chain: KrillApp.DataPoint on the server โ KrillApp.Trigger.HighThreshold on the DataPoint โ KrillApp.Executor.OutgoingWebHook on the Trigger. Remember the parent is only the default source โ the tree is organization, the wiring is flow.
- Overlay type-specific fields via the
meta argument โ e.g. {"dataType": "DOUBLE", "unit": "ยฐC", "precision": 1} for a temperature DataPoint, {"snapshot": {"timestamp": 0, "value": "100"}} for a HighThreshold (trigger/filter thresholds live in meta.snapshot.value โ TriggerMetaData and FilterMetaData have no separate value field). Unknown keys are silently dropped by the server (ignoreUnknownKeys = true), so extras are safe but typos go unnoticed โ stick to the field names in the MetaData classes. If a meta field can't be determined at creation time, create the node first and then call update_node {id, meta: {expression: "*/5 * * * * *"}} to set it โ the update propagates to connected clients via SSE so the UI reflects the change live.
- Wire the flow. The server's creation default (parent โ child's
sources) covers straight chains. For anything else โ a Calculation's formula variables, a DataPoint storing an executor's output, cross-branch or cross-server observation โ call set_node_wiring on the observer with sources / inputs / invocationTriggers. A Calculation's formula references its inputs with bracket tokens [<hostId>:<nodeId>] (the NodeIdentity string form); every referenced node must be in inputs.
- Verify with
get_node after each create. The create_node response echoes what was sent, not what persisted; a round-trip read is the only ground truth. The server also fills in defaults for meta fields you omitted (e.g. a DataPoint you posted without a snapshot comes back with snapshot: {timestamp: 0, value: ""}) and applies the parent-as-source default โ check meta.sources / meta.invocationTriggers on the read-back to see the wiring you actually got.
For "record values to a DataPoint" (single value or a backfill series)
- Get the target DataPoint id (via
list_nodes type=DataPoint or straight from the user).
- Call
record_snapshot with either {id, value, timestamp?} for a single reading or {id, snapshots: [{timestamp, value}, ...]} for a series. timestamp is epoch milliseconds. (id is the DataPoint's UUID โ same arg name as get_node / read_series / delete_node.) If MCP tools aren't in-session, snapshot writes have no Krill REST endpoint โ don't probe /data, /snapshot, /data/record, etc. on :8442 (they 404). Use the Direct-to-MCP JSON-RPC fallback in references/mcp-tools.md to reach record_snapshot through http://<host>:50052/mcp.
- Values are validated client-side against the DataPoint's
dataType: TEXT non-empty, DIGITAL โ {0, 1} (booleans auto-map to 0/1), DOUBLE parseable, JSON non-empty. COLOR values are the decimal string of a 24-bit RGB integer โ (R<<16)|(G<<8)|B, each channel 0โ255, no alpha. Examples: red "16711680" (0xFF0000), yellow "11778048" (0xB3B800), white "16777215", black "0". Don't pass hex strings like "#B3B800" or CSS names โ they'll fail validation. Alpha is never stored; the Krill client reconstitutes opaque alpha at render time. When in doubt, mirror the existing value โ get_node on a COLOR DataPoint and copy whatever string you see in meta.snapshot.value. If validation fails for any snapshot in a batch, nothing is posted โ the tool refuses to half-apply a series.
- Each POST returns 202 Accepted before the server finishes ingesting. Two rules for verifying with
read_series:
- Wait ~1.5 seconds before the first
read_series call, or be prepared to retry once. The ingest pipeline (scope.launch on the server) commonly returns 0 snapshots on an immediate follow-up read even on bare DataPoints with no filters โ a short delay or one retry reliably recovers. Don't interpret an empty first read as "the write failed" unless the retry also returns empty.
- Persistence-verification is mandatory when the DataPoint has a
DiscardAbove / DiscardBelow / Deadband / Debounce filter wired into its meta.inputs โ those can silently drop a snapshot for good. (Filters are evaluated from the DataPoint's inputs; a filter node merely parented under the DataPoint but absent from inputs does nothing.) On a bare DataPoint, verification is optional but still a useful round-trip.
Topology, auth, limits
- Two ports, two roles.
krill-mcp runs on :50052/mcp over plain HTTP. The underlying krill server runs on :8442 over HTTPS with a self-signed cert. Both accept the same PIN-derived bearer โ so when krill-mcp is wedged, you can fall back to curl -sk https://<host>:8442/... with the same token.
- Where to find the bearer token (in order of convenience):
- On the krill-mcp host:
sudo krill-mcp-token prints the connector URL + bearer.
- On any machine with
krill installed: ~/.krill/pin_token holds the 64-char hex bearer. Use as-is.
- On a server:
/etc/krill/credentials/pin_derived_key (mode 0400, owned by krill:krill).
- The write surface covers any node type, DataPoint value writes, delete, node action, and observer wiring. Project/Diagram helpers:
create_project, list_projects, create_diagram, update_diagram, get_diagram, upload_diagram_file, download_diagram_file. Generic node authoring: create_node, list_node_types. DataPoint time-series writes: record_snapshot. Cascading delete: delete_node. Observer wiring: set_node_wiring sets meta.sources, meta.inputs, meta.invocationTriggers, and/or meta.nodeAction on any node type โ every node implements SourceMetaData. There is no targets field: wiring lives on the node that observes, so to make B react to A you update B. Node action verb (narrow): set_node_action sets only meta.nodeAction (EXECUTE or RESET) on any node; prefer set_node_wiring when also setting wiring fields. Read all wiring via get_node โ meta.sources / meta.inputs / meta.invocationTriggers / meta.nodeAction.
- Diagrams must live under a Project. Never call
create_diagram with a projectId you haven't verified exists โ call list_projects first, or create one with create_project and reuse the returned id.
- Registry is seed-config-driven and bootstrap-only. There is no
add_server MCP tool. To add a Krill server to an MCP install, edit /etc/krill-mcp/config.json's seeds array and sudo systemctl restart krill-mcp. The server argument on tool calls only resolves servers already in the registry โ passing a new hostname won't register it.
- Auth is one shared bearer token per swarm. Anyone holding it can read everything AND write Diagrams; treat it like a household password.
- Self-signed TLS to Krill. The MCP daemon (and your fallback
curl -k) trusts any cert presented by Krill; security comes from the bearer token. Don't suggest TLS-pinning workarounds.
- Side-effect levels matter. Before recommending an
executor or action node, look at llmSideEffectLevel in its JSON โ high means real-world side effects (sends mail, hits webhooks, runs Python, controls hardware). Make the user explicitly confirm before proposing those.
House style
- Be concrete: when explaining a node, cite its full
KrillApp.<Type> name and link it back to its catalog entry.
- Treat node UUIDs as opaque โ show them when needed, never invent them.
- Prefer a small number of well-explained nodes over a sprawling tree. Krill is meant to be readable.