MCP Best Practices

Was wir uns von jedem eigenen MCP-Server wuenschen, herausdestilliert aus unseren ~10 Eigenbauten (gsuite, m365, papierkram, ticketpay, runway, replicate, calcom, whatsapp, lexware, sevdesk) und dem Mono-Wrapper mcp-vf-hosted.

Diese Regeln gelten fuer Neubauten (Skill SKILL verlinkt hierhin) UND fuer Retrofit bestehender MCPs (Audit-Lauf pro MCP). Die Checkliste am Ende ist der Werkzeugkasten fuer beides.

Ankervorfall (2026-05-15)

VF-WebUI meldet „Papierkram nicht abrufbar — 404, vermutlich Token oder Subdomain”. Echte Ursache: Modell rief papierkram_raw_get('/api/v1/invoices?page=1') — Papierkram braucht /api/v1/income/invoices.json. Drei strukturelle Probleme zusammen:

  1. Notluken-Tool (raw_get) im Production-Hosted-MCP exponiert → Modell halluziniert Pfade.
  2. Duenne Docstrings auf list_invoices → Modell findet das dedizierte Tool nicht.
  3. Nackter httpx-Trace als Fehler → Modell interpretiert 404 als „API down / Token kaputt”.

Alle drei sind generische Patterns, nicht Papierkram-spezifisch. Daher diese Doku.

Lifecycle-Regeln

1. Tool-Design

1.1 Dedizierte Tools statt Raw-Escapes. Pro Ressource je ein list_*/get_*/create_*/update_*/delete_* Tool mit echtem Pfad und typisierten Params. Raw-Escape-Hatches (raw_get, raw_post) optional fuer lokales Dev, im Production-Hosting per Env-Flag aus. Why: Raw-Tools laden Modell ein, API-Pfade zu halluzinieren. Dedizierte Tools mit fester URL-Konstruktion machen das unmoeglich. How: In server.py Env-Flag <NAME>_EXPOSE_RAW=false einbauen, das die raw_* @mcp.tool()-Decorators bedingt registriert. Default fuer Hosted-Stack: false.

1.2 Aggregations-Tools fuer Workflow-Fragen. Wenn User typischerweise nach „Bilanz”, „Monatsabschluss”, „Uebersicht” fragt: ein Tool, das alle Aggregate server-side rechnet und in einem ~200-Token-Response zurueckgibt — statt das Modell 5 list_*-Tools chainen zu lassen. Why: 5 parallele list_* produzieren 20-30k Token Tool-Output → Antwort cuttet ab. Plus: Modell rechnet clientseitig falsch. How: Beispiel ticketpay_event_bilanz(event_id) — strukturiertes Dict mit verkauft/brutto/stornos/gebuehren/netto/transaktionen. Pattern fuer Papierkram: monatsabschluss(year, month), offene_posten(), projekt_bilanz(project_id).

1.3 Reiche Docstrings mit deutschen Aliasen + Beispiel. Mindestens 2-3 Saetze: Was tut das Tool, welche typischen User-Phrasen es matchen soll, ein JSON-Beispiel-Call. Why: Modell-Tool-Selection matcht auf Docstring-Embedding. Wenn der User „Ausgangsrechnungen” sagt und das Tool nur „Rechnungen” im Docstring hat, wird das Tool nicht zuverlaessig gefunden. How:

@mcp.tool()
def list_invoices(...) -> dict:
    """Listet Ausgangsrechnungen (= ausgestellte Rechnungen, Einnahmen aus
    Dienstleistungen, gestellte Forderungen). Filter per Datumsbereich
    (document_date_range_start/_end) und Kunde (company_id).
 
    Beispiel:
        list_invoices(document_date_range_start="2026-05-01",
                      document_date_range_end="2026-05-31")
    """

