Pexon Consulting | 11 Min. Lesezeit | 10.06.2026
TL;DR
FastMCP Advanced Patterns sind die Komponenten, die in jedem FastMCP-Setup ab dem zweiten oder dritten produktiven Server gebraucht werden: Server-Composition für modulare Architektur, Resources statt Tools für Read-Only-Daten, Prompts als wiederverwendbare Templates, OAuth2-Auth für produktive Sicherheit, Multi-Tenant-Setup für Mandanten-getrennte Daten. Pexon Consulting baut diese Patterns als Standard-Komponenten in jedem MCP-Sprint — hier die konkreten Code-Beispiele und Production-Hinweise.
Warum FastMCP Advanced jenseits des Tutorials
Wer mit dem FastMCP-Einstiegs-Blog gestartet ist, hat einen produktiven Single-Server mit ein paar Tools. Das funktioniert für die ersten Use-Cases. Bei zwei oder drei produktiven Servern wird klar: Mit Tools allein kommst du nicht weit, wenn du saubere Architektur willst.
Die häufigsten Skalierungs-Pains: Erstens überlappende Tools zwischen Servern (Customer-Lookup im CRM-Server und nochmal im Service-Server). Zweitens unstrukturierte Daten, die als Tool-Output ständig durchgereicht werden müssen (Datenbank-Schemas, Konfigurations-Dateien). Drittens Prompt-Duplikation zwischen Apps, die alle den gleichen Server nutzen. Viertens Auth, das initial mit Service-Accounts funktioniert, aber bei produktiven Usern Identity-Pass-Through braucht.
FastMCP hat für jeden dieser Pains ein eingebautes Pattern. Die meisten werden in Tutorials nicht behandelt, weil sie über das Hello-World hinausgehen. Hier behandelt.
Pattern 1: Server-Composition
Statt eines monolithischen Servers mit 30 Tools baust du mehrere kleinere Server und mountest sie zusammen. Jeder Sub-Server hat seine eigene Verantwortung, der Top-Level-Server bündelt sie.
from fastmcp import FastMCP
customer_server = FastMCP("customer-server")
@customer_server.tool
def search_customer(query: str) -> list[dict]:
"""Suche Kunden im CRM."""
return crm.search(query)
@customer_server.tool
def get_customer_history(customer_id: str) -> list[dict]:
"""Holt Bestell-Historie des Kunden."""
return crm.get_history(customer_id)
order_server = FastMCP("order-server")
@order_server.tool
def create_order(customer_id: str, items: list[dict]) -> dict:
"""Legt eine neue Bestellung im ERP an."""
return erp.create_order(customer_id, items)
main = FastMCP("crm-orders")
main.mount(customer_server, prefix="customer")
main.mount(order_server, prefix="order")
main.run()
Was hier passiert: Der Top-Level-Server crm-orders exponiert customer_search_customer, customer_get_customer_history, order_create_order etc. Die Sub-Server bleiben separat testbar, der Code pro Domain ist sauber getrennt.
Bei einem Pexon-Kunden haben wir damit einen 47-Tool-Monolithen in fünf Sub-Server mit je 7-12 Tools refactored. Resultat: Team kann unabhängig an den Sub-Servern arbeiten, Test-Setup pro Server ist einfacher, Deployment-Risiko sinkt.
Pattern 2: Resources statt Tools
Tools sind für Aktionen — etwas tun, etwas suchen, etwas anlegen. Für statische oder semi-statische Daten ist die richtige FastMCP-Primitive @mcp.resource. Resources werden vom Agent gelesen, ohne dass sie als Tool-Call im Agent-Loop erscheinen.
from fastmcp import FastMCP
mcp = FastMCP("data-server")
@mcp.resource("crm://schema")
def crm_schema() -> str:
"""JSON-Schema des CRM-Datenmodells."""
return open("schemas/crm.json").read()
@mcp.resource("crm://field-glossary")
def field_glossary() -> str:
"""Glossar der CRM-Felder mit Geschäfts-Bedeutung."""
return open("docs/crm-glossary.md").read()
@mcp.resource("crm://standard-segments")
def standard_segments() -> dict:
"""Standard-Kundensegmente und ihre Definition."""
return {
"premium": {"min_revenue": 100000, "tier": "A"},
"standard": {"min_revenue": 25000, "tier": "B"},
"small": {"min_revenue": 0, "tier": "C"},
}
Warum das ein Game-Changer ist: Der Agent kann Resources lesen, ohne den Tool-Use-Loop zu strapazieren. Bei einem typischen Customer-Service-Setup haben wir vor der Resource-Migration 32 Tools mit Schema-Erklärungen in den Beschreibungen gehabt — Tokens waren teuer und das Modell verwirrt. Nach Migration auf Resources hat der Agent 12 Tools und greift bei Bedarf auf 8 Resources zu. Tool-Treffer-Quote stieg um 23 Prozentpunkte, Token-Verbrauch pro Konversation sank um 41%.
Pattern 3: Prompts als wiederverwendbare Templates
Wenn mehrere Apps den gleichen Server nutzen, brauchst du standardisierte Prompts. FastMCP hat @mcp.prompt als eingebaute Primitive.
from fastmcp import FastMCP
mcp = FastMCP("support-server")
@mcp.prompt
def customer_inquiry_template(
customer_id: str,
inquiry_type: str = "general",
) -> list[dict]:
"""
Standardisierter Prompt für Customer-Inquiry-Bearbeitung.
"""
return [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"""
Beantworte eine Anfrage zum Kunden {customer_id}.
Inquiry-Typ: {inquiry_type}
Vorgehen:
1. Hole Kundendaten via customer_search_customer.
2. Hole Bestell-Historie via customer_get_customer_history.
3. Wenn schreibende Aktion nötig: customer-Bestätigung einholen.
"""},
]
Die App-Seite ruft den Prompt ab und nutzt ihn — Prompt-Logik liegt nicht in der App, sondern beim Server. Damit kann die Prompt-Engineering-Verantwortung auf den Server-Owner liegen, die App-Entwickler konsumieren standardisierte Best-Practice-Prompts. Bei einem Pilotkunden mit drei Apps am gleichen MCP-Server haben wir damit Inkonsistenzen in der Prompt-Qualität beseitigt — alle drei Apps nutzten plötzlich die gleichen, qualitätsgesicherten Prompts.
Pattern 4: OAuth2-Auth mit User-Identity
Tutorial-Code arbeitet mit Service-Accounts: „MCP-Server hat einen Token, alle Tool-Calls laufen über diesen Token“. Für Production reicht das nicht — du brauchst die echte User-Identity im Tool-Handler, sonst hast du keinen sauberen Audit-Trail.
FastMCP unterstützt seit Version 2.3 native OAuth2-Auth mit User-Identity-Pass-Through.
from fastmcp import FastMCP, Context
from fastmcp.auth import BearerTokenAuth
from fastmcp.exceptions import ToolError
auth = BearerTokenAuth(
jwks_url="https://login.microsoftonline.com/TENANT_ID/discovery/v2.0/keys",
issuer="https://login.microsoftonline.com/TENANT_ID/v2.0",
audience="api://mcp-customer-server",
)
mcp = FastMCP("customer-server", auth=auth)
@mcp.tool
def get_customer_details(customer_id: str, ctx: Context) -> dict:
"""Holt Kundendetails mit User-Identity-Check."""
user_email = ctx.auth.claims.get("email")
user_roles = ctx.auth.claims.get("roles", [])
if "customer.read" not in user_roles:
raise ToolError(f"User {user_email} hat keine Lese-Berechtigung auf Kundendaten.")
audit_log.write(user=user_email, action="customer.read", target=customer_id)
return crm.get(customer_id)
Was hier passiert: Der MCP-Server validiert den Bearer-Token gegen das JWKS-Endpoint von Entra ID. User-Claims (Email, Rollen) landen im Context und sind im Tool-Handler verfügbar. Permissions werden pro Tool gecheckt, Audit-Trail wird mit User-Identity geschrieben.
Mehr zur Tiefe von Agent-Permissions und OAuth-Patterns bauen wir in unserem Agent-Permissions-OAuth-Blog aus.
Pattern 5: Multi-Tenant-Setup
Bei SaaS-Produkten oder Multi-Werk-Setups muss der MCP-Server Mandanten-getrennte Daten liefern. Du baust das nicht über separate Server pro Mandant (das skaliert nicht), sondern über Mandanten-Kontext im Tool-Handler.
@mcp.tool
def search_orders(query: str, ctx: Context) -> list[dict]:
"""Sucht Bestellungen im Mandanten-Bereich."""
tenant_id = ctx.auth.claims.get("tenant_id")
if not tenant_id:
raise ToolError("Kein Mandanten-Kontext im Token.")
return order_db.search(
query=query,
tenant_id=tenant_id,
limit=20,
)
Wichtig: Der Tenant-Filter muss auf Datenbank-Ebene durchgesetzt werden, nicht erst nach dem Fetch. Wir nutzen typisch Postgres Row-Level Security (RLS) plus dem Tenant-ID aus dem Token-Claim. Damit fällt das „Tenant-Leakage“-Risiko durch Programmierfehler weg — die Datenbank verweigert den Zugriff auf andere Tenants strukturell.
Häufig gestellte Fragen
Wie viele Sub-Server sind in einer Composition sinnvoll?
Aus unseren Projekten: 3-8 Sub-Server pro Top-Level-Server. Bei mehr wird die Mount-Logik unübersichtlich. Wir bauen dann hierarchische Compositions — mehrere Top-Level-Server, die gemeinsam einem App-spezifischen Aggregator zugeführt werden.
Können Resources auch dynamisch sein?
Ja, der @mcp.resource-Decorator unterstützt Funktionen, die bei jedem Lese-Aufruf neu evaluiert werden. Caching ist möglich, machen wir typisch mit einem 5-Minuten-TTL für semi-statische Daten und einem 1-Stunden-TTL für Schemas.
Wie integriere ich OAuth2 mit Claude Code oder Claude Agent SDK?
Claude Code spricht die MCP-Server transparent durch — wenn der Server Bearer-Token verlangt, muss der User-Token in der Claude-Code-Konfiguration hinterlegt sein. Für interaktive User-Sessions empfehlen wir, einen OAuth2-Callback-Flow in der App zu bauen, der den User-Token holt und an Claude Code übergibt.
Was, wenn der OAuth2-Provider down ist?
Wir bauen typisch einen Token-Cache mit 30-60 Minuten TTL. Bei kurzen Outage des IdP läuft der Server weiter mit gecachten Tokens. Bei längeren Outages wird der Server entsprechend Read-Only oder geht in Fail-Safe-Modus.
Wie testet man Multi-Tenant-Logik?
Mit klaren Integration-Tests pro Tenant-Szenario. Wir bauen typisch ein Test-Setup mit 3-5 Tenant-Mocks und prüfen automatisiert, dass Tool-Calls aus Tenant A keine Daten aus Tenant B sehen. Plus RLS-Test auf Datenbank-Ebene als zweite Verteidigungslinie.
Nächster Schritt
Wenn dein Team mit FastMCP über das Tutorial hinaus produktiv werden will: Wir machen einen 60-Minuten-Architektur-Call mit dir und idealerweise einem Senior-Engineer. Du beschreibst die geplanten Server-Strukturen, wir skizzieren die Advanced-Patterns mit Festpreis-Sprint.
Verwandte Themen
→FastMCP Server + LangChain MCP-Integration in der Praxis
→Universal MCP Client: Chatbot trifft 50 Custom-Apps
→Agent Routing in LangGraph: Conditional Edges, Supervisor, Swarm
→Claude Agent SDK vs OpenAI Agents SDK vs LangGraph 2026



