No description
Find a file
2026-09-15 20:38:16 +02:00
docs initial commit 2026-09-15 20:38:16 +02:00
examples initial commit 2026-09-15 20:38:16 +02:00
scripts initial commit 2026-09-15 20:38:16 +02:00
src/mcp_calculator initial commit 2026-09-15 20:38:16 +02:00
tests initial commit 2026-09-15 20:38:16 +02:00
.env.example initial commit 2026-09-15 20:38:16 +02:00
.gitignore initial commit 2026-09-15 20:38:16 +02:00
.python-version initial commit 2026-09-15 20:38:16 +02:00
BACKLOG.md initial commit 2026-09-15 20:38:16 +02:00
pyproject.toml initial commit 2026-09-15 20:38:16 +02:00
README.md initial commit 2026-09-15 20:38:16 +02:00
requirements.lock initial commit 2026-09-15 20:38:16 +02:00
uv.lock initial commit 2026-09-15 20:38:16 +02:00

mcp-calculator

MCP-Server, der LLMs exakte, deterministische Mathematik gibt. LLMs sagen Tokens voraus und verrechnen sich deshalb bei großen Zahlen, vielen Rechenschritten, Wahrscheinlichkeiten oder Einheiten. Dieser Server übernimmt das Rechnen. Das LLM formuliert nur den Aufruf und deutet das Ergebnis.

  • 13 Tools: Arithmetik mit beliebiger Präzision, Algebra, Gleichungen, Analysis, Kurvendiskussion, Differentialgleichungen, lineare Algebra, Statistik, Regression, Verteilungen, Hypothesentests, Zahlentheorie, Einheiten
  • Beide Transporte: stdio (lokal) und Streamable HTTP (zentral, zustandslos, skalierbar)
  • Sicher: Parser mit Whitelist statt eval, Worker-Prozesse mit hartem Timeout und Speicherlimit
  • Client-Verwaltung: API-Tokens (nur als Hash gespeichert) und Netz-Freigaben in einer Datei auf dem gemeinsamen Volume, änderbar zur Laufzeit per CLI oder Admin-API, mit Audit-Log
  • Überlastschutz: Verbindungslimit, Limit pro Client, Kapazitätsgrenze, Wartezeit-Limit, Timeout, Abbruch bei Verbindungsende
  • Betriebsbereit: Health/Readiness, Prometheus-Metriken, Graceful Shutdown, JSON-Logging, Konfiguration komplett über Env-Variablen, festgeschriebene Abhängigkeiten

Stack: Python ≥ 3.12, MCP Python SDK 2.x, SymPy, mpmath, NumPy, SciPy, Pint, Pebble, prometheus-client. Tests mit unittest (Standardbibliothek).

Offene Ideen und geplante Features: BACKLOG.md.


Inhalt

  1. Schnellstart
  2. Tools im Überblick
  3. Transporte
  4. Clients anbinden
  5. Konfiguration
  6. Sicherheit
  7. Betrieb
  8. Monitoring
  9. Architektur
  10. Entwicklung
  11. Grenzen

Die ausführliche Referenz aller Parameter steht in docs/tools.md.


Schnellstart

Alles wird ausschließlich im virtuellen Environment des Projekts installiert, exakt in den Versionen aus dem Lockfile.

# venv anlegen und exakt nach uv.lock installieren (inkl. Dev-Werkzeug ruff)
uv sync --frozen --extra dev --python 3.12
source .venv/bin/activate

# alternativ ohne uv, mit pip (Hashes werden geprüft)
python3.12 -m venv .venv && source .venv/bin/activate
pip install --require-hashes -r requirements.lock
pip install --no-deps -e .

# Tests (unittest aus der Standardbibliothek)
python -m unittest                           # alle Tests (~50 s)
MCP_CALC_SKIP_SLOW=1 python -m unittest      # ohne Subprozess-/Server-Tests (~1 s)

# Server starten
mcp-calculator                                   # stdio (Standard)
mcp-calculator --transport http --port 8080      # HTTP auf 127.0.0.1:8080/mcp
python -m mcp_calculator --help                  # alle Optionen