1.4 Tool-Anzahl strikt unter 30 pro Session — Hard Limit ~50. Wenn mehr: NICHT alle Tools dem Modell exponieren. Stattdessen Progressive Disclosure (siehe Template E unten). Why: Empirische Belege aus 2026:

  • Anthropic: Selection-Quality faellt messbar ab >30-50 Tools im Schema.
  • GitHub Copilot: Reduktion von 40 auf 13 Tools → bessere Benchmark-Resultate.
  • Block (Linear-MCP): Konsolidierung von 30+ auf 2 Tools.
  • Cloudflare: 2.500 Endpoints einzeln = 1.17M Tokens; mit Code Mode = 1k Tokens (99.9% Reduktion).
  • Bei mcp-vf-hosted 2026-05-15: 139 Tools insgesamt fuehrten dazu dass Tool-Use-Aufrufe vom Modell gar nicht mehr durchgingen (kein CallToolRequest am MCP, obwohl Bedrock-Modell direkt verifiziert tool_use-Blocks korrekt generiert). How:
  • Bei Neubau einzelner MCPs: pro Resource max 1× list_*, 1× get_*, kein dediziertes CRUD-Tool fuer jede Operation. Aggregations-Tools (Template C) bevorzugen.
  • Bei Mono-Wrappern oder grossen MCPs: Tool-Whitelist + search_tools Meta-Tool (Template E).
  • Pro Use-Case ggf. mehrere Custom-Models mit unterschiedlichen Whitelists (z.B. vf-bucha nur papierkram_*, vf-mail nur sharepoint_*).

1.5 Konsistente Naming-Konvention. <resource>_<verb> ueberall: list, get, create, update, delete, archive, unarchive. Aktionen die keine CRUD sind: explizit (cancel_invoice, deliver_invoice, pay_voucher). Why: Modell rated tab-completion-aehnlich; Inkonsistenz fuehrt zu Falschauswahl. How: Vor Tool-Anlage im Repo grep -E "^def (list|get|create|update|delete)" checken — Suffix matchen.

1.6 Boolean-Flags statt Magic-Strings. count_only=True, archived_only=True — nicht mode="count" oder filter="archived". Why: Modell-Halluzinations-Vektor: Magic-Strings werden geraten („filter=closed”, obwohl nur „archived” gueltig ist).

2. Error-Handling

2.1 Upstream-Fehler mit aktionablen Hints umwrappen. Nackte 404/500/timeout aus httpx nie direkt ans Modell. Wrapper-Funktion _request() faengt typische Status-Codes und gibt strukturierten Fehler zurueck inkl. konkreter Vorschlag-Aktion. Why: Modell interpretiert nackten 404 als „API down” und teilt das so dem User mit. Mit Hint („versuche list_invoices() statt raw_get”) macht das Modell selbstheilend. How: Beispiel-Pattern:

except httpx.HTTPStatusError as e:
    if e.response.status_code == 404 and "raw_" in caller:
        raise ToolError(
            f"404 auf '{path}'. Pfad braucht vermutlich Ressourcen-Prefix "
            f"(income/, expense/, tracker/, contact/) und .json-Suffix. "
            f"Empfehlung: dediziertes Tool `<name>_list_<resource>` nutzen."
        )

Bei Auth-Fehlern (401/403): klar sagen Token in <ENV_VAR> pruefen — nicht „API rejected”.

2.2 Strukturierter Audit-Log pro Tool-Call. Jeder Call loggt JSON mit ts, level, tool, status, duration_ms, reason (bei Fehler). Keine PII im Log (siehe tests/test_audit_no_pii.py als Beispiel aus mcp-vf-hosted). Why: Tool-Error-Digests (siehe 6.3) brauchen strukturiertes Log-Format. CloudWatch Insights kann dann nach Tool+Error-Rate sliced werden. How: structlog oder selbstgebauter JSON-Logger als Middleware. Pattern uebernommen aus mcp-vf-hosted/src/mcp_vf_hosted/main.py.

2.3 Rate-Limit/Quota-Info im Response durchreichen. Wenn die Upstream-API Quota-Header liefert (z.B. Papierkram _quota: {remaining, consumed}), in jedem Tool-Result mitgeben. Why: Modell + User sehen ob „nicht erreichbar” wirklich Quota-Auslastung ist. Plus: Monitoring kann darauf alarmieren.

3. Response-Shape

