Einen eigenen MCP-Server in TypeScript bauen dauert für ein read-only ERP-Tool etwa einen bis zwei Entwicklertage inklusive Auth und Logging. Das offizielle SDK @modelcontextprotocol/sdk registriert Tools per JSON-Schema, der Server antwortet auf JSON-RPC und kapselt Ihre REST- oder SQL-Anbindung. Dieser Artikel begleitet eine sanitisierte Referenz für Artikelstamm-Lookup (SKU → Bezeichnung, Lager, Preisliste). Enthalten: Projektaufbau, Tool-Definition, Bearer-Auth, Fehlerbehandlung und Sicherheits-Basics aus unserem Security-Artikel. Für Produktion: HTTP hinter nginx auf IONOS, nicht stdio auf dem Laptop.
Projektstruktur
mcp-erp-artikel/
├── package.json
├── src/
│ ├── index.ts # Server-Einstieg
│ ├── tools/
│ │ └── lookupArticle.ts
│ ├── erp/
│ │ └── client.ts # REST zum ERP
│ └── auth.ts
├── Dockerfile
└── README.md
Abhängigkeiten
{
"name": "mcp-erp-artikel",
"type": "module",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.12.0",
"zod": "^3.24.0"
}
}
Primärquelle SDK: npm @modelcontextprotocol/sdk.
Server-Einstieg (stdio, Entwicklung)
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { registerLookupArticle } from "./tools/lookupArticle.js";
const server = new McpServer({
name: "mcp-erp-artikel",
version: "1.0.0",
});
registerLookupArticle(server);
const transport = new StdioServerTransport();
await server.connect(transport);
Tool-Definition mit Zod
import { z } from "zod";
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { erpGetArticle } from "../erp/client.js";
export function registerLookupArticle(server: McpServer) {
server.tool(
"lookup_article",
"Liest öffentliche Artikelstamm-Felder per SKU (read-only)",
{
sku: z
.string()
.max(32)
.regex(/^[A-Z0-9-]+$/i, "Nur alphanumerisch und Bindestrich"),
},
async ({ sku }) => {
const article = await erpGetArticle(sku.toUpperCase());
if (!article) {
return {
content: [{ type: "text", text: `SKU ${sku} nicht gefunden` }],
isError: true,
};
}
return {
content: [
{
type: "text",
text: JSON.stringify({
sku: article.sku,
name: article.name,
stock: article.stockAvailable,
priceList: article.priceListCode,
}),
},
],
};
},
);
}
Bewusst keine Einkaufspreise, keine Lieferanten-Namen, wenn der Use Case das nicht braucht (Datenminimierung).
ERP-Client mit Timeout und Fehler
const ERP_BASE = process.env.ERP_BASE_URL!;
const ERP_TOKEN = process.env.ERP_API_TOKEN!;
export async function erpGetArticle(sku: string) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10_000);
try {
const res = await fetch(`${ERP_BASE}/articles/${encodeURIComponent(sku)}`, {
headers: { Authorization: `Bearer ${ERP_TOKEN}` },
signal: controller.signal,
});
if (res.status === 404) return null;
if (!res.ok) throw new Error(`ERP ${res.status}`);
return (await res.json()) as {
sku: string;
name: string;
stockAvailable: number;
priceListCode: string;
};
} finally {
clearTimeout(timeout);
}
}
Auth für HTTP-Transport (Produktion)
Für stdio übernimmt oft der Host die Sicherheit. Remote-Server brauchen Token-Prüfung am HTTP-Layer (nginx auth_request oder Middleware):
// Pseudocode Middleware
export function requireBearer(req: IncomingMessage): void {
const header = req.headers.authorization ?? "";
const token = header.replace(/^Bearer /i, "");
if (!token || token !== process.env.MCP_AUTH_TOKEN) {
throw new UnauthorizedError();
}
}
Details: MCP-Sicherheit im Unternehmen.
Docker für IONOS
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY dist ./dist
USER node
ENV NODE_ENV=production
CMD ["node", "dist/index.js"]
Kein SSH, kein curl im Image, read-only filesystem where possible.
Test ohne LLM
# MCP Inspector oder curl gegen JSON-RPC Endpoint
npx @modelcontextprotocol/inspector node dist/index.js
Manuell prüfen: ungültige SKU, Timeout ERP, 401 Auth.
Von der Referenz zum Kundenprojekt
| Schritt | Aufwand |
|---|---|
| Felder an ERP anpassen | 0,5 bis 1 Tag |
| HTTP + TLS + nginx | 1 Tag |
| Audit-Log an ELK | 0,5 Tag |
| Freigabe durch Security | 1 Woche Kalender |
Nentix liefert das als Festpreis-Modul innerhalb MCP-Integration. Konzeptionell einordnen: MCP für IT-Leiter.
GitHub-Strategie
Der vollständige sanitisierte Stand liegt als Referenz in unseren GitHub-PoCs (Anfrage im Discovery-Call). README verlinkt auf diesen Artikel und auf Potenzial-Check. So finden Entwickler den Code, Entscheider den Kontext.
Wann selbst bauen, wann beauftragen?
Selbst bauen: Inhouse-TypeScript-Team, ein Tool, klare API, Security-Review intern.
Beauftragen: DATEV/lexoffice, mehrere Tools, Betrieb auf IONOS, Festpreis-Garantie als Entwicklungspartner aus Oberhausen.
HTTP-Transport für Produktion (Erweiterung)
Für Remote-Betrieb auf IONOS ergänzen Sie neben stdio einen HTTP-Entry-Point. Das SDK bietet dafür separate Transport-Module. Architektur:
Internet → nginx (TLS, Rate Limit) → Node HTTP Transport → MCP Server
Wichtig: niemals stdio und ungeschütztes HTTP parallel ohne Auth. Jeder Endpoint braucht Bearer-Token und IP-Allowlist.
Monitoring und Betrieb
| Metrik | Alarm-Schwelle |
|---|---|
| p95 Latenz Tool-Call | über 3 s |
| Fehlerquote 5xx | über 1 % in 15 min |
| Auth-Fehler | über 10 in 1 h |
| ERP Timeout | 2 aufeinanderfolgend |
Betrieb als Retainer bei Nentix: Patches, SDK-Upgrades, Incident. Quartals-Review der MCP-Spec, weil das Protokoll sich noch schnell entwickelt.
Erweiterung: zweites Tool hinzufügen
// tools/lookupSupplier.ts — gleiches Muster
export function registerLookupSupplier(server: McpServer) {
server.tool(
"lookup_supplier",
"Lieferanten-Status read-only",
{ supplierId: z.string().max(16) },
async ({ supplierId }) => {
const s = await erpGetSupplier(supplierId);
return {
content: [{ type: "text", text: JSON.stringify({
id: s.id,
approved: s.approved,
blocked: s.blocked,
}) }],
};
},
);
}
Jedes neue Tool bekommt eigenes Schema, eigenes Rate-Limit und eigenen Eintrag im Audit-Log. Kein monolithisches ERP-Tool mit 30 Operationen.
package.json Scripts (Entwicklung)
{
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "tsx watch src/index.ts",
"inspect": "npx @modelcontextprotocol/inspector tsx src/index.ts"
}
}
Mit pnpm dev starten Sie lokal, mit inspect sehen Sie Tool-Aufrufe ohne LLM. So debuggen Sie Schema-Fehler, bevor der Agent-Host angebunden wird.
CI-Pipeline (Minimal)
pnpm lint && pnpm test- Docker build
- Schema-Snapshot-Test (Tool-Liste unverändert oder bewusst versioniert)
- Deploy auf Staging-VM
- Smoke-Test gegen ERP-Sandbox
Nentix liefert diese Pipeline als Teil der MCP-Integration, damit SDK-Updates nicht jedes Quartal manuell explodieren.
Häufige Fehler beim ersten MCP-Server
Monolith-Tool mit zwanzig Operationen. Besser: ein Tool, eine Aufgabe.
Keine Schema-Limits bei String-Feldern. Besser: maxLength und Regex wie oben.
Secrets im Git-Repo. Besser: IONOS Secrets oder Vault, Rotation dokumentiert.
Kein Timeout am ERP-Client. Besser: AbortController mit 10 Sekunden, sonst hängt der Agent-Host.
Versionierung und Releases
Taggen Sie Docker-Images mit MCP-Server-Version und Git-SHA. Bei Incident rollback auf letztes grünes Image. Breaking Changes in @modelcontextprotocol/sdk kommen vor. Lesen Sie Release Notes, fahren Sie Tests, dann deployen.
Semantic Versioning für Tool-Schemas: neue Felder optional, entfernte Felder nur mit Major-Version und Migration.
Lokale Entwicklung mit Claude Desktop
In claude_desktop_config.json tragen Sie den stdio-Server ein, starten neu, testen Tool-Aufrufe im Chat. So validieren Fachbereiche Lookup-Ergebnisse, bevor der Agent-Host angebunden wird. Der Weg spart teure Integrationsschleifen in n8n.
Halten Sie README und Artikel synchron: gleiche Tool-Namen, gleiche Env-Variablen. GitHub-PoCs ohne Doku veralten in Wochen, nicht Quartalen.
TypeScript ist 2026 der Default für neue MCP-Server im Enterprise. Python bleibt valide, wenn Ihr Team dort schneller ist. Entscheidend ist konsistentes Schema-Design, nicht die Sprache. Publizieren Sie Breaking Changes im Changelog, wenn Sie Tool-Schemas anpassen. Ihre Agenten-Hosts hängen an stabilen Schemas. Investieren Sie in automatisierte Schema-Tests in CI, nicht nur manuelle Chat-Tests. So erkennen Sie SDK-Upgrades, bevor der Agent-Host in Produktion bricht.
Nächster Schritt
Häufige Fragen
Welches SDK nutzt man für MCP in TypeScript?
Das offizielle Paket @modelcontextprotocol/sdk (npm). Es stellt Server, Tool-Registrierung und JSON-RPC-Transport bereit. Stand 2026 ist TypeScript die gängigste Wahl für neue MCP-Server im Enterprise-Umfeld.
Wie viele Zeilen Code braucht ein Minimal-Server?
Ein read-only Server mit einem Tool und Bearer-Auth liegt bei etwa 120 bis 180 Zeilen TypeScript inklusive Fehlerbehandlung. Ohne Auth und Logging sind es weniger, für Produktion aber unzureichend.
stdio oder HTTP?
stdio für lokale Entwicklung mit Claude Desktop oder Cursor. HTTP mit SSE für produktive Server auf einer VM hinter Reverse Proxy und TLS.
Wie sichere ich den Server ab?
Bearer-Token pro Client, JSON-Schema-Validierung, Rate Limits, kein Shell-Tool, read-only ERP-Credentials, Audit-Log. Details in MCP-Sicherheit im Unternehmen.
Kann Nentix den Server betreiben?
Ja, als Teil der MCP-Integration mit Festpreis und Retainer auf IONOS in Deutschland. Der Referenzcode dient als Startpunkt für Ihr ERP, nicht als Copy-Paste ohne Anpassung.