Files
homelab/docs/08-omada-mcp.md

273 lines
7.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 08 · TPLink Omada MCP Server
**Erstellt:** 2026-03-18
**Aktualisiert:** 2026-03-18 — Credentials rotiert (CLIENT_ID/SECRET), Service auf NodePort 31777 umgestellt
**Namespace:** `omada-mcp`
**Image:** `jmtvms/tplink-omada-mcp:latest`
**Status:** Running · Pod `omada-mcp-dfdbfbcf8-x6t5k` · NodePort `31777` → Container `3000`
---
## Übersicht
Der [tplink-omada-mcp](https://github.com/jmtvms/tplink-omada-mcp) Server stellt eine MCP-Schnittstelle (Model Context Protocol) für den TPLink Omada Controller bereit. Claude und andere KI-Assistenten können darüber das Netzwerk verwalten (Clients, SSIDs, VLANs, Geräte usw.).
Der Server läuft im HTTP-Modus auf Port **3000** (`MCP_SERVER_USE_HTTP=true`).
---
## Dateien
```
homelab/k8s/omada-mcp/
├── kustomization.yaml # Kustomize-Einstiegspunkt
├── namespace.yaml # Namespace omada-mcp
├── secret.yaml # Credentials (Platzhalter vor Anwenden befüllen!)
├── deployment.yaml # Deployment (1 Replica)
└── service.yaml # NodePort Service Port 3000 → NodePort 31777
```
---
## Credentials (Secret)
Das Secret `omada-mcp-credentials` enthält folgende Schlüssel:
| Key | Beschreibung |
|-----|-------------|
| `OMADA_BASE_URL` | URL des Omada Controllers, z. B. `https://192.168.11.29` |
| `OMADA_CLIENT_ID` | OAuth Client-ID aus dem Omada Controller |
| `OMADA_CLIENT_SECRET` | OAuth Client-Secret |
| `OMADA_OMADAC_ID` | Omada-Controller-ID (zu finden in den Controller-Einstellungen) |
| `OMADA_STRICT_SSL` | `false` deaktiviert SSL-Verifikation (Self-signed Certs) |
| `MCP_SERVER_USE_HTTP` | `true` aktiviert HTTP-Modus |
| `MCP_HTTP_BIND_ADDR` | `0.0.0.0` lauscht auf allen Interfaces |
### Secret anlegen (empfohlen kein base64 nötig)
```bash
kubectl create secret generic omada-mcp-credentials \
--namespace omada-mcp \
--from-literal=OMADA_BASE_URL='https://192.168.11.29' \
--from-literal=OMADA_CLIENT_ID='dein-client-id' \
--from-literal=OMADA_CLIENT_SECRET='dein-client-secret' \
--from-literal=OMADA_OMADAC_ID='dein-omadac-id' \
--from-literal=OMADA_STRICT_SSL='false' \
--from-literal=MCP_SERVER_USE_HTTP='true' \
--from-literal=MCP_HTTP_BIND_ADDR='0.0.0.0'
```
Danach `secret.yaml` **nicht** anwenden (das Secret existiert bereits).
### Alternativ: secret.yaml befüllen
```bash
# Werte base64-kodieren
echo -n 'https://omada.example.com' | base64
echo -n 'dein-client-id' | base64
echo -n 'dein-client-secret' | base64
echo -n 'dein-omadac-id' | base64
```
Die Ausgaben in `secret.yaml` bei den `REPLACE_WITH_*`-Platzhaltern eintragen.
---
## Deployment
### Voraussetzungen
- Namespace existiert (wird durch `namespace.yaml` erstellt)
- Secret ist befüllt (siehe oben)
### Anwenden mit Kustomize
```bash
# Dry-run Manifeste prüfen
kubectl apply -k homelab/k8s/omada-mcp/ --dry-run=client
# Anwenden
kubectl apply -k homelab/k8s/omada-mcp/
```
### Einzelne Manifeste anwenden
```bash
kubectl apply -f homelab/k8s/omada-mcp/namespace.yaml
kubectl apply -f homelab/k8s/omada-mcp/secret.yaml # nur wenn nicht per kubectl create
kubectl apply -f homelab/k8s/omada-mcp/deployment.yaml
kubectl apply -f homelab/k8s/omada-mcp/service.yaml
```
---
## Umgebungsvariablen im Container
| Variable | Wert | Quelle |
|----------|------|--------|
| `OMADA_BASE_URL` | `https://192.168.11.29` | Secret `omada-mcp-credentials` |
| `OMADA_CLIENT_ID` | aus Secret | `omada-mcp-credentials` |
| `OMADA_CLIENT_SECRET` | aus Secret | `omada-mcp-credentials` |
| `OMADA_OMADAC_ID` | aus Secret | `omada-mcp-credentials` |
| `OMADA_STRICT_SSL` | `false` | `omada-mcp-credentials` |
| `MCP_SERVER_USE_HTTP` | `true` | `omada-mcp-credentials` |
| `MCP_HTTP_BIND_ADDR` | `0.0.0.0` | `omada-mcp-credentials` |
---
## Verifikation
```bash
# Pod-Status prüfen
kubectl get pods -n omada-mcp
# Logs anzeigen
kubectl logs -n omada-mcp deployment/omada-mcp
# Service prüfen
kubectl get svc -n omada-mcp
# Direkter Test via Port-Forward
kubectl port-forward -n omada-mcp svc/omada-mcp 3000:3000
curl http://localhost:3000/
```
### Erwartete Ausgabe (Pod läuft)
```
NAME READY STATUS RESTARTS AGE
omada-mcp-xxxxxxxxx-xxxxx 1/1 Running 0 1m
```
---
## MCP-Endpunkt in Claude Code einbinden
Sobald der Pod läuft, kann der MCP-Server per Port-Forward oder Ingress angebunden werden.
### Option A: NodePort (dauerhaft, direkt erreichbar)
Der Service ist auf NodePort **31777** auf allen Nodes erreichbar:
| Node | URL |
|------|-----|
| rnk-cp01 | `http://192.168.11.170:31777` |
| rnk-wrk01 | `http://192.168.11.171:31777` |
| rnk-wrk02 | `http://192.168.11.172:31777` |
Claude Code `.mcp.json`:
```json
{
"mcpServers": {
"omada": {
"type": "http",
"url": "http://192.168.11.171:31777/mcp"
}
}
}
```
### Option B: Port-Forward (lokal/temporär)
```bash
kubectl port-forward -n omada-mcp svc/omada-mcp 3000:3000 &
```
### Option B: Ingress (dauerhaft, mit Traefik)
Ingress-Manifest erstellen (optional):
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: omada-mcp
namespace: omada-mcp
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
cert-manager.io/cluster-issuer: letsencrypt-production
spec:
ingressClassName: traefik
tls:
- hosts:
- omada-mcp.homelab.local
secretName: omada-mcp-tls
rules:
- host: omada-mcp.homelab.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: omada-mcp
port:
number: 3000
```
---
## Ressourcen
- CPU: 50m Request / 200m Limit
- Memory: 64Mi Request / 256Mi Limit
---
## Troubleshooting
```bash
# Pod-Events anzeigen
kubectl describe pod -n omada-mcp -l app.kubernetes.io/name=omada-mcp
# Secret-Inhalte prüfen (base64-dekodiert)
kubectl get secret omada-mcp-credentials -n omada-mcp -o jsonpath='{.data.OMADA_BASE_URL}' | base64 -d
# Pod neu starten
kubectl rollout restart deployment/omada-mcp -n omada-mcp
```
### Häufige Fehler
| Fehler | Ursache | Lösung |
|--------|---------|--------|
| `ImagePullBackOff` | Image nicht erreichbar | Internet-Zugang der Nodes prüfen |
| `CrashLoopBackOff` | Falsche Credentials | Logs prüfen, Secret aktualisieren |
| `Pending` | Ressourcen fehlen | `kubectl describe pod` → Events |
| SSL-Fehler | Self-signed Cert | `OMADA_STRICT_SSL=false` bestätigen |
---
## Deployment-Geschichte
| Datum | Aktion | Ergebnis |
|-------|--------|---------|
| 2026-03-18 | Namespace, Secret, Deployment, Service erstellt | Pod in CrashLoopBackOff wegen falscher Probe-Pfade |
| 2026-03-18 | Liveness/Readiness Probe auf `/healthz` geändert | Pod `1/1 Running`, `/healthz``{"status":"ok"}` |
| 2026-03-18 | MCP Initialize-Request getestet | Server antwortet korrekt, Version 0.1.0 |
| 2026-03-18 | Service von ClusterIP auf NodePort 31777 umgestellt | Extern erreichbar auf allen Nodes |
| 2026-03-18 | Credentials rotiert (CLIENT_ID + CLIENT_SECRET) | Pod-Rollout erfolgreich, `1/1 Running` |
### Bekannte Besonderheit: Probe-Pfad
Der MCP-Server gibt auf `GET /` HTTP 404 zurück (MCP-Protokoll beantwortet nur POST mit korrekten Headers).
Der Health-Endpoint ist `/healthz``{"status":"ok"}`.
### MCP-Endpunkt aufrufen
```bash
curl -s -X POST http://192.168.11.171:31777/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}'
```
## Nächste Schritte
- [x] Credentials im Secret befüllen und anwenden
- [x] Pod-Status und Logs nach Deployment prüfen
- [ ] MCP-Server in Claude Code `.mcp.json` eintragen
- [ ] Optional: Ingress für dauerhaften Zugriff erstellen
- [ ] Optional: Horizontal Pod Autoscaler bei Bedarf hinzufügen