Erstellen Sie einen suchbaren Arbeitsjournal-MCP-Server in 190 Zeilen TypeScript
Ein kleiner MCP-Server für Markdown-Dateien: vier auf Intenten basierende Tools, beschreibungen im Prompt-Stil, ein stdio-Beispielklient, der gleichzeitig als Test dient, sowie echte Fallstricke.
Quellen: I Built an MCP Server That Keeps My Work Journal — Here’s Everything I Learned
Ein Model Context Protocol-Server mit etwa 190 Zeilen in TypeScript kann eine Ordnerstruktur mit Markdown-Dateien in ein suchbares Arbeitsjournal umwandeln, das von jedem KI-Assistenten gelesen und bearbeitet werden kann. Er eignet sich außerdem gut als erstes MCP-Projekt und zeigt, wie man einen Server ohne Editor demonstrieren kann.
Warum ein Tagebuch?
Tägliche Protokollierungsgewohnheiten halten selten länger als eine Woche an. Der KI-Assistent hingegen, der die Ressourcenkonflikte behoben, die Hälfte der Tests mitgeschrieben hat und jede Umstrukturierung verfolgt hat, weiß bereits, was den ganzen Tag über passiert ist. Disziplin war nie das eigentliche Hindernis. Das Protokoll befindet sich schließlich an einem anderen Ort als die Arbeit.
Um die Anordnung zu ändern: Anstatt dass eine Person ein Tagebuch führt, sollte der Assistent über Werkzeuge verfügen, um eines zu führen. MCP, der Standard, der es LLM-Klienten ermöglicht, externe Werkzeuge zu entdecken und aufzurufen, eignet sich hier perfekt. Ein dev-diary-Server, der einen einfachen Ordner mit Markdown-Dateien umschließt, umfasst alle grundlegenden Aspekte (Werkzeuge, Schemata, I/O-Transport, zustandsloses Speichern) in weniger als 200 Zeilen – was ihn zu einer geeigneten Lernmethode macht.
Das Tagebuch an sich ist nicht das Ziel. Es gibt bereits Tagebuch-Apps. Das Thema ist einfach, langweilig und allgemein verständlich, was es für ein Lernprojekt geeignet macht. Die eigentliche Frage ist, ob jemand einen MCP-Server für alles erstellen kann. Ja – und das Protokoll stellt sich als der einfachere Teil heraus. Entscheidend ist vielmehr, was abgebildet werden soll. Ein Fitness-Log, eine Leseliste, eine interne Wiki oder ein Smart Home können hinter einem MCP-Server liegen, solange Code sie lesen oder schreiben kann.
Die vier Werkzeuge
| Werkzeug | Zweck |
|---|---|
log_work | Fügt einen mit Zeitstempel und Tag versehenen Eintrag zum heutigen Datei hinzu |
search_diary | Volltextsuche in allen Einträgen |
daily_summary | Alles, was an einem ausgewählten Datum aufgezeichnet wurde |
stats | Gesamtwerte, Serien und häufigste Tags |
Die Speicherung ist absichtlich einfach gehalten: eine Markdown-Datei pro Tag mit solchen Einträgen.
## 14:32 #bugfix #websocket
Fixed the race condition in the WebSocket broadcast queue
## 16:10 #testing
Wrote E2E test covering two-client sync
Es gibt keine Datenbank. Die Dateien können mit grep durchsucht, gespeichert und manuell bearbeitet werden, und diese Entscheidung zahlt sich später aus.
Der Server wird aufgebaut
Die offizielle TypeScript SDK-Version (v2, die die Spezifikation vom 2026-07-28 umsetzt), bildet die Grundlage. Für die Installation sind das Server-Paket sowie Zod erforderlich:
npm install @modelcontextprotocol/server zod
Die Imports bringen die Serverklasse, den stdio-Transport sowie den Einstiegspunkt von Zod v4 mit:
import { McpServer } from '@modelcontextprotocol/server';
import { StdioServerTransport } from '@modelcontextprotocol/server/stdio';
import * as z from 'zod/v4';
Das Server-Objekt erhält einen Namen und eine Version:
const server = new McpServer({ name: 'dev-diary', version: '1.0.0' });
Jedes Tool wird mit einem Namen, einer Konfiguration und einem Handler registriert. Dies ist log_work:
server.registerTool(
'log_work',
{
description: 'Append a timestamped entry to the developer diary...',
inputSchema: z.object({
text: z.string().min(1),
tags: z.array(z.string()).optional(),
date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
}),
},
async ({ text, tags = [], date }) => {
// ...append to diary/YYYY-MM-DD.md...
return { content: [{ type: 'text', text: 'Logged.' }] };
},
);
Zuletzt: Verbinden über stdio:
await server.connect(new StdioServerTransport());
Das ist ein vollständiger Server. Vier Konzepte tragen dazu bei:
registerTool(name, config, handler)erstellt das Toolkatalog.inputSchemaist ein Zod-Schema. Das SDK wandelt es in ein JSON Schema für den Client um und validiert die Argumente vor dem Ausführen des Handlers.- Der Rückgabewert in der Form
{ content: [{ type: 'text', text: '...' }] }ist das, was das LLM als Ergebnis liest. - Der Transport,
StdioServerTransport(), ermöglicht es dem Client, den Prozess zu starten und über stdin sowie stdout JSON-RPC zu verwenden. Für den lokalen Einsatz sind weder Ports noch Authentifizierung erforderlich.
Beschreibungen sind Anweisungen
Das ist der größte Denkwechsel. Eine Toolbeschreibung dient nicht als API-Dokumentation für Menschen. Sie ist eine Anweisung, die das Modell liest, um zu entscheiden, wann und wie das Tool aufgerufen werden soll. Vergleichen Sie dazu eine beschreibungsartige Dokumentation:
// ❌ documentation-style
description: 'Appends an entry to the diary.'
mit einer prompt-basierten Variante:
// ✅ prompt-style
description: 'Append a timestamped entry to the developer diary for today.
Use this whenever the user says they finished/did/fixed something and
wants it recorded.'
Der zweite Teil gibt dem Modell an, wann es das Werkzeug verwenden soll. Sein Satz „Verwende dieses immer dann“ wandelt einen beiläufigen Kommentar darüber, endlich einen fehleranfälligen Test zu beheben, in einen Aufruf des Werkzeugs um – ohne explizite Anweisung. Die Beschreibungen der Parameter funktionieren genauso: Ein Hinweis wie Optional labels, z. B. ["bugfix","api"] zeigt durch Beispiel die erwartete Struktur auf.
Werkzeuge beziehen sich auf Fragen, nicht auf Operationen
Ein erster Entwurf mit CRUD-Tools (add_entry, update_entry, delete_entry) hat die falsche Struktur. Niemand bittet ein Tagebuch, Eintrag 47 zu aktualisieren – stattdessen wird gefragt, was mit dem WebSocket-Bug letzte Woche passiert ist. Daher folgt die endgültige Version dem ursprünglichen Ziel: search_diary findet Inhalte, daily_summary fasst einen Tag zusammen, stats liefert eine motivierende Übersicht und log_work dient als einziger Schreibwerkzeug. Die vier Tools überschneiden sich in keiner Hinsicht und beantworten jeweils eine Frage, die Entwickler tatsächlich stellen. Die Faustregel lautet: Ein Tool pro Nutzerfrage – nicht pro Datenoperation.
Demo ohne Editor
Ein schwierigeres Problem ist es, zu zeigen, wie ein MCP-Server funktioniert. Das Verbinden von Claude Desktop und das Aufnehmen eines Chat-Screenshots beweist nichts Reproduzierbares, hängt von einer geschlossenen Anwendung ab und lässt sich weder in CI-Umgebungen noch auf dem Rechner eines Lesers ausführen.
Die stille Stärke der Spezifikation kommt hier zum Tragen: MCP besteht lediglich aus Client und Server über stdio. Nichts hindert ein weiteres kleines Programm daran, sich auf die gleiche Weise wie ein Editor anzuschließen und den Server von Anfang bis Ende zu steuern. Seine Importe:
import { Client } from '@modelcontextprotocol/client';
import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';
Es startet den Server als Kindprozess:
const transport = new StdioClientTransport({
command: 'node',
args: ['dist/server.js'], // spawns the server as a child process
});
const client = new Client({ name: 'demo-client', version: '1.0.0' });
await client.connect(transport);
Dann führt es das aus, was ein Desktop-Client im Hintergrund tut, indem es Tools auflistet und sie aufruft:
// Exactly what Claude Desktop does under the hood:
const { tools } = await client.listTools();
await client.callTool({ name: 'log_work', arguments: {
text: 'Fixed the race condition in the broadcast queue',
tags: ['bugfix', 'websocket'],
}});
Etwa fünfzig Zeilen später gibt npm run demo eine vollständige Protokollsession als Beweis aus. Die aufgelisteten Tools:
=== 1. listTools ===
• log_work — Append a timestamped entry to the developer diary...
• search_diary — Full-text search across every entry...
• daily_summary — Everything logged on a given date...
• stats — Totals, active days, streaks, top tags...
Drei Schreibvorgänge:
=== 2. log_work x3 ===
Logged to 2026-08-22.md at 19:05 (tags: bugfix, websocket)
...
Und der Statistik-View:
=== 5. stats ===
📊 1 entries across 2 day(s)
🔥 Streak: 2 consecutive day(s)
🏷️ Top tags: #bugfix (1), #websocket (1)
Weil es sich um ein Skript handelt, dient es gleichzeitig als Integrationstest. Nach jeder Änderung am Server wird die Demo ausgeführt, und ein fehlerhafter Handshake oder eine veränderte Schema-Struktur führen zu einem deutlichen Fehler. Demo, Dokumentation und Testsuite bilden somit ein einziges Artefakt. Für eine visuelle Bedienung öffnet der offizielle Inspector, der mit npx @modelcontextprotocol/inspector node dist/server.js gestartet wird, eine Web-Oberfläche, über die man die Tools manuell nutzen kann. Beides zusammen deckt Entwicklung und Demonstration ab.
Achtungspunkte
- Top-level await kann die Kompilierung stören. Die README-Dokumentation des SDK verwendet
await server.connect(...)im Modulbereich. Das funktioniert bei ESM-Paketen ("type": "module") einwandfrei, führt aber bei der Kompilierung mit TypeScript zu CommonJS zu einem schwerwiegenden Fehler. Die Initialisierung inmain()kostet nichts und funktioniert überall. exactOptionalPropertyTypeskollidiert mit optionalen Schemata. Unter strengen TypeScript-Einstellungen führt das Übergeben vontags: undefinedan einz.string().optional()-Feld zu Fehlern. Entweder muss die Einstellung gelockert werden oder die Argumente an der Grenze des Handlers normalisiert werden.- Beschreibungen beanspruchen Speicherplatz. Jede Tool-Beschreibung wird im Modell jeder Konversation gespeichert, in der der Server verbunden ist. Marketingtexte verschwenden Tokens und trüben nützliche Anweisungen, weshalb kurze, imperative Texte, die auf dem Zweck ausgerichtet sind, vorteilhaft sind.
- Statelessness ist vorteilhaft. Der Server speichert nichts im Speicher und liest den Markdown bei jedem Aufruf erneut. Manuelle Änderungen zwischen den Aufrufen erzeugen niemals veraltete Daten, und zwei Clients in derselben Ordnerstruktur teilen kostenlos die aktuelle Information. Verzögern Sie den Einsatz von Caches für auf Dateien basierende Tools, bis Profilanalysen ihre Notwendigkeit belegen.
- Zeitzonen haben selbst bei einem Spielzeug Auswirkungen.
new Date().toISOString().slice(11,16)gibt UTC zurück. Jemand, der sich in UTC-3 befindet und um 18:00 Uhr einträgt, sieht 21:05. Mit einer einzigen Zeile lässt sich das beheben, sobald es bemerkt wird.
Einrichten
In Claude Desktop, Cursor oder jedem MCP-fähigen Client sieht die Konfiguration (im Stil von claude_desktop_config.json) wie folgt aus:
{
"mcpServers": {
"dev-diary": {
"command": "node",
"args": ["/absolute/path/to/dev-diary-mcp/dist/server.js"],
"env": { "DIARY_DIR": "/home/you/journal" }
}
}
}
Weisen Sie DIARY_DIR auf ein Verzeichnis innerhalb eines dotfiles-Repositories hin – als Nebeneffekt entstehen so die Versionen des Tagebuchs. Nach einer Woche liefert eine Anfrage wie „Zusammenfassung meiner Woche aus dem Tagebuch, gruppiert nach Tag“ einen Statusbericht, den niemand extra schreiben musste.
Warum reines Markdown
Die am stärksten befürwortete Wahl verdient eine Begründung. SQLite bietet bessere Abfragen, aber unklare Datenspeicherung, und JSON lässt sich leicht parsen, aber schlecht vergleichen. Markdown gewann aus vier Gründen:
- Der Mensch ist der erste Kunde. Das Tagebuch liest sich problemlos in einem Editor, mit grep oder in git-Logs – ohne zusätzliche Tools.
- Git bietet Backup und Historie.
git log diary/ist eine Auditspur, die keine Datenbank standardmäßig bereitstellt. - LLMs verarbeiten Markdown sehr gut. Was das Modell schreibt, entspricht genau dem, was gespeichert ist – ohne Serialisierungsunterschiede.
Die Lektion geht über Journal-Systeme hinaus: Wenn sowohl Menschen als auch große Sprachmodelle Ihre Daten verarbeiten, sollten Sie diese in einem Format speichern, das beide bereits verstehen.
Ausführen
Klonen, kompilieren und die Demo ausführen:
git clone https://github.com/rogeriolaa/dev-diary-mcp
cd dev-diary-mcp && npm install && npm run build && npm run demo
Fügen Sie den Server anschließend zur MCP-Konfiguration des Editors hinzu und lassen Sie den Assistenten mit dem Protokollieren beginnen. Server, Demo-Klient und README zusammen umfassen inklusive der Konfiguration etwa 400 Zeilen.
Kleine Server wie dieser sind eine effektive Möglichkeit, MCP zu lernen: Jedes Konzept wird berührt, das Ergebnis wird täglich genutzt und das Umfang bleibt klein genug, um es vollständig zu verstehen.