d76c16d9a7
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>
105 lines
4.3 KiB
Markdown
105 lines
4.3 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
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.
|