Anatomie einer Enterprise Tool Library
Die meisten Teams laufen am selben Freitagnachmittag gegen dieselbe Wand. Eine Handvoll MCP-Server lief in der Entwicklung problemlos. Inzwischen sind es fünfzehn, die Hälfte davon hängt an Produktion, und niemand kann die einfachen Fragen beantworten — wer hat was aufgerufen, mit welchem Credential, gegen welche Policy, und wo ist das Log. Das Problem ist nicht ein einzelner Server. Das Problem ist, dass es darunter keine gemeinsame Schicht gibt.
In diesem Artikel geht es um genau diese Schicht. Er beschreibt, wie eine Enterprise Tool Library gebaut ist: die sechs Anliegen, die sie in einer Runtime vereinen muss, die fail-closed Pipeline, die jeder Tool-Call durchläuft, und die beiden Ideen — DADL und Code Mode — die das Ganze auf Dutzende Backends skalierbar machen, ohne den Kontext des Agenten aufzublähen. Die geschäftliche Einordnung auf Deutsch liefert der Schwester-Artikel auf dunkel.cloud (DE).
Was eine Enterprise Tool Library tatsächlich ist
Abschnitt betitelt „Was eine Enterprise Tool Library tatsächlich ist“Der Begriff „MCP Gateway” wird für zwei sehr verschiedene Dinge verwendet. Das eine ist ein Passthrough-Proxy, der Tool-Calls an einen einzelnen Upstream-Server weiterreicht. Das andere ist eine kontrollierte Ausführungsschicht, die vor jedem Backend der Organisation sitzt. Nur das Zweite ist eine Enterprise Tool Library.
Das unterscheidende Merkmal ist nicht die Anzahl der Backends. Es ist strukturell. Eine Library hat:
- Eine Authentifizierungs- und Autorisierungsgrenze. Jeder Tool-Call — egal welches Upstream-System er am Ende trifft — durchläuft dieselbe Identitätsprüfung und dieselbe Zugriffsentscheidung pro Tool. Eine einzige Quelle der Wahrheit für „wer darf was aufrufen”.
- Eine kohärente Audit-Linie. Jeder Call landet im selben Store, mit denselben Feldern, abfragbar mit einer SQL-Query. Nicht „Logs hier drüben, MCP-Server-Logs da drüben, ein CSV, das jemand letztes Quartal exportiert hat”.
- Eine gemeinsame deklarative Beschreibungssprache. REST-APIs, MCP-Server, interne Dienste — alle über dasselbe Definitionsformat zugänglich, sodass Policy, Retry, Pagination und Credential-Injection Runtime-Anliegen statt maßgeschneiderter Wrapper-Code sind.
- Ein konstanter Kontext-Footprint. Das zwanzigste Backend hinzuzufügen verdoppelt nicht die Tokens, die der Agent mitschleppen muss. Die Kosten für „mehr Tools” trägt die Library, nicht jeder Prompt.
Ein einzelner MCP-Server hat nichts davon. Ein Passthrough-Gateway hat bestenfalls die ersten zwei, und das auch nur für ein Backend gleichzeitig. Eine Enterprise Tool Library ist das, was entsteht, wenn diese vier Eigenschaften über die gesamte Tool-Oberfläche hinweg gelten.
ToolMesh ist unsere Open-Source-Implementierung dieser Form. Der Rest dieses Artikels läuft durch, wie die Teile zusammenpassen.
Die sechs Säulen als Mechanik
Abschnitt betitelt „Die sechs Säulen als Mechanik“ToolMesh ordnet die Arbeit in sechs Säulen. Jede löst ein konkretes Problem, lebt in einem bestimmten Paket und hat einen einzigen Config-Berührungspunkt.
1. Any Backend — zwei Eingangspfade, eine Runtime
Abschnitt betitelt „1. Any Backend — zwei Eingangspfade, eine Runtime“Die Library muss vor allem stehen können, was die Organisation bereits betreibt. ToolMesh unterstützt zwei Eingangspfade in dieselbe Ausführungs-Pipeline:
- MCP-Backends umschließen bestehende MCP-Server via HTTP- oder STDIO-Transport. Wer schon einen funktionierenden Server hat, behält ihn; ToolMesh legt die Governance-Schicht drumherum.
- DADL-Backends beschreiben REST-APIs deklarativ in YAML. Kein eigener Server-Code, keine separate Runtime — die Beschreibung ist die Integration.
Beide Pfade registrieren sich über dasselbe interne backend.Register()-Interface und produzieren Tools, die für das Modell identisch aussehen. Der Backend-Typ wird zum Implementierungsdetail; die Sicherheitsgarantien hängen nicht davon ab. Konfiguration lebt in einer einzigen Datei:
backends: - name: github type: rest file: github.dadl - name: cloudflare type: rest file: cloudflare.dadl - name: legacy-internal type: mcp transport: http url: http://internal-mcp:8080Ein Backend hinzuzufügen heißt, eine Datei zu editieren — keinen Dienst zu bauen.
2. Code Mode — flache Tool-Oberfläche, konstanter Kontext
Abschnitt betitelt „2. Code Mode — flache Tool-Oberfläche, konstanter Kontext“Ein naives MCP-Setup gibt jede Tool-Definition direkt an das Modell. Bei einem Backend ist das in Ordnung. Bei zwanzig wird es ruinös: Dutzende Tool-Schemata, jedes mit eigenen Parametern und Beschreibungen, allesamt in den Kontext geladen, bevor der Agent die Frage des Nutzers überhaupt gelesen hat. Rund 50.000 Tokens an reinen Tool-Metadaten ist eine normale Beobachtung in der Praxis.
ToolMesh umgeht das vollständig. Das Modell sieht zwei Meta-Tools — discover_tools und execute_code — und ein kompaktes TypeScript-Interface, das die verfügbaren Funktionen beschreibt. Dieses Interface umfasst grob 1.000 Tokens, egal ob ein oder zwanzig Backends angeschlossen sind. Der Agent ruft discover_tools einmal auf, um die Typ-Signaturen zu sehen, und schreibt dann JavaScript dagegen:
const issues = await toolmesh.github_list_issues({ owner: "DunkelCloud", repo: "ToolMesh", state: "open",});const recent = issues.filter(i => Date.now() - new Date(i.created_at) < 7*864e5);return recent.map(i => ({ number: i.number, title: i.title }));Der execute_code-Runner parst dieses JavaScript mit einem Go-AST-Walker, extrahiert jeden toolmesh.*-Aufruf und schickt ihn durch die normale Pipeline. Das Modell führt nie beliebigen Code gegen Ihre Infrastruktur aus — es produziert ein Skript, dessen Tool-Calls statisch extrahiert und einzeln autorisiert werden. Code Mode ist im Detail unter /de/code-mode/ beschrieben.
3. Credential Store — Laufzeit-Injection, nie im Prompt
Abschnitt betitelt „3. Credential Store — Laufzeit-Injection, nie im Prompt“Das Anliegen, das die meisten Setups stillschweigend ignorieren. Eine typische MCP-Konfiguration legt API-Keys in Client-Configs, in Env-Vars auf Entwickler-Laptops oder — im schlimmsten Fall — direkt in den Modell-Kontext. Nichts davon rotiert gut, nichts davon auditiert gut, und jedes davon kann in ein Transkript lecken.
ToolMesh referenziert Credentials per Namen in der DADL-Datei und löst sie serverseitig zur Ausführungszeit auf. Das Modell sieht das Tool-Interface und die gefilterte Antwort. Es sieht nie das Token. Die Credential-Schicht ist über credentials.Register() austauschbar:
| Stufe | Backend | Einsatz |
|---|---|---|
| Embedded | Env-Vars (CREDENTIAL_*) | Lokale Entwicklung, Single-Tenant |
| Geplant | Infisical | Zentrales Secret-Management |
| Geplant | HashiCorp Vault / OpenBao | Enterprise-Secret-Governance |
Das Embedded-Backend ist der Default und ist im Binary enthalten. Die gehosteten Backends sind über den Enterprise-Build-Tag geplant — das Registrierungs-Muster (inspiriert von Go’s database/sql-Treibern) ist bereits an Ort und Stelle, also erfordert das Hinzufügen eines Backends keinen Eingriff in den Executor.
4. OpenFGA — ReBAC, ehrlich gegenüber dem Default
Abschnitt betitelt „4. OpenFGA — ReBAC, ehrlich gegenüber dem Default“Autorisierung in ToolMesh ist beziehungsbasiert, betrieben von OpenFGA. Das Modell ist user → plan → tool: Nutzer gehören zu Plänen, Pläne haben Tool-Berechtigungen, und der Executor fragt OpenFGA, ob der Aufrufer dieses spezifische Tool aufrufen darf, bevor irgendetwas läuft.
Heute werden zwei Modi ausgeliefert:
OPENFGA_MODE | Verhalten |
|---|---|
bypass (Default) | Keine Autorisierungs-Checks. Nur für Dev. |
restrict | OpenFGA bei jedem Tool-Call durchgesetzt. Produktion. |
Der Default ist bypass, damit docker compose up eine lauffähige Instanz produziert, ohne jeden Mitwirkenden durch ein OpenFGA-Setup zu zwingen. Das ist eine bewusste DX-Entscheidung, keine Sicherheitsaussage. Produktiv-Deployments wechseln auf restrict und führen ./config/openfga/setup.sh aus, um das Modell zu seeden. Den Default auf bypass zu setzen ist ehrlich darüber, wo die Dev-Oberfläche beginnt; die Seite Authorization dokumentiert die Produktions-Checkliste.
5. Output Gate — deterministisches JS, mit Wachstumsraum
Abschnitt betitelt „5. Output Gate — deterministisches JS, mit Wachstumsraum“Nicht jede Response sollte das Modell unverändert erreichen. Kundendatensätze enthalten PII. Interne Systeme liefern Metadaten zurück, die der Agent nicht braucht. Fehlermeldungen leaken gelegentlich Infrastruktur-Details.
Heute liefert ToolMesh Layer 1 des Gates aus: deterministische JavaScript-Policies, ausgeführt in der eingebetteten goja-Engine. Eine Policy bekommt die Response und den Caller-Kontext und gibt einen gefilterten Payload oder eine Ablehnung zurück:
export function onResponse(response, ctx) { if (ctx.user.callerClass === "trusted") return response; return redactEmails(redactPhones(response));}Policies sind reviewbarer Code, der im Repo eingecheckt ist, keine Black-Box-Konfiguration. Das Gate sitzt auf einer austauschbaren Evaluator-Registry (gate.RegisterEvaluator()) — weitere Layer sind geplant, darunter LLM-basierte Inhaltsklassifikation für Compliance-Anwendungen. Heute liefern wir einen Layer aus. Wir tun nicht so, als wären es mehr. Siehe /de/output-gate/.
6. Audit — slog und SQLite, SQL-abfragbar
Abschnitt betitelt „6. Audit — slog und SQLite, SQL-abfragbar“Jeder Tool-Call wird aufgezeichnet: wer ihn aufgerufen hat, welches Tool, mit welchen Parametern, was das Ergebnis war, wie lange er gedauert hat und ob er erfolgreich war. ToolMesh liefert zwei Audit-Backends aus — Gos strukturiertes slog für log-orientierte Setups und einen Append-only-SQLite-Store für abfragbare Compliance-Audits.
Wenn jemand fragt „was hat dieser Agent letzten Dienstag zwischen 14:00 und 15:00 gemacht?”, ist die Antwort eine SELECT-Query — keine forensische Übung über fünf Log-Aggregatoren hinweg.
Die fail-closed Request-Pipeline
Abschnitt betitelt „Die fail-closed Request-Pipeline“Die Säulen sind wichtig, aber ihr Wert entsteht durch die Reihenfolge, in der sie laufen. Ein einzelner Tool-Call läuft diesen Weg:
┌─────────────────────────────────────────────────────────────────┐│ Client (Claude Desktop / ChatGPT / CLI / Hosted Agent) │└──────────────────────────────┬──────────────────────────────────┘ │ ▼ ┌───────────────────────┐ │ 1. Authentication │ OAuth 2.1 PKCE / API-Key │ → UserContext │ → Identität feststeht └──────────┬────────────┘ ▼ ┌───────────────────────┐ │ 2. Authorization │ OpenFGA: user × plan × tool │ (fail closed) │ → allowed oder DENY └──────────┬────────────┘ ▼ ┌───────────────────────┐ │ 3. Credential │ Serverseitig aufgelöst, │ Injection │ nie an das Modell exponiert └──────────┬────────────┘ ▼ ┌───────────────────────┐ │ 4. Output Gate (in) │ Optionaler Pre-Execution-Check │ (fail closed) │ → allowed oder REJECT └──────────┬────────────┘ ▼ ┌───────────────────────┐ │ 5. Execute │ MCP-Server oder REST via DADL │ │ → Rohe Response └──────────┬────────────┘ ▼ ┌───────────────────────┐ │ 6. Output Gate (out) │ PII-Redaktion, Shaping │ │ → Gefilterte Response └──────────┬────────────┘ ▼ ┌───────────────────────┐ │ 7. Audit │ Append-only, abfragbar │ │ → SQLite / slog └──────────┬────────────┘ ▼ Zurück an ClientDie Pipeline ist fail-closed: Lehnt AuthZ ab, läuft nichts. Lehnt das Gate ab, läuft nichts. Schlägt die Credential-Auflösung fehl, läuft nichts. Der Default bei jeder Unsicherheit ist „nicht ausführen”. Das ist die Umkehrung dessen, was die meisten Ad-hoc-Setups tun, wo ein fehlender Check stillschweigend „weiter” bedeutet.
CallerClass — die zweite Achse, die sonst niemand hat
Abschnitt betitelt „CallerClass — die zweite Achse, die sonst niemand hat“Die meisten Gateways behandeln Autorisierung als eine einzige Achse: wer ist der Nutzer? Das ist notwendig, aber nicht hinreichend. Derselbe Nutzer, mit derselben Frage, läuft auf einem ganz anderen Vertrauensniveau, wenn er in eine lokale Claude-Desktop-Session tippt, im Vergleich dazu, wenn ein hosted Agent den Call in seinem Namen als Teil eines geplanten Workflows macht.
ToolMesh behandelt das als erstklassiges Konzept: CallerClass, an jeden Request angehängt, mit drei Werten:
| CallerClass | Typischer Ursprung | Typische Filterung |
|---|---|---|
trusted | Lokale CLI, Entwickler-Laptop | Minimal — nur Credentials werden redigiert |
standard | Authentifizierter Nutzer über bekannten Client | PII-Redaktion, Schema-Shaping |
untrusted | Hosted Agents, CI-Bots, Drittclients | Streng — keine Admin-Tools, aggressive Redaktion |
CallerClass moduliert zwei Dinge gleichzeitig: welche Tools überhaupt erreichbar sind (ein untrusted-Aufrufer kann von admin-Tools ausgeschlossen werden, selbst wenn der Plan des Nutzers sie normalerweise zulassen würde) und was das Output Gate vor dem Modell redigiert. Dasselbe Backend, derselbe Nutzer, anderer Trust-Envelope — automatisch durchgesetzt.
Das ist der Teil, den es in Passthrough-Gateways nicht gibt. Sie modellieren den Nutzer. Sie modellieren nicht „wer fragt im Namen des Nutzers”. Für Agenten-Infrastruktur liegt das echte Risiko vor allem auf dieser zweiten Achse.
Beispiel aus der Praxis: ein Backend hinzufügen
Abschnitt betitelt „Beispiel aus der Praxis: ein Backend hinzufügen“Den list_issues-Endpunkt von GitHub als kontrolliertes Tool hinzuzufügen, sieht in voller Länge so aus:
# github.dadlbackend: name: github type: rest base_url: https://api.github.com
auth: type: bearer credential: github_token # zur Laufzeit aufgelöst, nie im Prompt
defaults: errors: retry_on: [429, 502, 503] retry_strategy: max_retries: 3 backoff: exponential
tools: list_issues: method: GET path: /repos/{owner}/{repo}/issues access: read # → über OpenFGA auf Plan-Berechtigungen gemappt description: "List issues in a repository" params: owner: { type: string, in: path, required: true } repo: { type: string, in: path, required: true } state: { type: string, in: query, default: open } per_page: { type: integer, in: query, default: 30 } pagination: strategy: link_headerDreiundzwanzig Zeilen. Datei in die Registry legen, in backends.yaml referenzieren, neu starten. Was Sie kostenlos dazu bekommen:
- Authentication — OAuth oder API-Key am Eingang, Bearer-Token-Injection auf der Outbound-Seite, beides von der Runtime erledigt.
- Authorization —
access: readordnet das Tool im OpenFGA-Modell ein; der Request wird vor der Ausführung abgelehnt, wenn der Plan des Aufrufers ihn nicht enthält. - Credential-Isolation —
github_tokenwird zur Ausführungszeit aus dem Credential Store geholt; das Modell sieht den Wert nie. - Retry und Pagination — in der Beschreibung deklariert, von der Runtime ausgeführt.
- Audit — jeder Call landet im Audit-Store mit Aufrufer, Parametern, Status, Latenz.
- Output-Filterung — das Gate läuft auf der Response, bevor das Modell sie sieht, und wendet die für die CallerClass passende Policy an.
Nichts davon ist Wrapper-Code, den jemand speziell für GitHub geschrieben hat. Das ist, was die Runtime für jedes per DADL beschriebene Tool tut. Dieselbe Governance um einen handgerollten MCP-Server zu bauen, sind mehrere hundert Zeilen Boilerplate pro Backend — Arbeit, die die meisten Teams ungebaut ausliefern und später bereuen.
Der Schwester-Artikel Stop Rebuilding REST API Wrappers for MCP geht tiefer auf das DADL-Format ein und darauf, was wegfällt, wenn man es einsetzt.
Warum der Kontext-Footprint konstant bleibt
Abschnitt betitelt „Warum der Kontext-Footprint konstant bleibt“Die meisten Gateways skalieren linear mit der Anzahl der Backends — jeder neue Server fügt Tool-Definitionen hinzu, die in jedem Prompt mitfahren. Zwanzig Backends, zwanzig Schema-Bundles, fünfzigtausend Tokens, bevor der Nutzer überhaupt ein Wort getippt hat.
Code Mode bricht diese Skalierung. Das Modell sieht discover_tools und execute_code sowie ein kompaktes TypeScript-Interface, das auflistet, was verfügbar ist. Ein zusätzliches Backend lässt die Interface-Beschreibung wachsen, aber nicht den Overhead pro Request — der Agent schlägt Tools bei Bedarf nach, statt sie permanent mitzuschleppen.
In der Praxis surft eine ToolMesh-Instanz vor der aktuellen öffentlichen DADL-Registry — 23 APIs, 2.982 einzelne Tools über GitHub, Cloudflare, GitLab, Stripe, Hetzner Cloud, Linode, NetBox, DeepL und andere — gegenüber dem Modell mit ungefähr denselben Kontext-Kosten wie ein einziger handgebauter MCP-Server mit einem Dutzend Tools. Genau dieses Verhältnis ist der ganze Punkt. Ohne es funktioniert die Library-Form überhaupt nicht.
Scope und Ehrlichkeit: ausgeliefert vs. geplant
Abschnitt betitelt „Scope und Ehrlichkeit: ausgeliefert vs. geplant“ToolMesh ist Early Stage. Ein Teil der Architektur ist ausgeliefert, ein Teil ist in Bewegung. Das explizit zu benennen ist nützlicher als ambitionierte Behauptungen.
Heute ausgeliefert:
- Alle sechs Säulen durch die fail-closed Pipeline verdrahtet
- Authentication (OAuth 2.1 PKCE, API-Keys, Multi-User via
users.yaml) - Authorization (OpenFGA, Modi
bypass/restrict) - Credential Store mit eingebettetem Env-Var-Backend
- Output Gate Layer 1 (deterministische goja-JS-Policies)
- Audit via slog und SQLite
- MCP- und DADL-Backends, Registry-basiert
- Code Mode mit AST-extrahierten Tool-Calls
Bewusste Entwickler-Defaults — für Produktion umschalten:
OPENFGA_MODE=bypass(keine Autorisierung)- Debug-Logging an
- HTTP+TLS-Reverse-Proxy davor angenommen für TLS-Terminierung
Diese Defaults sorgen dafür, dass docker compose up sauber in der Entwicklung läuft. Sie sind als nicht-produktionsreif dokumentiert. Die Seite Configuration listet jede Variable und ihre Produktions-Empfehlung auf.
Geplant, registriert, aber noch nicht ausgeliefert:
- Infisical- und HashiCorp-Vault-Credential-Backends (Erweiterungspunkt existiert)
- Weitere Gate-Layer (LLM-basierte Klassifikation, Compliance-Filter)
- Erweiterte DADL-Registry-Abdeckung (Community-Beiträge willkommen)
Diese als „geplant” zu bezeichnen ist das richtige Maß an Selbstvertrauen. Die Registrierungs-Mechanik steht; die Arbeit, sie anzubinden, liegt vor uns.
Backends in der Praxis
Abschnitt betitelt „Backends in der Praxis“Dieser Artikel ist die architektonische Referenz. Die detaillierten Connector-Walkthroughs — wie man NetBox aus Cloud-APIs befüllt, wie man OPNsense hinter eine Tool-Schicht setzt, wie man den HashiCorp-Stack integriert — leben in eigenen Beiträgen. Die aktuellen Folgen:
- Populating NetBox from Real Infrastructure with ToolMesh
- Weitere folgen zu OPNsense, der HashiCorp-Suite und weiteren registry-getriebenen Integrationen
Wer Infrastruktur betreibt, die ein gutes Worked Example abgäbe, beginnt am besten in der DADL-Registry — die meisten APIs mit OpenAPI-Spec können an einem Nachmittag zu DADL-Dateien werden.
Verwandte Themen
Abschnitt betitelt „Verwandte Themen“- Das Warum, auf Deutsch: dunkel.cloud — Enterprise Tool Library — geschäftliche Einordnung, keine Architektur
- Der Skalierungs-Trick: /de/code-mode/ — wie der Kontext-Footprint konstant bleibt
- Die Protokoll-Frage: Why MCP Gateways Alone Don’t Solve the Real Problem — Gateway vs. Description Layer
- Die Beschreibungssprache: Stop Rebuilding REST API Wrappers for MCP — was DADL ersetzt
- Häufige Fragen: /de/faq/ — kurze Antworten zu MCP, DADL, ToolMesh
- Quellcode: GitHub — DunkelCloud/ToolMesh
- Connector-Format: dadl.ai — Registry und Spec
Ausprobieren
Abschnitt betitelt „Ausprobieren“Der schnellste Weg von diesem Artikel zu einer laufenden Instanz:
- Hosted Demo. Try the Demo — ein gemanagtes ToolMesh mit einem kuratierten Backend-Set, kein Setup.
- Self-Host-Quickstart. Repo klonen,
docker compose up, MCP-Client aufhttp://localhost:8080zeigen lassen. Der vollständige Walkthrough liegt unter Getting Started.
Eine Enterprise Tool Library ist keine Bibliothek von Tools. Sie ist die Schicht darunter, die „any backend” zu einer Eigenschaft der Runtime macht, statt zu einer Eigenschaft jedes einzelnen Wrappers. Die sechs Säulen, die fail-closed Pipeline und die CallerClass-Achse sind das, was einen Haufen MCP-Server in etwas verwandelt, das ein Unternehmen tatsächlich betreiben kann.