MCP Server Development: KI-Werkzeuge richtig bauen
Wie ein KI-Assistent sicher an eigene Systeme kommt: Werkzeugschnitt, Rechtetrennung, Freigabe-Schranken und Pruefprotokoll — aus dem eigenen Produktivbetrieb.
Harald Schwankl
Dipl.-Ing., Fullstack Developer & KI-Spezialist
Auf dieser Seite
Das Problem hinter MCP
Ein Sprachmodell kann formulieren, zusammenfassen und Code schreiben. Was es nicht kann: in Ihr Warenwirtschaftssystem schauen. Sobald ein Assistent etwas ueber Ihre Welt wissen oder darin etwas veraendern soll, braucht er einen Zugang.
Frueher hat jedes Werkzeug diesen Zugang selbst erfunden. Ein Anbieter hatte seine Plugin-Schnittstelle, ein anderer seine Funktionsaufrufe, ein dritter etwas Eigenes. Wer sein System an drei Assistenten anbinden wollte, baute drei Adapter — und pflegte sie dreifach weiter.
MCP loest genau dieses Problem, nicht mehr und nicht weniger. Es ist ein offener Standard dafuer, wie ein Assistent erfaehrt, welche Werkzeuge es gibt, was sie erwarten und was sie zurueckgeben. Der Assistent spricht ein Protokoll, Ihr System spricht dasselbe Protokoll, und der Adapter existiert nur noch einmal.
Der Nutzen ist nicht die Technik, sondern die Richtung der Abhaengigkeit. Sie bauen einen Server, der Ihre Faehigkeiten beschreibt. Welcher Assistent ihn nutzt, entscheiden Sie spaeter — und koennen es aendern, ohne den Server anzufassen.
Was ein MCP-Server nicht ist:
- Keine Datenbank-Freigabe. Er stellt Faehigkeiten bereit, keine Tabellen.
- Keine offene Programmierschnittstelle. Er ist fuer eine bestimmte Nutzung gedacht, nicht fuer beliebige Zugriffe.
- Kein Ersatz fuer Berechtigungen. Er ist die Stelle, an der Berechtigungen durchgesetzt werden.
Der letzte Punkt ist der wichtigste und der am haeufigsten uebersehene. Wer einen MCP-Server baut, baut eine neue Tuer in sein System. Wie gut sie verschlossen ist, entscheidet sich hier und nirgendwo sonst.
Server, Werkzeuge, Transport
Drei Begriffe genuegen fuer den Anfang.
Ein Server buendelt Faehigkeiten fuer einen bestimmten Zweck und eine bestimmte Nutzergruppe. Ein Werkzeug ist eine einzelne Faehigkeit mit Namen, Beschreibung und beschriebenen Parametern. Der Transport ist der Weg, auf dem Assistent und Server miteinander reden.
Beim Transport kennt die Spezifikation heute zwei Varianten:
- stdio — der Assistent startet den Server als lokalen Prozess und redet ueber die Standard-Ein- und -Ausgabe mit ihm. Der einfachste Fall, ohne Netzwerkfreigabe.
- Streamable HTTP — fuer entfernt betriebene Server. Ab diesem Moment gelten alle ueblichen Regeln fuer oeffentlich erreichbare Dienste.
Wichtig fuer bestehende Installationen: Der aeltere Transport HTTP+SSE gilt seit der Protokoll-Revision 2025-03-26 als veraltet und wird in der Spezifikation nur noch aus Kompatibilitaetsgruenden gefuehrt. Wer heute noch Server mit "type": "sse" betreibt, sollte den Wechsel einplanen — einzelne Anbieter haben die Unterstuetzung bereits mit festen Stichtagen beendet. Ein paralleler Betrieb beider Endpunkte ist als Uebergang vorgesehen.
Die Entscheidung, die zu Beginn faellt und spaeter teuer wird: ein Server fuer alles, oder mehrere getrennte?
Wir betreiben drei. Einen fuer administrative Aufgaben, einen fuer Kundenzugriffe und einen fuer Entwicklungswissen. Das ist auf den ersten Blick mehr Arbeit und war rueckblickend die wichtigste Entscheidung.
Der Grund ist nicht Ordnungsliebe. Ein einzelner Server mit allen Faehigkeiten hat genau einen Schluessel. Wer ihn hat, kann alles. Bei getrennten Servern kann ein Kundenzugang gar nicht erst administrative Werkzeuge sehen — nicht weil eine Pruefung sie verbietet, sondern weil sie auf diesem Server nicht existieren.
Das ist der Unterschied zwischen "darf nicht" und "gibt es nicht". Der zweite Zustand ist der sicherere, weil er keine korrekte Pruefung voraussetzt.
Ein Werkzeug richtig schneiden
Hier entscheidet sich, ob ein Assistent Ihr System sinnvoll nutzt oder daran scheitert. Und der haeufigste Fehler ist derselbe wie beim Entwurf von Schnittstellen fuer Menschen: zu allgemein.
Ein Werkzeug namens datenbank_abfrage mit einem Parameter sql ist verlockend, weil es alles kann. Es ist gleichzeitig unbrauchbar und gefaehrlich. Unbrauchbar, weil das Modell die Struktur Ihrer Datenbank erraten muss. Gefaehrlich, weil Sie damit die Kontrolle vollstaendig abgeben.
Besser sind Werkzeuge, die einer fachlichen Absicht entsprechen: "offene Rechnungen eines Kunden auflisten" statt "beliebige Abfrage ausfuehren".
Was ein gut geschnittenes Werkzeug ausmacht:
- Eine Absicht pro Werkzeug. Wenn die Beschreibung ein "und" enthaelt, sind es wahrscheinlich zwei.
- Enge Parameter. Eine Kundennummer, ein Zeitraum, ein Status aus einer festen Liste — keine Freitextfelder, wo eine Auswahl genuegt.
- Eine Beschreibung fuer das Modell, nicht fuer Entwickler. Sie ist Teil der Funktion. Steht dort "holt Daten", waehlt das Modell falsch. Steht dort, wann das Werkzeug passt und wann nicht, waehlt es richtig.
- Vorhersagbare Rueckgaben. Gleiche Struktur bei Erfolg und bei Fehlschlag, damit das Modell nicht raten muss.
- Begrenzte Menge. Ein Werkzeug, das im Zweifel zehntausend Zeilen liefert, sprengt den Kontext und kostet bei jedem Aufruf Geld.
Der letzte Punkt hat eine unterschaetzte Nebenwirkung. Alles, was ein Werkzeug zurueckgibt, landet im Kontext des Modells und wird bezahlt. Ein Werkzeug, das grosszuegig antwortet, ist nicht nur langsam, sondern dauerhaft teuer. Grenzen Sie ein, geben Sie Zusammenfassungen zurueck, und bieten Sie Details als zweiten Schritt an.
Faustregel aus dem Betrieb: Wenn ein Mensch mit der Werkzeugbeschreibung allein nicht entscheiden koennte, ob er dieses Werkzeug braucht, kann es das Modell auch nicht.
Rechte trennen, nicht verwalten
Ein MCP-Server ist eine Tuer in Ihr System. Die Frage ist nicht, ob jemand daran ruettelt, sondern was passiert, wenn er aufgeht.
Trennung schlaegt Pruefung. Der wirksamste Schutz ist, dass ein Zugang bestimmte Faehigkeiten gar nicht kennt. Deshalb getrennte Server statt einer Rechtematrix in einem einzigen. Eine Rechtepruefung kann fehlerhaft sein; ein Werkzeug, das auf diesem Server nicht existiert, kann nicht aufgerufen werden.
Jeder Zugang bekommt ein eigenes Token. Nicht eines pro Server, sondern eines pro Zugang. Nur so laesst sich ein einzelner sperren, ohne alle anderen auszusperren, und nur so sagt das Protokoll spaeter, wer etwas getan hat.
Schreiben ist etwas anderes als Lesen. Werkzeuge, die etwas veraendern, gehoeren technisch und organisatorisch getrennt von denen, die nur lesen. In der Praxis heisst das: unterschiedliche Server, unterschiedliche Token, und fuer veraendernde Aufrufe zusaetzlich eine Freigabe (siehe naechster Abschnitt).
Alles wird protokolliert. Welcher Zugang, welches Werkzeug, welche Parameter, welches Ergebnis, welcher Zeitpunkt. Ohne dieses Protokoll koennen Sie nach einem Vorfall nicht rekonstruieren, was passiert ist. Bei Hochrisiko-Anwendungen kommt eine gesetzliche Aufzeichnungspflicht hinzu (Art. 12 AI Act); darunter ist es schlicht die einzige Moeglichkeit, spaeter Fragen zu beantworten.
Eingaben bleiben Eingaben. Was ein Werkzeug zurueckliefert, ist Inhalt, keine Anweisung. Wenn eine Datenbank einen Text enthaelt, der wie eine Anweisung an den Assistenten aussieht, darf er nicht als solche wirken. Das klingt selbstverstaendlich und ist der Angriffsweg, der in der Praxis am haeufigsten funktioniert.
Was wir bewusst nicht oeffentlich beschreiben: unsere Endpunkte, die Namen unserer Werkzeuge und den genauen Zuschnitt der Rechte. Das ist keine Geheimniskraemerei, sondern dieselbe Ueberlegung, aus der man auch keine Grundrisse seines Serverraums veroeffentlicht.
Freigabe-Schranken
Der Unterschied zwischen einem Assistenten, den man einsetzen darf, und einem, den man nicht einsetzen darf, ist eine einzige Eigenschaft: Er haelt an, bevor etwas nach aussen wirkt.
Eine Freigabe-Schranke ist ein Punkt in einem Ablauf, an dem die Ausfuehrung pausiert und auf eine menschliche Entscheidung wartet. Der Ablauf ist damit nicht beendet — er ruht und laeuft nach der Freigabe an derselben Stelle weiter.
Wo eine Schranke hingehoert:
- vor jeder kundensichtbaren Ausgabe: E-Mail, Rechnung, veroeffentlichter Text
- vor jeder Geldbewegung
- vor jeder Aenderung an Personendaten
- vor jedem Ausrollen in den Produktivbetrieb
Wo keine hingehoert: bei reinen Leseoperationen und bei Schritten, die sich folgenlos wiederholen lassen. Wer ueberall Schranken einbaut, erzeugt Klickarbeit, die nach zwei Wochen blind bestaetigt wird — und dann schlechter ist als keine Schranke, weil sie Sicherheit vortaeuscht.
Technisch braucht eine Schranke drei Dinge: einen dauerhaft gespeicherten Zustand des Ablaufs, damit er einen Neustart ueberlebt; eine Benachrichtigung an einen Menschen; und einen Weg, ihn fortzusetzen oder abzubrechen. Der haeufigste Fehler ist, den Zustand nur im Speicher zu halten — dann ist ein wartender Ablauf nach dem naechsten Ausrollen verschwunden.
Was der EU AI Act dazu wirklich sagt — und was oft zu weit ausgelegt wird: Die Pflicht zur menschlichen Aufsicht (Art. 14, fuer Betreiber Art. 26) gilt nur fuer Hochrisiko-Systeme nach Anhang III. Ein Werkzeugserver, der Rechnungen abfragt oder Termine anlegt, faellt in aller Regel nicht darunter.
Eine Freigabe-Schranke ist dort also keine Pflicht, sondern gute Praxis. Zur Pflicht wird sie, sobald der Anwendungsfall in Anhang III liegt — etwa bei Entscheidungen ueber Bewerbungen, Kreditwuerdigkeit oder Zugang zu Leistungen. Und genau dann ist sie auch die technische Form, in der sich die Anforderung nachweisen laesst: Wer sie hat, muss nicht argumentieren, dass ein Mensch im Spiel ist — er kann es zeigen.
Unabhaengig von der Rechtslage bleibt der praktische Grund bestehen: Eine Automatik, die ohne Rueckfrage Rechnungen verschickt, ist ein Risiko fuer das eigene Geschaeft.
Umsetzung in Python
Ein Werkzeug besteht aus drei Teilen: einer Beschreibung, die das Modell liest; einem Vertrag ueber die Parameter; und der Ausfuehrung.
Beschreibung und Parameter. Die Beschreibung ist Teil der Funktion, nicht Dokumentation. Sie entscheidet, ob das Modell das richtige Werkzeug waehlt:
from pydantic import BaseModel, Field
class OffeneRechnungenEingabe(BaseModel):
"""Parameter sind bewusst eng — kein Freitext, wo eine Auswahl genuegt."""
kundennummer: str = Field(description="Kundennummer, exakt wie im System")
faellig_seit_tagen: int = Field(
default=0, ge=0, le=365,
description="Nur Rechnungen, die seit mindestens so vielen Tagen faellig sind",
)
limit: int = Field(default=20, ge=1, le=100, description="Hoechstzahl der Treffer")
WERKZEUG_BESCHREIBUNG = (
"Listet offene Rechnungen eines einzelnen Kunden auf. "
"Nutze dieses Werkzeug fuer Fragen nach Zahlungsrueckstaenden. "
"Nutze es NICHT, um Rechnungen zu erstellen oder zu aendern."
)Der Satz mit dem "NICHT" wirkt ueberfluessig und ist es nicht. Er verhindert, dass das Modell dieses Werkzeug fuer Aufgaben waehlt, fuer die es nicht gedacht ist.
Ausfuehrung mit Begrenzung und Protokoll:
async def offene_rechnungen(eingabe: OffeneRechnungenEingabe, zugang: Zugang) -> dict:
if not zugang.darf("rechnungen:lesen"):
# Klarer Fehler statt leerer Liste: sonst haelt das Modell
# "keine Rechte" faelschlich fuer "keine Rechnungen".
return {"status": "verweigert", "grund": "Kein Leserecht fuer Rechnungen"}
treffer = await repo.offene_rechnungen(
kundennummer=eingabe.kundennummer,
faellig_seit_tagen=eingabe.faellig_seit_tagen,
limit=eingabe.limit,
)
await pruefprotokoll.schreibe(
zugang=zugang.id, werkzeug="offene_rechnungen",
parameter=eingabe.model_dump(), treffer=len(treffer),
)
return {
"status": "ok",
"anzahl": len(treffer),
"gekuerzt": len(treffer) == eingabe.limit,
"rechnungen": [r.kurzform() for r in treffer],
}Zwei Details lohnen den zweiten Blick. Das Feld gekuerzt sagt dem Modell, dass es nicht alles gesehen hat — ohne diese Angabe zieht es aus einer abgeschnittenen Liste falsche Schluesse. Und kurzform() liefert bewusst nicht den vollstaendigen Datensatz: Was nicht gebraucht wird, gehoert nicht in den Kontext.
Eine Schranke einbauen:
async def rechnung_versenden(eingabe: VersandEingabe, zugang: Zugang) -> dict:
"""Wirkt nach aussen — also niemals ohne Freigabe."""
vorgang = await ablauf.pausiere(
art="rechnung_versenden",
vorschau=await vorschau_erzeugen(eingabe),
angefordert_von=zugang.id,
)
await benachrichtige_freigeber(vorgang)
return {
"status": "wartet_auf_freigabe",
"vorgang": vorgang.id,
"hinweis": "Die Rechnung wurde NICHT versendet. Ein Mensch muss freigeben.",
}Der Hinweistext ist fuer das Modell bestimmt. Ohne ihn meldet es dem Nutzer moeglicherweise "Rechnung versendet", obwohl nichts versendet wurde.
Was im Betrieb wirklich zaehlt
Zwischen einem funktionierenden Prototyp und einem Server, der ein Jahr laeuft, liegen ein paar Erfahrungen, die man ungern selbst macht.
Werkzeuge veralten schneller als Code. Wenn sich ein Feld in Ihrem System aendert, aendert sich die Beschreibung, die das Modell liest. Beschreibungen gehoeren deshalb neben die Implementierung, nicht in eine getrennte Dokumentation, die niemand pflegt.
Zu viele Werkzeuge sind schlimmer als zu wenige. Jedes Werkzeug erscheint im Kontext des Modells und kostet bei jeder Anfrage. Ab einer gewissen Zahl waehlt das Modell schlechter, nicht besser. Trennen Sie nach Zweck, statt alles in einen Server zu legen.
Zeitlimits gehoeren an jedes Werkzeug. Ein Aufruf, der haengt, blockiert nicht nur sich selbst, sondern den ganzen Ablauf. Wir haben das an anderer Stelle teuer gelernt: Ein Startvorgang ohne Zeitlimit hat eine komplette Website vier Minuten lang stumm geschaltet, weil eine Datenbank nicht antwortete.
Fehler brauchen Klartext. "Fehler 500" hilft dem Modell nicht. "Kunde nicht gefunden — pruefe die Kundennummer" fuehrt dazu, dass es sich selbst korrigiert, statt es unveraendert erneut zu versuchen.
Das Protokoll ist die Investition mit der besten Rendite. Es beantwortet drei Fragen, die frueher oder later kommen: Was hat der Assistent getan? Wer hat es freigegeben? Und, am wichtigsten: Warum hat er dieses Werkzeug gewaehlt? Die dritte Frage laesst sich nur beantworten, wenn Sie auch die Parameter mitschreiben.
Und der unpopulaerste Rat: Fangen Sie mit lesenden Werkzeugen an. Ein Assistent, der Ihr System nur ansehen darf, stiftet ueberraschend viel Nutzen und kann fast nichts kaputt machen. Schreibende Faehigkeiten kommen dazu, wenn Sie das Protokoll lesen und verstehen, wie er tatsaechlich arbeitet.
Fazit
MCP ist kein grosses Thema. Der Standard selbst ist an einem Nachmittag verstanden. Was Zeit kostet und ueber Erfolg entscheidet, sind drei Entscheidungen, die nichts mit dem Protokoll zu tun haben:
- Wie schneiden Sie Ihre Werkzeuge? Nach fachlicher Absicht, nicht nach Datenbanktabellen.
- Wie trennen Sie Rechte? Ueber getrennte Server, nicht ueber eine Rechtematrix in einem einzigen.
- Wo haelt der Ablauf an? Vor allem, was nach aussen wirkt.
Wer diese drei Punkte sauber loest, bekommt einen Assistenten, der echte Arbeit uebernimmt und dabei nachvollziehbar bleibt. Wer sie ueberspringt, bekommt eine beeindruckende Demonstration und ein Sicherheitsproblem.
Wir betreiben unsere eigene Firma ueber diese Bausteine — getrennte Zugaenge, Freigaben vor kundensichtbaren Aktionen, Protokoll ueber jeden Lauf. Nicht weil es vorgeschrieben ist, sondern weil wir sonst nicht ruhig schlafen wuerden, waehrend eine Automatik Rechnungen anfasst.
KI-Assistenten an Ihre Systeme anbinden
Wir betreiben unsere eigene Firma ueber genau diese Bausteine — mit Rechtetrennung, Freigabe-Schranken und Pruefprotokoll. Wenn Sie das fuer Ihr Unternehmen erwaegen, sprechen wir gern ueber den Zuschnitt.