🧰 Praxis: eigene MCP-Server

Sieben selbst gebaute Server aus ~/nextcloud/Projects/MCPAgenten mit zusammen 115 Tools. Übernommen wurden nur Tool-Namen, Schemas und Architektur – keine Zugangsdaten, Hostnamen oder Inhalte. Alle laufen lokal über stdio und nutzen das offizielle TypeScript-SDK @modelcontextprotocol/sdk mit Zod.

7
Server
115
Tools
stdio
Transport
0
Resources / Prompts

🗂️Die Server im Überblick

Gleicher Grundaufbau: McpServer → Tools registrieren → StdioServerTransport. Unterschiede liegen in der Anbindung und in Schutzmechanismen.

🌍 mcp-translate

6 Tools

Texte über die eigene Übersetzungs-Web-App übersetzen, Verlauf und Sprachen abrufen.

Sprache
JavaScript (ESM, Node.js)
SDK
@modelcontextprotocol/sdk ^1.26.0 · zod ^4.3.6
Transport
stdio
Anbindung
REST-API der Web-App (Login → API-Token → Authorization: Bearer)
Stil
server.tool(name, beschreibung, zodShape, handler)
env
MCP_TRANSLATE_API_URL · MCP_TRANSLATE_USER · MCP_TRANSLATE_PASSWORD · MCP_TRANSLATE_PROVIDER · MCP_TRANSLATE_SOURCE_LANG · MCP_TRANSLATE_TARGET_LANG
  • Client-Klasse meldet sich bei HTTP 401 genau einmal neu an (AuthError) – abgelaufene Tokens heilen sich selbst.
  • Start mit --selftest prüft die Verbindung nur lesend, ohne MCP.
  • isMain-Wächter: Beim Import durch Tests startet der Server nicht – Client-Klassen sind einzeln testbar.
  • Standard-Sprachpaar pl→de und Anbieter kommen aus Umgebungsvariablen.
Alle Tools anzeigen
translate_texttranslate_languagestranslate_historytranslate_detailtranslate_statstranslate_whoami

📚 mcp-wiki

19 Tools

MediaWiki lesen, durchsuchen, Seiten und Abschnitte anlegen/ändern, Dateien hochladen, Semantic-Abfragen.

Sprache
JavaScript (ESM, Node.js)
SDK
@modelcontextprotocol/sdk ^1.26.0 · zod ^4.3.6
Transport
stdio
Anbindung
MediaWiki Action API (Bot-Login, Edit-Token) hinter zusätzlicher HTTP-Basic-Auth
Stil
server.tool(name, beschreibung, zodShape, handler)
env
MCP_WIKI_URL · MCP_WIKI_USER · MCP_WIKI_PASSWORD · MCP_WIKI_HTTP_USER · MCP_WIKI_HTTP_PASSWORD · MCP_WIKI_TIMEOUT
  • Deutsche Tool- und Parameternamen (titel, inhalt, abschnitt) – das Modell versteht beides.
  • Zwei Anmeldeschichten getrennt über Umgebungsvariablen: Webserver-Basic-Auth und Wiki-Login.
  • Abschnittsweises Bearbeiten spart Tokens gegenüber dem Neuschreiben ganzer Seiten.
Alle Tools anzeigen
wiki_seiten_auflistenwiki_suchenwiki_seite_lesenwiki_seite_erstellenwiki_seite_bearbeitenwiki_seite_anhaengenwiki_abschnitt_bearbeitenwiki_seite_infowiki_versionsgeschichtewiki_letzte_aenderungenwiki_verweisewiki_kategorienwiki_seite_verschiebenwiki_seite_loeschenwiki_markdown_uploadwiki_datei_hochladenwiki_statistikwiki_semantic_abfragewiki_zufallsseite

📬 mcp-mails-imap

18 Tools

Postfach per IMAP durchsuchen, lesen, markieren, verschieben; Ordnerbaum und Quota.

Sprache
JavaScript (ESM, Node.js)
SDK
@modelcontextprotocol/sdk ^1.26.0 · zod ^4.3.6
Transport
stdio
Anbindung
Direkt per IMAP (imapflow) + mailparser – keine eigene Web-API dazwischen
Stil
server.tool(name, beschreibung, zodShape, handler)
env
MCP_IMAP_HOST · MCP_IMAP_PORT · MCP_IMAP_SECURE · MCP_IMAP_USER · MCP_IMAP_PASSWORD · MCP_IMAP_MAILBOX · MCP_IMAP_TIMEOUT
  • Ordner-Aliase („sent“, „papierkorb“, Teilname) werden in lib/mailbox.js aufgelöst – das Modell muss keine IMAP-Pfade kennen.
  • mail_read begrenzt den Text (max_chars) – schützt das Kontextfenster.
  • Mail-Inhalte sind fremder Text → klassische Quelle für Prompt Injection (siehe Sicherheit).
