Aufgabe
Native Implementierung eines Model Context Protocol (MCP)-Servers direkt innerhalb der Oracle-Datenbank – in PL/SQL mit einem eigenen Datenmodell.
Das Hauptziel dieses Architekturansatzes ist es, Large Language Models (LLMs) wie Mistral sicher und performant über ein REST-Interface anzubinden, ohne auf externe Serverkomponenten oder zusätzliche Middleware angewiesen zu sein.
Die vorgestellte Lösung nutzt ein zentrales, JSON-basiertes MCP-Interface in PL/SQL, um folgende Komponenten bereitzustellen:
Die gesamte Applikation ist in Oracle APEX realisiert.
Eine Orchestrierungsschicht in PL/SQL übernimmt das Mapping zwischen LLM-Anfragen und den spezifischen Datenbankfunktionen (Oracle AI Vector Search, Graph Studio, SQL).
Die Datenhaltung erfolgt in klassischen Tabellen, wodurch die Verwaltung über eine APEX-UI transparent und einfach wartbar bleibt.
Über die Ablaufsteuerung mittels Execution Plans wird gesteuert, wie ein Chat mit dem LLM ablaufen soll.
Zusätzlich zum LLM-eigenen Tool-Konzept wird hier ein eigenes Tool-Konzept für die Prompt-Analyse umgesetzt.
Ziel ist es, die Daten bereits vor der Übergabe an das LLM so aufzubereiten, dass das Modell die passenden Tools gezielter aufrufen kann.
MCP Begriffswelt:
Das Model Context Protocol (MCP) ist ein offener Standard, der es ermöglicht, KI-Modellen einen standardisierten Zugriff auf Datenquellen und Werkzeuge zu gewähren. Erkennt das LLM das für eine korrekte Antwort noch Daten fehlen, kann das LLM diese Fehlenden Daten über einen „Tools Call“ nachladen.
In diesem Szenario fungiert dazu die Oracle-Datenbank selbst als MCP-Server d.h. wir setzen die LLM Anfrage aus dem Datenbank Kern ab und das LLM kann im Chat Verlauf mit den „Tools“ weitere Daten aus der DB anfragen.
Die wichtigsten Konzepte sind dabei:
Dies sind die Daten, die das LLM einlesen kann.
In der Oracle-Welt entsprechen diese z. B. Dokumenteninhalten aus BLOB/CLOB-Spalten wie den Tarifdaten / „harte Stammdaten“ aus der Relationen Welt eines ERP System und weitere Metadaten aus dem Data Dictionary der DB.
Unsere Tools sind Funktionen in der Datenbank, deren Aufruf die das LLM anfordern kann.
Unsere DB Ressourcen werden wir mit PL/SQL Routinen abfragen.
Die Prompts sind vordefinierte Anweisungen oder Kontext-Templates, die das LLM bei der Aufgabenbewältigung unterstützen.
Diese werden zentral in Tabellen gepflegt, um die Logik von der Anwendung zu trennen.
Der Katalog bildet die „Capabilities“ des MCP-Servers ab.
In der Datenbank-Implementierung dient eine Tabelle als Verzeichnis aller verfügbaren Werkzeuge.
In diesem Szenario fungiert die Oracle-Datenbank selbst als MCP-Server:
Die Datenbank stellt dem LLM nicht nur Rohdaten bereit, sondern bietet über PL/SQL-Schnittstellen direkt ausführbare „Tools“ an.
Je nach aufgerufenen Interface und je nach definierten Exekution Plan behält durch diese Architektur die Datenbank die volle Kontrolle darüber, welche Informationen (Ressourcen) und Funktionen (SQL, Vektorsuche) dem Modell zur Verfügung gestellt werden, während die Kommunikation über ein einheitliches JSON-Format erfolgt.
Die Schritte die für einen vollständigen Chat notwendig werden in diesem Konzept über einen Ausführungsplan (Execution Plan) Tabelle gesteuert.
Die Idee dahinter ist die User Anfrage zuvor aktiv zu analysieren (siehe Oracle 26ai – Named Entity Extraction (NER) mit CTX_ENTITY zur Analyse von Tarifvertragsanfragen mit einem Hybrid Vector Ansatz um dann einen passenden Ausführungplan aufzurufen.
Diese Plan enthält dann die entsprechenden Prompts (System / Erweiterter System Prompt ) und die Tools mit denen dann ein Chat Lauf mit dem LLM gestartet wird.
Der Prozess der LLM-Interaktion folgt einem zyklischen Muster, das durch die PL/SQL-Schicht gesteuert wird.
Bei der ersten Anfrage (JSON mit System & User Prompt) übermittelt die Datenbank mit einem JSON Record im OpenAI Format den dynamisch generierten Tool-Katalog.
Das LLM erfährt so durch die technische Definition im `tools`-Array, welche Datenbankoperationen (z. B. Dokumente suche) es ausführen kann.
Wichtig: Im System-Prompt wird explizit auf die Strategie der Tool-Verwendung hinterlegt, um Halluzinationen zu vermeiden und die Entscheidungssicherheit des Modells zu erhöhen!
Die Datenbankumgebung empfängt die Antwort des Modells als JSON-Record. Die PL/SQL-Logik evaluiert nun zwei wesentliche Merkmale:
stop, ist die Antwort final und kann direkt an den Benutzer in der APEX-UI ausgegeben werden.Die Orchestrierungsschicht erkennt hierbei den `tool_name` (z.B. `getTarifVertrag`) und die vom Modell generierten `arguments`.
In diesem Fall wird der reguläre Antwortweg unterbrochen, um die Phase 3 einzuleiten.
Erkennt der Parser einen Tool-Wunsch, extrahiert er die Parameter aus dem übergebenen JSON und führt die entsprechende PL/SQL-Funktion (z. B. Vektorsuche) aus.
Das Ergebnis der Funktion wird in ein standardisiertes Tool-Output-Format überführt. Dieses enthält:
Das Ergebnis der Datenbankoperation wird als neue Nachricht im Chat-Verlauf (History) an das LLM zurückgesandt.
Die PL/SQL-Orchestrierung baut hierbei einen vollständigen Kontext-Stack auf.
Der Kontext-Stack umfasst:
Agentic Workflow & Chaining:
Basierend auf diesem erweiterten Kontext generiert das Modell einen neuen Antwort-Token.
Context Management: Da bei jedem Rekursionsschritt die gesamte Historie mitgesendet wird, überwacht die PL/SQL-Schicht das Token-Fenster des LLMs.
Bei komplexen „Multi-Tool-Abfragen“ wird ein intelligentes Pruning (Kürzen) der Historie oder eine Zusammenfassung (Summarization) alter Tool-Ergebnisse durchgeführt, um die Verarbeitungsgrenzen (Context Window) nicht zu überschreiten.
Übersicht über den Ablauf:
<code text>
[ User / APEX ] [ PL/SQL Orchestrator ] [ LLM (External API) ]
| | |
|---(1) User Query ---->| |
| |---(2) Request + Tools ----->|
| | |
| |<--(3) Tool-Call Request ----|
| | |
| |--[Execute DB Function] |
| |--[e.g. Vector Search ] |
| | |
| |---(4) Tool Result Context ->|
| | |
| |<--(5) Final Text Response --|
|<--(6) Formatted Ans.--| |
</code>
Um den Service betriebsbereit zu machen, müssen folgende technische Bausteine implementiert werden:
Als ein sehr aufwendigen Part stellt sich das „saubere“ Handling der JSON Strukturen heraus.
Ein direktes Arbeiten mit dem JSON Request und Response Payload der Chat Schnittstelle erzeugt doch relativ schnell einen sehr unübersichtlichen Code der sich schwer warten lässt.
Daher werden die JSON Elemente des Chat Verlaufs in eine jeweilige passende Type Struktur überführt. Im weiteren Flow der Business Logik wird mit diesem Type Objekten gearbeitet und die passende Type Struktur für die Antwort erzeugt.
Aus dem Antwort Type wird dann wieder das passende und wohlgeformte JSON erzeugt.
Für die Tools wird ein eigenes Package erstellt:
Beispiel JSON für die OpenAI Syntax (Tools Definition), um dem LLM die entsprechenden Fähigkeiten zu präsentieren:
{
"tools": [
{
"type": "function",
"function": {
"name": "getDialog",
"description": "Wird aufgerufen, wenn die Benutzeranfrage unzureichende Informationen enthält (z.B. fehlendes Tarifgebiet oder Branche), um diese gezielt vom Anwender zu erfragen.",
"parameters": {
"type": "object",
"properties": {
"missing_parameters": {
"type": "array",
"items": { "type": "string" },
"description": "Liste der fehlenden Parameter, die erfragt werden muessen."
},
"context": {
"type": "string",
"description": "Kurze Beschreibung des aktuellen Gesprächsstandes."
}
},
"required": ["missing_parameters"]
}
}
},
{
"type": "function",
"function": {
"name": "getTarifVertrag",
"description": "Liefert eine Liste relevanter Tarifvertraege basierend auf konkreten Fachparametern wie Tarifgebiet, Fachbereich und fachlichen Suchbegriffen.",
"parameters": {
"type": "object",
"properties": {
"tarifgebiet": {
"type": "string",
"description": "Geographische Region des Tarifvertrags."
},
"fachbereich": {
"type": "string",
"description": "Die Branche oder der fachliche Bereich."
},
"search_terms": {
"type": "array",
"items": { "type": "string" },
"description": "Liste von fachlichen Begriffen für die semantische Suche."
}
},
"required": ["tarifgebiet", "fachbereich"]
}
}
}
],
"tool_choice": "auto"
}
Konfiguration des Parameter tool_choice:
Der Parameter `tool_choice` steuert die Autonomie des Modells bei der Werkzeugnutzung:
Dieser Punkt stellt die größte Herausforderung in der Praxis dar, da LLM-Antworten oft unvorhersehbare Anomalien aufweisen.
Ein bloßes `JSON_ELEMENT_T.parse` reicht hier nicht aus.
Die PL/SQL-Schicht benötigt eine spezialisierte Sanitizing-Logik:
Die LLM-Antworten sind „untyped“ und oft fehleranfällig (ungültige Zeichen, Markdown-Artefakte, fehlende Klammern). Ein wiederholtes Parsen des rohen JSON-Strings innerhalb der Geschäftslogik führt zu hoher Komplexität und Fehlern.
Die Lösung:
Nachdem ein Validierer (Response Repair Engine) das JSON bereinigt hat, wird die Antwort sofort in ein strukturiertes PL/SQL Type Object (Object-Relational-Mapping) überführt.
Dieses Objekt dient als „Single Source of Truth“.
Alle weiteren Prozessschritte (Routing, Tool-Ausführung, APEX-Output) arbeiten nur noch mit diesem validen Objekt.
Funktionsweise: 1. Der Orchestrator erhält das rohe JSON. 2. Er ruft `t_mcp_response.from_json(l_repaired_json)` auf. 3. Innerhalb dieser Funktion wird `JSON_TABLE` genutzt, um die Daten einmalig in das Objekt zu laden. 4. Falls das System nun wissen muss, ob ein Tool gerufen wurde, genügt ein Aufruf von `l_res_obj.has_tools()`.
Referenzen: