Skip to main content
Glama
HamzaOuadid

mcp-starter-template

by HamzaOuadid

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

auth.py, identity.py

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

registry.py, server.yaml

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 allowed_write_tools steht; alles andere ist wirkungslos. Verhindert, dass „wir ein Tool hinzugefügt und vergessen haben, dass es Dinge löschen kann.“

Dry-Run-Modus

tools/tickets.py, dryrun.py, server.py

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

limiter.py

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 retry_after abgelehnt, nicht nur der eine Aufruf, der das Limit ausgelöst hat. Verhindert, dass „ein Fehler im Client zu einer unbegrenzten API-Rechnung wurde.“

Strukturiertes Audit-Log

audit.py

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 + SQLite
  • Auth-Middleware (auth.py) löst ein Bearer-Token über MockIdentityProvider (identity.py) in einen User auf – 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 Abschnitt tools: von server.yaml abgeglichen – 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 in allowed_write_tools steht; es bleibt trotzdem in list_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: bool und berührt für create_ticket bei true niemals TicketSystemClient.create (die stellvertretende nachgelagerte API) – stattdessen wird eine synthetische ID DRYRUN-... zurückgegeben. dryrun.py formatiert die Audit-Zeile [DRY RUN].

  • Raten-/Kostenbegrenzer (limiter.py) ist ein Zähler mit festem Fenster pro session_id: calls_per_min und cost_per_session (Tool-Kosten stammen aus der Registry) werden gemeinsam alle window_seconds zurückgesetzt. Abgelehnte Aufrufe verbrauchen selbst kein Budget.

  • Audit-Log (audit.py) schreibt JSON-Lines in eine Datei und spiegelt jeden Datensatz in eine SQLite-Tabelle audit_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, sind token und session_id explizite Tool-Argumente – eine übliche, dokumentierte Vereinfachung für lokale/Entwicklungs-MCP-Server. Mit diesem Server würde ein tatsächlicher MCP-Client (Claude Desktop, die mcp-CLI usw.) kommunizieren.

  • http_app.py – ein FastAPI-HTTP-Transport, bei dem das Token aus einem echten Authorization: Bearer <token>-Header und die Sitzung aus X-Session-Id stammt, 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 von alice (Engineering) und bob (Vertrieb) liefert unterschiedliche Ergebnisse.

  • create_ticket(title, body) -> TicketIdSchreibvorgang, 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 (Abschnitt tools: in server.yaml): read_only, cost_units, description pro Tool-Name.

  • session_limits: In-Memory-Fenster pro Sitzung (call_count, cost_used, zurückgesetzt bei window_seconds), gesteuert durch rate_limit: in server.yaml.

Fehlervertrag

Jede Ablehnung ist ein strukturierter MCPError{code, message, retry_after?, details?} – niemals eine nackte Exception oder ein stiller No-op:

Code

Wann

UNAUTHENTICATED

Token fehlt oder wird nicht erkannt

WRITE_NOT_ALLOWED

Schreib-Tool aufgerufen, aber nicht in allowed_write_tools

RATE_LIMIT_EXCEEDED

Sitzung hat calls_per_min oder cost_per_session überschritten

TOOL_NOT_FOUND

Unbekannter Tool-Name

INVALID_ARGUMENTS

Handler hat bei den angegebenen Argumenten einen TypeError ausgelöst

Ü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 tools

Tatsä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 demo

Tatsä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 8000
curl 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-stdio

Dies 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: 5

Um 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/ -v

Tatsä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 sauberen TOOL_NOT_FOUND.

  • Probelauf (test_dry_run.py) — setzt einen Spy auf TicketSystemClient.create direkt an, um zu bestätigen, dass es im Probelauf tatsächlich nie aufgerufen wird, und nicht nur, dass die Antwort synthetisch aussieht; nutzt caplog, 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 FastAPIs TestClient und über die asynchronen call_tool/list_tools des echten FastMCP-Servers gesteuert werden, nicht nur über den transportunabhängigen Kern.

  • CLI (test_cli.py) — tools und demo (mit und ohne --allow-writes) laufen Ende-zu-Ende ohne Fehler über typer.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 in audit.py) ist reines SQL ohne SQLite-spezifische Syntax, sodass eine spätere Migration zu Postgres ein Treiberwechsel (sqlite3.connectpsycopg2/asyncpg) plus AUTOINCREMENTSERIAL/IDENTITY ist, 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 der Authorization-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 von AuthMiddleware.authenticate gegen einen echten IdP zu implementieren; der Rest der Pipeline (Registry, Limiter, Probelauf, Audit) ist davon unberührt, da er nur davon abhängt, einen User zurü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ührt session_limits und tool_registry als Tabellen neben audit_log auf. Die tool_registry-Klassifizierung liegt in server.yaml (wohl eine bessere Single Source of Truth als eine DB-Tabelle, die ein Prüfer erst abfragen müsste), und session_limits ist 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 limits

Lizenz

MIT — siehe LICENSE.

-
license - not tested
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

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

View all MCP Connectors

Latest Blog Posts

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