Alle Tools anzeigen
mail_statusmail_foldersmail_listmail_searchmail_unseenmail_readmail_attachmentsmail_sentmail_draftsmail_special_foldersmail_folder_statusmail_folder_treemail_search_allmail_markmail_movemail_deletemail_create_foldermail_quota

🏖️ mcp-ferien-buchungen

30 Tools

Buchungen, Belegung, Preise, Angebote und Abrechnungen einer Ferienwohnungs-Verwaltung.

Sprache
JavaScript (ESM, Node.js)
SDK
@modelcontextprotocol/sdk ^1.26.0 · zod ^3.25.76
Transport
stdio
Anbindung
REST-API der Verwaltungs-Web-App (Session-Login)
Stil
server.registerTool(name, { title, description, inputSchema }, handler)
env
MCP_BUCHUNGEN_API_URL · MCP_BUCHUNGEN_USER · MCP_BUCHUNGEN_PASSWORD · MCP_BUCHUNGEN_ALLOW_WRITE · MCP_BUCHUNGEN_TIMEOUT
  • Schreibsperre: Schreibende Tools sind nur aktiv, wenn der Server mit MCP_BUCHUNGEN_ALLOW_WRITE=1 gestartet wird – sonst nur Lesen.
  • Neuere API registerTool mit title (Anzeigename) statt server.tool.
  • Datumsfelder mit Regex-Muster ^\d{4}-\d{2}-\d{2}$ – Fehleingaben scheitern schon an der Schema-Prüfung.
Alle Tools anzeigen
buchungen_statusbuchungen_listebuchung_detailbuchung_suchenbuchung_belegungbuchungen_kalenderbuchungen_statistikbuchung_anmerkungenbuchung_dateienbuchung_abrechnungbuchung_dokumente_statusbuchungen_doppelbuchungen_pruefenangebote_listeangebot_detailangebot_textvorlagepreise_zeitraumpreis_berechneneinnahmen_listereinigung_abrechnungverfuegbare_monatebuchungen_apartmentsbuchungen_apartment_waehlenbuchungen_uebersicht_statisticsbuchung_anlegenbuchung_aendernbuchung_stornierenbuchung_loeschenbuchung_abrechnung_flag_setzenkurtaxe_status_setzenkurtaxe_bezahlt_setzen

💬 mcp-whatsapp

13 Tools

WhatsApp-Archiv durchsuchen, Kontext um eine Nachricht laden, Statistik, Anhänge, Übersetzung.

Sprache
JavaScript (ESM, Node.js)
SDK
@modelcontextprotocol/sdk ^1.26.0 · zod ^4.3.6
Transport
stdio
Anbindung
REST-API des eigenen Archiv-Servers
Stil
server.tool(name, beschreibung, zodShape, handler)
env
MCP_WHATSAPP_API_URL · MCP_WHATSAPP_TIMEOUT
  • Tool-Beschreibungen erklären dem Modell Fallstricke (IDs sind nicht chronologisch → whatsapp_find_by_date nutzen).
  • whatsapp_find_by_date sucht per Binärsuche über Seiten statt per Raten – wenige API-Aufrufe.
Alle Tools anzeigen
whatsapp_list_chatswhatsapp_get_messageswhatsapp_get_contextwhatsapp_find_by_datewhatsapp_searchwhatsapp_statisticswhatsapp_senderswhatsapp_syncwhatsapp_sync_historywhatsapp_attachmentswhatsapp_attachment_statswhatsapp_bookmarkwhatsapp_translate

🧾 mcp-ustva

14 Tools

Umsatzsteuer-Voranmeldung: Zeiträume, Einnahmen/Ausgaben, Aufteilung, Exporte, Statistik.

Sprache
JavaScript (ESM, Node.js)
SDK
@modelcontextprotocol/sdk ^1.26.0 · zod ^4.3.6
Transport
stdio
Anbindung
REST-API der Buchhaltungs-Web-App
Stil
server.tool(name, beschreibung, zodShape, handler)
env
MCP_USTVA_API_URL · MCP_USTVA_USER · MCP_USTVA_PASSWORD · MCP_USTVA_TIMEOUT
  • Aufzählungen (z.enum) werden zu JSON-Schema-enum – das Modell sieht die erlaubten Werte direkt.
  • Überwiegend lesend; ein einziges Änderungs-Tool (ustva_aufteilung_update).
