Skip to main content
Glama
wikijs-mcp-google-auth
by MaxPopov

wikijs-mcp-google-auth

在现有 Wiki.js 2.5.x 之上包一层 MCP:企业用户用 Google Workspace 登录,然后通过 LLM(claude.ai、Claude Desktop 或任意 MCP 客户端)操作 wiki —— 严格遵守该用户自己的 Wiki.js 权限范围。

核心原则:授权判断以 Wiki.js 为唯一事实来源。 MCP 服务器本身没有任何用户、组或权限,也没有全局 API 密钥。每个操作都以该用户自己的原生 Wiki.js JWT 执行,是允许还是拒绝(Groups / Permissions / Page Rules)完全由 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 定制的认证模块——验证 MCP 服务器签发的 RS256 断言,并返回原生 Wiki.js JWT(详情)

packages/mcp-server/

远程 MCP 服务器(Streamable HTTP):面向 MCP 客户端的 OAuth 2.1 授权服务器,底层基于 Google OIDC + 令牌代理 + 工具

packages/e2e-ui/

仅测试:浏览器 UI 端到端(Playwright)和一个独立的自研假 Google IdP 模拟器

deploy/docker-compose.dev.yml

隔离的测试环境(Wiki.js 2.5.303 + Postgres + ACL 种子数据)——仅用于开发/CI

deploy/docker-compose.e2e.yml

完整 UI 端到端测试栈(假 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.company.com/mcp,并执行 OAuth 2.1(动态客户端注册 + PKCE)。Google 不支持 DCR,因此 MCP 服务器本身充当客户端侧的授权服务器,而 Google 仅用于认证“人”。Google 的令牌永远不会离开服务器;客户端收到的是 MCP 服务器自己的不透明令牌。Google 登录完成后,用户会看到一个同意屏幕(consent screen),其中标明应用程序名称及其重定向 URI——这是针对“混淆代理”(confused deputy)问题的防御:第三方注册的客户端无法在用户不知情的情况下获取用户令牌;该批准按“用户 × 客户端”分别记忆。

  2. 验证 Google 的 id_token(验签、iss、aud、email_verified,以及 hd = 你的 Workspace 域名)。

  3. MCP 服务器的令牌代理(token broker)把 Google 身份交换成原生 Wiki.js JWT:它对一段短时有效的 RS256 断言(TTL 60 秒、jti 唯一)签名,然后以 mcpdelegation 策略调用标准 GraphQL mutation authentication.login。Wiki.js 模块验签断言、按邮箱解析用户,并通过标准 refreshToken() 流程返回 JWT。该 JWT 会被缓存,并在过期前续签。

  4. 每次工具调用都会携带 Authorization: Bearer <用户的 JWT> 访问 Wiki.js GraphQL。无权限的页面无法读取或修改,不会出现在搜索结果或列表中——已由端到端测试验证(两个分属不同组的用户构成的 allow / forbidden 矩阵)。

工具

工具

说明

whoami

当前用户的身份 + 其在 Wiki.js 中的组与权限(访问诊断)

search_wiki

全文搜索;结果按当前用户权限过滤

get_page

按 id 或路径获取页面(元数据 + 完整 markdown)

list_pages

列出当前用户可见的页面(支持按路径前缀过滤)

create_page

创建页面(markdown)

update_page

更新页面:读-合并-写,未指定的字段保持不变

delete_page

删除页面(破坏性操作;Wiki.js 会强制检查 delete:pages 权限)


集成到你的 Wiki.js:分步指南

你需要:Wiki.js 2.5.x(已在 2.5.303 上验证)、能访问其文件系统或 Docker 配置;一台具备公网 HTTPS 端点的 MCP 服务器主机;以及你的 Workspace 的 Google Cloud Console 管理员权限。

第 1 步:在 Wiki.js 中安装认证模块

Docker: 给 wiki 服务添加一个 volume,然后重启容器:

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)

裸机部署: 把 packages/wikijs-auth-module/ 复制到 <wiki>/server/modules/authentication/mcpdelegation/,然后重启 Wiki.js。

配置完成(第 3 步)后,Wiki.js 日志中会出现:

Authentication Strategy MCP Delegation: [ OK ]。

第 2 步:生成断言密钥

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 → 添加策略 → MCP Delegation:

  • 断言公钥(PEM) —— 填写 mcp-assertion-key.pub.pem 中的全部内容;

  • 期望的 Audience / Issuer —— 保持默认值(urn:wikijs:mcp-delegation / urn:wikijs-mcp-google-auth);

  • User Lookup Provider Priority —— 按邮箱查找用户时各个 Provider 的顺序。如果你的同事是通过 Google/OIDC 登录 wiki 的,把它放到最前面(也接受模块键名:google、oidc、local);

  • (可选)自助注册 + 域名白名单 + 自动加组 —— 这样新 Workspace 用户在第一次通过 MCP 发起请求时会被自动创建;

  • 保存。

列表中会显示该策略实例的 key(也就是 MCP 服务器要用到的 WIKIJS_STRATEGY_KEY;如果你是通过界面手动创建的,Wiki.js 会生成一个 uuid —— 复制它即可)。

已开启 TFA 的账号无法通过委托方式使用 —— MCP 服务器会返回一个明确的错误。

第 4 步:创建 Google OAuth 客户端

Google Cloud Console → APIs & Services → Credentials → 创建凭据 → OAuth 客户端 ID:

  • 应用类型:Web 应用;

  • 授权重定向 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

