mcp-starter-template
mcp-starter-template
Ein Referenz-MCP-Server-Gerüst, das Sicherheitsmuster umsetzt, die die meisten öffentlichen MCP-Beispiele auslassen: Auth-Passthrough pro Benutzer (niemals ein gemeinsames Dienstkonto), standardmäßig schreibgeschützte Tools mit explizitem Write-Opt-in, ein Dry-Run-Modus für Schreib-Tools und ein Pro-Sitzungs-Kosten-/Ratenlimit mit strukturierten Ablehnungen statt stiller No-ops oder Abstürzen. Jede Schutzvorkehrung ist durch einen automatisierten Test abgesichert, nicht nur durch einen Docstring.
Entstanden als Portfolio-Arbeit, nachdem ich in einem Produktions-MCP-Fork, der keine dieser Schutzvorkehrungen hatte, tote Tool-Handler auditiert und entfernt habe.
Ein Schwesterprojekt, mcp-issue-tracker, verwendet genau diese Sicherheitsarchitektur (Auth-Passthrough, allowlist-gesteuerte Schreibvorgänge, Dry-Run, Ratenbegrenzung, Audit-Trail) erneut, angewendet auf eine reale, lokale Issue-Tracker-Domäne – dasselbe Muster, zweimal statt einmal bewährt.
Warum es das gibt
Die meisten öffentlichen MCP-Serverbeispiele verbinden einen Assistenten direkt mit einem Dienstkonto mit vollen Berechtigungen und ohne Schutzvorkehrungen. So kann ein Assistent, der eine vernünftige Frage beantwortet, am Ende Daten preisgeben, die der Fragesteller nicht sehen sollte, oder stillschweigend einen Schreibvorgang ausführen, den niemand genehmigt hat. Dieses Repository zeigt, wie die sicherere Standardlösung aussieht – klein genug, um es in einer Sitzung komplett durchzulesen.
Schutzmechanismen und was sie jeweils verhindern
Schutzmechanismus | Wo | Was es verhindert |
Auth-Passthrough |
| Ein Tool-Aufruf läuft niemals unter einer gemeinsamen/pauschalen Berechtigung. Jeder Aufruf löst die Identität dieses spezifischen Aufrufers auf, und jede nachgelagerte Prüfung verwendet diese Identität, nicht ein Admin-/Dienstkonto. Verhindert, dass „der Assistent alles sieht, was das Dienstkonto sehen kann, unabhängig davon, wer gefragt hat.“ |
Standardmäßig schreibgeschützt + explizite Write-Allowlist |
| Ein neu hinzugefügtes oder falsch konfiguriertes Schreib-Tool, das ausgeführt wird, bevor es jemand ausdrücklich überprüft und aktiviert hat. Ein Tool ist nur dann als Schreibvorgang aufrufbar, wenn sein Name in |
Dry-Run-Modus |
| Der echte nachgelagerte Seiteneffekt eines Schreib-Tools wird ausgelöst, während eine Bedienperson das Verhalten noch validiert. Im Dry-Run wird der echte API-Client überhaupt nicht aufgerufen – in Tests verifiziert, indem die Client-Methode selbst ausspioniert wird, nicht nur durch Prüfen der Antwort. Verhindert, dass „wir in Produktion getestet haben, weil Dry-Run heimlich doch geschrieben hat.“ |
Pro-Sitzungs-Raten-/Kostenlimit |
| Ein unbegrenzter oder außer Kontrolle geratener Client, der Kosten verbrennt oder eine nachgelagerte API bombardiert. Sobald das Fensterbudget einer Sitzung (Aufrufe oder Kosteneinheiten) erschöpft ist, wird jeder weitere Aufruf in diesem Fenster mit einem strukturierten Fehler und einem |
Strukturiertes Audit-Log |
| Ein Sicherheitsvorfall, der im Nachhinein nicht rekonstruierbar ist. Jeder Aufruf – erlaubt oder abgelehnt, Lese- oder Schreibvorgang, Dry-Run oder echt – wird als ein JSON-Lines-Datensatz und eine SQLite-Zeile geschrieben: Zeitstempel, Sitzung, Benutzer, Tool, Lese-/Schreibvorgang, Dry-Run-Flag, Erlaubt-Flag, Latenz. Verhindert, dass „wir nicht wirklich wissen, was passiert ist.“ |
Architektur
┌─────────────────────────────┐
MCP client ───────▶ │ transport adapter │
(stdio / HTTP) │ mcp_app.py / http_app.py │
└──────────────┬───────────────┘
│ token, session_id, tool_name, args
▼
┌─────────────────────────────┐
│ MCPStarterServer │ server.py — single
│ .call_tool() │ choke point every
└──────────────┬───────────────┘ call passes through
1) resolve tool ────┤
2) authenticate ────┤──▶ AuthMiddleware ──▶ MockIdentityProvider
3) allowlist check ─┤──▶ ToolRegistry
4) rate/spend check ┤──▶ SessionLimiter
5) execute ─────────┤──▶ tool handler (search_docs / create_ticket)
6) audit log ───────┴──▶ AuditLogger ──▶ audit.jsonl + SQLiteAuth-Middleware (
auth.py) löst ein Bearer-Token überMockIdentityProvider(identity.py) in einenUserauf – eindeutig als nur für Entwicklung gekennzeichnet, mit zwei verschiedenen Testbenutzern (alice/Engineering,bob/Vertrieb) sowie einem Admin. Fehlende oder nicht erkannte Tokens werden abgelehnt; es gibt keine Fallback-Identität.Tool-Registry (
registry.py) ist der einzige Ort, an dem die Lese-/Schreibklassifizierung jedes Tools lebt; sie wird zur Registrierungszeit gegen den Abschnitttools:vonserver.yamlabgeglichen – ein Widerspruch zwischen dem, was der Code deklariert, und dem, was die Konfiguration sagt, verhindert den Start. Ein Schreib-Tool ist erst dann aufrufbar, wenn sein Name inallowed_write_toolssteht; es bleibt trotzdem inlist_tools()sichtbar, sodass eine prüfende Person die vollständige Angriffsfläche sehen kann, nicht nur das aktuell Aktivierte.Dry-Run-Wrapper: Jeder Schreib-Tool-Handler akzeptiert ein
dry_run: boolund berührt fürcreate_ticketbeitrueniemalsTicketSystemClient.create(die stellvertretende nachgelagerte API) – stattdessen wird eine synthetische IDDRYRUN-...zurückgegeben.dryrun.pyformatiert die Audit-Zeile[DRY RUN].Raten-/Kostenbegrenzer (
limiter.py) ist ein Zähler mit festem Fenster prosession_id:calls_per_minundcost_per_session(Tool-Kosten stammen aus der Registry) werden gemeinsam allewindow_secondszurückgesetzt. Abgelehnte Aufrufe verbrauchen selbst kein Budget.Audit-Log (
audit.py) schreibt JSON-Lines in eine Datei und spiegelt jeden Datensatz in eine SQLite-Tabelleaudit_log, die dem Datenmodell der Spezifikation entspricht, sodass es als Text verfolgt oder mit SQL abgefragt werden kann.
Zwei Transporte umschließen denselben MCPStarterServer-Kern:
mcp_app.py– ein echter MCP-Stdio-Server, der auf dem offiziellen MCP Python SDK (FastMCP) basiert. Da Stdio ein einzelner lokaler Prozess ohne Pro-Request-Header ist, sindtokenundsession_idexplizite Tool-Argumente – eine übliche, dokumentierte Vereinfachung für lokale/Entwicklungs-MCP-Server. Mit diesem Server würde ein tatsächlicher MCP-Client (Claude Desktop, diemcp-CLI usw.) kommunizieren.http_app.py– ein FastAPI-HTTP-Transport, bei dem das Token aus einem echtenAuthorization: Bearer <token>-Header und die Sitzung ausX-Session-Idstammt, so wie es eine echte Multi-Tenant-Bereitstellung verwenden würde.
Beispiele für Tools
search_docs(query) -> list[DocResult]– schreibgeschützt. Durchsucht einen kleinen statischen In-Memory-Korpus, gefiltert auf Dokumente, die für das Team des aufrufenden Benutzers sichtbar sind (oder unternehmensweite Dokumente). Genau das macht den Auth-Passthrough beweisbar: dieselbe Abfrage vonalice(Engineering) undbob(Vertrieb) liefert unterschiedliche Ergebnisse.create_ticket(title, body) -> TicketId– Schreibvorgang, allowlist-gesteuert. Steht für eine echte Ticketing-API (TicketSystemClient); Dry-Run greift, bevor dieser Client überhaupt berührt wird.
Datenmodell
audit_log:timestamp, session_id, user_id, tool_name, read_or_write, dry_run, allowed, latency_ms, error_code, detail– SQLite-Tabelle + JSON-Lines-Datei, bei jedem Aufruf geschrieben.tool_registry-Konfiguration (Abschnitttools:inserver.yaml):read_only, cost_units, descriptionpro Tool-Name.session_limits: In-Memory-Fenster pro Sitzung (call_count, cost_used, zurückgesetzt beiwindow_seconds), gesteuert durchrate_limit:inserver.yaml.
Fehlervertrag
Jede Ablehnung ist ein strukturierter MCPError – {code, message, retry_after?, details?} – niemals eine nackte Exception oder ein stiller No-op:
Code | Wann |
| Token fehlt oder wird nicht erkannt |
| Schreib-Tool aufgerufen, aber nicht in |
| Sitzung hat |
| Unbekannter Tool-Name |
| Handler hat bei den angegebenen Argumenten einen |
Über HTTP entsprechen diese jeweils 401 / 403 / 429 / 404 / 400, mit demselben {code, message, ...}-Body im detail der Antwort.
Installation
Erfordert Python 3.10+ (entwickelt und getestet auf 3.10; die Spezifikation verlangte 3.11+ – siehe Abweichungen unten, warum stattdessen 3.10 verwendet wurde).
git clone https://github.com/HamzaOuadid/mcp-starter-template.git
cd mcp-starter-template
pip install -e ".[dev]"Verwendung
Tool-Registry auflisten (Sicherheitsüberprüfung)
mcp-starter toolsTatsächliche Ausgabe aus diesem Repository:
create_ticket WRITE [DISABLED (not allowlisted)] cost=5 Create a ticket in the downstream ticket system (write, allowlist-gated).
search_docs read-only cost=1 Search internal docs visible to the calling user's team (read-only).Das ausgearbeitete „Was das verhindert“-Demo ausführen
Das ist das Ergebnis von Meilenstein 4: Simulieren Sie das Szenario mit zwei Testbenutzern Ende zu Ende und zeigen Sie, dass die Berechtigungsgrenze hält, unter Verwendung der echten server.yaml aus diesem Repository (dry_run: true, leere allowed_write_tools).
mcp-starter demoTatsächliche Ausgabe eines echten Laufs gegen die server.yaml dieses Repositorys (rate_limit.calls_per_min: 5):
=== 1. Per-user auth passthrough: same tool, same query, different results ===
alice (engineering): sees docs ['eng-001', 'eng-002', 'all-001']
bob (sales): sees docs ['sales-001', 'sales-002', 'all-001']
=== 2. Missing/invalid identity is rejected, not defaulted ===
token=None -> ok=False error={'code': 'UNAUTHENTICATED', 'message': 'Missing or invalid identity token; call rejected.'}
=== 3. Write tool default posture ===
create_ticket denied: {'code': 'WRITE_NOT_ALLOWED', 'message': "Tool 'create_ticket' is a write tool and is not in allowed_write_tools. Add it to server.yaml's allowlist to enable it."}
=== 4. Rate limit: burst of calls past the cap ===
call 1/6: allowed
call 2/6: allowed
call 3/6: allowed
call 4/6: allowed
call 5/6: allowed
call 6/6: DENIED (RATE_LIMIT_EXCEEDED)
=== Audit log written to <repo>\demo_audit.jsonl ===
{"allowed": true, "detail": "", "dry_run": false, "error_code": null, "latency_ms": 0.0, "read_or_write": "read", "session_id": "demo-burst-session", ...}
{"allowed": true, ...}
{"allowed": false, "error_code": "RATE_LIMIT_EXCEEDED", "detail": "Session 'demo-burst-session' exceeded its rate/spend cap (5 calls or 10 cost units per 60s window).", ...}alice (Engineering) und bob (Vertrieb) sehen disjunkte Dokumentmengen plus das gemeinsame unternehmensweite Handbuch (all-001) – die Berechtigungsgrenze hält bei genau demselben Tool und derselben Abfrage. Ein None-Token wird direkt abgelehnt. create_ticket wird verweigert, weil die Allowlist standardmäßig leer ist. Der 6. Aufruf in einer Sitzung mit 5 Aufrufen pro Minute wird mit einem strukturierten Fehler abgelehnt.
Führen Sie es mit allowlistiertem Schreib-Tool aus (weiterhin Dry-Run, da das die Standardeinstellung der Konfiguration ist), um die Form der Dry-Run-Antwort zu sehen:
mcp-starter demo --allow-writes=== 3. Write tool default posture ===
create_ticket allowed (allowlisted): TicketId(ticket_id='DRYRUN-8ffc09d5', dry_run=True)Es wurde kein echtes Ticket erstellt – TicketSystemClient.created bleibt im Dry-Run-Modus leer; das wird direkt in tests/test_dry_run.py geprüft, indem die Client-Methode selbst ausspioniert wird.
Den HTTP-Transport ausführen
mcp-starter serve-http --port 8000curl http://127.0.0.1:8000/tools
curl -X POST http://127.0.0.1:8000/tools/search_docs/call \
-H "Authorization: Bearer token-alice" \
-H "X-Session-Id: demo-1" \
-H "Content-Type: application/json" \
-d '{"arguments": {"query": ""}}'
# Write tool, denied by default (empty allowlist):
curl -i -X POST http://127.0.0.1:8000/tools/create_ticket/call \
-H "Authorization: Bearer token-alice" \
-H "X-Session-Id: demo-1" \
-H "Content-Type: application/json" \
-d '{"arguments": {"title": "Broken build", "body": "CI red on main"}}'
# -> HTTP 403, {"detail":{"code":"WRITE_NOT_ALLOWED", ...}}Entwicklungs-Tokens: token-alice (Engineering), token-bob (Vertrieb), token-admin (Engineering, Admin-Flag gesetzt).
Den echten MCP-Stdio-Server ausführen
mcp-starter serve-stdioDies startet einen echten FastMCP-Stdio-Server – richten Sie einen MCP-Client (z. B. mcp dev der mcp-CLI oder die Konfiguration von Claude Desktop) auf python -m mcp_starter.mcp_app aus. Tools: search_docs(query, token, session_id), create_ticket(title, body, token, session_id), list_tools().
Konfiguration
Bearbeiten Sie server.yaml:
dry_run: true # write tools log-and-simulate instead of executing
allowed_write_tools: [] # empty = no write tool is callable, by design
rate_limit:
calls_per_min: 5
cost_per_session: 10
window_seconds: 60
tools:
search_docs:
read_only: true
cost_units: 1
create_ticket:
read_only: false
cost_units: 5Um die Ticket-Erstellung tatsächlich zu aktivieren: Fügen Sie create_ticket zu allowed_write_tools hinzu und setzen Sie dry_run: false. Nur eines von beiden reicht nicht aus – es bleibt entweder für Schreibvorgänge unsichtbar oder simuliert.
Testen
pytest tests/ -vTatsächliche Ausgabe aus diesem Repository (40 Tests, alle bestanden):
tests/test_audit_log.py::test_audit_jsonl_reconstructs_a_session PASSED
tests/test_audit_log.py::test_audit_sqlite_table_matches_data_model PASSED
tests/test_audit_log.py::test_query_filters_by_session PASSED
tests/test_audit_log.py::test_rate_limit_denial_is_also_audited PASSED
tests/test_auth_passthrough.py::test_two_users_see_different_results_from_same_tool PASSED
tests/test_auth_passthrough.py::test_missing_token_is_rejected_not_defaulted PASSED
tests/test_auth_passthrough.py::test_invalid_token_is_rejected PASSED
tests/test_auth_passthrough.py::test_empty_string_token_is_rejected PASSED
tests/test_auth_passthrough.py::test_unknown_tool_name_does_not_crash PASSED
tests/test_cli.py::test_tools_command_lists_both_example_tools PASSED
tests/test_cli.py::test_demo_command_runs_full_scenario PASSED
tests/test_cli.py::test_demo_command_with_allow_writes_flag PASSED
tests/test_dry_run.py::test_dry_run_never_invokes_the_real_downstream_client PASSED
tests/test_dry_run.py::test_dry_run_logs_the_would_be_action_with_marker PASSED
tests/test_dry_run.py::test_dry_run_off_with_allowlist_actually_calls_downstream PASSED
tests/test_dry_run.py::test_dry_run_plus_write_tool_never_executes_even_when_allowlisted_repeatedly PASSED
tests/test_dry_run.py::test_read_only_tool_is_unaffected_by_dry_run_flag PASSED
tests/test_http_transport.py::test_list_tools_endpoint PASSED
tests/test_http_transport.py::test_auth_header_passthrough_two_users_differ PASSED
tests/test_http_transport.py::test_missing_auth_header_returns_401 PASSED
tests/test_http_transport.py::test_write_not_allowed_returns_403 PASSED
tests/test_http_transport.py::test_rate_limit_returns_429 PASSED
tests/test_http_transport.py::test_unknown_tool_returns_404 PASSED
tests/test_mcp_stdio.py::test_stdio_server_lists_all_three_tools PASSED
tests/test_mcp_stdio.py::test_stdio_server_two_users_differ PASSED
tests/test_mcp_stdio.py::test_stdio_server_write_tool_denied_by_default PASSED
tests/test_mcp_stdio.py::test_stdio_server_missing_token_rejected PASSED
tests/test_rate_limit.py::test_burst_of_n_plus_one_rejects_the_last_call PASSED
tests/test_rate_limit.py::test_calls_keep_being_rejected_until_window_resets PASSED
tests/test_rate_limit.py::test_cost_cap_is_enforced_independent_of_call_count PASSED
tests/test_rate_limit.py::test_sessions_are_isolated_from_each_other PASSED
tests/test_rate_limit.py::test_rate_limit_via_server_returns_structured_error PASSED
tests/test_rate_limit.py::test_denied_write_does_not_consume_rate_budget PASSED
tests/test_registry_allowlist.py::test_registry_describes_every_tool_classification PASSED
tests/test_registry_allowlist.py::test_all_write_tools_default_to_disabled PASSED
tests/test_registry_allowlist.py::test_write_tool_not_in_allowlist_is_denied PASSED
tests/test_registry_allowlist.py::test_write_tool_in_allowlist_becomes_enabled PASSED
tests/test_registry_allowlist.py::test_registration_refuses_undeclared_tool PASSED
tests/test_registry_allowlist.py::test_registration_refuses_classification_mismatch PASSED
tests/test_registry_allowlist.py::test_unknown_tool_call_is_tool_not_found PASSED
======================== 40 passed, 1 warning in 6.91s ========================Abdeckung nach Aspekt:
Auth-Durchreichung (
test_auth_passthrough.py) — zwei Mock-Benutzer, gleiches Tool, unterschiedliche Ergebnisse; fehlendes/ungültiges/leeres Token wird abgelehnt und niemals standardmäßig gesetzt; unbekannter Tool-Name schlägt sauber fehl, anstatt abzustürzen.Registry / Zulassungsliste (
test_registry_allowlist.py) — jede Tool-Klassifizierung ist überprüfbar; alle Schreib-Tools sind standardmäßig deaktiviert; die Registrierung lehnt Tools ab, die in der Konfiguration fehlen oder deren Code-/Konfigurationsklassifizierung nicht übereinstimmt; ein unbekannter Tool-Name führt zu einem sauberenTOOL_NOT_FOUND.Probelauf (
test_dry_run.py) — setzt einen Spy aufTicketSystemClient.createdirekt an, um zu bestätigen, dass es im Probelauf tatsächlich nie aufgerufen wird, und nicht nur, dass die Antwort synthetisch aussieht; nutztcaplog, um zu bestätigen, dass die Markierung[DRY RUN]tatsächlich protokolliert wird; bestätigt, dass der echte Client aufgerufen wird, sobald der Probelauf deaktiviert und das Tool in der Zulassungsliste ist; wiederholt die Kombination aus Probelauf und zugelassenem Schreib-Tool mehrfach, um Regressionen vorzubeugen; bestätigt, dass reine Lesetools von der Flagge unberührt bleiben.Ratenbegrenzung (
test_rate_limit.py) — ein Burst von N+1 lehnt genau den (N+1)-ten ab; über eine Fake-Clock werden Anrufe für den Rest des Fensters weiterhin abgelehnt, nicht nur derjenige, der die Begrenzung ausgelöst hat; das Kostenlimit wird unabhängig von der Anzahl der Anrufe durchgesetzt; Sitzungen sind voneinander isoliert; ein verweigerter Schreibvorgang verbraucht selbst kein Ratenbudget.Audit-Log (
test_audit_log.py) — JSONL und SQLite erfassen beide eine vollständige Sitzung mit ausreichend Details, um wer/was/zugelassen/Probelauf zu rekonstruieren; SQLite-Zeilen sind nach Sitzung filterbar; auch Ablehnungen wegen Ratenbegrenzung werden im Protokoll erfasst, nicht nur Erfolge.Beide Transporte (
test_http_transport.py,test_mcp_stdio.py) — dieselben Schutzmaßnahmen gelten, wenn sie über FastAPIsTestClientund über die asynchronencall_tool/list_toolsdes echtenFastMCP-Servers gesteuert werden, nicht nur über den transportunabhängigen Kern.CLI (
test_cli.py) —toolsunddemo(mit und ohne--allow-writes) laufen Ende-zu-Ende ohne Fehler übertyper.testing.CliRunner.
Abweichungen von der Spezifikation und warum
Python 3.10, nicht 3.11+. Die Entwicklungs-/CI-Umgebung bringt 3.10 mit; nichts in dieser Codebasis verwendet ein nur in 3.11 verfügbares Feature, daher wurde die
requires-python-Untergrenze gelockert, anstatt auf ein Interpreter-Upgrade zu warten. CI ist auf 3.10 festgelegt, um dem tatsächlich Getesteten zu entsprechen.SQLite, nicht PostgreSQL, für das Audit-Protokoll. Die Spezifikation erlaubt beides; Docker/Postgres sind in dieser Umgebung nicht verfügbar. Das Audit-Schema (
audit_log-Tabelle inaudit.py) ist reines SQL ohne SQLite-spezifische Syntax, sodass eine spätere Migration zu Postgres ein Treiberwechsel (sqlite3.connect→psycopg2/asyncpg) plusAUTOINCREMENT→SERIAL/IDENTITYist, kein Redesign.Auth-Durchreichung über stdio nutzt ein explizites
token-Argument, keinen Transport-Header. Der stdio-Transport von MCP ist ein einzelner lokaler Prozess ohne Header pro Anfrage, daher gibt es nichts abzufangen, so wie derAuthorization-Header von HTTP dem HTTP-Transport (http_app.py) echte Anmeldeinformationen pro Anfrage liefert. Das explizite Übergeben des Tokens hält den Effekt (eine aufgelöste, nicht standardmäßige Identität, die jeden Aufruf absichert) auf beiden Transporten identisch und testbar; es ist eine dokumentierte Vereinfachung, keine Behauptung, dass stdio „echte“ Multi-User-Authentifizierung hat. Eine Produktivumgebung mit mehreren Benutzern sollte den HTTP-Transport verwenden oder einen stdio-Transport, der von einem authentifizierenden Proxy umschlossen ist, der die echten Anmeldedaten vorgelagert zu diesem Code injiziert.Kein OAuth/JWT/mTLS in
MockIdentityProvider. Es ist ein statisches Token→User-Wörterbuch und laut dem Risikohinweis der Spezifikation eindeutig nur für die Entwicklung gedacht. Echte Verifizierung einzubauen bedeutet, die Token-Suche vonAuthMiddleware.authenticategegen einen echten IdP zu implementieren; der Rest der Pipeline (Registry, Limiter, Probelauf, Audit) ist davon unberührt, da er nur davon abhängt, einenUserzurückzubekommen.Gestrichen: v0.1-Git-Tag. Meilenstein 4 verlangt das Taggen einer
v0.1-Version. Dieses Repository arbeitet mit einem Commit pro User Story statt mit einem PR pro Meilenstein, daher bleibt das Taggen dem Maintainer überlassen, sobald das auf einem Standard-Branch mit grüner CI landet (git tag v0.1.0 && git push --tags), anstatt ein Repository selbst zu taggen, das nie irgendwohin gepusht wurde.Gestrichen: keine persistenten
session_limits/tool_registry-Tabellen. Das Datenmodell der Spezifikation führtsession_limitsundtool_registryals Tabellen nebenaudit_logauf. Dietool_registry-Klassifizierung liegt inserver.yaml(wohl eine bessere Single Source of Truth als eine DB-Tabelle, die ein Prüfer erst abfragen müsste), undsession_limitsist nur im Speicher (limiter.py), was für einen Starter mit einem Prozess korrekt ist, aber einen Neustart nicht übersteht und nicht über Prozesse skaliert; dies sollte als erstes behoben werden (z. B. durch Redis-gestützte Zähler), bevor man das hinter mehr als einem Serverprozess betreibt.Zwei Beispiel-Tools, nicht drei oder mehr. Die Spezifikation verlangt „2-3“ — geliefert wurden genau zwei (ein Lese-, ein Schreib-Tool), da ein drittes reines Lesetool keine Schutzmaßnahme abdecken würde, die nicht schon die ersten beiden abdecken.
Projektstruktur
src/mcp_starter/
identity.py mock identity provider (dev-only) + User model
auth.py auth passthrough middleware
config.py server.yaml loading/validation (pydantic)
registry.py tool registry: classification + allowlist enforcement
limiter.py per-session fixed-window rate/spend limiter
audit.py JSONL + SQLite structured audit logging
dryrun.py "[DRY RUN]" audit-line formatting
errors.py structured MCPError + error codes
server.py MCPStarterServer.call_tool — the orchestration core
mcp_app.py real MCP stdio server (official MCP Python SDK)
http_app.py FastAPI HTTP transport (Authorization header passthrough)
cli.py `mcp-starter` CLI: tools / demo / serve-http / serve-stdio
tools/
docs.py search_docs (read-only example tool)
tickets.py create_ticket (write example tool) + TicketSystemClient
tests/ 37 tests across every guardrail and both transports
server.yaml tool classification, allowlist, dry-run, rate limitsLizenz
MIT — siehe LICENSE.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
A paid remote MCP for CLI tool MCP, built to return verdicts, receipts, usage logs, and audit-ready
Hosted MCP server for agent governance: MCP config audits, injection scans, scope-policy checks.
The official MCP Server from Mia-Platform to interact with Mia-Platform Console
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/HamzaOuadid/mcp-starter-template'
If you have feedback or need assistance with the MCP directory API, please join our Discord server