
Warum wir bei codehero n8n hinter uns gelassen haben und stattdessen MCP-Server entwickeln – für typsichere, testbare, AI-native Automatisierung.
n8n hat die Automatisierungsszene aufgemischt: Open Source, Self-Hosted, visueller Workflow-Builder. Für viele Teams das Go-to-Tool, um Services zu verbinden und Prozesse zu automatisieren. Wir haben es selbst eingesetzt. Und dann haben wir aufgehört.
Nicht weil n8n schlecht ist – sondern weil sich die Anforderungen an Automatisierung fundamental verändert haben. Mit dem Model Context Protocol (MCP) von Anthropic gibt es jetzt einen Standard, der AI-native Tool-Integrationen ermöglicht. Und der zeigt schonungslos, wo die Grenzen von No-Code-Plattformen liegen.
In diesem Artikel erklären wir, warum wir bei codehero auf selbst entwickelte MCP-Server setzen – und wann n8n trotzdem noch Sinn macht.
n8n ist eine visuelle Workflow-Automatisierungsplattform. Man zieht Nodes auf eine Canvas, verbindet sie mit Linien, und definiert Trigger, Transformationen und Aktionen. Es gibt 400+ Integrationen: Slack, Gmail, Notion, Airtable, Webhooks, HTTP Requests.
Wo n8n glänzt:
Das Model Context Protocol (MCP) ist ein offener Standard von Anthropic, der definiert, wie AI-Modelle (Claude, GPT, etc.) mit externen Tools und Datenquellen kommunizieren. Statt dass ein Modell eine vordefinierte API aufruft, beschreibt ein MCP-Server seine Fähigkeiten – und das Modell entscheidet selbstständig, welche Tools es wann und wie nutzt.
Ein MCP-Server stellt drei Dinge bereit:
Der entscheidende Unterschied: MCP-Server werden programmiert, nicht zusammengeklickt. Sie sind typisiert, testbar, versioniert und deployen wie jeder andere Microservice.
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { z } from 'zod'
const server = new McpServer({
name: 'crm-server',
version: '1.0.0',
})
// Tool: Neuen Lead anlegen
server.tool(
'create-lead',
'Erstellt einen neuen Lead im CRM mit Validierung und Enrichment',
{
company: z.string().describe('Firmenname'),
email: z.string().email().describe('Kontakt-Email'),
source: z.enum(['website', 'outreach', 'referral']).describe('Lead-Quelle'),
notes: z.string().optional().describe('Freitext-Notizen'),
},
async ({ company, email, source, notes }) => {
// Echte Business-Logik: Validierung, Duplikat-Check, Enrichment
const existing = await db.leads.findByEmail(email)
if (existing) {
return {
content: [{ type: 'text', text: `Lead existiert bereits: ${existing.id}` }],
}
}
const enriched = await enrichCompanyData(company)
const lead = await db.leads.create({
company,
email,
source,
notes,
enrichedData: enriched,
score: calculateLeadScore(enriched),
})
return {
content: [{ type: 'text', text: `Lead ${lead.id} erstellt (Score: ${lead.score})` }],
}
}
)Solange Workflows linear sind (Trigger → Transform → Action), funktioniert n8n hervorragend. Aber bei realen Geschäftsprozessen werden die Limitierungen schnell sichtbar:
n8n hat ein Error-Trigger-Konzept, aber keine granulare Fehlerbehandlung pro Node. Wenn ein HTTP-Request in Schritt 5 fehlschlägt, gibt es kein deklaratives Retry mit Backoff, kein Circuit-Breaker-Pattern, kein Fallback auf einen alternativen Service. Man baut sich IF error → retry-Nodes zusammen, die schnell unübersichtlich werden.
n8n-Workflows sind zustandslos. Jede Ausführung startet von Null. Braucht man einen mehrstufigen Prozess, der über Tage läuft (z.B. Onboarding-Sequenzen, Approval-Workflows), muss man den Zustand extern in einer Datenbank verwalten und bei jedem Trigger neu laden. Das ist fehleranfällig und schwer zu debuggen.
Daten fließen als untypisierte JSON-Objekte durch den Workflow. Ein Tippfehler in {{ $json.emial }} statt {{ $json.email }} fällt erst zur Laufzeit auf – wenn ein Kunde eine leere Email bekommt. Es gibt kein Schema, keine Validierung, kein TypeScript, das zur Entwicklungszeit Fehler fängt.
Man kann n8n-Workflows nicht unit-testen. Es gibt keine Möglichkeit, einen Workflow mit Mock-Daten automatisch zu testen, keinen CI/CD-Pipeline-Support, keine Snapshot-Tests. Jede Änderung muss manuell getestet werden – bei komplexen Workflows mit 20+ Nodes ein Albtraum.
Die Workflows sind im n8n-eigenen JSON-Format gespeichert. Migration zu einem anderen System bedeutet: komplett neu bauen. Der visuelle Editor ist gleichzeitig Stärke und Falle – man kann Workflows nicht in Git diff-en, reviewen oder mergen.
n8n hat zwar AI-Nodes (OpenAI, Claude), aber die behandeln das Modell als einen weiteren HTTP-Endpoint. Das Modell bekommt statische Inputs und liefert einen Output. Es kann nicht entscheiden, welchen nächsten Schritt es ausführen will. Es hat keinen Kontext über die verfügbaren Tools. Es kann nicht auf neue Situationen reagieren, die der Workflow-Designer nicht vorhergesehen hat.
Jedes MCP-Tool hat ein Zod-Schema, das Inputs validiert. Das AI-Modell sieht die Beschreibung jedes Parameters und kann korrekte Aufrufe generieren. Fehler werden vor der Ausführung gefangen – nicht erst, wenn der Kunde eine defekte Email bekommt.
// Das Modell sieht diese Tool-Beschreibung und weiß exakt,
// welche Parameter es übergeben muss
server.tool(
'send-proposal',
'Sendet ein individuelles Angebot an einen Lead',
{
leadId: z.string().uuid().describe('Lead-ID aus dem CRM'),
template: z.enum(['starter', 'professional', 'enterprise'])
.describe('Angebotsvorlage'),
customizations: z.object({
discount: z.number().min(0).max(30).optional()
.describe('Rabatt in Prozent (0-30%)'),
validUntil: z.string().date()
.describe('Gültigkeitsdatum (YYYY-MM-DD)'),
}).describe('Individuelle Anpassungen'),
},
async ({ leadId, template, customizations }) => {
// Validierung ist bereits durch Zod passiert
// Hier nur noch Business-Logik
}
)MCP-Server sind TypeScript-Module. Man kann sie mit Jest/Vitest unit-testen, Integration-Tests schreiben, in CI/CD-Pipelines einbinden und mit Code Reviews qualitätssichern.
import { describe, it, expect } from 'vitest'
import { createTestClient } from './test-helpers'
describe('CRM MCP Server', () => {
it('should create a lead with enrichment', async () => {
const client = createTestClient()
const result = await client.callTool('create-lead', {
company: 'Test GmbH',
email: 'test@example.com',
source: 'website',
})
expect(result.content[0].text).toContain('Lead')
expect(result.content[0].text).toContain('Score:')
})
it('should reject duplicate emails', async () => {
const client = createTestClient()
await client.callTool('create-lead', {
company: 'Test GmbH',
email: 'dup@example.com',
source: 'website',
})
const result = await client.callTool('create-lead', {
company: 'Other GmbH',
email: 'dup@example.com',
source: 'outreach',
})
expect(result.content[0].text).toContain('existiert bereits')
})
})Der fundamentale Unterschied: Bei n8n definiert ein Mensch den exakten Ablauf vorab. Bei MCP beschreibt der Entwickler die verfügbaren Fähigkeiten – und das AI-Modell orchestriert sie situativ.
Beispiel: Ein Kunde schreibt eine Support-Email. Bei n8n:
Bei MCP:
Das Modell kann Situationen handhaben, die der Entwickler nicht vorhergesehen hat – weil es nicht einem statischen Flowchart folgt, sondern seine Tools intelligent kombiniert.
MCP-Server sind Code. Sie leben in Git-Repositories, haben Semantic Versioning, durchlaufen Code Reviews und werden über CI/CD deployed. Wenn etwas schiefgeht, macht man ein Rollback – nicht ein manuelles Zurückklicken im visuellen Editor.
| Aspekt | n8n | MCP-Server (Custom) |
|---|---|---|
| Zielgruppe | Non-Developers, Ops-Teams | Entwickler, Engineering-Teams |
| Setup-Zeit | Minuten | Stunden bis Tage |
| Komplexität | Niedrig (bis es komplex wird) | Initial höher, skaliert besser |
| Typsicherheit | Keine | Vollständig (Zod + TypeScript) |
| Testing | Manuell | Unit-Tests, Integration-Tests, CI/CD |
| Fehlerbehandlung | Basis (Error Trigger) | Granular, deklarativ, testbar |
| AI-Integration | AI als Node (statisch) | AI orchestriert Tools (dynamisch) |
| Zustandsverwaltung | Zustandslos | Frei implementierbar |
| Versionierung | JSON-Export | Git, Semantic Versioning |
| Code Review | Nicht möglich | Standard Pull-Request-Workflow |
| Vendor Lock-in | n8n JSON-Format | Offener Standard (MCP) |
| Skalierung | Self-Hosted Node.js | Beliebig (Lambda, ECS, etc.) |
Bei codehero entwickeln wir MCP-Server als eigenständige TypeScript-Packages, die in unsere AI-Workflows integriert werden. Hier ein Einblick in unsere Architektur:
// Jeder MCP-Server ist ein eigenständiges Package
mcp-servers/
├── crm-server/ # CRM-Integration (Leads, Kontakte, Deals)
│ ├── src/
│ │ ├── server.ts # MCP-Server Setup + Tool-Registrierung
│ │ ├── tools/ # Ein Tool pro Datei
│ │ │ ├── create-lead.ts
│ │ │ ├── search-contacts.ts
│ │ │ └── update-deal.ts
│ │ ├── resources/ # Datenquellen für das Modell
│ │ │ └── schema.ts
│ │ └── lib/ # Shared Business-Logik
│ ├── tests/
│ └── package.json
├── email-server/ # Email-Versand + Tracking
├── billing-server/ # Rechnungswesen + Stripe
└── docs-server/ # Interne WissensdatenbankJedes Tool folgt einem klaren Pattern:
import { z } from 'zod'
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
export function registerSearchTools(server: McpServer) {
server.tool(
'search-knowledge-base',
'Durchsucht die interne Wissensdatenbank mit semantischer Suche',
{
query: z.string().min(3).describe('Suchanfrage in natürlicher Sprache'),
category: z.enum(['technical', 'process', 'product', 'legal'])
.optional()
.describe('Optional: Auf Kategorie einschränken'),
limit: z.number().min(1).max(20).default(5)
.describe('Maximale Anzahl Ergebnisse'),
},
async ({ query, category, limit }) => {
const embedding = await generateEmbedding(query)
const results = await vectorDb.search({
embedding,
filter: category ? { category } : undefined,
topK: limit,
})
if (results.length === 0) {
return {
content: [{
type: 'text',
text: 'Keine relevanten Dokumente gefunden. ' +
'Versuche eine andere Formulierung oder Kategorie.',
}],
}
}
const formatted = results
.map((r, i) => `${i + 1}. **${r.title}** (Score: ${r.score.toFixed(2)})\n ${r.excerpt}`)
.join('\n\n')
return {
content: [{ type: 'text', text: formatted }],
}
}
)
}Unsere MCP-Server laufen als stdio-basierte Prozesse (für lokale Entwicklung mit Claude Desktop) oder als SSE-Endpoints auf AWS (ECS/Lambda) für produktive AI-Agents. Deployment über CDK, wie jeder andere Microservice.
Wir sind keine n8n-Hater. Es gibt klare Use Cases, bei denen n8n die bessere Wahl ist:
MCP-Server sind die richtige Wahl, wenn:
No-Code-Plattformen wie n8n, Make und Zapier haben Automatisierung demokratisiert. Das war wichtig und richtig. Aber mit AI-nativen Protokollen wie MCP verschiebt sich der Sweet Spot:
Statt Menschen visuell definieren zu lassen, was ein Workflow tun soll, beschreiben Entwickler die verfügbaren Fähigkeiten – und AI-Modelle orchestrieren sie situativ. Das ist kein marginaler Unterschied. Das ist ein Paradigmenwechsel.
n8n wird nicht verschwinden. Aber die Workflows, die heute 50 Nodes in n8n haben, werden morgen 5 MCP-Tools sein, die ein AI-Modell intelligent kombiniert.
n8n ist ein solides Tool für einfache Automatisierungen. Aber für ernsthafte, AI-getriebene Geschäftsprozesse stößt es an fundamentale Grenzen: keine Typsicherheit, kein Testing, keine AI-native Orchestrierung, kein Code Review.
Bei codehero setzen wir auf selbst entwickelte MCP-Server: TypeScript-basiert, vollständig getestet, in CI/CD integriert und deployed auf AWS. Das initiale Setup dauert länger als ein n8n-Workflow zusammenzuklicken. Aber der erste Production-Incident zeigt, warum sich der Aufwand lohnt.
Die Frage ist nicht „n8n oder MCP?" – die Frage ist: „Ist mein Prozess wichtig genug, um ihn richtig zu bauen?"
Sie möchten AI-native Automatisierung mit MCP-Servern in Ihrem Unternehmen einführen? Sprechen Sie mit uns – wir entwickeln maßgeschneiderte Tool-Integrationen für Ihre Geschäftsprozesse.
Wolfgang Müller
CEO & Fullstack-Developer
Kostenloses Erstgespräch — ehrlich, technisch fundiert, unverbindlich. Antwort innerhalb von 24 Stunden.