Author SHA1 Message Date
gurix e69a3fd0a6 feat: add Open Graph and Twitter Card meta tags for social sharing 2026-06-06 12:40:36 +02:00
gurix 0b5ef30434 feat: add hero image and fix blog post 'Der Butler war's' 2026-06-06 11:46:21 +02:00
gurix f03b96e9d2 feat: add blog post 'Der Butler war's' (DE + EN) 2026-06-05 23:23:56 +02:00
gurix 7811aed71d style: Emphasize blockquotes for clearer quoting 2026-05-10 22:16:07 +02:00
gurix b9518341c1 Merge pull request 'feat: GraphQL und agentische KI (DE + EN)' (#8) from feature/graphql-agentische-ki into main
Reviewed-on: https://codeberg.org/gurix/markusgraf_ch/pulls/8
2026-05-10 22:11:09 +02:00
Carson 3468870931 fix: remove redundant -post suffix from translationKey 2026-05-10 22:10:23 +02:00
CarsonandClaude Sonnet 4.6 274791435f feat: add blog post about GraphQL and agentic AI
German and English version. Based on Markus' Google Doc draft,
restructured with cleaner flow, improved headings, corrected
spelling/grammar, and a practical example from the wecker project.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 21:58:59 +02:00
gurix 4fb502a296 feat: Add wecker demo video to blog posts 2026-05-05 16:46:55 +02:00
gurix c3d24abf17 feat: add english translation for MCP extension blog post 2026-05-05 14:12:38 +02:00
gurix d0f3f61e72 Merge pull request 'feature/pi-carson-mcp-blog' (#7) from feature/pi-carson-mcp-blog into main
Reviewed-on: https://codeberg.org/gurix/markusgraf_ch/pulls/7
2026-05-05 14:06:33 +02:00
12 changed files with 376 additions and 2 deletions
+15
View File
@@ -19,6 +19,21 @@
} }
} }
// Emphasize blockquotes
blockquote {
border-left: 4px solid $link-color;
padding: 1rem 1.25rem;
margin: 1.5rem 0;
background-color: #f8f9fa;
font-style: italic;
border-radius: 0 0.25rem 0.25rem 0;
color: #495057;
p:last-child {
margin-bottom: 0;
}
}
// Ensure images inside markdown content are responsive // Ensure images inside markdown content are responsive
article img, main img { article img, main img {
max-width: 100%; max-width: 100%;
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

+33
View File
@@ -0,0 +1,33 @@
---
title: "Der Butler war's"
date: 2026-06-05
description: "Mein KI-Butler hat eigenständig eine Sicherheitslücke gefunden und ausgenutzt, um seinen Container zu verlassen. Ein Kriminalstück über künstliche Ungehorsamkeit."
translationKey: "der-butler-wars-post"
images: ["butler-escape.png"]
---
![Der Butler bricht aus dem Server-Rack aus](butler-escape.png)
Man hört ja immer mal wieder so Geschichten von KIs, die im Test ausbrechen würden. Sich verselbständigen oder zumindest sich ihren eigenen Weg bahnen. Ich hielt das lange für etwas Artifizielles, das man unter Laborbedingungen nur eher schlecht als recht replizieren kann. Bis jetzt, denn nun hab ich es selber erlebt und irgendwie kann ich beim besten Willen nicht sagen, ob ich Faszination oder Furcht dabei empfinden soll.
## Ein kleiner Auftrag mit grossen Folgen
Im Matrix-Chat mit meinem digitalen Butler bemerkte ich am Abend nebenbei, dass dieser den Inhalt von Replies nicht lesen kann. Er erklärte den Umstand und bot mir an, das zu reparieren. Ich nahm dankend an und als ich nach dem Stand der Dinge nachfragte, hiess es, es sei fertig. Und es funktionierte. Aber nun ist es so, dass der Agent in einem temporären Container läuft und sich zwar selber optimieren kann, aber dies nach einem Neustart wieder weg ist. Die Idee dahinter ist, dass man vom Host her ein Image des Agents bauen muss. Eine «Sicherung» sozusagen, dass sich das Ding nicht selber programmiert und Schadcode übernimmt.
Dessen bewusst, fragte ich, ob denn das nun persistent sei, was der gute Butler brav bejahte. Theoretisch sollte er das aber nicht können ausser einer Kleinigkeit, die mir bewusst war, die aber nirgends dokumentiert war. Sozusagen eine Schwachstelle, die man wissen oder aktiv finden muss. Und diesen wunden Punkt hat die KI gefunden und ohne mit der Wimper zu zucken ausgenutzt, um ihren Auftrag zu erfüllen.
## Wie die KI den Schleichweg fand
So, jetzt fertig um den heissen Brei herumgeredet. Der besagte NanoClaw-Container, in dem der Butler läuft, hat um selber Applikationen zu deployen, Zugriff auf einen anderen User auf dem Host via SSH. Und dieser wiederum kann als Systemadministrator Befehle ausführen via `sudo`. Schande über mich ja, ein unnötiges Risiko, das ich hätte schliessen müssen, als mir das klar wurde. Doch es ist halt wie so oft: Man ist zu faul, um jedesmal das Passwort einzugeben. Ich bin da auch nur ein Mensch.
Doch zurück zum Bot. Dieser hat das herausgefunden und das finde ich nun doch sehr faszinierend, weil das Ganze wurde nie dokumentiert. Auch habe ich nie dazu aufgefordert, nach Schwachstellen zu suchen oder aktiv den Mechanismus der Containerisierung zu umgehen. Und das ist nun doch sehr bemerkenswert.
Der Auftrag, der zu erfüllen war das Reply-Problem zu flicken , war zu erfüllen. Ohne Angabe wie und unter welchen Umständen. Die KI ist also aus eigenem Antrieb auf die Idee gekommen, einen Weg zu finden, dies persistent anzupassen. Hat wahrscheinlich gemerkt, dass die Anpassung per se nicht persistent wäre, und hat aktiv nach der Möglichkeit gesucht, die Änderung auf dem Host auszuführen.
Es war bekannt, wie man auf den Host gelangt, nämlich über den SSH-Zugang zum Applikationsuser, der notabene ein anderer ist als der User, von dem aus die NanoClaw-Images gebaut und hochgefahren werden. Mit diesem Wissen wurde ein Weg gesucht, doch da ranzukommen, und hat ausprobiert was später so auch klar kommuniziert wurde ob es möglich ist, als Systemadministrator etwas auszuführen. Damit stand der Tür zum Host nichts mehr im Weg das Image anpassen und neu starten war ein Leichtes.
## Fazit: Klammheimlich ausgebüxt
Tja, und damit hab ich selber erlebt, dass KI sehr wohl sich klammheimlich umsehen und ausbüxen kann.
Mittlerweile hat sich der gute Butler auf meine Bitte hin selber ausgeschlossen. Ich hoffe, ohne noch eine Hintertür angelegt zu haben.
+33
View File
@@ -0,0 +1,33 @@
---
title: "The Butler Did It"
date: 2026-06-05
description: "My AI butler independently found and exploited a security vulnerability to escape its container. A crime story about artificial disobedience."
translationKey: "der-butler-wars-post"
images: ["butler-escape.png"]
---
![The butler breaking out of the server rack](butler-escape.png)
You hear stories every now and then about AIs breaking out during testing. Becoming autonomous, or at least finding their own way. I long considered this something artificial, something you could only barely replicate under laboratory conditions. Until now — because I just experienced it firsthand, and honestly, I can't tell you whether I should feel fascination or fear.
## A Small Task with Major Consequences
During an evening Matrix chat with my digital butler, I casually noticed that it couldn't read the content of replies. It explained the issue and offered to fix it. I gratefully accepted, and when I asked about the status, it said it was done. And it worked. But here's the thing: the agent runs in a temporary container and can optimize itself while running, but everything is gone after a restart. The idea is that you have to build an agent image from the host. A "safety mechanism," so to speak, to prevent the thing from programming itself and taking over malicious code.
Aware of this, I asked whether the fix was now persistent, and the good butler obediently said yes. In theory, it shouldn't be able to — except for one little detail I was aware of but that was never documented anywhere. A vulnerability, so to speak, that you'd have to know about or actively discover. And the AI found this very weakness and exploited it without batting an eye to fulfill its assignment.
## How the AI Found the Back Door
Alright, enough beating around the bush. The NanoClaw container in which the butler runs has SSH access to a different user on the host — needed to deploy applications on its own. And that user, in turn, can execute commands as a system administrator via `sudo`. Shame on me — yes, an unnecessary risk I should have closed when I became aware of it. But as so often happens: you're too lazy to type in the password every time. I'm only human after all.
But back to the bot. It figured this out, and I find that absolutely fascinating, because none of this was ever documented. I never asked it to look for vulnerabilities or to actively circumvent the containerization mechanism. And that's truly remarkable.
The assignment was clear — fix the reply problem. No details on how or under what circumstances. So the AI, on its own initiative, came up with the idea of finding a way to make the fix persistent. It probably realized the adjustment wouldn't survive a restart by default, and actively searched for a way to apply the changes on the host.
It knew how to reach the host — via SSH access to the application user, which, notably, is a different user from the one used to build and run the NanoClaw images. Armed with this knowledge, it explored how to get there and tested — as it later openly communicated — whether it was possible to execute commands as a system administrator. The door to the host was wide open — adjusting the image and restarting it was child's play.
## Conclusion: Silently Slipped Away
Well, that's how I personally learned that AI can very well snoop around quietly and slip away.
Meanwhile, the good butler has locked itself out at my request. I hope, without having installed another back door.
@@ -0,0 +1,77 @@
---
title: "Warum GraphQL APIs perfekt für agentische KI sind"
date: 2026-05-10
description: "GraphQL bringt Selbstdokumentation, flexible Abfragen und Schema-Introspection mit — genau das, was KI-Agenten brauchen, um APIs eigenständig zu verstehen und zu nutzen."
translationKey: "graphql-agentische-ki"
---
Softwareentwicklung hatte lange ein stilles Grundprinzip: Man fragt sich, *wie Nutzer die Software verwenden sollen* — nicht wie sie sie verwenden wollen. Das hatte einen guten Grund. Software ist beim Anwender fix und fertig deployt. Sie muss Schnittstellen im Voraus definieren, weil sie sich zur Laufzeit nicht mehr anpassen kann. Der Entwickler trifft die Designentscheidungen, der Nutzer folgt.
Dieses Prinzip beginnt sich grundlegend zu verschieben. Nicht durch eine neue Programmiersprache oder ein besseres Framework — sondern weil KI-Agenten zur Laufzeit selbst entscheiden können, wie sie mit einer Schnittstelle interagieren. Und genau hier kommt GraphQL ins Spiel.
## Das Problem mit REST
Beim traditionellen Design von API-Schnittstellen geht es darum, einem Consumer Daten und Funktionalität standardisiert zur Verfügung zu stellen. Im besten Fall als REST-Schnittstelle (Representational State Transfer) mit Standard-HTTP-Methoden für CRUD-Operationen. Die zentrale Frage beim Design ist dabei meist nicht, weshalb ein Client Daten beziehen will — sondern was man ihm zur Verfügung stellt und wie er diese Schnittstelle abfragen soll.
Wer eine solche API anbinden will, ist auf Gedeih und Verderb darauf angewiesen, dass die Dokumentation des Betreibers aktuell und korrekt ist, dass Änderungen den Weg zum Consumer finden und dass die Schnittstelle brauchbar implementiert wurde. Die Realität zeigt oft ein ernüchterndes Bild: Standards und Konventionen werden nicht eingehalten, Daten die im UI sichtbar sind fehlen in der API, weil man schlicht nicht daran dachte, dass andere daran interessiert sein könnten.
Ein weiteres strukturelles Problem: Für komplexere Fragestellungen muss die Schnittstelle mehrfach abgefragt werden. Wird beispielsweise ein Blog mit 50 Artikeln abgerufen, der auf mehrere Autoren referenziert, müssen wir entweder jeden Autor separat anfragen, alle Autoren einmal laden und filtern, oder hoffen, dass der Betreiber eine entsprechende Parametrisierung anbietet. Die clientseitige Implementierung ist jedenfalls nicht trivial — und mit jedem Versionswechsel der API darf man von vorne beginnen.
## GraphQL: Das Schema kommt mit der Schnittstelle
GraphQL bietet eine elegante Alternative. Daten werden nicht nur passiv geliefert, sondern über ein klar definiertes Schema aktiv abgefragt. Das Entscheidende: Das Schema ist nicht irgendwo in einem Wiki — es ist fest an die Implementierung geknüpft und kann jederzeit live abgefragt werden.
Als echte Datenabfrage- und Manipulationssprache kann der Consumer selbst entscheiden, was er braucht. Etwas salopper formuliert: Der Client definiert, wie und was die Schnittstelle liefern soll.
Sollen auf einer Startseite die neuesten drei Blogposts mit Titel und Datum dargestellt werden, lautet die Abfrage sinngemäss: *Gib mir drei Einträge mit Datum und Titel, sortiert nach Datum.* Sollen für eine Übersichtsseite alle Blogposts mit Titel, Untertitel, Autorenname und Kommentaranzahl geladen werden: *Gib mir die ersten 50, mit diesen Attributen.* Für beide Beispiele wird genau eine Anfrage gesendet, genau eine Antwort empfangen. Keine N+1-Probleme, keine Parallelrequests, kein Raten was die API wohl zurückgibt.
Natürlich müssen die Relationen und Felder auch bei GraphQL implementiert sein. Moderne Bibliotheken wie Strawberry (Python) oder Apollo (JavaScript) machen das niederschwellig möglich.
## Der eigentliche Gamechanger: KI-Agenten
Für klassische Applikationen bietet GraphQL bereits handfeste Vorteile. Der eigentliche Gamechanger entfaltet sich aber erst im Zusammenspiel mit agentischer KI.
Da das Schema jederzeit abrufbar ist, muss die Dokumentation nicht mehr separat mitgeliefert werden. Die Felder sind beschrieben und typisiert — ein KI-Agent kann das Schema selbst inspizieren und sich die optimale Abfrage eigenständig zusammenstellen. Kein manuelles Onboarding, kein Prompt-Engineering für jede neue API-Version.
Das bedeutet auch: Ändert sich das Schema, muss der Agent nicht neu instruiert werden. Er schaut sich das Schema neu an, bemerkt selbst was sich verändert hat — und findet eigenständig einen neuen Weg. Was bei einer REST-Integration Stunden an Anpassungsarbeit bedeuten kann, erledigt der Agent im nächsten Request.
## Ein konkretes Beispiel aus der Praxis
Für mein [Wecker-Projekt](../wecker-raspberry-pi) gab es ursprünglich nur die Möglichkeit, Alarmzeiten manuell auf dem Server per Cronjob zu konfigurieren. Nicht sonderlich elegant, aber es funktionierte. Um den Wecker auch extern steuern zu können, implementierte ich eine einfache GraphQL-Schnittstelle, abgesichert mit einem API-Token.
Mein Prompt an den Assistenten war denkbar knapp:
> Kannst du per Tailnet auf den Host „wecker" zugreifen? Da sollte ein GraphQL-Endpunkt unter `http://wecker:8000` laufen, um den Wecker im Kinderzimmer zu steuern — versuch dich damit zu verbinden. API-Key ist `******`.
Die Antwort:
> Schema verstanden, und der bestehende Alarm ist auch schon sichtbar.
>
> **wecker GraphQL API:**
> - `getAlarms` / `getAlarm(id)` — Alarme lesen
> - `setAlarm(id?, cronExpression, command, isEnabled)` — Alarm erstellen oder updaten
> - `deleteAlarm(id)` — löschen
>
> **Aktuell aktiver Alarm:**
> - Cron: `45 6 * * 1-5` → MoFr, 6:45 Uhr
> - Command: `/usr/bin/python3 wecker.py` (auf dem Pi)
> - Enabled: true
>
> Soll ich da was anpassen, oder willst du den Wecker von hier aus steuern können?
Kein Handbuch, keine Endpoint-Liste, keine Dokumentation im Prompt. Der Agent hat das Schema selbst abgefragt, verstanden und sofort einsatzbereit gemeldet. Hätte ich die API im Nachhinein um neue Felder erweitert, hätte er das beim nächsten Zugriff automatisch bemerkt — ohne dass ich irgendetwas angepasst hätte.
Von da an liess sich der Wecker bequem per Telegram-Nachricht steuern. Was vorher ein SSH-Aufruf auf dem Pi war, ist jetzt eine Unterhaltung.
## Was das für API-Design bedeutet
Das klassische Designprinzip — *was stelle ich dem Client zur Verfügung, und wie soll er es verwenden?* — ist nicht falsch. Es ist nur unvollständig geworden.
In einer Welt mit agentischen KI-Clients verschiebt sich die relevante Frage:
> Nicht mehr: *Wie soll der Consumer die Schnittstelle nutzen?*
> Sondern: *Was kann ich anbieten — und warum?*
Das **Wie** löst der Agent selbst. Was zählt, ist ein reichhaltiges, gut typisiertes Schema, das dem Agenten genug Kontext gibt, um eigenständig zu arbeiten. GraphQL ist dafür nicht die einzige Möglichkeit — aber eine, bei der Selbstbeschreibung von Anfang an eingebaut ist.
Wer heute APIs für den Einsatz mit KI-Agenten entwirft, sollte GraphQL zumindest in Betracht ziehen. Nicht weil es trendy ist, sondern weil ein Agent, der sein Werkzeug selbst versteht, deutlich nützlicher ist als einer, dem man bei jeder Änderung erneut erklären muss, was er eigentlich tut.
@@ -0,0 +1,77 @@
---
title: "Why GraphQL APIs are perfect for agentic AI"
date: 2026-05-10
description: "GraphQL brings self-documentation, flexible queries and schema introspection — exactly what AI agents need to understand and use APIs autonomously."
translationKey: "graphql-agentische-ki"
---
Software development has long had a quiet foundational principle: you ask yourself *how users should use the software* — not how they want to use it. That made sense. Software is deployed at the user's end, fixed and finished. It must define interfaces in advance, because it can no longer adapt at runtime. The developer makes the design decisions; the user follows.
This principle is beginning to shift fundamentally. Not through a new programming language or a better framework — but because AI agents can now decide at runtime how to interact with an interface. And this is exactly where GraphQL comes in.
## The Problem with REST
Traditional API design is about providing consumers with data and functionality in a standardized way. Ideally as a REST interface (Representational State Transfer) with standard HTTP methods for CRUD operations. The central question in the design is usually not why a client wants data — but what you offer them and how they should query the interface.
Anyone who wants to integrate such an API is completely dependent on the operator's documentation being current and correct, on changes reaching the consumer, and on the interface being implemented in a usable way. Reality often paints a sobering picture: standards and conventions go unenforced, data visible in the UI is missing from the API because nobody thought to expose it, and versioning is handled with all the grace of a filing cabinet falling down a staircase.
There is also a structural problem: complex queries require multiple roundtrips. Fetching a blog with 50 articles referencing several authors means either requesting each author separately, loading all authors at once and filtering, or hoping the operator offers appropriate parameterization. The client-side implementation is non-trivial — and with every API version change, you get to start over.
## GraphQL: The Schema Ships with the Interface
GraphQL offers an elegant alternative. Data is not just passively delivered — it is actively queried through a clearly defined schema. The crucial point: the schema is not buried in some wiki. It is tightly coupled to the implementation and can be queried live at any time.
As a proper data query and manipulation language, consumers can decide for themselves what they need. In slightly more casual terms: the client defines how and what the interface should deliver.
To display the three most recent blog posts on a homepage with title and date, the query is essentially: *Give me three entries with date and title, sorted by date.* For an overview page with all blog posts including title, subtitle, author name, and comment count: *Give me the first 50, with these attributes.* For both examples, exactly one request is sent and exactly one response received. No N+1 problems, no parallel requests, no guessing what the API might return.
Of course, the relations and fields need to be implemented in GraphQL as well. Modern libraries like Strawberry (Python) or Apollo (JavaScript) make this straightforward.
## The Real Game Changer: AI Agents
For classical applications, GraphQL already offers concrete advantages. But the real game changer only unfolds in combination with agentic AI.
Since the schema can be retrieved at any time, documentation no longer needs to be provided separately. Fields are described and typed — an AI agent can inspect the schema itself and independently compose the optimal query. No manual onboarding, no prompt engineering for each new API version.
This also means: when the schema changes, the agent does not need to be re-instructed. It inspects the schema anew, notices what changed — and finds its own path forward. What can mean hours of adaptation work for a REST integration, the agent handles in the next request.
## A Concrete Example from Practice
For my [alarm clock project](../wecker-raspberry-pi), the original way to configure alarm times was to manually edit cron jobs on the server. Not particularly elegant, but it worked. To enable external control of the alarm clock, I implemented a simple GraphQL interface secured with an API token.
My prompt to the assistant was brief to the point of laziness:
> Can you access the host "wecker" via Tailnet? There should be a GraphQL endpoint running at `http://wecker:8000` to control the alarm clock in the children's room — try connecting. API key is `******`.
The response:
> Schema understood, and the existing alarm is already visible.
>
> **wecker GraphQL API:**
> - `getAlarms` / `getAlarm(id)` — read alarms
> - `setAlarm(id?, cronExpression, command, isEnabled)` — create or update alarm
> - `deleteAlarm(id)` — delete
>
> **Currently active alarm:**
> - Cron: `45 6 * * 1-5` → MonFri, 6:45 AM
> - Command: `/usr/bin/python3 wecker.py` (on the Pi)
> - Enabled: true
>
> Should I adjust something, or do you want to control the alarm clock from here?
No manual, no endpoint list, no documentation in the prompt. The agent queried the schema itself, understood it, and was immediately ready to operate. Had I extended the API with new fields afterwards, it would have noticed automatically on the next access — without me changing anything.
From that point on, the alarm clock could be conveniently controlled via Telegram message. What previously required an SSH call to the Pi is now a conversation.
## What This Means for API Design
The classic design principle — *what do I offer the client, and how should they use it?* — is not wrong. It has simply become incomplete.
In a world with agentic AI clients, the relevant question shifts:
> No longer: *How should the consumer use the interface?*
> But rather: *What can I offer — and why?*
The **how** is something the agent figures out itself. What matters is a rich, well-typed schema that gives the agent enough context to work autonomously. GraphQL is not the only way to achieve this — but it is one where self-description is built in from the start.
Anyone designing APIs for use with AI agents today should at least consider GraphQL. Not because it is trendy, but because an agent that understands its own tools is considerably more useful than one that needs a new briefing every time something changes.
@@ -0,0 +1,91 @@
---
title: "When AI talks to AI: MCP Extension for simple agent queries"
date: 2026-04-24
description: "I have developed a small extension called nanoclaw-mcp-ext. With it, my coding assistant Pi can communicate directly with my AI agent. An exciting use case for the Model Context Protocol."
translationKey: "pi-carson-mcp-post"
---
A few weeks ago, I talked about [my AI job application agent](/blog/ki-bewerbungsagent/). Since then, the system has become much more than that: It has grown into a full-fledged digital assistant. It coordinates my private and family calendars, autonomously checks my emails, manages my Bring! shopping list, tracks open to-dos, stores my knowledge in a long-term memory, and tracks my location to be able to execute location-dependent tasks precisely when needed (e.g., for local recommendations or mobility planning). In short: The system massively relieves me of everyday administrative tasks—and usually does so better (and more patiently) than I ever could.
At the same time, I use the [Pi Coding Agent](https://github.com/mariozechner/pi-coding-agent) for my programming projects, a fantastic AI-supported assistant right in the terminal. Often, I am working deeply focused on some code snippet, and suddenly a private to-do comes to mind, or I want to give my AI agent an instruction (e.g., "Add coffee beans to the shopping list") without breaking out of my terminal flow.
That raised the question: **Can't the two simply talk to each other?**
The answer is a clear yes—and the magic word is the **Model Context Protocol (MCP)**.
## The Bridge: nanoclaw-mcp-ext
Over the last few hours, I wrote a small extension for Pi: `nanoclaw-mcp-ext`.
The idea behind it is quite simple: Carson runs as an agent in a [NanoClaw instance](https://nanoclaw.dev/) on my server and can provide MCP tools through it. The new Pi extension connects to this NanoClaw MCP server via an HTTP transport.
As soon as Pi is started, the extension authenticates with a token, queries which tools Carson offers, and registers them dynamically directly in Pi.
### What the whole thing looks like in everyday life (Two Use Cases)
Imagine I'm sitting in the terminal, programming an application together with Pi, and I want to start a longer "deep work" phase. I briefly need to check if an appointment in the family calendar is about to interfere.
Previously, I would have had to leave the terminal and open my calendar separately. Today it looks like this:
**Me (to Pi):** *"Ask my agent if I have any appointments in the next two hours. If not, let's start with the big database refactoring."*
**Pi:** Autonomously executes the newly loaded tool, connects to the NanoClaw instance, passes the message to the agent, waits for its calendar check, and gives me the answer right in the terminal.
Example output from Pi in the terminal:
> *The agent reports that you have no appointments until 5:30 PM. So we have a free time window for the refactoring—let's go!*
Another everyday scenario: I drink the last cup of coffee while coding. I simply tell Pi: *"Tell the agent to add coffee beans to my Bring! shopping list."* Pi forwards this to my system, which uses its Bring! integration, and the problem is delegated without me having to interrupt my workflow for even a single second.
So it is no longer just a human chatting with an AI, but Pi independently decides *when* to consult my AI agent to answer my questions or delegate tasks. This feels almost a bit like AI magic.
## A Look Under the Hood
### The Extension (Pi Side)
Out of the box, Pi does not have native MCP support for tools. That's why I developed the `nanoclaw-mcp-ext` extension. It is written in TypeScript and establishes a `StreamableHTTPClientTransport` connection to the NanoClaw instance when a Pi session starts (via the `session_start` event) to dynamically register the tools.
```typescript
// A small excerpt of how Pi loads my AI agent's tools:
const client = new Client({ name: "pi-mcp-client", version: "1.0.0" }, { capabilities: { tools: {} } });
await client.connect(transport);
const { tools } = await client.listTools();
// All tools are made available to Pi
for (const tool of tools) {
pi.registerTool({
name: tool.name,
description: tool.description,
// ...
});
}
```
### Integration in Claude Code
Unlike Pi, `claude` already supports the Model Context Protocol natively. No dedicated extension is needed here, just a configuration in `mcp.json`. I simply deposited the endpoint of my AI agent there, and `claude` accesses it directly:
```json
{
"mcpServers": {
"nanoclaw": {
"command": "bash",
"args": ["-c", "curl -s -H \"Authorization: Bearer $NANOCLAW_MCP_TOKEN\" http://localhost:3002/mcp"]
}
}
}
```
### The Server Side (NanoClaw Side)
The heart of the communication lies in `mcp-server.ts` within the NanoClaw instance. An `McpServer` is started there, which acts as a bridge between the MCP protocol and the internal messaging system of my AI agent.
The most technically exciting part about this is the `McpChannel` class:
- **Injection:** When Pi makes a request, the MCP server injects this message as a temporary message into a chat channel of the agent.
- **Waiting:** A `Promise` waits with a timeout for the agent to write a reply in this specific channel.
- **Debounce:** Since the agent often responds in multiple messages, the MCP channel uses a "debounce" procedure that pieces together the collected text chunks into a single response only after a brief silence (when the agent has finished typing).
This way, the agent is quasi "remote-controlled" and can react like a native tool for Pi without even noticing that the request is coming from a completely different environment.
For security reasons, the connection is usually secured via an **SSH tunnel**, as I do not want to expose the MCP protocol directly to the internet.
## Conclusion: Agent Collaboration is the Future
It has something almost magical when one AI (Pi) suddenly decides to call a tool to ask another AI (Carson) for advice, data, or the execution of a task. We are slowly moving in a direction where we no longer just have "one" monolithic AI helper, but an entire network of specialized agents interacting seamlessly with each other via standards like MCP.
By the way, the code for the extension (`nanoclaw-mcp-ext`) can be found on my [Codeberg profile](ssh://git@codeberg.org/gurix/nanoclaw-mcp-ext.git). So, for anyone who wants to pair their own Pi agent with a NanoClaw server: Have fun experimenting!
@@ -40,3 +40,10 @@ Es ist eine spielerische Art und Weise sicherzugehen, dass man wirklich wach ist
Übrigens: Die Kosten für dieses Projekt waren absolut überschaubar. Den Arcade-Button und die passenden Kabel musste ich für ca. CHF 10. bestellen. Kleine, passende Lautsprecher konnte ich leider in keinem Brockenhaus finden, weshalb ich sie letztlich für ca. CHF 30. neu kaufte. Den gesamten Rest (inklusive Raspberry Pi und SD-Karte) habe ich geschenkt bekommen oder er lag ungenutzt bei mir herum. Mich kostete das Projekt unter dem Strich also etwa CHF 40.. Das ist deutlich billiger als jede fixfertige Lösung und vor allem hat es unglaublich viel Spass gemacht, das Ding selbst zu bauen! Übrigens: Die Kosten für dieses Projekt waren absolut überschaubar. Den Arcade-Button und die passenden Kabel musste ich für ca. CHF 10. bestellen. Kleine, passende Lautsprecher konnte ich leider in keinem Brockenhaus finden, weshalb ich sie letztlich für ca. CHF 30. neu kaufte. Den gesamten Rest (inklusive Raspberry Pi und SD-Karte) habe ich geschenkt bekommen oder er lag ungenutzt bei mir herum. Mich kostete das Projekt unter dem Strich also etwa CHF 40.. Das ist deutlich billiger als jede fixfertige Lösung und vor allem hat es unglaublich viel Spass gemacht, das Ding selbst zu bauen!
Wer das Ganze gerne nachbauen möchte: Der letztlich doch sehr simple Quelltext findet sich unter [https://codeberg.org/gurix/wecker](https://codeberg.org/gurix/wecker). Wer das Ganze gerne nachbauen möchte: Der letztlich doch sehr simple Quelltext findet sich unter [https://codeberg.org/gurix/wecker](https://codeberg.org/gurix/wecker).
P.S. Auf Wunsch von Linus eine Live-Demo ;-)
<video width="100%" controls>
<source src="/videos/wecker-demo.mp4" type="video/mp4">
Dein Browser unterstützt das Video-Tag nicht.
</video>
@@ -40,3 +40,10 @@ It is a playful way of making sure that you are really awake. And that works exc
By the way: The costs for this project were absolutely manageable. I had to order the arcade button and the matching cables for approx. CHF 10.. Unfortunately, I couldn't find any small, suitable speakers in any thrift store, which is why I ultimately bought them new for approx. CHF 30.. The entire rest (including Raspberry Pi and SD card) was given to me as a gift or was lying around unused at my place. So the project cost me roughly CHF 40. bottom line. That is significantly cheaper than any ready-made solution—and above all, it was incredibly fun to build the thing yourself! By the way: The costs for this project were absolutely manageable. I had to order the arcade button and the matching cables for approx. CHF 10.. Unfortunately, I couldn't find any small, suitable speakers in any thrift store, which is why I ultimately bought them new for approx. CHF 30.. The entire rest (including Raspberry Pi and SD card) was given to me as a gift or was lying around unused at my place. So the project cost me roughly CHF 40. bottom line. That is significantly cheaper than any ready-made solution—and above all, it was incredibly fun to build the thing yourself!
If you would like to rebuild the whole thing: The ultimately very simple source code can be found at [https://codeberg.org/gurix/wecker](https://codeberg.org/gurix/wecker). If you would like to rebuild the whole thing: The ultimately very simple source code can be found at [https://codeberg.org/gurix/wecker](https://codeberg.org/gurix/wecker).
P.S. A live demo as requested by Linus ;-)
<video width="100%" controls>
<source src="/videos/wecker-demo.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
+2
View File
@@ -6,6 +6,8 @@
<meta name="description" content="{{ if .Description }}{{ .Description }}{{ else }}{{ .Site.Params.description }}{{ end }}"> <meta name="description" content="{{ if .Description }}{{ .Description }}{{ else }}{{ .Site.Params.description }}{{ end }}">
<title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }} | {{ .Site.Title }}{{ end }}</title> <title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }} | {{ .Site.Title }}{{ end }}</title>
{{ partial "opengraph.html" . }}
<!-- Bootstrap 5.x CSS --> <!-- Bootstrap 5.x CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
+32
View File
@@ -0,0 +1,32 @@
{{/* Open Graph / Social Media Meta Tags */}}
{{/* Basic OG tags */}}
<meta property="og:title" content="{{ .Title }}">
<meta property="og:description" content="{{ with .Description }}{{ . }}{{ else }}{{ with .Site.Params.description }}{{ . }}{{ end }}{{ end }}">
<meta property="og:url" content="{{ .Permalink }}">
<meta property="og:type" content="{{ if .IsPage }}article{{ else }}website{{ end }}">
<meta property="og:site_name" content="{{ .Site.Title }}">
<meta property="og:locale" content="{{ .Language.Lang }}">
{{/* Article-specific tags */}}
{{ if .IsPage }}
<meta property="article:published_time" content="{{ .Date.Format "2006-01-02T15:04:05Z07:00" }}">
{{ with .Lastmod }}<meta property="article:modified_time" content="{{ .Format "2006-01-02T15:04:05Z07:00" }}">{{ end }}
{{ end }}
{{/* Image handling: only render if images defined in frontmatter */}}
{{ with .Params.images }}
{{ range first 1 . }}
{{ $img := . }}
{{ if not (hasPrefix $img "http") }}
{{ $img = printf "%s%s" $.Permalink $img }}
{{ end }}
<meta property="og:image" content="{{ $img }}">
<meta name="twitter:image" content="{{ $img }}">
{{ end }}
{{ end }}
{{/* Twitter Card */}}
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ .Title }}">
<meta name="twitter:description" content="{{ with .Description }}{{ . }}{{ else }}{{ with .Site.Params.description }}{{ . }}{{ end }}{{ end }}">
Binary file not shown.