Ausprobieren:

# Beispiel-Client (startet den Server selbst über stdio)
python examples/client_example.py

# gegen einen laufenden HTTP-Server
python examples/client_example.py http://localhost:8080/mcp [TOKEN]

# interaktiv im Browser mit dem MCP Inspector (braucht Node.js)
npx @modelcontextprotocol/inspector .venv/bin/mcp-calculator

Tools im Überblick

Tool Zweck Beispiel
evaluate Wert exakt und dezimal, beliebige Präzision 1000*(1+0.05)^10 → 1628.89462678
transform simplify, expand, factor, apart, substitute, nsimplify … x^2-5x+6 → (x-3)(x-2)
solve Gleichungen, Systeme, Ungleichungen, numerisch ["x+y=10","x-y=2"] → x=6, y=4
calculus Ableitung, Integral, Grenzwert, Reihe, Summe, Gradient, Hesse, Jacobi ∫e^(-x²) von -∞ bis ∞ → sqrt(pi)
analyze_function komplette Kurvendiskussion Nullstellen, Extrema, Wendepunkte, Asymptoten …
solve_ode Differentialgleichungen mit Anfangswerten y''+y=0, y(0)=0, y'(0)=1 → sin(x)
matrix Determinante, Inverse, Eigenwerte, LGS, SVD … (exakt oder numerisch) [[1,2],[3,4]] → det -2
statistics deskriptive Statistik, Korrelation Mittel, Median, Quartile, Schiefe …
regression linear, polynomial, exponentiell, logarithmisch, Potenz Koeffizienten, R², Vorhersage
probability 14 Verteilungen: pdf/cdf/sf/Quantil/Intervall P(85 < X ≤ 115) bei N(100, 15)
hypothesis_test t-Tests, ANOVA, χ², Mann-Whitney, Wilcoxon, Shapiro, Binomial p-Wert und Entscheidung im Klartext
number_theory Primzahlen, ggT/kgV, Modular, CRT, Kombinatorik, Basen factorize 360 → 2^3 * 3^2 * 5
convert_units Einheiten umrechnen und mit Einheiten rechnen 100 km/h → 27.78 m/s

Designentscheidungen, die die Treffsicherheit des LLMs erhöhen:

  • Wenige Tools, klar abgegrenzt. Verwandte Operationen hängen an einem operation-Enum. Das LLM wählt zuverlässiger aus 13 Tools als aus 60.
  • Server-instructions erklären beim Verbindungsaufbau, welches Tool wofür gedacht ist, und beschreiben die Syntax.
  • Verkettbare Ergebnisse: result hat dieselbe Syntax wie die Eingabe. Eine Ableitung lässt sich also direkt an solve weitergeben.
  • Exakt statt Gleitkomma: 0.1 + 0.2 = 3/10. Dezimalwerte kommen als String mit der gewünschten Stellenzahl zurück.
  • Selbsterklärende Ergebnisse: meaning bei Wahrscheinlichkeiten (P(X > 7) = P(X >= 8)), conclusion bei Tests, note bei Sonderfällen.
  • Hilfreiche Fehler: Tool-Fehler mit konkretem Hinweis (matrix is singular …; try 'pseudo_inverse'), damit das LLM den Aufruf selbst korrigieren kann.

Transporte

stdio Streamable HTTP
Start Client startet den Prozess Server läuft dauerhaft
Einsatz lokal, Einzelplatz, Entwicklung zentral fürs Team, Pod
Auth keine API-Token pro Client oder Netz-Freigabe, siehe docs/auth.md
Endpoint – POST http://<host>:8080/mcp

Der HTTP-Transport läuft zustandslos (stateless_http, JSON-Antworten). Es gibt keine MCP-Sessions, deshalb braucht es keine Sticky Sessions, und beliebig viele Replicas sind möglich.

# Client anlegen (Token wird einmalig angezeigt)
mcp-calculator-admin --file ./data/clients.json create mein-client