容器以非 root 的 node 用户运行——挂载进去的密钥文件必须对它可读(chmod 644);私钥本身的保护仍由宿主机 keys/ 目录的权限来保证。

.env 变量:

变量

值

MCP_IMAGE

带标签的镜像(当版本更新合并到 main 时,Release on main 工作流会自动发布 ghcr.io/<owner>/wikijs-mcp-server:vX.Y.Z;也可以本地构建:docker build -f packages/mcp-server/Dockerfile -t wikijs-mcp-server:local .)

PUBLIC_URL

MCP 服务器的公网 HTTPS 地址

WIKIJS_URL

你的 Wiki.js 地址(建议使用内网地址)

WIKIJS_STRATEGY_KEY

第 3 步得到的策略实例密钥(如果你把它命名为 mcpdelegation,这里就填它)

GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET

来自第 4 步

GOOGLE_ALLOWED_DOMAIN

你的 Workspace 域名,例如 company.com —— 外部账号一律拒绝

在 8000 端口前放一个 TLS 反向代理。最简 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 → 添加自定义连接器并填同一个 URL(旧版本也可以用 mcp-remote)。

MCP Inspector(诊断工具): 运行 npx @modelcontextprotocol/inspector → Transport 选 Streamable HTTP → URL 填 https://mcp.company.com/mcp → 点 Open Auth,完整走一遍流程。

第 7 步:验证

在 LLM 对话中:

  1. “我在 wiki 里能做什么?” → whoami 工具应显示你的邮箱、所属组以及 Wiki.js 中的权限。

  2. 让它查找/打开你有权限的页面 → 正常。

  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 + 单元测试 + 构建);重量级的 docker e2e(e2e)和浏览器 ui-e2e 只在推送到 dev/main 时(即合并前)运行,这样不会拖慢 PR 的迭代速度。

按角色运行浏览器 UI 端到端(Playwright)

另一套 docker compose 栈 deploy/docker-compose.e2e.yml 会拉起一个假 Google IdP 模拟器(packages/e2e-ui/idp/ —— 一个带角色选择器的登录页,而不是真实 Google IdP)、Wiki.js、MCP 服务器以及一个Playwright 运行器,用来在不同角色(John/Kate/域外用户)下完整驱动浏览器的 OAuth + 授权流程。该模拟器和 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

发布

发布是自动的。在 dev 分支上修改根目录 package.json 中的 version,创建一个 dev → main 的 PR 并合并即可。随后,Release on main 工作流会在推送到 main 时构建并推送 ghcr.io/<owner>/wikijs-mcp-server:vX.Y.Z(以及 :latest),同时创建 git 标签 vX.Y.Z 和 GitHub Release——全部在这一次运行中完成,只需使用内置的 GITHUB_TOKEN(无需配置 PAT/secret)。如果版本号没有变化,该次运行就是 no-op,因此普通合并到 main 不会产生 release。

要让这生效,需要一次性仓库设置:Settings → Actions → General → 工作流权限 = 读取和写入权限;如果你用规则集(ruleset)保护标签,还要允许 GitHub Actions 创建 v* 标签。

安全说明

  • 断言(Assertion):使用 RS256、TTL 60 秒、唯一的 jti,并提供防重放保护;私钥只存在于 MCP 服务器上。私钥一旦泄露,就意味着能冒充任意 wiki 用户登录——请将其视为根密钥(root secret)并轮换(生成新的密钥对,并更新对应 strategy 中的公钥)。

  • Google 身份:规范标识符是 iss+sub;邮箱只用于查找。hd 域名是从签名的 id_token 中验证的,而不是从参数里取。

  • 混淆代理(confused deputy)防御:在释放授权码之前,用户必须经过一个针对该客户端的同意屏幕(requireConsent 只有在可信的第一方场景中才可以关闭)。这可以防止攻击者通过 DCR 注册自己的 OAuth 客户端后静默获取受害者的令牌。

  • Wiki.js 速率限制:authentication.login 为每个 IP 每分钟 5 次,而所有委托登录都来自 MCP 服务器的 IP。broker 会缓存 JWT(默认 30 分钟),并在限流时“尝试等待并重试”,因此在正常运行中无感知;但在大规模接入期间,最多可能出现大约一分钟的延迟。

  • 撤销:标准 OAuth /revoke(按 token 撤销);在 Wiki.js 中停用用户后,下次 JWT 刷新时(≤30 分钟)委托会被中断;删除 SESSION_STORE_FILE 并重启 MCP 服务器,即可一次性清除所有会话。

  • 审计:工具调用会记录为结构化日志(谁、调用了哪个工具、ok/denied),且不包含页面内容。

  • MCP 端点:仅 Bearer 认证,每个 token 120 次请求/分钟,带安全响应头;OAuth 端点受 SDK 内置的速率限制保护。

限制与计划

  • RAG/语义搜索是一个独立的未来服务。接入点已就绪:search_wiki 通过 SearchBackend 接口工作(src/search/ — v1 是原生 Wiki.js 搜索;RAG 服务将接收用户的 Wiki.js JWT 并保留 ACL 模型)。参见 docs/rag-integration.md。

  • 当前是单实例 MCP 服务器(FileStore + 内存重放缓存)。高可用需要共享存储(Redis)——对应接口 KVStore 已经拆好。

  • Wiki.js 3.x 的认证机制不同——本模块以 2.5.x 为目标。

许可证

Apache-2.0

Related MCP Connectors

Related MCP Servers