Bild von Stefan Lieser
Stefan Lieser

MCP Server implementieren in C#

Mit MCP Servern können Programme und Tools an die KI angebunden werden. So kannst du mit einem MCP Server für deine Buchhaltungssoftware bspw. mit OpenCode herauszufinden, ob Belege fehlen. Oder mit einem MCP Server für Excalidraw Diagramme zeichnen lassen. MCP Server sind also nicht nur für die Integration von Entwicklertools sinnvoll, sondern auch für viele andere Aufgaben.

Einen MCP Server in C# zu erstellen ist im Grunde leicht, weil Microsoft mit dem NuGet Paket ModelContextProtocol alles liefert, was man braucht. Das Paket ist derzeit noch ein Prerelease, d.h. man muss dies beim Hinzufügen ggf. durch ein Häkchen im Feld „Prerelease“ berücksichtigen. Ein MCP Server kann mit diesem Paket als eigenständige Anwendung implementiert oder in eine bestehende Anwendung integriert werden. Im folgenden Beispiel zeige ich die Standalone Variante. Die Integration in eine bestehende Anwendung verläuft sehr ähnlich.

MCP Server Anbindung

Vereinfacht gesagt können MCP Server auf zwei Weisen von der KI angesprochen werden:

  • Kommandozeile (in OpenCode mit local bezeichnet)
  • http (in OpenCode mit remote bezeichnet, auch wenn der Server lokal laufen kann)

Das folgende Beispiel zeigt einen MCP Server, der über http angesprochen wird. Dazu ist das NuGet Paket ModelContextProtocol.AspNetCore einzubinden.

Im Beispiel werden zwei verschiedene Tools angeboten:

  • Echo
  • Monkey

Das Echo Beispiel zeigt den grundsätzlichen Aufbau. Das Tool gibt den als Parameter übergebenen String wieder an den Aufrufer zurück, so dass man sehen kann, dass der Aufruf tatsächlich stattgefunden hat.

Das Monkey Beispiel ist ein MCP Server für die URL https://www.montemagno.com/monkeys.json Es simuliert quasi einen entfernten API Aufruf, der dann als MCP Tool bereitgestellt wird.

Als Startpunkt habe ich mit einer Konsolenanwendung begonnen, die das ASP.NET Core Hosting übernimmt. Die Anbindung an bspw. OpenCode erfolgt somit über http. Ein MCP Tool wird in einer Klasse implementiert, die mit dem Attribut [McpServerToolType] versehen wird. Als Konvention benenne ich solche Klassen mit dem Suffix Tool.

				
					using System.ComponentModel;
using Microsoft.AspNetCore.Mvc;
using ModelContextProtocol.Server;

namespace mymcp;

[McpServerToolType]
public class EchoTool
{
    [HttpGet("echo"), McpServerTool, Description("Echoes the message back to the client.")]
    public string Echo([FromQuery] string message) {
        return $"Hello from C#: {message}";
    }

    [HttpGet("reverse"), McpServerTool, Description("Echoes in reverse the message sent by the client.")]
    public string ReverseEcho([FromQuery] string message) {
        return new string(message.Reverse().ToArray());
    }
}
				
			

Innerhalb der Klasse werden dann Methoden bereitgestellt, die jeweils mit dem Attribut [McpServerTool] versehen werden. Dadurch werden sie zur Laufzeit in der Auflistung der bereitgestellten Tools aufgeführt. Fragt also die KI den MCP Server nach einer Liste von Tools, werden alle Klassen mit dem Attribut McpServerToolType verwendet und alle darin mit McpServerTool versehenen Methoden jeweils als Tool aufgelistet. Die Attribute dienen also dazu, die Metadaten bereitzustellen, die zur Auflistung der MCP Tools benötigt werden.

Jedes Tool muss über eine Beschreibung verfügen, die mit dem Attribut Description bereitgestellt wird. Der Text wird von der KI verwendet um zu entscheiden, ob sie das Tool verwenden sollte. Es kommt hier also unbedingt auf eine gute Beschreibung der bereitgestellten Funktionalität an, die gleichzeitig nicht zu lang ausfallen sollte.