# HTTP, wie im Container
MCP_CALC_TRANSPORT=http \
MCP_CALC_HOST=0.0.0.0 \
MCP_CALC_CLIENTS_FILE=./data/clients.json \
MCP_CALC_LOG_FORMAT=json \
mcp-calculator

Rohtest mit curl:

curl -s -X POST http://localhost:8080/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer mcpc_…" \
  -H "MCP-Protocol-Version: 2025-06-18" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"evaluate","arguments":{"expression":"2^100"}}}'

Clients anbinden

Die Konfigurationsformate der Clients ändern sich gelegentlich. Die Beispiele entsprechen dem Stand bei der Erstellung. Bitte gegen die Doku der eingesetzten Client-Version prüfen.

Jeder Client (Team, IDE-Installation, Dienst) bekommt ein eigenes Token: mcp-calculator-admin create <name>. Das Token wird im Header Authorization: Bearer <token> mitgeschickt, alternativ als X-API-Key: <token>. Verwaltung und Sperren: docs/auth.md.

opencode

opencode.json (Projekt) oder ~/.config/opencode/opencode.json (global):

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "calculator": {
      "type": "remote",
      "url": "https://calculator.intern.example/mcp",
      "enabled": true,
      "headers": {
        "Authorization": "Bearer {env:MCP_CALC_TOKEN}"
      }
    }
  }
}

Lokal über stdio:

{
  "mcp": {
    "calculator": {
      "type": "local",
      "command": ["/pfad/zu/mcp-calculator/.venv/bin/mcp-calculator"],
      "enabled": true
    }
  }
}

Cline

cline_mcp_settings.json (in Cline über „MCP Servers → Configure“ erreichbar):

{
  "mcpServers": {
    "calculator": {
      "type": "streamableHttp",
      "url": "https://calculator.intern.example/mcp",
      "headers": {
        "Authorization": "Bearer <token>"
      },
      "disabled": false,
      "autoApprove": [
        "evaluate", "transform", "solve", "calculus", "analyze_function", "solve_ode", "matrix",
        "statistics", "regression", "probability", "hypothesis_test", "number_theory", "convert_units"
      ]
    }
  }
}

Alle Tools sind rein lesend, deshalb ist autoApprove unbedenklich. Lokal über stdio:

{
  "mcpServers": {
    "calculator": {
      "command": "/pfad/zu/mcp-calculator/.venv/bin/mcp-calculator",
      "args": []
    }
  }
}

Claude Code

claude mcp add --transport http calculator https://calculator.intern.example/mcp \
  --header "Authorization: Bearer <token>"
# oder lokal
claude mcp add calculator -- /pfad/zu/mcp-calculator/.venv/bin/mcp-calculator

Eigene Tools / Agenten

Jede Anwendung mit MCP-Client-SDK (Python, TypeScript, Java, Go …) kann den Server nutzen. Ein lauffähiges Python-Beispiel liegt in examples/client_example.py:

import httpx2
from mcp import Client
from mcp.client.streamable_http import streamable_http_client

async with httpx2.AsyncClient(headers={"Authorization": f"Bearer {token}"}) as http:
    async with Client(streamable_http_client("https://calculator.intern.example/mcp", http_client=http)) as client:
        tools = (await client.list_tools()).tools          # -> als Tool-Definitionen ans LLM
        result = await client.call_tool("solve", {"equations": "x^2 = 2", "domain": "real"})
        print(result.structured_content)

Einbindung in eine eigene LLM-Schleife:

  1. list_tools() ausführen und name, description und input_schema jedes Tools als Tool-Definition an das Modell geben.
  2. Fordert das Modell einen Tool-Call an, call_tool(name, arguments) aufrufen.
  3. result.content[0].text zurückgeben. Bei result.is_error den Rückgabewert als Fehler markieren.

Hinweis: Serverseitige MCP-Connectoren von LLM-Anbietern rufen den MCP-Server aus der Cloud des Anbieters auf. Ein nur intern erreichbarer Server funktioniert damit nicht. Bei internem Betrieb muss der eigene Code MCP-Client sein (wie oben).


