273 lines
7.9 KiB
Markdown
273 lines
7.9 KiB
Markdown
# 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
|