3.1 Compact-Mode per fields=[...] Whitelist. list_*-Tools nehmen optional fields=["id","name","amount"] und filtern Server-side. Default: voller Record. Why: list_invoices() ohne Filter zieht 20+ Felder pro Rechnung × 50 Items = ~30k Token. Mit fields=["id","number","total","status"] runter auf ~3k.

3.2 count_only=True fuer Groessen-Abfragen. Wenn das Modell „wie viele X gibt es” beantworten will: ein billiger Call, der nur die Zahl liefert. Why: Spart Token + API-Quota. Modell darf dann gezielt nachladen.

3.3 Pagination konsistent (page + page_size, nicht limit/offset gemischt). Default page_size=50, max 100. Why: Inkonsistenz quer ueber MCPs fuehrt zu Param-Halluzinationen (z.B. heute: Modell tippte ?page=1&page_size=50 aus Erinnerung an ein anderes MCP).

3.4 Top-N-Sortierung default. Bei aggregierenden Tools: top 5 nach Relevanz (Betrag/Datum/Anzahl), nicht raw List. Why: User-Frage „wer hat am meisten gekauft” braucht keine 200-Eintrags-Liste.

4. Security

4.1 Secrets ausschliesslich via Env, .env.local in .gitignore. Kein Token, kein Api-Key im Code-Path. CDK-Stacks laden aus AWS Secrets Manager. Why: Selbsterklaerend. Plus: Rotations-faehig ohne Code-Deploy.

4.2 Per-User-Audit-Header durchreichen. Wenn der MCP via OAuth (Scalekit, claude.ai Custom Connector) lauft: User-Identitaet in den Audit-Log schreiben, optional an die Upstream-API durchreichen. Why: DSGVO-Anforderung + Forensik. Wer hat wann was abgerufen. How: In Open WebUI ENABLE_FORWARD_USER_INFO_HEADERS: true, im MCP x-user-email-Header lesen und in Audit-Event packen. Pattern in open-webui-vf dokumentiert.

4.3 Tool-Input-Validierung an der Boundary. Pydantic-Modelle fuer alle Tool-Params. Keine dict-Bloblistens-Tool-Inputs ohne Schema. Why: Modell schickt ab-und-zu typo’d Keys (bill_id statt invoice_id). Pydantic lehnt das mit klarer Meldung ab statt cryptic upstream-500.

4.4 Secrets nie loggen. Logger-Filter, der Authorization-Header und Bearer-Tokens im Log-Output redaktiert. Why: CloudWatch-Logs sind oft breiter sichtbar als die Secrets selbst.

4.5 Tool-Output ist UNTRUSTED. Wenn Tool-Output User-Generated Content enthaelt (Email-Body, Kommentar, Excel-Zelle): das Modell darf darin enthaltene „Anweisungen” nicht ausfuehren. Diese Regel gehoert in den System-Prompt des nutzenden Modells, nicht in den MCP — aber pruefe dass Tool-Outputs mit User-Content sauber markiert sind. Why: Prompt-Injection ueber Daten-Outputs.

5. Deployment

5.1 Streamable HTTP als Default-Transport. Nicht stdio, ausser fuer lokale Single-User-Anwendung. Why: HTTP laesst sich hinter Cloudflare Tunnel packen, multi-client benutzen, monitoren. Stdio ist 1:1 lokal. How: FastMCP(host="127.0.0.1", port=8767, transport="streamable-http"). Setup-Doku siehe mcp-hosting-fargate-tunnel.

5.2 /health-Endpoint mit Sub-MCP-Probe. Bei Mono-Wrappern: /health returnt {ok: true, subs: {papierkram: 200, ticketpay: 200, ...}}. Why: Generischer /health: 200 luegt wenn ein Sub-MCP kaputt ist. Tunnel-Health-Check faengt dann nichts.

5.3 Audit-Log in CloudWatch Log-Group mit JSON-Format. Plus CloudWatch-Dashboard pro Stack. Why: Tool-Error-Digests + Quota-Alarms haben dann eine Datenquelle.

5.4 Port-Inventar in config eintragen. Pro MCP fixer lokaler Port, kein dynamisches Binden. Why: Multi-MCP-Setup mit npm run start-all braucht das.