Dritter Bestandteil sind die Parameter. Benötigt ein MCP Tool Parameter, können diese als Parameter der Methode definiert werden. Bei einem so simplen Tool wie Echo wird aus der Beschreibung und dem Parameternamen bereits klar, dass eine „Message“ bereitgestellt werden muss. In realen MCP Servern wird man die Parameter zusätzlich mit einer Beschreibung versehen, wie wir es später beim Monkey Beispiel sehen werden.

Initialisierung des MCP Servers

Zum Start des MCP Servers muss die übliche Initialisierung vorgenommen werden.

				
					namespace mymcp;

internal class Program
{
    private async static Task Main(string[] args) {
        var builder = WebApplication.CreateBuilder(args);
        builder.Logging.AddConsole(consoleLogOptions => {
            consoleLogOptions.LogToStandardErrorThreshold = LogLevel.Trace;
        });

        builder.Services.AddHttpClient();
        builder.Services.AddSingleton<MonkeyService>();
        builder.Services.AddControllers();

        builder.Services
            .AddMcpServer()
            .WithHttpTransport(options => options.Stateless = true)
            .WithToolsFromAssembly();

        var app = builder.Build();
        app.MapControllers();
        app.MapMcp("/mcp");

        await app.RunAsync("http://localhost:3001");
    }
}

				
			

Als erstes wird das Logging initialisiert, hier ganz simpel auf die Konsole. Anschließend werden einige Services in den DI Container hinzugefügt. Dann folgt der MCP Server spezifische Teil: es wird ein MCP Server hinzugefügt und konfiguriert. Dieser Demo MCP Server wird als zustandslos definiert. Dadurch werden einige Features der MCP Server Implementation ausgeschaltet, die nur bei zustandsbehafteten Servern erforderlich sind. Unter anderem wird dadurch der Endpunkt /sse nicht bereitgestellt. Durch app.MapMcp(„/mcp“) wird der MCP Server dann auf dem gewünschten Endpunkt bereitgestellt.

Einrichten in OpenCode und Claude Code

In OpenCode kann der Server bspw. durch Editieren der Datei ~/.config/opencode.json bereitgestellt werden:

				
					{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "mo-mcp": {
      "type": "remote",
      "url": "http://127.0.0.1:3001/mcp"
    }
  }
}

				
			

In Claude Code wird er über folgenden Kommandozeilenaufruf integriert:

				
					claude mcp add --scope user --transport http mymcp http://localhost:3001/mcp

				
			

Nun kann der MCP Server verwendet werden.

MCP Server echo

Aktuelle Clean Code Trainings

Wir führen alle Seminare auch als geschlossene Firmenkurse für Sie durch.
Bei Interesse oder Fragen kontaktieren Sie uns gerne.

Eine entferne API anbinden

Wenn eine entfernte API keinen MCP Server bereitstellt, kann man diesen auch leicht selber implementieren. Das folgende Beispiel bindet die URL https://www.montemagno.com/monkeys.json quasi als API an. Die URL liefert ein JSON Ergebnis zurück, so wie es eine „echte“ API auch tun würde.

Als erstes implementiert man nun einen Service, der diese API anbindet:

				
					namespace mymcp;

public class Monkey
{
    public string? Name { get; set; }

    public string? Location { get; set; }

    public string? Details { get; set; }

    public string? Image { get; set; }

    public int Population { get; set; }

    public double Latitude { get; set; }

    public double Longitude { get; set; }
}

				
			
				
					using System.Text.Json.Serialization;

namespace mymcp;

public class MonkeyService(IHttpClientFactory httpClientFactory)
{
    private readonly HttpClient _httpClient = httpClientFactory.CreateClient();

    private List<Monkey> _monkeyList = new();

    public async Task<List<Monkey>> GetMonkeys() {
        if (_monkeyList.Count > 0) {
            return _monkeyList;
        }

        var response = await _httpClient.GetAsync("https://www.montemagno.com/monkeys.json");
        if (response.IsSuccessStatusCode) {
            _monkeyList = await response.Content.ReadFromJsonAsync(MonkeyContext.Default.ListMonkey) ?? [];
        }

        _monkeyList ??= [];

        return _monkeyList;
    }

    public async Task<Monkey?> GetMonkey(string name) {
        var monkeys = await GetMonkeys();
        return monkeys.FirstOrDefault(m => m.Name?.Equals(name, StringComparison.OrdinalIgnoreCase) == true);
    }
}

