Build
Hands-on tutorials and practical guides for developers.

Swap the Sensor, the Control Stays — How Not to Build a Greenhouse Twice
The real reason greenhouse controllers are expensive is that sensors and control are wired 1:1 inside the code. Let the nodes declare themselves and write rules by kind rather than by model, and swapping the temperature probe leaves nothing to fix. A record of running two different greenhouses on the same server and the same rules.
One server stood up beside the payment terminal already in the shop, with a tablet, a PC and a kitchen screen attached. No app was built for any of the three, and each became a kiosk, a POS and a kitchen display. The card authorisation path was not touched by a single line. Full code, run log, and the three screens exactly as rendered.
The STM32H723 over USB serial, the ESP32 discovered by mDNS and attached over Wi-Fi TCP. Each board hands over its own screen definition, and pressing a button turns a real LED on. The client code does not differ by a character between the two. No simulator — in this piece alone, verification fails without hardware.
Every screen in this series learned by asking. A screen in your hand can do that; a kitchen screen bolted to a wall cannot — it either asks constantly or is late. After subscribing, the screen made zero tool calls while three orders arrived.
Writing about retries is mostly written from the caller's side. This piece counts from the receiving side. Four callers got through a 700-millisecond outage two different ways, and both eventually succeeded. What differed is how many times the gateway was hit meanwhile — 64 and 25.
Two people take the same slot at the same moment. Described in words, the two handlers do exactly the same thing — look whether it is free, and if it is, take it. One of them confirms to both people; the other does not. The difference is one await in the middle.
Two people open the same screen. The void button is visible to both. When the assistant presses it the server refuses, and the refusal goes into the ledger. The screen file is the same file, and we printed its hash into the log.
Every sample in this series so far kept its screen files next to the app. This client has not a single screen. It asks the server what to draw, draws it, and asks again when the server says it changed. No reinstall, no restart, no build.
Type "save that to my notes" into Claude Desktop and a few-dozen-line server on my laptop executes. No store, no deployment — one line of config. We build that round trip from beginning to end and make it check itself with dart test.
Screen course, final part. conditional splits the screen, list builds rows without knowing how many, and sixteen pieces across three tracks meet.
A chip in February, instruments in March, a dashcam in April, a greenhouse in May. Each has its own app and knows nothing about the others. Yet the code that draws the screen is byte-identical across all twenty-seven copies. Not the same version — the same file.
Screen course, part 4. There is no code in onTap, only a tool name. Whether that name actually reached the host cannot be seen in a picture, so the harness presses the buttons and writes down what arrived.
Screen course, part 3. One `{{now}}` splits the screen from the value. Whether it really split is settled by rendering the same file twice under two different states.
Screen course, part 2. Placing several things looks like it needs coordinates, but linear takes only a direction and a gap. Why avoiding coordinates is a condition here, not a taste.
The sensors on a greenhouse are all different. One measures in Celsius, one in Fahrenheit, one in ppm. Once the nodes declare themselves and the rules speak in kinds instead of model numbers, the probe could be swapped and two nodes added without a character changing in the server code or the rules.
Screen course, part 1. What exactly was that blob the server sent in client part 4. It starts with one four-line JSON file and one host that never looks inside it.
Last in the mcp_client course. Subscribe, get told, then read. That polling costs resources is secondary — the real problem is that when a value changes twice between two polls, the middle is never seen.
Fourth in the mcp_client course. Read the screen definition from the server and hand it to a runtime. This file holds not one line about what the desk looks like — and that is checked.
A fleet manager needs plenty from a dashcam besides footage. Trip logs, card space, impact sensitivity. So the usual build is one API that returns everything with a permission check in front of the video. Delete that check and it's over. This app didn't lock the video tool — it never made one.
Third in the mcp_client course. Call a tool and read the answer. The result is a content list rather than a value, and a server's refusal does not arrive as a throw — so it has to be read, not caught.
Second in the mcp_client course. Ask for the tool and resource lists. Two lines, and whether they exist decides between "a client for that server" and "a client that stands in front of any server."
First in the mcp_client course. The client attaches to the server. What to notice is not the connection code but what the transport turns out to be — not a socket to open but a command to launch, which is why changing transports later becomes changing one line.
Last in the mcp_server course. The screen stops asking every second. The server tells it, and the notification carries the uri and no value. And nothing at all goes to a client that did not subscribe.
Instruments already know how to talk. The problem is that three vendors answer three ways — exponent notation, a carriage return, a value with the unit glued on. Those three were made one shape and put on one screen. And the screen shows the number the meter said, not the number the knob was set to.
Fifth in the mcp_server course. A waiting count in a variable dies with the process. It gets written to a file — the whole file, every time. Slower than a partial update, and it never leaves a half-written record.
Fourth in the mcp_server course. The server holds the screen as a file and hands it over. Edit the file and the next connection is different, with no rebuild and no reinstall — as long as it is read on every request.
Third in the mcp_server course. An input schema goes on, and a handler that does not trust it. Including why "admit 0" and "admit 99" are refused for two different reasons.
Putting a screen on an eval board usually means adding a graphics stack and reflashing. Instead the board hands over its capabilities as tools and its screen as a resource. Attached to an STM32H723 on the desk, turned the LED on, and rendered the 656-byte screen the board handed over.
Second in the mcp_server course. One tool gets registered. It is four pieces — name, description, input schema, handler — and three of them are read not by us but by whoever calls. So writing the description well is worth more than writing the handler well.
Two weeks ago the number board got buttons. Press one and the number moves. But who holds that number is the question. If the screen holds it, a second panel shows a different number. If the counter holds it, you can unplug the panel and plug it back in and the number is the same.
The panel's capabilities as tools, the screen as a definition, the runtime as the renderer. We lay out a sample where that whole chain actually runs, from beginning to end, and show how the packages mesh. The screens were not drawn — they were really rendered and captured.
Building a waiting-room number board. The screen definition is nine lines, and those nine lines are not inside the client. The server holds them as a file and hands them over on connect. That is what "no build, no install" means.
First in the mcp_server course. No tools, no resources — just a server standing up. There is one thing to confirm here: does a client connect and get an answer to initialize. And one rule that has to be set now or it breaks later.