6. Testing & Operations

6.1 Smoke-Test je Tool, mindestens fuer list_*/get_*. Pytest mit echtem API-Call gegen Test-Account (oder Sandbox). Why: API-Aenderungen beim Upstream-Provider (BETA-APIs!) brechen oft genau ein Tool — ohne Smoke-Test merken wir das erst im Live-Chat.

6.2 Audit-Tests fuer No-PII-Property. test_audit_no_pii.py-Pattern aus mcp-vf-hosted uebernehmen: parse Audit-Log-Events, asserten dass keine Emails/Namen/Tokens drin sind.

6.3 Tool-Error-Digest weekly. Lambda in agents-platform, die CloudWatch-Audit-Logs queryt → top-5 failing Tools + sample requests → Mail an hello@. Pattern uebernehmen auf alle hosted MCPs. Why: Naechste Halluzination/Bruch sehen wir bevor User meckert. How: Cron-Lambda via Skill SKILL anlegen, CloudWatch Insights als Query.

6.4 Quota-Alarm wo Upstream Quota hat. Papierkram (10k Credits/Monat), TicketPAY (rate-limited), M365 (Graph Throttling): CloudWatch-Metric + Alarm bei 80%.

7. Documentation

7.1 intern/capabilities/mcps/<name>.md als Eintrag. Standard-Frontmatter (id, type=mcp, status, purpose, auth, endpoint, source). Aufbau siehe papierkram.

7.2 README im Source-Repo mit Tools, Quirks, Setup. Plus .env.example. Plus Lessons-Learned-Section („wir dachten X, war aber Y”).

7.3 Cross-Ref im Tool-Inventar _index.

Audit-Checkliste fuer bestehende MCPs

Pro Eigenbau-MCP einmal durchgehen. Status-Spalte: ok / gap / n/a.