Alle Tools anzeigen
ustva_aktueller_zeitraumustva_zeitraeumeustva_zeitraum_detailsustva_buchungenustva_einnahmenustva_ausgabenustva_ergebnisustva_aufteilungustva_aufteilung_detailsustva_aufteilung_updateustva_voranmeldungenustva_exportustva_reportsustva_statistik

🪞 magicmirror-mcp

15 Tools

MagicMirror-Anzeige steuern (Module, Monitor, Hinweise) und Home Assistant abfragen/schalten.

Sprache
TypeScript
SDK
@modelcontextprotocol/sdk ^1.0.0 · zod ^3.23.8
Transport
stdio
Anbindung
MagicMirror-Remote-API + Home-Assistant-REST-API (zwei Clients)
Stil
Plugins: register(server, context) → server.tool(…)
env
MAGICMIRROR_URL · MAGICMIRROR_API_KEY · HOMEASSISTANT_URL · HOMEASSISTANT_TOKEN
  • Plugin-Architektur: pluginLoader lädt vier Plugins (mirror, notifications, homeAssistant, system); jedes registriert seine Tools selbst.
  • Tool-Namen mit Punkten (mirror.alert.show). In Claude Code erscheinen sie als mcp__magicmirror__mirror_alert_show.
  • Fehler werden korrekt als Tool-Ausführungsfehler mit isError: true gemeldet.
Alle Tools anzeigen
mirror.healthmirror.module.listmirror.module.showmirror.module.hidemirror.monitor.onmirror.monitor.offmirror.monitor.statusmirror.restartmirror.notification.sendmirror.alert.showmirror.alert.hidehomeassistant.entities.listhomeassistant.entity.gethomeassistant.service.callsystem.schema

🔍Tool-Katalog mit echten Schemas

Elf ausgewählte Tools – Schema ansehen und Argumente mit Ajv prüfen.

Tool-Definition (wie in tools/list)

{
  "name": "translate_text",
  "description": "Übersetzt einen Text über die Translate-Web-App. Standard-Anbieter ist DeepSeek. Quell- und Zielsprache sind optional (Standard pl→de). Die Übersetzung wird serverseitig im Verlauf gespeichert.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "text": {
        "type": "string",
        "minLength": 1,
        "description": "Der zu übersetzende Text (max. 50.000 Zeichen)"
      },
      "source_lang": {
        "type": "string",
        "description": "Quellsprache als Code, z.B. \"pl\", \"de\", \"en\" (Standard \"pl\")"
      },
      "target_lang": {
        "type": "string",
        "description": "Zielsprache als Code, z.B. \"de\", \"en\", \"pl\" (Standard \"de\")"
      }
    },
    "required": ["text"]
  }
}

Argumente prüfen

{
  "text": "Dzień dobry, jak się masz?",
  "source_lang": "pl",
  "target_lang": "de"
}
Ajv · Draft 2020-12
✅ Passt zum Schema.

Synthetische Beispielwerte. Das Schema ist aus dem Zod-Quelltext abgeleitet.

🏗️So sieht der Code aus

Gekürzte Auszüge aus dem Quelltext (ohne Konfigurationswerte).

MCPTranslate/index.js – server.tooljs
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';

const server = new McpServer({ name: 'mcp-translate', version: '1.0.0' });

server.tool(
  'translate_text',
  'Übersetzt einen Text über die Translate-Web-App … (Standard pl→de).',
  {
    text:        z.string().min(1).describe('Der zu übersetzende Text (max. 50.000 Zeichen)'),
    source_lang: z.string().optional().describe('Quellsprache als Code, z.B. "pl", "de", "en"'),
    target_lang: z.string().optional().describe('Zielsprache als Code, z.B. "de", "en", "pl"'),
  },
  async ({ text, source_lang, target_lang }) => {
    try {
      const res = await client.post('translate.php', { text, source_lang: src, target_lang: tgt });
      …
      return ok(lines.join('\n'));          // { content: [{ type: 'text', text }] }
    } catch (e) { return fail(e); }
  }
);

