Eigener MCP-Server in TypeScript: Referenz-Implementierung für ein ERP-Tool (Repo)

MCP Server erstellen in TypeScript: Tool-Definition, Auth, Fehlerbehandlung, Sicherheit. Referenz für ERP-Artikelstamm-Lookup. SDK @modelcontextprotocol/sdk.

/ MCP & Agentic AI /

Autor
Onur Turp
Veröffentlicht
Aktualisiert
Aktualisiert
Lesezeit
5 Min. Lesezeit

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

SchrittAufwand
Felder an ERP anpassen0,5 bis 1 Tag
HTTP + TLS + nginx1 Tag
Audit-Log an ELK0,5 Tag
Freigabe durch Security1 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

MetrikAlarm-Schwelle
p95 Latenz Tool-Callüber 3 s
Fehlerquote 5xxüber 1 % in 15 min
Auth-Fehlerüber 10 in 1 h
ERP Timeout2 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)

  1. pnpm lint && pnpm test
  2. Docker build
  3. Schema-Snapshot-Test (Tool-Liste unverändert oder bewusst versioniert)
  4. Deploy auf Staging-VM
  5. 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.

Konkreter werden?

Im Potenzial-Check sehen Sie in 15 Minuten, welcher Hebel bei Ihnen zuerst Sinn ergibt. Wenn Sie lieber direkt sprechen möchten, erreichen Sie uns ohne Warteschleife.