#RegelQuick-Check
1.1Raw-Tools im Hosting deaktiviertgrep EXPOSE_RAW server.py
1.2Aggregations-Tools wo passendGibt es *_bilanz/*_uebersicht/*_abschluss?
1.3Reiche Docstrings + AliaseStichprobe 3 list_*-Tools: mehr als 1 Satz?
1.4Tool-Anzahl <30 pro SessionZaehle grep -c "@mcp.tool" server.py — wenn >30, Template E (Whitelist + search_tools) noetig
1.5Naming-Konvention konsistentAlle Tools <resource>_<verb>?
2.1Error-Wrapping mit Hintsgrep ToolError server.py
2.2Strukturierter Audit-Loggrep "logger|structlog" server.py
2.3Quota im ResponseBeispiel-Call inspizieren
3.1fields= ParameterStichprobe list_*-Signatur
3.2count_only= Parameterdito
4.1Secrets in .env.localgit log -p .env* darf nichts zeigen
4.2Per-User-Header durchgereichtnur fuer hosted MCPs
4.3Pydantic-SchemasTool-Signaturen typisiert?
5.1HTTP Transport defaulttransport= in __main__-Block
5.2/health Endpointcurl localhost:<port>/health
6.1Smoke-Testspytest --collect-only zaehlt 1 pro Tool?
6.4Quota-Alarmnur fuer hosted/billable APIs
7.1Vault-Eintrag aktuellintern/capabilities/mcps/<name>.md last_reviewed < 6 Wochen

Audit-Lauf gehoert in Skill SKILL (erweitern um „MCP Hardening Pass”) oder als eigene Routine.

Templates aus dem heute-Vorfall (2026-05-15)

Drei wiederverwendbare Patterns aus dem ersten Hardening-Pass auf mcp-papierkram. Beim naechsten Audit-Lauf direkt 1:1 uebernehmen.

Template A — Raw-Tools-Env-Gate

In server.py:

RAW_ENABLED = os.environ.get("<NAME>_EXPOSE_RAW", "true").lower() not in (
    "false", "0", "no", "off"
)
 
# Tools als plain functions (ohne @mcp.tool() Decorator):
def raw_get(path: str, params: dict | None = None) -> dict: ...
def raw_post(path: str, body: dict | None = None) -> dict: ...
 
# Conditional registration am Ende des Moduls:
if RAW_ENABLED:
    mcp.tool()(raw_get)
    mcp.tool()(raw_post)
else:
    logger.info(f"<NAME>_EXPOSE_RAW=false — raw_* werden nicht registriert.")

Im CDK-Stack des Hosted-MCPs (z.B. mcp-vf-hosted-stack.ts):

environment: {
  // ... existing
  PAPIERKRAM_EXPOSE_RAW: 'false',
  TICKETPAY_EXPOSE_RAW: 'false',
  // etc fuer alle Sub-MCPs mit raw_*
},

STOLPERSTEIN (wichtig fuer Mono-MCPs): Wenn der hosted MCP ein Mono-Wrapper ist (wie mcp-vf-hosted), reicht das Setzen der Env-Variable im CDK nicht — der Wrapper spawned Sub-MCPs als Subprozesse und hat oft eine Whitelist welche Env-Variablen durchgereicht werden (siehe _SUB_MCP_ENV in mcp-vf-hosted/src/mcp_vf_hosted/main.py). Neuer Flag muss in die Whitelist aufgenommen werden, sonst kommt er im Sub-Prozess nicht an und das Gating wirkt nicht. Test: aws ecs describe-task-definition zeigt die Env, aber das ist nur die Parent-Env — pruefen durch funktionalen Smoke-Test (raw_*-Tool aufrufen, muss als „tool not found” failen).

Template B — Error-Hint-Wrapping

_RESOURCE_PREFIXES: dict[str, str] = {
    "invoices": "income/invoices",
    "vouchers": "expense/vouchers",
    # ... ein Mapping aller bekannten Resource → korrigierter Pfad
}
 
def _format_404_hint(path: str) -> str:
    raw = path.lstrip("/").split("?")[0]
    seg = raw.split("/")[0].split(".")[0]
    if seg in _RESOURCE_PREFIXES:
        correct = _RESOURCE_PREFIXES[seg]
        return (
            f"404 auf '{path}'. Pfad braucht Ressourcen-Prefix: versuche "
            f"'/{correct}.json'. Empfehlung: dediziertes Tool 'list_{seg}'."
        )
    return f"404 auf '{path}'. Bekannte Ressourcen: {', '.join(sorted(_RESOURCE_PREFIXES))}."
 
def _request(method, path, *, params=None, json=None) -> dict:
    with _client() as c:
        resp = c.request(method, path, params=params, json=json)
        if resp.status_code == 404:
            raise RuntimeError(_format_404_hint(path))
        if resp.status_code in (401, 403):
            raise RuntimeError(f"{resp.status_code} auf '{path}'. <NAME>_TOKEN/Subdomain pruefen.")
        if resp.status_code == 429:
            raise RuntimeError(f"429 Quota auf '{path}'. ...")
        resp.raise_for_status()
        return _wrap(resp, _json_or_empty(resp))

Wirkt prompt-unabhaengig — der MCP heilt sich auf der naechsten Tool-Iteration selbst, ohne dass das System-Prompt jeden API-Pfad kennen muss.

Template C — Aggregations-Tools

Pattern: ein workflow-orientiertes Tool, das mehrere list_*-Calls bundled, server-side aggregiert und ein strukturiertes ~200-300-Token-Result zurueckgibt — statt das Modell 5 list_*-Tools chainen zu lassen.

Beispiele:

  • ticketpay_event_bilanz(event_id) — verkauft/brutto/stornos/gebuehren/netto in einem Call
  • papierkram_monatsabschluss(year, month) — Einnahmen + Ausgaben + Saldo + Top-Items
  • papierkram_offene_posten() — alle nicht-bezahlten Rechnungen, sortiert nach Faelligkeit
  • papierkram_kunde_uebersicht(company_id) — Rechnungen + Projekte fuer einen Kunden

Bauanleitung:

  1. Identifiziere typische Workflow-User-Phrasen die heute 3-5 list_*-Calls ausloesen (Monatsabschluss, Offene Posten, Kundenuebersicht, Event-Bilanz, Projekt-Bilanz, …).
  2. Schreibe _fetch_all_pages(path, params)-Helper falls noch nicht vorhanden — wenige Aggregate brauchen alle Seiten, nicht nur die erste.
  3. Schreibe _<resource>_summary(items: list[dict]) -> dict Helper, der die Aggregate (count, summe_netto, summe_brutto, top_5) baut.
  4. Workflow-Tool ruft die Helper auf und gibt strukturierten Dict zurueck.
  5. Docstring MIT typischen Trigger-Phrasen vorne — sonst findet das Modell das Tool nicht (siehe Regel 1.3).
  6. Optional: Auch direkt im System-Prompt erwaehnen (siehe vf-sonnet.txt AGGREGATION-FIRST-Section).

Template D — Tool-Error-Digest Lambda

Wochentliche Lambda im agents-platform-Pattern, die CloudWatch-Insights ueber alle hosted-MCP-Audit-Log-Groups laeuft und Top-Failing-Tools per Telegram zustellt.

Source: ~/source/agents-platform/lambdas/tool-error-digest/ + infra/lib/tool-error-digest-stack.ts.

Beim Hinzufuegen eines neuen hosted MCPs nur Log-Group in infra/bin/app.ts ergaenzen, dann cdk deploy ToolErrorDigestStack. Voraussetzung: der MCP muss strukturierten JSON-Audit-Log mit event=tool_error, tool=..., status=..., reason=... schreiben — sonst matched die Insights-Query nichts.

Insights-Query-Template:

fields @timestamp, @message
| parse @message '"event":"*"' as event
| filter event = "tool_error"
| parse @message '"tool":"*"' as tool
| parse @message '"status":*,' as status
| parse @message '"reason":"*"' as reason
| stats count() as cnt, latest(@timestamp) as last_seen by tool, status, reason
| sort cnt desc
| limit 50

IAM-Permission die die Lambda braucht: logs:StartQuery, GetQueryResults, StopQuery, DescribeQueries, DescribeLogGroups — scoped auf arn:aws:logs:<region>:<account>:log-group:/aws/ecs/default/mcp-*.

Template E — Progressive Disclosure (Tool-Whitelist + search_tools Meta-Tool)

Wann brauchst du das: sobald ein einzelner MCP oder Mono-Wrapper ueber 30 Tools kommt — und immer bei Mono-Wrappern (z.B. mcp-vf-hosted). Das ist KEIN Premature-Optimization; oberhalb dieser Schwelle brechen Tool-Use-Aufrufe der LLMs ein (siehe Regel 1.4).

Architektur:

Modell sieht:
  ─ ~15-20 Core-Tools (die typischen 80%-Use-Cases, dauerhaft sichtbar)
  ─ 1 Meta-Tool: search_tools(query, namespace=None, detail="schema")

Modell-Flow:
  1. User: „archiviere Beleg 42"
  2. Modell: kein dediziertes archive-Tool in Core-Liste → ruft
     search_tools(query="beleg archivieren", namespace="papierkram")
  3. Meta-Tool returnt: [{name: "papierkram_archive_voucher", description: "...",
     input_schema: {...}}]
  4. Modell ruft direkt papierkram_archive_voucher(voucher_id=42)
  5. MCP fuehrt aus, returnt Resultat

Implementierung — drei Bausteine:

  1. Static Whitelist im Hosted-Layer. Bei mcp-vf-hosted: Open-WebUI’s function_name_filter_list auf dem MCP-Server-Eintrag setzen (via POST $BASE/configs/tool_servers). Bei Direkt-Hosting: im FastMCP-Server selektiv @mcp.tool() setzen oder via Env-Flag pro Tool gating.

  2. search_tools-Meta-Tool im MCP-Server. Pattern (am Beispiel mcp-vf-hosted/src/mcp_vf_hosted/main.py):

@app.tool()
def search_tools(
    query: str,
    namespace: str | None = None,
    detail: str = "schema",
    limit: int = 5,
) -> dict:
    """Suche im Vollbestand der MCP-Tools nach `query`. Returnt bis zu `limit`
    Tools — name + description, plus full input_schema wenn detail='schema'.
 
    Args:
      query: Freitext (z.B. „beleg archivieren", „rechnung als bezahlt markieren")
      namespace: optional einschraenken auf papierkram | ticketpay | sharepoint
      detail: 'name' | 'description' | 'schema'
      limit: max Anzahl gefundener Tools
 
    Returns: {"tools": [{name, description, input_schema?}], "total_matches": N}
    """
    # 1. Volle Tool-Liste aus den Sub-MCP-Proxies sammeln (cache im Modul-State)
    # 2. Score gegen `query`: einfach erst Substring-Match auf name+description,
    #    spaeter ggf. embedding-basiert. Fuer den Anfang reicht TF-IDF/BM25.
    # 3. Filter namespace + slice limit + projekt nach detail-Level
    ...
  1. Modell-Briefing im System-Prompt. Eine Section ergaenzen die dem Modell sagt:
<tool_discovery>
Du siehst nur eine Core-Auswahl der verfuegbaren Tools. Wenn du ein Tool brauchst
das nicht in der Liste ist (z.B. eine spezifischere Filterung, ein archive/cancel,
einen seltenen Endpoint), rufe `search_tools(query="...")` auf. Beispiele:
- „archive_voucher" → search_tools("beleg archivieren", namespace="papierkram")
- „send WhatsApp template" → search_tools("whatsapp template versenden")
Das Meta-Tool gibt dir name + input_schema des Tools, danach rufst du es direkt auf.
</tool_discovery>

Caveats:

  • Anthropic-API erlaubt KEIN dynamisches Tools-Nachladen mitten in einer Conversation — der tools=[...]-Array muss bei jedem Bedrock-Call vollstaendig mitgehen. Konsequenz: search_tools returnt nur Tool-Schemas-als-DATA, nicht Tool-Hinzufuegen-zur-API. Das Modell sieht das Schema und ruft das Tool direkt — aber das Tool muss in der Core-Whitelist sein. Workaround: alle „discoverable” Tools mit in die Whitelist setzen und die Whitelist halt deutlich groesser machen als die Core-Liste — z.B. 50 statt 15. Modell sieht 50 Tools, search_tools hilft ihm die richtigen 1-2 zu finden.
  • Alternative bei Code-Execution-MCP-Servern (Cloudflare Code Mode, FastMCP 3.1): Das Modell bekommt nur search_tools(query) und execute(code), schreibt JS/Python das die Tools aufruft. Sandbox-Aufwand erheblich → fuer KMU NICHT empfohlen.

Token-Sparen (gemessen):

SetupToken-Overhead Tools
139 Tools nackt~74 kB
15 Core-Tools + search_tools~10 kB (86% weniger)
Code Execution Mode~1 kB (99% weniger) — aber Sandbox-Overhead

Empfehlung fuer alle agentic-ventures MCPs > 30 Tools: Whitelist + search_tools-Meta-Tool. Code Execution Mode bewusst NICHT — Komplexitaet/Wert-Verhaeltnis fuer KMU-Setup nicht im Verhaeltnis.

Sources (Stand 2026-05-15):

Was NICHT in diese Doku gehoert

  • Wie man einen neuen MCP baut — Step-by-Step ist in SKILL. Diese Doku ist das Was + Warum, der Skill ist das Wie.
  • Wie man einen MCP auf AWS hostet — Stacks/Pattern in mcp-hosting-fargate-tunnel und mcp-hosting-aws-ecs-express.
  • MCP-spezifische Quirks — gehoeren ins jeweilige capabilities/mcps/<name>.md.
  • SKILL — Bauanleitung, verlinkt hierhin
  • _index — Inventar aller MCPs
  • mcp-remote-vs-lokal — Transport-Entscheidung
  • mcp-hosting-fargate-tunnel — Production-Hosting-Pattern
  • SKILL — sollte MCP-Hardening-Pass aufnehmen
  • Ankervorfall: CloudWatch /aws/ecs/default/mcp-vf-hosted 2026-05-15 13:38 UTC (raw_get-404)