Skip to main content
Glama
MaxPopov
by MaxPopov

wikijs-mcp-google-auth

MCP-слой поверх существующей Wiki.js 2.5.x: корпоративный пользователь входит через Google Workspace и работает с вики через LLM (claude.ai, Claude Desktop, любой MCP-клиент) — строго в рамках своих прав Wiki.js.

Главный принцип: Wiki.js — единственный источник правды для авторизации. У MCP-сервера нет собственных пользователей/групп/прав и нет глобального API-ключа. Каждая операция выполняется под собственным JWT конкретного пользователя Wiki.js, и именно Wiki.js решает, разрешить или запретить (Группы / Права / Правила страниц).

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)

Компоненты

Каталог

Что это

packages/wikijs-auth-module/

Кастомный модуль аутентификации для Wiki.js 2.5.x — проверяет RS256-утверждения, подписанные MCP-сервером, и возвращает нативный JWT Wiki.js (подробнее)

packages/mcp-server/

Удалённый MCP-сервер (Streamable HTTP): сервер авторизации OAuth 2.1 для MCP-клиентов поверх Google OIDC + брокер токенов + инструменты

packages/e2e-ui/

Только для тестов: браузерный UI e2e (Playwright) и автономный эмулятор фейкового Google IdP

deploy/docker-compose.dev.yml

Изолированный тестовый стенд (Wiki.js 2.5.303 + Postgres + ACL seed) — только для разработки/CI

deploy/docker-compose.e2e.yml

Полный стек UI e2e (фейковый IdP + Wiki.js + MCP + Playwright) — только для тестов

deploy/docker-compose.prod.yml

Продакшен-развёртывание: только MCP-сервер, указывающий на вашу существующую Wiki.js

deploy/seed/run.mjs

Точка входа для сидинга тестового стенда (seed.mjs — библиотека)

Related MCP server: wikijs-mcp

Как это работает

  1. MCP-клиент подключается к https://mcp.agnostic.comparison/mcp и выполняет OAuth 2.1 (динамическая регистрация клиентов + PKCE). Google не поддерживает DCR, поэтому MCP-сервер сам является сервером авторизации для клиентов, а Google используется только для проверки личности человека. Токены Google никогда не покидают сервер; клиенты получают собственные непрозрачные (opaque) токены MCP-сервера. После входа через Google пользователь видит экран согласия, где указаны приложение и его redirect URI — это защита от «перепорученного посредника» (confused deputy), чтобы сторонний зарегистрированный клиент не мог получить токен пользователя без его ведома; согласие запоминается для каждого пользователя и клиента.

  2. Проверяется id_token от Google (подпись, iss, aud, email_verified, hd = ваш домен Workspace).

  3. Брокер токенов MCP-сервера обменивает идентичность Google на нативный JWT Wiki.js: он подписывает короткоживущее RS256-утверждение (TTL 60 секунд, уникальный jti) и вызывает стандартную GraphQL-мутацию authentication.login со стратегией mcpdelegation. Модуль Wiki.js проверяет утверждение, находит пользователя по почте и возвращает JWT через стандартный поток refreshToken(). JWT кэшируется и обновляется до истечения срока действия.

  4. Каждый вызов инструмента идёт в GraphQL Wiki.js с Authorization: Bearer <JWT пользователя>. Страницу, доступ к которой запрещён, невозможно прочитать или изменить, и она не появляется в поисковой выдаче или списках — это подтверждено e2e-тестами (матрица разрешить/запретить для двух пользователей в разных группах).

Инструменты

Инструмент

Описание

whoami

Личность пользователя + его группы и права в Wiki.js (диагностика доступа)

search_wiki

Полнотекстовый поиск; результаты фильтруются по правам пользователя

get_page

Страница по id или пути (метаданные + полный markdown)

list_many

Страницы, видимые пользователю (фильтр по префиксу пути)

create_page

Создать страницу (markdown)

update_page

Обновление: чтение-слияние-запись, неуказанные поля сохраняются

delete_page

Удаление (необратимо, Wiki.js требует delete:pages)


Интеграция с вашей Wiki.js: пошагово

Вам понадобится: Wiki.js 2.5.x (тестировано на 2.5.303) с доступом к её файловой системе / Docker-конфигурации; хост для MCP-сервера с публичным HTTPS-эндпоинтом; доступ администратора к Google Cloud Console вашего Workspace.

Шаг 1. Установите проверочный модуль в Wiki.js

Docker: добавьте volume в сервис wiki и перезапустите контейнер:

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