Konfiguration

Jede Option ist per Umgebungsvariable MCP_CALC_<NAME> oder per CLI-Flag --<name> setzbar. Die CLI hat Vorrang vor der Umgebung, die Umgebung vor den Defaults. Eine Vorlage liegt in .env.example. Die Datei wird nicht automatisch geladen, sie dient als Referenz für die Pod-Umgebung.

Variable Default Beschreibung
MCP_CALC_TRANSPORT stdio stdio oder http
MCP_CALC_HOST 127.0.0.1 Bind-Adresse; im Container 0.0.0.0
MCP_CALC_PORT 8080 HTTP-Port
MCP_CALC_PATH /mcp Pfad des MCP-Endpoints
MCP_CALC_ALLOWED_HOSTS – erlaubte Host-Header, kommagetrennt; aktiviert den DNS-Rebinding-Schutz
MCP_CALC_ALLOWED_ORIGINS – erlaubte Origin-Header (nur für Browser-Clients relevant)
MCP_CALC_TRUSTED_PROXIES – IPs/CIDRs von Proxys (OpenShift-Router), deren X-Forwarded-For ausgewertet wird
MCP_CALC_MAX_REQUEST_BODY_BYTES 1048576 maximale Request-Größe
MCP_CALC_MAX_CONNECTIONS 200 gleichzeitige HTTP-Verbindungen; darüber antwortet uvicorn mit 503
MCP_CALC_METRICS true Prometheus-Metriken unter /metrics (nur HTTP)
Authentifizierung nur HTTP; Details in docs/auth.md
MCP_CALC_CLIENTS_FILE – Client-Datei (z. B. /data/clients.json auf RWX-PVC); aktiviert die Authentifizierung
MCP_CALC_CLIENTS_RELOAD_SECONDS 5 wie oft die Datei auf Änderungen geprüft wird
MCP_CALC_AUTH_TOKENS – statische Tokens name:token,… (einfache Alternative, Änderung nur per Neustart)
MCP_CALC_AUDIT_FILE audit.log neben der Client-Datei Änderungsprotokoll
MCP_CALC_ADMIN_TOKENS – Admin-Tokens name:token,…; nur wenn gesetzt, startet die Admin-API
MCP_CALC_ADMIN_HOST 127.0.0.1 Bind-Adresse der Admin-API (Default: nur per oc port-forward/exec)
MCP_CALC_ADMIN_PORT 9090 Port der Admin-API
MCP_CALC_EXECUTOR process process (isolierter Worker-Pool) oder inline (nur Entwicklung)
MCP_CALC_WORKERS 2 Anzahl Worker-Prozesse (≈ CPU-Kerne)
MCP_CALC_MAX_QUEUE 20 so viele Berechnungen dürfen auf einen freien Worker warten; darüber wird sofort mit „busy“ abgelehnt (0 = keine Warteschlange)
MCP_CALC_MAX_QUEUE_WAIT_SECONDS 30 maximale Wartezeit auf einen freien Worker, danach „busy“
MCP_CALC_CLIENT_MAX_CONCURRENT 10 gleichzeitige Berechnungen pro Client (0 = unbegrenzt); pro Client überschreibbar
MCP_CALC_TIMEOUT_SECONDS 10 harter Timeout pro Berechnung
MCP_CALC_MAX_TASKS_PER_WORKER 200 Worker nach N Aufgaben recyceln (0 = nie)
MCP_CALC_MEMORY_LIMIT_MB 512 zusätzlicher Speicher, den eine Berechnung pro Worker belegen darf (nur Linux; 0 = aus), siehe docs/memory.md
MCP_CALC_MAX_INPUT_LENGTH 5000 max. Zeichen pro Ausdruck
MCP_CALC_MAX_OUTPUT_LENGTH 20000 längere Ergebnisse werden gekürzt
MCP_CALC_MAX_PRECISION 1000 max. signifikante Stellen
MCP_CALC_MAX_MATRIX_SIZE 30 max. Zeilen/Spalten
MCP_CALC_MAX_DATA_POINTS 1000000 max. Werte pro Datensatz
MCP_CALC_MAX_LIST_ITEMS 10000 max. Einträge sonstiger Listen
MCP_CALC_LOG_LEVEL INFO DEBUG, INFO, WARNING, ERROR
MCP_CALC_LOG_FORMAT text text oder json (eine Zeile pro Eintrag)

