Skip to main content
Glama
MaxPopov
by MaxPopov

wikijs-mcp-google-auth

Ein MCP-Layer auf Basis einer bestehenden Wiki.js 2.5.x: Ein Unternehmensbenutzer meldet sich mit Google Workspace an und arbeitet über ein LLM (claude.ai, Claude Desktop, beliebiger MCP-Client) mit dem Wiki — streng innerhalb seiner eigenen Wiki.js-Berechtigungen.

Kernprinzip: Wiki.js ist die einzige Quelle der Wahrheit für die Autorisierung. Der MCP-Server hat keine eigenen Benutzer/Gruppen/Berechtigungen und keinen globalen API-Schlüssel. Jede Operation läuft unter dem nativen Wiki.js-JWT des jeweiligen Benutzers, und Wiki.js selbst entscheidet über Erlauben/Verweigern (Gruppen / Berechtigungen / Seitenregeln).

Google Workspace ──OAuth/OIDC──▶ MCP Server ──signed assertion──▶ Wiki.js
                                     │         auth module "mcpdelegation"
                                     │         → refreshToken() → native JWT
                                     │
 MCP client (claude.ai / Desktop) ◀──┴── tools: search / get / list /
                                          create / update / delete / whoami
                                          (all via GraphQL with the user's JWT)

Komponenten

Verzeichnis

Beschreibung

packages/wikijs-auth-module/

Eigenes Authentifizierungsmodul für Wiki.js 2.5.x — prüft RS256-Assertions, die vom MCP-Server signiert wurden, und gibt ein natives Wiki.js-JWT zurück (Details)

packages/mcp-server/

Entfernter MCP-Server (Streamable HTTP): ein OAuth-2.1-Autorisierungsserver für MCP-Clients auf Basis von Google OIDC + Token-Broker + Tools

packages/e2e-ui/

Nur für Tests: Browser-UI-E2E (Playwright) und ein eigenständiger Fake-Google-IdP-Emulator

deploy/docker-compose.dev.yml

Isolierter Teststand (Wiki.js 2.5.303 + Postgres + ACL-Seed) — nur für Entwicklung/CI

deploy/docker-compose.e2e.yml

Vollständiger UI-E2E-Stack (Fake-IdP + Wiki.js + MCP + Playwright) — nur für Tests

deploy/docker-compose.prod.yml

Produktions-Deployment: nur der MCP-Server, der auf Ihre bestehende Wiki.js zeigt

deploy/seed/run.mjs

Einstiegspunkt zum Befüllen des Teststands (seed.mjs ist die Bibliothek)

Related MCP server: wikijs-mcp

So funktioniert es

  1. Der MCP-Client verbindet sich mit https://mcp.company.com/mcp und führt OAuth-Lauf 2.1 durch (Dynamic Client Registration + PKCE). Google unterstützt kein DCR, daher ist der MCP-Server selbst der Autorisierungsserver für die Clients, und Google wird nur zur Authentifizierung des Menschen verwendet. Google-Tokens verlassen den Server nie; die Clients erhalten die eigenen opaken Tokens des MCP-Servers. Nach dem Google-Login sieht der Benutzer einen Zustimmungsbildschirm, der die Anwendung und ihre Redirect-URI nennt — eine Verteidigungsmaßnahme gegen das Confused-Deputy-Problem (damit ein registrierter Drittanbieter-Client den Token des Benutzers nicht ohne dessen Wissen erhalten kann); die Zustimmung wird pro Benutzer und pro Client gespeichert.

  2. Das id_token von Google wird verifiziert (Signatur, iss, aud, email_verified, hd = Ihre Workspace-Domain).

  3. Der Token-Broker des MCP-Servers tauscht die Google-Identität gegen ein Wiki-JWT: Er signiert eine kurzlebige RS256-Assertion (TTL 60 s, eindeutiges jti) und ruft mit der Standardmutation GraphQL authentication.login die Strategie mcpdelegation auf. Das Wiki.js-Modul prüft die Assertion, ermittelt den Benutzer anhand der E-Mail und gibt über den Standard-Flow refreshToken() ein JWT zurück. Das JWT wird gecacht und vor seinem Ablauf erneuert.

  4. Jeder Tool-Aufruf erfolgt mit Authorization: Bearer <user's JWT> an die Wiki.js-GraphQL-API. Eine nicht erlaubte Seite kann weder gelesen noch verändert werden und erscheint nicht in der Suche oder in Listen — durch E2E-Tests verifiziert (Erlaubt/Verweigert-Matrix für zwei Benutzer in verschiedenen Gruppen).

Tools

Tool

Beschreibung

whoami

Die Identität des Benutzers + dessen Wiki.js-Gruppen und -Berechtigungen (Zugriffsdiagnose)

search_wiki

Volltextsuche; Ergebnisse werden anhand der Benutzerrechte gefiltert

get_page

Eine Seite per ID oder geben Sie den Pfad (Metadaten + vollständiges Markdown)

list_pages

Für den Benutzer sichtbare Seiten (Filterung nach Pfad-Präfix)

create_page

Seite erstellen (Markdown)

update_page

Aktualisieren: Lesen-Zusammenführen-Schreiben; nicht angegebene Felder bleiben erhalten

delete_page

Löschen (destruktiv; Wiki.js erzwingt delete:pages)


Integration in Ihre eigene Wiki.js: Schritt für Schritt

Sie benötigen: Wiki.js 2.5.x (getestet mit 2.5.303) mit Zugriff auf das Dateisystem bzw. die Docker-Konfiguration; einen Host für den MCP-Server mit einem öffentlichen HTTPS-Endpunkt; Administratorzugriff auf die Google Cloud Console Ihres Workspace.

Schritt 1: Auth-Modul in Wiki.js installieren

Docker: Fügen Sie dem Wiki-Dienst ein Volume hinzu und starten Sie den Container neu:

services:
  wiki:
    image: ghcr.io/requarks/wiki:2.5.303
    volumes:
      - /opt/wikijs-mcp/wikijs-auth-module:/wiki/server/modules/authentication/mcpdelegation:ro

(Der Inhalt von packages/wikijs-auth-module/ aus diesem Repository wird nach /opt/wikijs-mcp/wikijs-auth-module gelegt; der Zielordnername muss exakt mcpdelegation lauten.)

Bare Metal: Kopieren Sie packages/wikijs-auth-module/ nach <wiki>/server/modules/authentication/mcpdelegation/ und starten Sie Wiki.js neu.

Nach der Konfiguration (Schritt 3) erscheint im Wiki.js-Log: Authentication Strategy MCP Delegation: [ OK ].

Schritt 2: Assert-Schlüssel generieren

openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out mcp-assertion-key.pem
openssl pkey -in mcp-assertion-key.pem -pubout -out mcp-assertion-key.pub.pem

Der private Schlüssel (mcp-assertion-key.pem) bleibt nur auf dem MCP-Server-Host. Der öffentliche Schlüssel wird im nächsten Schritt in Wiki.js eingetragen.

Schritt 3: Strategie in der Wiki.js-Administration konfigurieren

Administration → Auth → Strategie hinzufügen → MCP-Delegation:

  • Assertion Public Key (PEM) — den Inhalt von mcp-assertion-key.pub.pem;

  • Expected Audience / Issuer — die Standardwerte beibehalten (urn:wikijs:mcp-delegation / urn:wikijs-mcp-google-auth);

  • User Lookup Provider Priority — die Reihenfolge der Provider, über die der Benutzer anhand seiner E-Mail gefunden wird. Wenn Ihre Leute über Google/OIDC im Wiki anmelden, diesen Provider an die erste Stelle setzen (Modul-Keys sind ebenfalls zulässig: google, oidc, local);

  • (optional) Self-Registration + Domain-Whitelist + Auto-Enrollment-Gruppen — damit neue Workspace-Nutzer bei ihrer ersten MCP-Anfrage automatisch angelegt werden;

  • Speichern.

Der Strategie-Instanzschlüssel wird in der Liste angezeigt (dies ist für den MCP-Server der WIKIJS_STRATEGY_KEY; wenn Sie die Strategie manuell über die Oberfläche erstellt haben, erzeugt Wiki.js eine UUID — kopieren Sie diese).

Konten mit aktiviertem TFA können nicht über Delegation verwendet werden — der MCP-Server liefert eine klare Fehlermeldung.

Schritt 4: Google-OAuth-Client erstellen

Google Cloud Console → APIs und Dienste → Anmeldedaten → Anmeldedaten erstellen → OAuth-Client-ID.

  • Anwendungstyp: Webanwendung;

  • Autorisierte Weiterleitungs-URI: https://mcp.company.com/oauth/google/callback (Ihre PUBLIC_URL + /oauth/google/callback);

  • OAuth-Zustimmungsbildschirm: Typ Intern (nur Ihr Workspace).

Speichern Sie die Client-ID und das Client-Secret.

Schritt 5: MCP-Server bereitstellen

cd deploy
cp .env.example .env        # fill in the values
mkdir -p keys && cp /path/to/mcp-assertion-key.pem keys/
chmod 644 keys/mcp-assertion-key.pem   # the container runs as non-root node (uid 1000)
docker compose -f docker-compose.prod.yml up -d

Der Container läuft als Nicht-Root-Benutzer node — die eingehängte Schlüsseldatei muss für ihn lesbar sein (chmod 644); der private Schlüssel selbst bleibt durch die Berechtigungen des Host-Verzeichnisses keys/ geschützt.

.env-Variablen:

Variable

Wert

MCP_IMAGE

Getagtes Image (der Workflow Release on main veröffentlicht automatisch ghcr.io/<owner>/wikijs-mcp-server:vX.Y.Z, wenn ein Versionsbump nach main gemergt wird; oder lokal bauen: docker build -f packages/mcp-server/Dockerfile -t wikijs-mcp-server:local .)

PUBLIC_URL

Die öffentliche HTTPS-URL des MCP-Servers

WIKIJS_URL

Die URL Ihrer Wiki.js (intern bevorzugt)

WIKIJS_STRATEGY_KEY

Der Strategie-Instanzschlüssel aus Schritt 3 (mcpdelegation, falls Sie ihn so benannt haben)

GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET

Aus Schritt 4

GOOGLE_ALLOWED_DOMAIN

Ihre Workspace-Domain, z. B. company.com — Konten außerhalb werden abgelehnt

Stellen Sie einen TLS-Reverse-Proxy vor Port 8000. Minimal nginx:

server {
  listen 443 ssl http2;
  server_name mcp.company.com;
  # ssl_certificate ...; ssl_certificate_key ...;
  location / {
    proxy_pass http://127.0.0.1:8000;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header Host $host;
    proxy_buffering off;          # streamable HTTP
  }
}

Prüfen: curl https://mcp.company.com/healthz → {"ok":true}; curl https://mcp.company.com/.well-known/oauth-authorization-server → OAuth-Metadaten.

Schritt 6: Clients verbinden

claude.ai (Team/Enterprise): Einstellungen → Connectors → Benutzerdefinierten Connector hinzufügen → URL: https://mcp.company.com/mcp. Beim ersten Einsatz macht der Client folgende Aktionen: Client-Registrierung → Google-Login --> fertig.

Claude Desktop: Einstellungen → Connectors → Benutzerdefinierten Connector hinzufügen (mit derselben URL) (oder über mcp-remote für ältere Versionen).

MCP Inspector (Diagnose): npx @modelcontextprotocol/inspector → Transport: Streamable HTTP → URL https://mcp.company.com/mcp → Open Auth → Flow durchspielen.

Schritt 7: Prüfen

Im LLM-Chat:

  1. „Who am I in Wiki?“ → whoami sollte Ihre E-Mail, Gruppen und Berechtigungen aus Wiki-js zeigen.

  2. Bitten Sie danach, eine Seite zu finden/zu öffnen, auf die Sie Zugriff haben → OK.

  3. Bitten Sie eine Seite an, für die Sie keine Rechte haben → klare Ablehnung („Wiki.js hat diesen Vorgang abgelehnt …“), und diese Seite fehlt auch nicht in Suche und Anzeige.


Lokale Entwicklung

npm ci
npm run stand:up      # Wiki.js 2.5.303 + Postgres (docker)
npm run stand:seed    # finalize + groups/users/pages + strategy + dev keys
npm test              # unit tests (auth module + OAuth provider)
npm run build && npm run e2e   # in-process e2e: delegation, OAuth, tools — against a live stand
npm run stand:down

Teststand: admin@example.com/admin1234!, john@example.com (Engineering, kein Zugriff auf /admin), weil kate@example.com (Management). Jeder PR läuft durch die schnellen Checkpins (CI: Lint + Unit + Build); das schwere Docker-E2E (e2e) und das Browser-ui-e2e laufen nur bei Pushes auf dev/main (also vor dem Merge), damit PR-Iterationen nicht verlangsamt werden.

Browser-UI-E2E (Playwright) mit Rollen

Ein separater Docker-Stack deploy/docker-compose.e2e.yml bringt einen Fake-Google-IdP-Emulator (packages/e2e-ui/idp/ — eine Anmeldeseite mit Rollenauswahl statt echtem Google), Wiki.js, den MCP-Server und einen Playwright-Runner hoch, der den gesamten Browser-OAuth-/Consent-Flow unter verschiedenen Rollen durchläuft (John/Kate/outside-Domain). Emulator und Playwright kommen nur in diesem E2E-Stack vor — sie landen nie in den Produktions-/Dev-Images.

C=deploy/docker-compose.e2e.yml
docker compose -f $C build mcp
docker compose -f $C up -d db wiki idp   # no --wait on wiki: the seed script is the readiness gate
docker compose -f $C run --rm seed
docker compose -f $C up -d --wait mcp
docker compose -f $C run --rm playwright     # exit code = test result
docker compose -f $C down -v

Es prüft: Anmeldung als Rolle → der Zustimmungsbildschirm nennt den Client → Genehmigen → whoami und die auf die Rolle eingeschränkten Seiten (John kann management/* nicht sehen, Kate schon); Ablehnen → access_denied; ein Konto außerhalb der Domäne wird vor der Zustimmung abgewiesen. Ein separater CI-Workflow (ui-e2e) erledigt das bei Pushes auf dev/main.

Den MCP-Server manuell gegen den Teststand ausführen:

PUBLIC_URL=http://localhost:8000 \
WIKIJS_URL=http://127.0.0.1:3000 \
MCP_ASSERTION_PRIVATE_KEY_FILE=deploy/keys/mcp-assertion-key.pem \
GOOGLE_CLIENT_ID=... GOOGLE_CLIENT_SECRET=... GOOGLE_ALLOWED_DOMAIN=example.com \
npm run dev -w @wikijs-mcp/server

Releases

Releases erfolgen automatisch. Erhöhe die version in der Root-package.json auf dev, öffne einen dev → main-PR und merge ihn. Der Workflow Release on main baut und pusht bei dem beiden Push auf main das Image ghcr.io/<owner>/wikijs-mcp-server:vX.Y.Z (+ :latest) und erstellt den Git-Tag vX.Y.Z sowie ein GitHub-Release – alles in einem einzigen Workflow-Lauf, und zwar nur mit dem eingebauten GITHUB_TOKEN (kein PAT/Secret zu konfigurieren). Wenn die Version unverändert ist, ist der Lauf ein No-op, sodass gewöhnliche Merges auf main keine Releases erzeugen.

Einmalige Repository-Einstellungen dafür: Settings → Actions → General → Workflow permissions = Read and write permissions; und falls du Tags mit einem Ruleset schützst, erlaube GitHub Actions, v*-Tags zu erstellen.

Sicherheitshinweise

  • Assertion: RS256, TTL 60 s, eindeutige jti, Replay-Schutz; der private Schlüssel liegt ausschließlich auf dem MCP-Server. Ein kompromittierter Schlüssel bedeutet, dass man sich als beliebiger Wiki-Benutzer anmelden kann – behandle ihn also wie ein Root-Geheimnis und rotiere ihn (neues Schlüsselpaar erzeugen und den öffentlichen Schlüssel in der der Strategie aktualisieren).

  • Google-Identität: Der kanonische Identifikator ist iss+sub; die E-Mail dient zur Nachschlagen. Die hd-Domäne wird aus dem signierten id_token verifiziert, nicht aus Parametern.

  • Confused-Deputy-Schutz: Bevor ein Autorisierungscode ausgegeben wird, muss der Benutzer einen eine, auf den jeweiligen Client zugeschnittenen Zustimmungsbildschirm durchlaufen (kann nur für ein vertrauenswürdiges First-Party-Szenario über requireConsent deaktiviert werden). Das verhindert, dass ein Angreifer, der über DCR eine eigenen eigenen OAuth-Client registriert hat, unbemerkt an das Token des Opfers gelangt.

  • Wiki.js-Ratenlimit: authentication.login ist pro IP auf 5 Aufrufe/min begrenzt, und alle Delegations-Logins kommen von der IP des MCP-Servers. Der Broker, cachen JWTs (standardmäßig 30 min) und wiederholt bei Erreichen des Limits, sodass das im normalen Betrieb unsichtbar ist; verzögertungen von bis zu einer Minute sind möglich.

  • Widerruf: Standard-OAuth /revoke (pro Token); eine Deaktivierung eines Benutzers in Wiki.js unterbricht die Delegation beim nächste JWT-Refresh (≤30 min); das Löschen der Datei SESSION_STORE_FILE + Neustart des MCP-Servers beendet alle Sessions auf der einmal.

  • Audit: Jeder Tool-Aufruf wird strukturiert protokolliert (wer, welches Tool, ok/denied) – ohne Seiteninhalt.

  • MCP-Endpunkt: bearer-only, 120 requests/min pro Token, Security-Header; die OAuth-Endpunkte werden durch die eingebaute Ratenbegrenzung des SDK geschützt.

Einschränkungen und Pläne

  • RAG/semantische Suche ist ein separater zukünftiger Dienst. Der Anschlusspunkt ist bereit: search_wiki funktioniert über die SearchBackend-Schnittstelle (src/search/ – v1 = native Wiki.js-Suche; der RAG-Dienst erhält das Wiki.js-JWT des Benutzers und bewahrt dadurch das ACL-Modell). Siehe docs/rag-integration.md.

  • Nur eine MCP-Serverinstanz (FileStore + In-Memory-Replay-Cache). HA erfordert einen gemeinsamen Store (Redis) – die KVStore-Schnittstelle ist bereits extrahiert.

  • Wiki.js 3.x verwendet unterschiedlichen Authentifizierungsmechanismus – das Modul zählt auf 2.5.x.

Lizenz

Apache-2.0

Related MCP Connectors

Related MCP Servers