MCP, VS Code und ein bisschen Realität: Mein erster lokaler Agent

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.

mcp1

   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

mcp2

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

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:

  1. Das LLM denkt
  2. sieht das Tool ListFiles
  3. ruft es auf
  4. bekommt JSON zurück
  5. 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

  1. MCP ist kein Webservice-Modell
  2. stdio ist kein „Server“, sondern ein Pipe-Prozess
  3. VS Code ist der eigentliche Orchestrator
  4. LLM entscheidet – Code führt aus
  5. 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.

Kommentare sind geschlossen