(содержимое packages/wikijs-auth-module/ из этого репозитория помещается в /opt/wikijs-mcp/wikijs-auth-module; имя целевого каталога должно быть точно mcpdelegation)

Bare metal: скопируйте packages/wikijs-auth-module/ в <wiki>/server/modules/authentication/mcpdelegation/ и перезапустите Wiki.js.

После настройки (шаг 3) в логе Wiki.js будет видно:
Authentication Strategy MCP Delegation: [ OK ].

Шаг 2. Сгенерируйте ключи для утверждений (assertion)

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

Приватный ключ (mcp-assertion-key.pem) остаётся только на хосте MCP-сервера. На следующем шаге публичный ключ добавляется в Wiki.js.

Шаг 3. Настройте стратегию в панели администратора Wiki.js

Администрирование → Auth → Add Strategy → MCP Delegation:

  • Assertion Public Key (PEM) — содержимое файла mcp-assertion-key.pub.pem;

  • Expected Audience / Issuer — оставьте значения по умолчанию (urn:wikijs:mcp-delegation / urn:wikijs-mcp-google-auth);

  • User Lookup Provider Priority — порядок провайдеров для поиска пользователя по email. Если ваши пользователи входят через Google/OIDC, поставьте этого провайдера первым (принимаются и ключи модулей: google, oidc, local);

  • (необязательно) Self-registration + белый список доменов + автозачисление в указанные группы — чтобы новые пользователи Workspace автоматически создавались при первом запросе через MCP;

  • Сохраните.

Ключ стратегии отображается в списке (это WIKIJS_STRATEGY_KEY для MCP-сервера; если вы создавали стратегию вручную через UI, Wiki.js генерирует UUID — скопируйте его).

Учётные записи с включённой TFA нельзя использовать через делегирование — MCP-сервер вернёт понятную ошибку.

Шаг 4. Создайте Google OAuth-клиент

Google Cloud Console → APIs & Services → Credentials → Create credentials → Create OAuth client ID:

  • Тип приложения: Web application;

  • Authorized redirect URI: https://mcp.company.com/oauth/google/callback (то есть ваш PUBLIC_URL + /oauth/google/callback);

  • Экран согласия OAuth: тип Internal (только ваш Workspace).

Сохраните Client ID и Client Secret.

Шаг 5. Разверните MCP-сервер

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

Контейнер работает под непривилегированным пользователем node — смонтированный файл ключа должен быть доступен ему для чтения (chmod 644); сам приватный ключ при этом защищён правами хостовой директории keys/.

Переменные .env:

Переменная

Значение

MCP_IMAGE

Тегированный образ (workflow Release on main автоматически публикует ghcr.io/<owner>/wikijs-mcp-server:vX.Y.Z, когда пушится изменение версии и вливаетесь в main, или соберите локально: docker build -f packages/mcp-server/Dockerfile -t wikijs-mcp-server:local .)

PUBLIC_URL

Публичный HTTPS URL MCP-сервера

WIKIJS_URL

URL вашей Wiki.js (внутренний предпочтителен)

WIKIJS_STRATEGY_KEY

Описание стратегии из шага 3 (mcpdelegation если вы дали такое имя)

GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET

Из шага 4

GOOGLE_ALLOWED_DOMAIN

Домен вашего Workspace, например company.com — аккаунты вне домена отклоняются

Поставьте TLS-реверс-прокси перед портом 8000. Минимальный конфиг 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
  }
}

Проверка: curl https://mcp.company.com/healthz → {"ok":true};
curl https://mcp.company.com/.well-known/oauth-authorization-server → метаданные OAuth.

Шаг 6. Подключение клиентов

claude.ai (Team/Enterprise): Settings → Connectors → Add custom connector → URL: https://mcp.company.com/mcp. При первом использовании клиент выполнит OAuth: регистрация клиента → вход в Google → готово.

Claude Desktop: Settings → Connectors → Add custom connector с тем же URL (или через mcp-remote для старых версий).

MCP Inspector (диагностика):
npx @modelcontextprotocol/inspector → Transport: Streamable HTTP → URL https://mcp.company.com/mcp → Open Auth → пройдите поток.

Шаг 7. Проверка

В чате LLM:

  1. «Кто я в вики?» — инструмент whoami должен показать вашу почту, группы и права из Wiki.js.

  2. Попросите найти/открыть страницу, к которой у вас есть доступ, — OK.

  3. Запросите страницу, на которую нет прав, — получите явный отказ («Wiki.js denied this operation…»), и эта страница также отсутствует в результатах поиска/списка.


