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:
- Notluken-Tool (
raw_get) im Production-Hosted-MCP exponiert → Modell halluziniert Pfade. - Duenne Docstrings auf
list_invoices→ Modell findet das dedizierte Tool nicht. - 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-hosted2026-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_toolsMeta-Tool (Template E). - Pro Use-Case ggf. mehrere Custom-Models mit unterschiedlichen Whitelists (z.B.
vf-buchanurpapierkram_*,vf-mailnursharepoint_*).
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.
| # | Regel | Quick-Check |
|---|---|---|
| 1.1 | Raw-Tools im Hosting deaktiviert | grep EXPOSE_RAW server.py |
| 1.2 | Aggregations-Tools wo passend | Gibt es *_bilanz/*_uebersicht/*_abschluss? |
| 1.3 | Reiche Docstrings + Aliase | Stichprobe 3 list_*-Tools: mehr als 1 Satz? |
| 1.4 | Tool-Anzahl <30 pro Session | Zaehle grep -c "@mcp.tool" server.py — wenn >30, Template E (Whitelist + search_tools) noetig |
| 1.5 | Naming-Konvention konsistent | Alle Tools <resource>_<verb>? |
| 2.1 | Error-Wrapping mit Hints | grep ToolError server.py |
| 2.2 | Strukturierter Audit-Log | grep "logger|structlog" server.py |
| 2.3 | Quota im Response | Beispiel-Call inspizieren |
| 3.1 | fields= Parameter | Stichprobe list_*-Signatur |
| 3.2 | count_only= Parameter | dito |
| 4.1 | Secrets in .env.local | git log -p .env* darf nichts zeigen |
| 4.2 | Per-User-Header durchgereicht | nur fuer hosted MCPs |
| 4.3 | Pydantic-Schemas | Tool-Signaturen typisiert? |
| 5.1 | HTTP Transport default | transport= in __main__-Block |
| 5.2 | /health Endpoint | curl localhost:<port>/health |
| 6.1 | Smoke-Tests | pytest --collect-only zaehlt 1 pro Tool? |
| 6.4 | Quota-Alarm | nur fuer hosted/billable APIs |
| 7.1 | Vault-Eintrag aktuell | intern/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 Callpapierkram_monatsabschluss(year, month)— Einnahmen + Ausgaben + Saldo + Top-Itemspapierkram_offene_posten()— alle nicht-bezahlten Rechnungen, sortiert nach Faelligkeitpapierkram_kunde_uebersicht(company_id)— Rechnungen + Projekte fuer einen Kunden
Bauanleitung:
- Identifiziere typische Workflow-User-Phrasen die heute 3-5 list_*-Calls ausloesen (Monatsabschluss, Offene Posten, Kundenuebersicht, Event-Bilanz, Projekt-Bilanz, …).
- Schreibe
_fetch_all_pages(path, params)-Helper falls noch nicht vorhanden — wenige Aggregate brauchen alle Seiten, nicht nur die erste. - Schreibe
_<resource>_summary(items: list[dict]) -> dictHelper, der die Aggregate (count, summe_netto, summe_brutto, top_5) baut. - Workflow-Tool ruft die Helper auf und gibt strukturierten Dict zurueck.
- Docstring MIT typischen Trigger-Phrasen vorne — sonst findet das Modell das Tool nicht (siehe Regel 1.3).
- Optional: Auch direkt im System-Prompt erwaehnen (siehe
vf-sonnet.txtAGGREGATION-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:
-
Static Whitelist im Hosted-Layer. Bei mcp-vf-hosted: Open-WebUI’s
function_name_filter_listauf dem MCP-Server-Eintrag setzen (viaPOST $BASE/configs/tool_servers). Bei Direkt-Hosting: im FastMCP-Server selektiv@mcp.tool()setzen oder via Env-Flag pro Tool gating. -
search_tools-Meta-Tool im MCP-Server. Pattern (am Beispielmcp-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
...- 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_toolsreturnt 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)undexecute(code), schreibt JS/Python das die Tools aufruft. Sandbox-Aufwand erheblich → fuer KMU NICHT empfohlen.
Token-Sparen (gemessen):
| Setup | Token-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):
- Anthropic — Code Execution with MCP — Hauptpaper,
search_tools+ Progressive Disclosure - Cloudflare — Code Mode — 2-Tool-Pattern (search + execute), V8-Isolate
- Synaptic Labs — Meta-Tool Pattern — Discovery + Execution Pattern
- Lunar — Dynamic Tool Discovery — Benchmarks (89% Token-Reduktion)
- Agentpmt — The Bloat Tax — GitHub Copilot + Block Linear-Case-Studies
- FastMCP 3.1 Code Mode — Ready-to-use Implementation (wenn doch Code-Exec gewuenscht)
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.
Related
- 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-hosted2026-05-15 13:38 UTC (raw_get-404)