Ich wollte eigentlich nur „mal schnell“ einen kleinen automatisierten Rechnungseingang bauen.
Was ich stattdessen bekommen habe: ein Crashkurs in MCP, stdio, VS Code Interna und die Erkenntnis, dass „self exploring“ nur die halbe Wahrheit ist.
Die Idee war simpel:
- PDFs landen in einem Ordner
- ein System erkennt neue Dateien
- versucht eine Bestellung zuzuordnen
- schreibt Metadaten zurück (NTFS ADS)
- und fertig
Also klassischer Workflow – nichts fancy. Sollte doch sicher irgendwie mit KI, Agent und MCP gehen
Erwartung vs. Realität
Meine naive Erwartung:
„Ich starte einen MCP Server und VS Code findet den schon.“
Realität:
„VS Code kennt gar nichts, bis du es ihm explizit sagst.“
Und genau hier liegt der erste wichtige Punkt:
MCP ist nicht discovery-driven, sondern client-driven.
Das heißt:
- Der Server sagt: „Ich kann das“
- Der Client entscheidet: „Ich nutze dich“
Und diese Entscheidung passiert über:
.vscode/mcp.json
Ja, wirklich. JSON.
Der Aha-Moment: stdio
Der zweite Stolperstein war stdio.
Ich dachte zuerst:
„Ich habe doch einfach ein .NET Programm gestartet.“
Nein.
Mein Code in program.cs in Visual Studio großzügig von ChatGPT erstellt
1: using Microsoft.Extensions.DependencyInjection;
2: using Microsoft.Extensions.Hosting;
3: using Microsoft.Extensions.Logging;
4: using ModelContextProtocol.Server;
5: using System.ComponentModel;
6: using System.Text.Json;
7:
8: var builder = Host.CreateApplicationBuilder(args);
9: builder.Logging.ClearProviders();
10: builder.Services
11: .AddMcpServer()
12: .WithStdioServerTransport()
13: .WithToolsFromAssembly();
14:
15: await builder.Build().RunAsync();
16:
17: [McpServerToolType]
18: public static class FileTools
19: {
20: [McpServerTool, Description("Liest alle PDF-Dateien in einem Ordner.")]
21: public static string ListFiles(
22: [Description("Absoluter Ordnerpfad")] string folder)
23: {
24: if (!Directory.Exists(folder))
25: return JsonSerializer.Serialize(new { error = "Ordner nicht gefunden." });
26:
27: var files = Directory
28: .GetFiles(folder, "*.pdf", SearchOption.TopDirectoryOnly)
29: .Select(path => new
30: {
31: Name = Path.GetFileName(path),
32: FullPath = path,
33: Size = new FileInfo(path).Length,
34: Created = File.GetCreationTime(path)
35: });
36:
37: return JsonSerializer.Serialize(files, new JsonSerializerOptions
38: {
39: WriteIndented = true
40: });
41: }
42:
43: [McpServerTool, Description("Schreibt Metadaten in einen NTFS-ADS-Stream.")]
44: public static string WriteAdsMetadata(
45: [Description("Absoluter Dateipfad")] string filePath,
46: [Description("Name des ADS-Streams, z.B. invoice")] string streamName,
47: [Description("Beliebiger Text oder JSON")] string content)
48: {
49: if (!File.Exists(filePath))
50: return "Datei nicht gefunden.";
51:
52: var adsPath = $"{filePath}:{streamName}";
53: File.WriteAllText(adsPath, content);
54: return $"ADS geschrieben: {adsPath}";
55: }
56:
57: [McpServerTool, Description("Liest Metadaten aus einem NTFS-ADS-Stream.")]
58: public static string ReadAdsMetadata(
59: [Description("Absoluter Dateipfad")] string filePath,
60: [Description("Name des ADS-Streams, z.B. invoice")] string streamName)
61: {
62: if (!File.Exists(filePath))
63: return "Datei nicht gefunden.";
64:
65: var adsPath = $"{filePath}:{streamName}";
66: return File.Exists(adsPath)
67: ? File.ReadAllText(adsPath)
68: : "ADS-Stream nicht vorhanden.";
69: }
70: }
Mein Server ist eigentlich nur eine DLL die bei Bedarf mit Parameter aufgerufen wird und einen Wert auswirft, spricht über stdin/stdout.
Als Client habe ich VS Code auserkoren. startet Prozess - redet über Pipes - fertig
Um den MPC Server konsumieren zu können braucht es eine mcp.json.

1: {
2: "servers": {
3: "filesystem-mcp": {
4: "command": "dotnet",
5: "args": [
6: "C:\\stateof\\FilesystemMcp\\bin\\Debug\\net10.0\\FilesystemMcp.dll"
7: ]
8: }
9: }
10: }
In VS Code wird per STRG SHIFT P das Eingabefeld Command Palette geöffnet. Dort sollte der MCP Server auftauchen. Suchen nach MCP

Diesen Server muss man auch starten können. Das passieert zwar automatisch, aber wenn es einen Konfig oder Compilerfehler gibt steht filesystem-mcp (stopped)

Logging killt stdio deswegen builder.Logging.ClearProviders();
Der eigentliche Magic Trick
Ich schreibe im Chat von Visual Studio Code
„Zeig mir die PDFs im Ordner“
Und intern passiert:
- Das LLM denkt
- sieht das Tool
ListFiles
- ruft es auf
- bekommt JSON zurück
- formuliert eine Antwort
Mein erstes echtes Tool
Ganz simpel:
[McpServerTool]
public static string ListFiles(string folder)
Und plötzlich ist das kein Helper mehr, sondern:
ein Agent Tool.
Dann noch:
WriteAdsMetadata
ReadAdsMetadata
Und ich hatte:
einen Mini-Workflow mit Zustand direkt im Filesystem.
ADS statt Datenbank
Weil ich möchte das die Infos am Dokument hängen eine ADS Stream
rechnung.pdf:invoice
mit JSON:
{
"status": "matched",
"orderId": 1234
}
Vorteile:
- kein DB-Change
- lokal
- simpel
Nachteile:
- nicht sichtbar im Explorer
- geht bei Copy/Zip verloren
Aber für den Einstieg perfekt.
Was ich gelernt habe
- MCP ist kein Webservice-Modell
- stdio ist kein „Server“, sondern ein Pipe-Prozess
- VS Code ist der eigentliche Orchestrator
- LLM entscheidet – Code führt aus
- Logging kann dein ganzes System killen
Fazit
Ich wollte:
„Ein bisschen Automatisierung.“
Ich bekommen habe:
„Einen lokalen Agenten, der meine Tools versteht und nutzt.“
Und das Ganze:
- ohne HTTP
- ohne Cloud-Zwang
- ohne komplexe Infrastruktur
Nur:
- VS Code
- ein bisschen C#
- und ein JSON-File
Nicht schlecht.
Nächster Schritt
Der nächste logische Schritt ist jetzt:
- nur neue PDFs anzeigen (ohne ADS)
- Matching gegen Bestellung
- automatische Vorschläge
Also:
vom Tool zum Workflow.
Und genau da wird MCP richtig interessant.
Wenn du bis hierhin gekommen bist:
Willkommen in der Welt der echten lokalen Agenten.