const transport = new StdioServerTransport();
await server.connect(transport);
console.error('🌍 MCP Translate Server gestartet …');   // Logs nach stderr!
MCPFerienBuchungen/index.js – registerTool + Schreibsperrejs
server.registerTool(
  'buchung_belegung',
  {
    title: 'Verfügbarkeit / Belegung prüfen',
    description: 'Prüft, ob ein Zeitraum frei ist, und listet alle kollidierenden Buchungen. …',
    inputSchema: {
      von: z.string().regex(DATUM_RE).describe('Anreisedatum (YYYY-MM-DD)'),
      bis: z.string().regex(DATUM_RE).describe('Abreisedatum (YYYY-MM-DD)'),
    },
  },
  async ({ von, bis }) => { … }
);

// Schreibsperre: schreibende Tools prüfen zuerst
const ALLOW_WRITE = /^(1|true|yes|ja|on)$/i.test(process.env.MCP_BUCHUNGEN_ALLOW_WRITE || '');
magicmirror-mcp – Plugins (TypeScript)ts
// pluginLoader.ts
const plugins: MCPPlugin[] = [
  (await import("./plugins/mirrorPlugin.js")).default,
  (await import("./plugins/notificationsPlugin.js")).default,
  (await import("./plugins/homeAssistantPlugin.js")).default,
  (await import("./plugins/systemPlugin.js")).default
]
for (const plugin of plugins) await plugin.register(server, context)

// notificationsPlugin.ts – Fehler korrekt als Tool-Ausführungsfehler
return { content: [{ type: "text", text: `Error showing alert: ${err}` }], isError: true }
💡 Beobachtung: isError
magicmirror-mcp meldet Fehler mit isError: true. Die Hilfsfunktion fail() in MCPTranslate liefert dagegen nur einen Text „❌ Fehler: …“ ohne isError. Das Modell liest den Text zwar, aber Host und Protokoll sehen einen Erfolg – mitisError: true wäre der Fehler auch maschinenlesbar.
🏷️ Wie der Host die Namen sieht
Claude Code setzt vor jedes Tool mcp__<Server-Schlüssel>__, z. B. mcp__mcp-translate__translate_text. Punkte werden ersetzt: mirror.alert.show erscheint als mcp__magicmirror__mirror_alert_show. So kollidieren gleichnamige Tools verschiedener Server nicht.

⚙️Einbinden in Claude Code

User-Scope: Top-Level-Schlüssel mcpServers in ~/.claude.json. Werte unten sind Platzhalter.

~/.claude.json (Auszug, synthetisch)json
{
  "mcpServers": {
    "mcp-translate": {
      "type": "stdio",
      "command": "node",
      "args": [
        "/pfad/zu/MCPAgenten/MCPTranslate/index.js"
      ],
      "env": {
        "MCP_TRANSLATE_API_URL": "https://translate.example.org",
        "MCP_TRANSLATE_USER": "<benutzer>",
        "MCP_TRANSLATE_PASSWORD": "<geheim>",
        "MCP_TRANSLATE_SOURCE_LANG": "pl",
        "MCP_TRANSLATE_TARGET_LANG": "de"
      }
    },
    "mcp-wiki": {
      "type": "stdio",
      "command": "node",
      "args": [
        "/pfad/zu/MCPAgenten/MCPWiki/index.js"
      ],
      "env": {
        "MCP_WIKI_URL": "https://wiki.example.org",
        "MCP_WIKI_USER": "<bot-benutzer>",
        "MCP_WIKI_PASSWORD": "<geheim>"
      }
    },
    "beispiel-remote": {
      "type": "http",
      "url": "https://mcp.example.org/mcp"
    }
  }
}
per CLIbash
claude mcp add --scope user mcp-translate \
  --env MCP_TRANSLATE_API_URL=https://translate.example.org \
  --env MCP_TRANSLATE_USER=<benutzer> --env MCP_TRANSLATE_PASSWORD=<geheim> \
  -- node /pfad/zu/MCPAgenten/MCPTranslate/index.js

claude mcp list      # → mcp-translate: … ✓ Connected
Drei Einbindungs-Orte
--scope project  →  .mcp.json im Projekt (geteilt, z. B. per Git)
--scope user     →  ~/.claude.json, Top-Level mcpServers (alle Projekte)
--scope local    →  ~/.claude.json, nur für das aktuelle Projekt
🔒 Secrets
In env stehen Passwörter im Klartext. Die Datei nie teilen oder einchecken; für geteilte .mcp.json Platzhalter wie ${MCP_TRANSLATE_PASSWORD} nutzen.