OpenAPI zum MCP-Server: 150 Endpunkte, 49 KI-Tools
Wie wir eine OpenAPI-Spezifikation in einen kuratierten MCP-Server mit 49 Tools umgewandelt haben. Schemakonvertierung, Toolbeschreibungen, Anmerkungen und zustandsloser HTTP-Transport.
Die REST-API von Botoi verfügt über mehr als 150 Endpunkte. Als wir den MCP-Server erstellt haben, haben wir 49 davon als Tools registriert. Nicht, weil der Rest nicht funktioniert. Denn einem KI-Modell 150 Werkzeuge zu geben, ist so, als würde man jemandem ein 200-seitiges Menü geben; Sie werden etwas auswählen, aber es wird nicht das Richtige sein.
In diesem Beitrag wird der gesamte Prozess beschrieben: Kuratieren der Toolliste, Konvertieren von OpenAPI-Schemas in Zod-Objekte, Schreiben von Beschreibungen, die KI-Modelle gut analysieren, Hinzufügen von MCP-Anmerkungen und Ausführen des Ganzen als zustandsloser Cloudflare Worker. Wenn Sie eine API verwalten und daraus einen MCP-Server erstellen möchten, ist dies das Playbook.
Warum 49 Werkzeuge, nicht 150
Jedes Tool, das Sie auf einem MCP-Server registrieren, wird im Kontextfenster des Modells serialisiert. Der Name des Tools, die Beschreibung und das vollständige Eingabeschema verbrauchen alle Token. Ein Manifest mit 150 Tools kann mehr als 30.000 Token verbrennen, bevor der Benutzer ein einziges Wort eingibt.
Dadurch entstehen zwei Probleme:
- Für die Konversation selbst sind weniger Token übrig
- Das Modell wählt häufiger das falsche Werkzeug aus, wenn die Liste lang ist
Wir haben das getestet. Da alle über 150 Endpunkte registriert waren, wählte Claude in etwa 72 % der Fälle gleich beim ersten Versuch das richtige Tool aus. Bei 49 kuratierten Tools stieg diese Zahl auf 94 %. Durch die kleinere, fokussierte Liste konnte das Modell seine Aufgabe besser erfüllen.
Die Kurationskriterien waren einfach:
- Braucht ein KI-Agent dies mitten im Gespräch? (DNS-Suche: ja. PDF-Generierung: selten.)
- Gibt das Tool strukturierte Daten zurück, über die das Modell nachdenken kann? (JSON: ja. Binärbild: nein.)
- Kann das Modell die erforderlichen Parameter aus natürlicher Sprache füllen? (Domänenname: ja. Komplexe verschachtelte Konfigurationsobjekte: nein.)
Die Werkzeugmanifeststruktur
Jedes kuratierte Tool ordnet einen MCP-Toolnamen einem API-Pfad, einer HTTP-Methode, einer Beschreibung und Anmerkungen zu. Hier ist die TypeScript-Schnittstelle:
export interface CuratedTool {
path: string;
method: 'get' | 'post';
title: string;
description: string;
annotations: {
readOnlyHint?: boolean;
destructiveHint?: boolean;
idempotentHint?: boolean;
openWorldHint?: boolean;
};
}
Und so sehen zwei Einträge in der Praxis aus:
// curated-tools.ts
export const CURATED_TOOLS: Record<string, CuratedTool> = {
lookup_dns: {
path: '/v1/dns/lookup',
method: 'post',
title: 'DNS Lookup',
description:
'Query DNS records (A, AAAA, MX, TXT, CNAME, NS) for a domain. ' +
'Use when you need to check DNS configuration or troubleshoot domain resolution.',
annotations: { readOnlyHint: true, openWorldHint: true },
},
dev_hash: {
path: '/v1/hash',
method: 'post',
title: 'Hash Text',
description:
'Generate a hash (MD5, SHA-1, SHA-256, SHA-512) of input text. ' +
'Use for checksums, data integrity, or fingerprinting.',
annotations: { readOnlyHint: true },
},
// ... 47 more tools
};
Der path Und method Felder verweisen auf den vorhandenen REST-Endpunkt. Der description teilt dem Modell mit, wann das Werkzeug verwendet werden soll. Der annotations Teilen Sie dem Modell mit, wie sich das Werkzeug verhält.
Konvertieren von OpenAPI-Schemas in Zod
Das MCP SDK erwartet Tool-Eingabeschemata als Zod-Objekte. Unsere API verfügt bereits über eine OpenAPI 3.1-Spezifikation mit vollständigen Anforderungstextdefinitionen für jeden Endpunkt. Der Schema-Builder liest diese Definitionen und generiert Zod-Typen beim Serverstart.
Die Kernkonvertierungsfunktion ordnet jeden OpenAPI-Eigenschaftstyp seinem Zod-Äquivalent zu:
// schema-builder.ts
import { z } from 'zod';
import { paths } from '../../openapi-paths';
function mapPropertyToZod(
prop: OpenApiProperty,
isRequired: boolean
): z.ZodTypeAny {
let schema: z.ZodTypeAny;
if (prop.enum && prop.enum.length > 0) {
schema = z.enum(prop.enum as [string, ...string[]]);
} else {
switch (prop.type) {
case 'number':
case 'integer':
schema = z.number();
break;
case 'boolean':
schema = z.boolean();
break;
case 'array':
schema = z.array(z.string());
break;
case 'object':
schema = z.record(z.unknown());
break;
default:
schema = z.string();
break;
}
}
if (prop.description) {
schema = schema.describe(prop.description);
}
if (prop.default !== undefined) {
schema = schema.default(prop.default);
}
if (!isRequired) {
schema = schema.optional();
}
return schema;
}
Die wichtigsten Entscheidungen in dieser Funktion:
enumWerte werdenz.enum(), wodurch das Modell einen festen Satz gültiger Optionen erhält- Erforderliche Felder bleiben Pflichtfelder; optionale Felder erhalten
.optional() - Die OpenAPI
descriptionüberträgt über.describe(), die das MCP SDK in das Tool-Manifest einfügt - Standardwerte werden weitergegeben
.default()
Der buildZodSchema Die Funktion verarbeitet sowohl POST- (Anfragetext) als auch GET-Endpunkte (Abfrageparameter):
export function buildZodSchema(
apiPath: string,
method: 'get' | 'post'
): Record<string, z.ZodTypeAny> {
const operation = getOperation(apiPath, method);
if (!operation) return {};
// POST: read from requestBody schema
if (method === 'post') {
const schema = operation.requestBody
?.content?.['application/json']?.schema;
if (!schema?.properties) return {};
const required = new Set(schema.required ?? []);
const result: Record<string, z.ZodTypeAny> = {};
for (const [key, prop] of Object.entries(schema.properties)) {
result[key] = mapPropertyToZod(prop, required.has(key));
}
return result;
}
// GET: read from query parameters
const params = operation.parameters;
if (!params || params.length === 0) return {};
const result: Record<string, z.ZodTypeAny> = {};
for (const param of params) {
if (param.in !== 'query') continue;
const prop: OpenApiProperty = {
type: param.schema?.type ?? 'string',
description: param.description ?? param.schema?.description,
default: param.schema?.default,
enum: param.schema?.enum,
};
result[param.name] = mapPropertyToZod(prop, param.required === true);
}
return result;
}
Diese Funktion wird während der Servererstellung einmal pro Tool ausgeführt. Es gibt eine Wohnung zurück Record<string, z.ZodTypeAny> dass das MCP SDK für das Tool-Manifest in das JSON-Schema serialisiert.
Beschreibungen von Werkzeugen können von KI-Modellen gut analysiert werden
Die Werkzeugbeschreibung ist das wichtigste Feld für die richtige Werkzeugauswahl. Modelle lesen es, um zu entscheiden, ob ein Tool der Absicht des Benutzers entspricht. Vage Beschreibungen führen zu falschen Werkzeugauswahlen.
Wir haben uns für ein Zwei-Satz-Muster entschieden:
- Erster Satz: was das Tool macht, beginnend mit einem Verb. Geben Sie die spezifischen Datentypen oder Formate an, die verarbeitet werden.
- Zweiter Satz: wann man es benutzt, beginnend mit „Verwenden wann“. Dadurch erhält das Modell eine Triggerbedingung.
Vergleichen Sie diese beiden Beschreibungen für dasselbe DNS-Suchtool:
| Version | Beschreibung | Problem |
|---|---|---|
| Schlecht | „DNS-Tool zum Nachschlagen“ | Keine Datensatztypen aufgeführt, keine Auslösebedingung, vage |
| Gut | „DNS-Einträge (A, AAAA, MX, TXT, CNAME, NS) für eine Domäne abfragen. Verwenden Sie diese Option, wenn Sie die DNS-Konfiguration überprüfen oder Probleme bei der Domänenauflösung beheben müssen.“ | Keiner |
Die gute Version teilt dem Modell die genauen Datensatztypen mit, die es abfragen kann (damit es weiß, dass dieses Tool MX-Suchen verarbeitet) und die Situationen, die es auslösen sollen (DNS-Konfigurationsprüfungen, Fehlerbehebung). Das Modell gleicht die Benutzerabsicht mit diesen Schlüsselwörtern ab.
MCP-Anmerkungen: Modellen mitteilen, wie sich Werkzeuge verhalten
Anmerkungen sind Metadaten-Flags für jedes Tool. Sie haben keinen Einfluss auf die Ausführung. Sie sagen dem Modell, welche Nebenwirkungen zu erwarten sind.
// Read-only tool that hits an external service
lookup_dns: {
annotations: { readOnlyHint: true, openWorldHint: true },
}
// Encryption tool: no external calls, same input = same output
security_encrypt: {
annotations: { idempotentHint: true },
}
Die vier Anmerkungen und was sie signalisieren:
| Anmerkung | Signal | Beispiel |
|---|---|---|
readOnlyHint |
Dieses Tool liest Daten, verändert aber nie etwas | DNS-Suche, WHOIS, SSL-Prüfung |
destructiveHint |
Dieses Tool löscht oder überschreibt Daten | Löschen des Webhook-Posteingangs (nicht in unserem kuratierten Set) |
idempotentHint |
Der zweimalige Aufruf dieses Tools mit derselben Eingabe führt zum gleichen Ergebnis | AES-Verschlüsselung, AES-Entschlüsselung |
openWorldHint |
Dieses Tool stellt externe Netzwerkanfragen | IP-Suche, URL-Metadaten, technische Erkennung |
Von unseren 49 Werkzeugen sind 44 tragbar readOnlyHint: true. Die 12 Nachschlagetools tragen ebenfalls openWorldHint: true weil sie externe DNS-Server oder WHOIS-Register aufrufen oder Live-Webseiten abrufen. Die Verschlüsselungs-/Entschlüsselungstools enthalten idempotentHint: true weil es sich um deterministische Transformationen handelt.
Keines unserer kuratierten Tools trägt destructiveHint. Das war eine bewusste Entscheidung. Wir haben Tools wie das Löschen des Webhook-Posteingangs und das Löschen von Einfügen aus dem kuratierten Satz ausgeschlossen, da KI-Modelle Benutzerdaten nicht ohne strenge Leitplanken löschen sollten.
Registrieren von Tools auf dem MCP-Server
Die Registrierungsschleife verbindet alles miteinander. Es durchläuft die kuratierte Toolliste, erstellt das Zod-Schema aus der OpenAPI-Spezifikation und registriert jedes Tool mit seiner Beschreibung und seinen Anmerkungen:
// server.ts
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { CURATED_TOOLS } from './curated-tools';
import { buildZodSchema } from './schema-builder';
function createMcpServer(apiKey: string | undefined, env: Env) {
const server = new McpServer(
{ name: 'botoi', version: '1.0.0' },
{ jsonSchemaValidator: new CfWorkerJsonSchemaValidator() }
);
for (const [toolName, tool] of Object.entries(CURATED_TOOLS)) {
const zodSchema = buildZodSchema(tool.path, tool.method);
server.registerTool(toolName, {
title: tool.title,
description: tool.description,
inputSchema: zodSchema,
annotations: tool.annotations,
}, async (args: Record<string, unknown>) => {
return callApi(tool.path, tool.method, args, apiKey, env);
});
}
return server;
}
Wenn das Modell ein Tool aufruft, empfängt die Handlerfunktion die geparsten Argumente und leitet sie an die interne API-Route weiter. Der callApi Die Funktion erstellt eine interne HTTP-Anfrage und gibt die Antwort als MCP-formatierten Inhalt zurück:
async function callApi(
path: string,
method: string,
body: unknown,
apiKey: string | undefined,
env: Env
) {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
};
if (apiKey) headers['X-API-Key'] = apiKey;
const req = new Request(\`http://internal\$\{path}\`, {
method: method.toUpperCase(),
headers,
body: method === 'post' ? JSON.stringify(body) : undefined,
});
const res = appFetcher
? await appFetcher(req, env)
: await fetch(req);
const json = await res.json();
if (!json.success) {
return {
content: [{ type: 'text', text: JSON.stringify(json.error, null, 2) }],
isError: true,
};
}
return {
content: [{ type: 'text', text: JSON.stringify(json.data, null, 2) }],
};
}
Der appFetcher Mit diesem Muster kann der MCP-Server die API-Routen über eine interne Funktionsreferenz aufrufen, anstatt eine externe HTTP-Anfrage zu stellen. Dadurch werden Netzwerk-Roundtrips vermieden. Der MCP-Handler und die API-Routen werden im selben Cloudflare-Worker ausgeführt, sodass das interne Routing ein Funktionsaufruf und kein HTTP-Hop ist.
Zustandsloser HTTP-Transport auf Cloudflare Workers
MCP unterstützt zwei Transporte: stdio (für lokale Prozesse) und Streamable HTTP (für Remote-Server). Wir haben uns für Streamable HTTP entschieden, da der Server auf Cloudflare Workers läuft, die keine lang laufenden Prozesse unterstützen.
// Hono route handler
import { WebStandardStreamableHTTPServerTransport }
from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
app.all('/mcp', async (c) => {
const apiKey =
c.req.header('X-API-Key') ||
c.req.header('Authorization')?.replace('Bearer ', '');
const server = createMcpServer(apiKey, c.env);
const transport = new WebStandardStreamableHTTPServerTransport();
await server.connect(transport);
return transport.handleRequest(c.req.raw);
});
Jede Anfrage erzeugt eine neue McpServer Beispiel. Zwischen Anfragen bleibt kein Sitzungsstatus bestehen. Das ist in Ordnung, da jeder Tool-Aufruf in sich geschlossen ist. Das Modell sendet den Werkzeugnamen und die Argumente und der Server gibt das Ergebnis zurück. Keine mehrstufigen Transaktionen.
Das zustandslose Design hat drei Vorteile:
- Kein Sitzungsspeicher erforderlich (kein Redis, kein KV, keine Datenbank)
- Skaliert im Leerlauf auf Null, skaliert horizontal unter Last
- Bereitstellung an über 300 Cloudflare-Edge-Standorten ohne Konfiguration
Die Verarbeitung von API-Schlüsseln erfolgt auf der MCP-Ebene. Der Client sendet den Schlüssel per X-API-Key oder Authorization: Bearer Kopfzeile. Die MCP-Route extrahiert es und leitet es an den internen API-Aufruf weiter. Keine separate Authentifizierungs-Middleware auf der MCP-Route selbst.
Das Playbook für Ihre eigene API
Wenn Sie über eine OpenAPI-Spezifikation verfügen und einen MCP-Server erstellen möchten, finden Sie hier die komprimierte Version:
- Kuratieren Sie Ihre Werkzeugliste. Wählen Sie die 20–80 Endpunkte aus, die strukturierte Daten zurückgeben und einfache Eingaben akzeptieren. Überspringen Sie Endpunkte, die Binärdaten zurückgeben, Datei-Uploads erfordern oder über tief verschachtelte Eingabeschemata verfügen.
- Schreiben Sie einen Schemakonverter. Ordnen Sie Ihre OpenAPI-Eigenschaftstypen Zod zu. Übernehmen Sie Beschreibungen, Standardwerte und Aufzählungswerte. Behandeln Sie sowohl Anforderungstext- (POST) als auch Abfrageparameter- (GET) Muster.
- Schreiben Sie Beschreibungen mit zwei Sätzen. Satz eins: Was das Tool macht, beginnend mit einem Verb. Satz zwei: „Verwenden, wenn“ + Auslösebedingung. Seien Sie bei Datentypen und -formaten genau.
- Anmerkungen hinzufügen. Markieren Sie schreibgeschützte Tools. Markieren Sie Tools, die externe Netzwerkaufrufe tätigen. Identifizieren Sie idempotente Operationen. Schließen Sie destruktive Tools aus, es sei denn, Sie verfügen über Bestätigungsflüsse.
- Wählen Sie Ihren Transport. Verwenden Sie Streamable HTTP für Remote-Server und stdio für lokale CLI-Tools. Das MCP SDK bietet beides.
- Leiten Sie Toolaufrufe an Ihre vorhandene API weiter. Schreiben Sie die Geschäftslogik nicht neu. Rufen Sie Ihre eigenen Routen intern auf. Der MCP-Server ist eine dünne Adapterschicht.
Botois MCP-Server besteht aus 4 Dateien: curated-tools.ts (49 Werkzeugdefinitionen), schema-builder.ts (OpenAPI-zu-Zod-Konverter), server.ts (Registrierung und Weiterleitung) und tools.ts (öffentlicher Manifest-Endpunkt). The whole thing adds about 400 lines of TypeScript to an existing API.
Probieren Sie es aus
Der Botoi MCP-Server ist live unter https://api.botoi.com/mcp. Verbinden Sie es in weniger als einer Minute mit Claude Desktop, Claude Code, Cursor oder VS Code. Siehe die MCP-Setup-Dokumente für Konfigurationsausschnitte für jeden unterstützten Client.
Durchsuchen Sie die Vollständiges Werkzeugmanifest um alle 49 Werkzeugdefinitionen mit ihren Schemata und Anmerkungen anzuzeigen. Der API-Dokumente decken den kompletten Satz von über 150 REST-Endpunkten hinter dem MCP-Server ab.
FAQ
- Wie erstellt man einen MCP-Server aus einer OpenAPI-Spezifikation?
- Analysieren Sie Ihre OpenAPI-Pfaddefinitionen, extrahieren Sie das Anforderungskörperschema (für POST) oder Abfrageparameter (für GET), konvertieren Sie jede Eigenschaft in einen Zod-Typ und registrieren Sie dann jedes Tool auf einer McpServer-Instanz mit dem Zod-Schema als inputSchema. Das MCP SDK übernimmt den JSON-RPC-Transport und die Tool-Erkennung.
- Warum nicht alle API-Endpunkte als MCP-Tools verfügbar machen?
- KI-Modelle haben eine Kontextfensterbeschränkung. Jede Werkzeugdefinition verbraucht Token. Ein Manifest mit 150 Werkzeugen kann mehr als 30.000 Token verschlingen, bevor die Konversation beginnt. Durch die Reduzierung auf 49 hochwertige Werkzeuge bleibt das Manifest unter 8.000 Token und verbessert die Genauigkeit der Werkzeugauswahl.
- Was sind MCP-Tool-Anmerkungen und warum sind sie wichtig?
- Anmerkungen sind Metadatenhinweise wie readOnlyHint, destructiveHint, idempotentHint und openWorldHint. Sie teilen KI-Modellen mit, ob ein Tool Daten liest oder schreibt, ob es externe Dienste kontaktiert und ob ein erneuter Versuch sicher ist. Modelle nutzen diese Hinweise, um mehrstufige Arbeitsabläufe zu planen und destruktive Aktionen ohne Bestätigung zu vermeiden.
- Kann ein MCP-Server auf Cloudflare Workers laufen?
- Ja. Verwenden Sie den WebStandardStreamableHTTPServerTransport aus dem MCP SDK. Es funktioniert mit jeder Laufzeit, die die Web Standards Request/Response API unterstützt. Cloudflare Workers, Deno Deploy und Vercel Edge Functions sind alle qualifiziert. Jede Anfrage erstellt eine neue McpServer-Instanz, sodass kein Sitzungsstatus erforderlich ist.
- Wie soll ich MCP-Toolbeschreibungen für KI-Modelle schreiben?
- Beginnen Sie mit einem Verb. Geben Sie in einem Satz an, was das Tool macht. Fügen Sie einen zweiten Satz hinzu, der mit „Verwenden Sie wann“ beginnt und die Auslösebedingung beschreibt. Implementierungsdetails überspringen. Beispiel: „DNS-Einträge (A, AAAA, MX, TXT, CNAME, NS) für eine Domäne abfragen. Verwenden Sie diese Option, wenn Sie die DNS-Konfiguration überprüfen oder Probleme bei der Domänenauflösung beheben müssen.“
Starte mit botoi zu entwickeln
150+ API-Endpunkte für Abfragen, Textverarbeitung, Bildgenerierung und Entwickler-Tools. Kostenloser Tarif, keine Kreditkarte nötig.