Локальная разработка

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

Тестовый стенд: admin@example.com/admin1234!, john@example.com (Engineering, без доступа к /management/*), kate@example.com (Management). Каждый внешний запрос (PR) запускает быстрые проверки (CI: lint + unit + build); тяжёлый docker e2e (e2e) и браузерные ui-e2e выполняются только на пушах в dev/main (т.е. перед merge), чтобы не замедлять итерации в PR.

Браузерный UI e2e (Playwright) под роли

Отдельный docker-стек deploy/docker-compose.e2e.yml поднимает эмулятор Google IdP (packages/e2e-ui/idpanton/ — страница входа с выбором роли вместо реального Google), Wiki.js, MCP-сервер и Playwright runner, который прогоняет весь браузерный OAuth+consent flow под разными ролями (John/Kate/out-of-domain). Эмулятор и Playwright поднимаются только в этом e2e-стеке — они никогда не попадают в prod/dev образы.

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

Проверяется: вход под ролью → на экране согласия указывается клиент → подтверждение → whoami и страницы в рамках роли (John не видит management/*, Kate видит); отказ → access_denied; аккаунт вне домена отклоняется до экрана согласия. Отдельный CI-воркфлоу (ui-e2e) делает это при пушах в dev/main.

Ручной запуск MCP-сервера для стенда:

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

Релизы

Релизы создаются автоматически. Обновите version в корневом package.json в dev, откройте PR из dev в main и смёржите его. Затем воркфлоу Release on main при пуше в main собирает и публикует ghcr.io/<owner>/wikijs-mcp-server:vX.Y.Z (а также :latest), создаёт git-тег vX.Y.Z и GitHub Release — всё в один запуск, используя только встроенный GITHUB_TOKEN (никакие PAT/секреты настраивать не нужно). Если версия не изменилась, запуск ничего не делает, поэтому обычные merge в main не создают релизов.

Разовые настройки репозитория для этого: Settings → Actions → General → Workflow permissions = Read and write permissions; а если вы защищаете теги ruleset-ом, разрешите GitHub Actions создавать теги v*.

Заметки по безопасности

  • Assertion: RS256, TTL 60 сек, уникальный jti, защита от повторного воспроизведения; приватный ключ живёт только на MCP-сервере. Компрометация ключа означает возможность войти под любым пользователем wiki — относитесь к нему как к корневому секрету и ротируйте его (новая пара ключей + обновление публичного ключа в стратегии).

  • Google identity: канонический идентификатор — iss+sub; email используется для поиска. Домен hd проверяется по подписанному id_token, а не по параметрам.

  • Защита от «запутанного посредника»: перед выдачей Authorization Code пользователь проходит через экран согласия для каждого клиента (может быть отключён через requireConsent только для доверенного собственного клиента). Это предотвращает ситуацию, когда атакующий, зарегистрировавший собственный OAuth-клиент через DCR, незаметно получает токен жертвы.

  • Rate limit Wiki.js: authentication.login — 5 вызовов в минуту на IP, и все делегированные входы исходят с IP MCP-сервера. Брокер кэширует JWT (по умолчанию 30 минут) и при достижении лимита ждёт и повторяет, поэтому в обычной работе этого незаметно; при массовом подключении пользователей возможны задержки до минуты.

  • Отзыв: стандартный OAuth /revoke (на каждый токен); деактивация пользователя в Wiki.js разрывает делегирование при следующем обновлении JWT (≤30 мин); удаление SESSION_STORE_FILE и перезапуск MCP-сервера завершают все сессии сразу.

  • Аудит: каждый вызов инструмента логируется в структурированном виде (кто, какой инструмент, ок/отказ) без содержимого страниц.

  • MCP-эндпоинт: только Bearer-token, 120 запросов в минуту на токен, защитные заголовки; OAuth-эндпоинты защищены встроенным rate-лимитом SDK.

Ограничения

  • RAG/семантический поиск — отдельный будущий сервис. Точка подключения уже готова: search_wiki работает через интерфейс SearchBackend (no — v1 = нативный поиск Wiki.js; RAG-сервис будет получать JWT пользователя Wiki.js и сохранит модель ACL). См. docs/rag-integration.md.

  • Один экземпляр MCP-сервера (FileStore + replay-кэш в памяти). Для высокой доступности требуется общее хранилище (Redis) — интерфейс KVStore уже выделен.

  • В Wiki.js 3.x другой механизм авторизации — модуль предназначен для 2.5.x.

Лицензия

Apache-2.0

Related MCP Connectors

Related MCP Servers