Files
mcp-sonarqube-proxy/CLAUDE.md
T
marcus d76c16d9a7 feat(auth): forward SONARQUBE_TOKEN to upstream as Bearer header
The upstream MCP container requires a SonarQube user token in the
Authorization header. Without one, every call returns 401.

- proxy: read SONARQUBE_TOKEN via sonarqube_token() at session-open
  time; raise TokenMissingError when unset/blank. upstream_session()
  attaches the token as "Authorization: Bearer <token>" via
  streamablehttp_client(headers=...).
- cli: fail fast in serve and check with a clear stderr message and
  exit 1 when the token is missing, before any network attempt. All
  exception text written to stderr passes through _redact() so an
  accidentally-leaked token from a third-party exception is replaced
  with [REDACTED] before display.
- The token is never stored on any object, never logged, and the
  TokenMissingError message contains no token material (it only
  describes how to generate one in SonarQube).
- Tests: header forwarding via mocked streamablehttp_client, missing-
  token exit code, redaction in CLI error paths, whitespace stripping
  on the token. Total: 25 tests.
- Docs: README/CLAUDE updated with the new env-var, Claude Desktop
  config snippet, and the security guarantees. CHANGELOG added.

Bumps version to 0.2.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 20:42:51 +02:00

4.3 KiB

CLAUDE.md

Entwicklungshinweise fuer mcp-sonarqube-proxy.

Projekt

Stdio-MCP-Server, der beim Start eine Streamable-HTTP-Verbindung zum Upstream SonarQube-MCP-Server oeffnet, dessen Tools 1:1 weiterreicht und Aufrufe an diesen weiterleitet. Claude Desktop / Claude App spawnt diesen Prozess via stdio.

Architektur

  • src/mcp_sonarqube_proxy/proxy.py — eine upstream_session() (async context manager) haelt die persistente ClientSession zum Upstream. build_proxy_server() registriert zwei Handler auf einem mcp.server.lowlevel.Server, die Aufrufe transparent weiterleiten.
  • src/mcp_sonarqube_proxy/cli.py — Click-CLI mit serve (stdio) und check (einmaliger Verbindungstest).

Zentrale Architekturentscheidungen

  1. Low-Level Server, nicht FastMCP. FastMCP introspiziert die Handler-Signatur, um ein inputSchema zu generieren. Bei einem generischen **kwargs-Handler ist das Schema leer und der Client weiss nicht, wie er das Tool aufrufen soll. Der Low-Level-Server uebernimmt dagegen das Tool-Objekt mit allen Feldern (inputSchema, outputSchema, annotations, title, _meta) unveraendert vom Upstream.

  2. Eine persistente Upstream-Session, kein Per-Call-Reconnect. Die fruehere Variante hat fuer jeden Tool-Call eine neue HTTP-Session geoeffnet. Das bricht die Streamable-HTTP-Session-Id, multipliziert die Latenz und macht initialize() zur teuersten Operation im Hot Path. Jetzt: einmal beim Start verbinden, bei serve-Lifetime offenhalten, beim Beenden sauber schliessen.

  3. validate_input=False auf dem @call_tool-Decorator. Der Upstream ist die einzige Wahrheit ueber Schemata. Lokale Validierung wuerde nur duplizieren und koennte bei Schema-Drift zu falschen Ablehnungen fuehren.

  4. server.create_initialization_options() statt manueller Capabilities. Die Helper-Methode liefert die korrekten NotificationOptions() und experimental_capabilities={} und ueberlebt SDK-Updates besser als ein handgeschriebener Aufruf.

  5. Stateless Tool-Liste — kein Caching. Jeder tools/list-Request re-fetcht den Upstream. Weniger Code, immer aktuell, kein Cache-Coherency- Problem.

Logging und stdout/stderr

stdout ist fuer JSON-RPC reserviert. Jegliche Ausgabe (Logging, Startmeldungen, Fehlermeldungen) muss auf stderr. _configure_logging setzt das fuer das Logging-Modul, _stderr() ist der Helper fuer direkte Meldungen.

SONARQUBE_TOKEN — Sicherheit

Der Upstream verlangt einen User-Token im Authorization-Header. Wir lesen den Token in proxy.sonarqube_token() aus der Env-Var, leiten ihn als Bearer <token> an streamablehttp_client(headers=...) weiter, und speichern ihn nirgends. Vor jedem stderr-Output mit potenziell ungeprueftem Exception-Text laeuft cli._redact(), die den live-Token (falls in der Env) durch [REDACTED] ersetzt — defensive Schicht gegen Drittanbieter-Libraries, die in Fehlermeldungen Header einbetten koennten.

TokenMissingError enthaelt niemals Token-Material, weil dieser Fall genau heisst: es gibt keinen Token. Die Meldung enthaelt nur die operator-gerichtete Anleitung "My Account -> Security".

Lokale Entwicklung

# Editable install + Dev-Group
uv sync

# Upstream-Verbindung testen
SONARQUBE_MCP_URL=http://192.168.0.2:8765/mcp uv run mcp-sonarqube-proxy check

# Server lokal starten (stdio — z.B. via MCP Inspector)
SONARQUBE_MCP_URL=http://192.168.0.2:8765/mcp uv run mcp-sonarqube-proxy serve

# Tests
uv run pytest

# Lint / Format
uv run ruff check .
uv run ruff format .

Installation als Tool

uv tool install git+https://gitea.gecheckt.de/marcus/mcp-sonarqube-proxy.git

Bekannte offene Punkte

  • Reconnect bei Verbindungsabbruch: Wenn der Upstream waehrend serve weggeht, scheitern nachfolgende Tool-Calls bis zum Neustart des Proxies. Eine Reconnect-Logik mit Retry on anyio.ClosedResourceError/httpx.ReadError waere ein sinnvoller naechster Schritt, ist fuer v0.1 aber bewusst weggelassen.
  • Concurrent Tool-Calls: Die ClientSession korreliert Antworten ueber Request-IDs und sollte parallele Calls verkraften. Nicht explizit getestet.
  • SDK-Versionssprung: Auf main der python-sdk wurden die Decorators durch Konstruktor-Kwargs ersetzt. Bei Migration auf eine neue Major-Version muss build_proxy_server() angepasst werden.