[JsonSerializable(typeof(List<Monkey>))]
internal sealed partial class MonkeyContext : JsonSerializerContext
{
}

				
			

Die beiden Methoden GetMonkeys() und GetMonkey(string name) stellen die von der API bereitgestellten Funktionen dar. GetMonkeys() liest die JSON Daten von der URL. Ergebnis ist eine Liste von Affen. Da die API keine Möglichkeit bietet, direkt serverseitig nach einem Namen zu filtern, wird dies von der Methode GetMonkey(string name) übernommen. Sie ruft zunächst alle Affen von der URL ab und sucht dann nach dem ersten Element mit dem übergebenen Namen.

Nachdem wir nun die fremde API angebunden haben, müssen wir nun lediglich wieder die MCP Tools erstellen.

				
					using System.ComponentModel;
using System.Text.Json;
using Microsoft.AspNetCore.Mvc;
using ModelContextProtocol.Server;

namespace mymcp;

[McpServerToolType]
public class MonkeyTools(MonkeyService monkeyService)
{
    [McpServerTool, Description("Get a list of monkeys.")]
    public async Task<string> GetMonkeys() {
        var monkeys = await monkeyService.GetMonkeys();
        return JsonSerializer.Serialize(monkeys);
    }

    [McpServerTool, Description("Get a monkey by name.")]
    public async Task<string> GetMonkey([FromRoute, Description("The name of the monkey to get details for")] string name) {
        var monkey = await monkeyService.GetMonkey(name);
        return JsonSerializer.Serialize(monkey);
    }
}

				
			

Hier sehen wir in der Methode GetMonkey ein Beispiel für eine Parameterbeschreibung. Das Description Attribut kann hier verwendet werden, um der KI Hinweise über die Bedeutung des Parameters zu geben. Beim Namen des Affen ist das offensichtlich. Bei meinem MCP Server für die Buchhaltung sind diese Beschreibungen allerdings hilfreich, damit die KI weiß, wann und wie sie die Parameter verwenden kann.

Die folgende Abbildung zeigt die Verwendung des Monkey Tools in OpenCode.

GetMonkeys mcp server implementieren
GetMonkey mcp server implementieren

Fazit

Ein MCP Server ist mithilfe des ModelContextProtocol NuGet Pakets schnell erstellt. In der Praxis benötigt man etwas Zeit und Ausprobieren, bis der Server rund läuft. Da man sich beim Schreiben des MCP Servers sinnvollerweise von Claude Code oder OpenCode helfen lassen sollte, können diese auch bei der Fehlersuche hilfreich sein. Bei meiner Buchhaltungsanbindung gab es bspw. das Problem, dass der Service der Buchhaltung durch zu viele Anfragen in kurzer Folge überrannt wurde. Claude Code hat mir dann geholfen, eine Verzögerung einzubauen und so die Anbindung stabil zu machen.

Wenn du mehr über MCP Server und Claude Code lernen möchtest, ist vielleicht unser Seminar Einführung in Claude Code das richtige für dich.

Meinen MCP Server für die ProSaldo Buchhaltung Monkey Office findest du unter https://gitlab.com/slieser/mo-mcp.

Unsere Seminare

course
Clean Code Developer Basics

Prinzipien und Tests – Das Seminar wendet sich an Softwareentwickler, die gerade beginnen, sich mit dem Thema Softwarequalität auseinanderzusetzen. Es werden die wichtigsten Prinzipien und Praktiken der Clean Code Developer Initiative vermittelt.

zum Seminar »
course
Clean Code Developer Advanced

Mit Flow Design von den Anforderungen zum Clean Code – Lernen Sie mit Flow Design einen Softwareentwicklungsprozess kennen, der Sie flüssig von den Anforderungen zum Clean Code führt.

zum Seminar »
course
Clean Code Developer Trainer

Seminare als Trainer durchführen – Dieses Seminar wendet sich an Softwareentwickler, die ihr Wissen über die Clean Code Developer Prinzipien und Praktiken bzw. über Flow Design als Trainer an andere weitergeben möchten.

zum Seminar »

Kommentar verfassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

de_DEGerman