Sicherheit

Ausdrucks-Parser (ops/parsing.py). SymPys parse_expr nutzt intern eval. Abgesichert wird das in drei Schichten:

  1. Die Eingabe wird vorab geprüft: Zeichen-Whitelist (keine Quotes, [], {}, ;, :, \), kein __, kein Attributzugriff (.name), keine Python-Keywords, keine Namen mit _.
  2. Ausgewertet wird in einem Namensraum, der nur freigegebene SymPy-Funktionen und ein leeres __builtins__ enthält. Unbekannte Namen werden zu mathematischen Symbolen, nie zu Python-Objekten.
  3. Die Berechnung läuft isoliert in einem Worker-Prozess (siehe unten).

Die Tests in tests/test_parsing.py decken typische Injection-Versuche ab (__import__, ().__class__, lambda, open(...), getattr, …).

Worker-Pool (executor.py). Jede Berechnung läuft in einem separaten Prozess (Pebble-Pool, spawn):

  • Wird der Timeout überschritten, wird der Worker hart beendet und ersetzt. 9^9^9^9 blockiert also nichts.
  • Unter Linux darf eine Berechnung pro Worker höchstens MCP_CALC_MEMORY_LIMIT_MB zusätzlich belegen (RLIMIT_AS, gesetzt nach dem Laden der Bibliotheken). Ein Ausreißer endet mit MemoryError, statt den Pod zu killen.
  • BLAS/OpenMP laufen pro Worker einthreadig. Die Parallelität kommt aus der Worker-Anzahl, das CPU-Limit des Pods wird nicht überbucht.
  • Worker werden nach N Aufgaben recycelt, damit die SymPy-Caches nicht unbegrenzt wachsen.
  • Ist der Pool defekt, startet er sich selbst neu.

HTTP.

  • Authentifizierung pro Client (API-Token oder Netz-Freigabe). Tokens sind 256 Bit zufällig und werden nur als SHA-256-Hash gespeichert; Sperren, Ablaufdatum, Netz-Einschränkung und Rotation sind zur Laufzeit möglich. Details: docs/auth.md.
  • Die Admin-API läuft auf eigenem Port, standardmäßig nur auf 127.0.0.1, mit eigenen Admin-Tokens; jede Änderung landet im Audit-Log.
  • /healthz, /readyz und /metrics bleiben offen für Probes und Prometheus.
  • Request-Body-Limit (Default 1 MiB), dazu Längenlimits für alle Eingaben.
  • DNS-Rebinding-Schutz: bei 127.0.0.1 automatisch aktiv, bei 0.0.0.0 über MCP_CALC_ALLOWED_HOSTS einschaltbar.
  • Ohne Client-Konfiguration und nicht an localhost gebunden, protokolliert der Server beim Start eine Warnung.

Die Einheiten-Umrechnung (Pint) nutzt einen eigenen Tokenizer ohne eval.


Betrieb

Die Anbindung an OpenShift ist nicht Teil dieses Projekts. Für die eigene Integration relevant:

Punkt Wert
Start mcp-calculator (Entry Point) oder python -m mcp_calculator
Pflicht-Env im Container MCP_CALC_TRANSPORT=http, MCP_CALC_HOST=0.0.0.0
Ports 8080 MCP; 9090 Admin-API (nur mit MCP_CALC_ADMIN_TOKENS, Default nur 127.0.0.1). Unprivilegiert, beliebige UID möglich
Volume RWX-PVC z. B. unter /data für clients.json und audit.log, sonst wird nichts geschrieben
Liveness GET /healthz → 200 {"status":"ok","version":…}
Readiness GET /readyz → 200 erst, wenn die Worker gestartet sind und ihre Bibliotheken geladen haben, sonst 503
Metriken GET /metrics (Prometheus, ohne Token), siehe Monitoring
Shutdown SIGTERM: keine neuen Verbindungen, laufende Requests werden fertig gerechnet, danach wird der Worker-Pool geordnet gestoppt (Log: worker pool stopped)
Überlast Sechs Schutzschichten (Verbindungen, pro Client, Kapazität, Wartezeit, Timeout, Abbruch bei Verbindungsende); Ablehnungen sind Tool-Fehler mit „retry“-Hinweis. Siehe docs/auth.md → Überlastschutz
Skalierung zustandslos, horizontal skalierbar, keine Sticky Sessions
Ressourcen MCP_CALC_WORKERS ≈ CPU-Limit. Leerlauf gemessen (macOS, RSS): ~90 MB + ~140 MB je Worker, mit 2 Workern ~360 MB. Pod-Limit ≥ Leerlauf + WORKERS × MEMORY_LIMIT_MB. Analyse, Profile und Messskript: docs/memory.md
Logs stderr; MCP_CALC_LOG_FORMAT=json für Log-Aggregation. Pro Tool-Aufruf: tool=<name> client=<client> status=… duration_ms=…; abgelehnte Anfragen: auth rejected: reason=… client=… ip=…
Startzeit /readyz nach ~1,2 s (Worker vorgewärmt); der erste Aufruf ist danach sofort schnell

Monitoring

GET /metrics liefert Prometheus-Textformat. Wie /healthz und /readyz ist der Endpoint ohne Token erreichbar, damit Prometheus im Cluster scrapen kann. Er enthält Zähler und Client-Namen, keine Rechen-Eingaben. Wer das nicht möchte, schränkt den Zugriff per NetworkPolicy ein oder setzt MCP_CALC_METRICS=false.

Metrik Typ Bedeutung
mcp_calc_tool_calls_total{tool, client, status} Counter Aufrufe je Tool, Client und Ergebnis: ok, error (Mathe-/Eingabefehler), timeout, busy (Kapazität, Wartezeit oder Client-Limit), cancelled (Client hat Verbindung getrennt), crashed (Worker gestorben). client ist local bei stdio, anonymous ohne Client-Konfiguration
mcp_calc_tool_duration_seconds{tool} Histogram Dauer inkl. Wartezeit in der Queue
mcp_calc_inflight_computations Gauge laufende + wartende Berechnungen
mcp_calc_capacity Gauge WORKERS + MAX_QUEUE; darüber wird abgelehnt
mcp_calc_workers Gauge konfigurierte Worker
mcp_calc_pool_restarts_total Counter Neustarts des Pools nach internem Fehler
mcp_calc_client_inflight_computations{client} Gauge laufende + wartende Berechnungen je Client
mcp_calc_auth_failures_total{reason} Counter abgelehnte Anfragen: missing_credentials, invalid_token, disabled, expired, network_denied
mcp_calc_clients{status} Gauge konfigurierte Clients: active, disabled, expired
mcp_calc_build_info{version} Info Version
process_*, python_* Standard CPU, RSS, Dateien des Server-Prozesses (Worker nicht enthalten)

Nicht gezählt werden Aufrufe, die schon an der Schema-Validierung scheitern (falscher Parametertyp). Die beantwortet das MCP SDK, bevor der Tool-Code läuft.

Nützliche Abfragen:

# Fehler- und Timeout-Quote je Tool (5 min)
sum by (tool, status) (rate(mcp_calc_tool_calls_total{status!="ok"}[5m]))
  / ignoring(status) group_left sum by (tool) (rate(mcp_calc_tool_calls_total[5m]))

# p95-Dauer je Tool
histogram_quantile(0.95, sum by (tool, le) (rate(mcp_calc_tool_duration_seconds_bucket[5m])))

# Auslastung (Alarm z. B. > 0.8 über 5 min → mehr Worker oder Replicas)
mcp_calc_inflight_computations / mcp_calc_capacity

# Überlast-Ablehnungen, nach Client
sum by (client) (rate(mcp_calc_tool_calls_total{status="busy"}[5m]))

# Wer belegt gerade die Kapazität?
topk(5, mcp_calc_client_inflight_computations)

# Auffällige Auth-Fehler (falsch konfigurierte Clients oder Rateversuche)
sum by (reason) (rate(mcp_calc_auth_failures_total[5m]))

Architektur

                 ┌────────────────────────── Server-Prozess ──────────────────────────┐
 Client ─stdio──►│ MCPServer (server.py)                                               │
        ─HTTP───►│  └ Starlette/uvicorn (http_app.py): Bearer-Auth, /healthz, /readyz  │
                 │     Tool-Funktionen: Validierung (Pydantic-Schemas) → Executor      │
                 └───────────────────────────────┬─────────────────────────────────────┘
                                                 │ Task-Name + Argumente, Timeout
                              ┌──────────────────▼──────────────────┐
                              │ Worker-Pool (executor.py, Pebble)   │
                              │  ops/*.py  – reine Mathe-Funktionen │
                              │  SymPy · mpmath · NumPy · SciPy · Pint
                              └─────────────────────────────────────┘

Der Server-Prozess lädt nur MCP SDK und HTTP-Stack, die Worker nur die Mathe-Bibliotheken. Das halbiert den Speicherbedarf ungefähr (Details in docs/memory.md) und wird von tests/test_footprint.py abgesichert.

src/mcp_calculator/
├── __main__.py        Entry Point: Konfiguration laden, Logging, Transport starten
├── config.py          Settings aus Env/CLI, Validierung, Limits
├── server.py          MCP-Tools: Schemas, Beschreibungen, Server-Instructions
├── http_app.py        HTTP-Transport: ASGI-App, Abbruch bei Verbindungsende, Health, Admin-Server
├── auth.py            Authentifizierung (Token, Netz) und Client-Identität je Request
├── clients.py         Client-Modell, Client-Datei (Lock, atomar), Hot-Reload
├── admin.py           Client-Verwaltung (gemeinsame Logik), Audit-Log, Admin-API
├── admin_cli.py       CLI mcp-calculator-admin
├── ratelimit.py       Limit gleichzeitiger Berechnungen pro Client
├── executor.py        Worker-Pool: Timeout, Speicherlimit, Backpressure, Vorwärmen, Selbstheilung
├── metrics.py         Prometheus-Metriken
├── logging_setup.py   Text-/JSON-Logging nach stderr
└── ops/               reine, synchrone Mathe-Funktionen (dict rein, dict raus)
    ├── spec.py        Typen/Enums/Hilfetexte für die Tool-Schemas (import-leicht, auch im Server)
    ├── context.py     Fehlertyp und Limits (import-leicht)
    ├── parsing.py     sicherer Ausdrucks-Parser
    ├── common.py      Ergebnis-Formatierung, Limit-Prüfungen
    ├── algebra.py     evaluate, transform, solve
    ├── calculus.py    calculus, analyze_function, solve_ode
    ├── linalg.py      matrix
    ├── stats.py       statistics, regression, probability, hypothesis_test
    ├── number_theory.py
    └── units.py
tests/                 unittest: pro Modul + End-to-End (in-process, HTTP, stdio, Pool, Footprint)
examples/              lauffähiger MCP-Client
scripts/               measure_memory.py – Speicher messen
docs/tools.md          vollständige Tool-Referenz
docs/memory.md         Speicheranalyse und Dimensionierung
docs/auth.md           Authentifizierung, Client-Verwaltung, Überlastschutz (Betriebshandbuch)
uv.lock                Lockfile (uv), Quelle der Wahrheit für Versionen
requirements.lock      daraus exportiert, mit Hashes, für pip/Container-Builds
BACKLOG.md             geplante Features und offene Themen

Neues Tool hinzufügen

  1. Eine reine Funktion in ops/<bereich>.py schreiben: Parameter rein, JSON-fähiges dict raus. Erwartbare Fehler als MathError("verständliche Meldung") werfen. Bei einem neuen Modul dieses in executor.TASK_MODULES eintragen.
  2. Enums/Literal-Typen für die Parameter in ops/spec.py definieren, nicht im Rechenmodul. server.py darf nur spec importieren, sonst landen SymPy/SciPy wieder im Server-Prozess (tests/test_footprint.py schlägt dann fehl).
  3. In server.py ein @mcp.tool(annotations=READ_ONLY) mit typisierten, per Field(description=…) beschriebenen Parametern anlegen, das await run("<name>", "<modul>.<funktion>", **kwargs) aufruft.
  4. Das Tool in INSTRUCTIONS (Abschnitt „Which tool“) und in docs/tools.md ergänzen.
  5. Tests schreiben: Fachlogik direkt in tests/test_<bereich>.py, Aufruf über MCP in tests/test_server.py (dort auch die Liste EXPECTED_TOOLS anpassen).

Entwicklung

source .venv/bin/activate
python -m unittest                              # alle Tests
MCP_CALC_SKIP_SLOW=1 python -m unittest         # schnelle Tests ohne Subprozesse
python -m unittest tests.test_parsing -v        # nur Parser/Sicherheit
python -m unittest tests.test_algebra.SolveTest.test_quadratic   # ein einzelner Test
ruff check src tests examples scripts           # Linting (Konfiguration in pyproject.toml)
MCP_CALC_EXECUTOR=inline MCP_CALC_LOG_LEVEL=DEBUG mcp-calculator   # Debugging ohne Worker-Pool

Abhängigkeiten aktualisieren. pyproject.toml enthält die erlaubten Bereiche, uv.lock die exakt getesteten Versionen. Nach einer Aktualisierung immer beide Lock-Dateien committen:

uv lock --upgrade                                   # oder gezielt: uv lock --upgrade-package mcp
uv export --frozen --format requirements-txt --no-emit-project --no-header -o requirements.lock
uv sync --frozen --extra dev --python 3.12 && python -m unittest

Hinweis zur Python-Version: Ist in der Shell UV_PYTHON gesetzt (z. B. UV_PYTHON=3.13), hat das bei uv Vorrang vor .python-version. Deshalb steht in den Befehlen oben --python 3.12 explizit.

Code, Docstrings und Tool-Beschreibungen sind auf Englisch. Das ist die Sprache, mit der LLMs Tool-Schemas am zuverlässigsten interpretieren. Die Projektdokumentation ist auf Deutsch.


Grenzen

  • Formulierung bleibt beim LLM. Der Server rechnet korrekt, aber das Modell muss die Aufgabe richtig in einen Aufruf übersetzen. Diese Fehlerquelle bleibt.
  • Symbolik hat Grenzen. Nicht jedes Integral, jede Gleichung und jede DGL hat eine geschlossene Form. Der Server fällt dann auf numerische Verfahren zurück (Integral, Summe) oder gibt einen Hinweis (numeric_only). Aufwendige Probleme laufen in den Timeout.
  • Numerisches Lösen (numeric_only) liefert bei nicht-polynomialen Gleichungen eine Wurzel nahe dem Startwert, nicht alle.
  • Speicherlimit pro Worker greift nur unter Linux. Unter macOS gilt nur der Timeout. Die Speicherwerte in der Doku sind unter macOS gemessen; im Container bitte mit scripts/measure_memory.py nachmessen.
  • Keine Plots, keine Finanzmathematik-Spezialfunktionen (IRR, Annuitäten lassen sich über evaluate/solve abbilden). Geplant siehe BACKLOG.md.
  • Warteschlange pro Replica. MAX_QUEUE begrenzt nur den einzelnen Pod; Lastverteilung über mehrere Replicas übernimmt der Service/Router.