Skip to content

v5.0.0: M365/Entra OAuth connector mode + GHCR image#7

Merged
ttpears merged 8 commits into
mainfrom
feat/m365-oauth-connector
Jun 29, 2026
Merged

v5.0.0: M365/Entra OAuth connector mode + GHCR image#7
ttpears merged 8 commits into
mainfrom
feat/m365-oauth-connector

Conversation

@ttpears

@ttpears ttpears commented Jun 28, 2026

Copy link
Copy Markdown
Owner

Lets bookstack-mcp run as a remote Claude Connector gated by Microsoft 365 / Entra ID login — users sign in, no API key or client credential to paste. Modeled on zereight/gitlab-mcp's OAuth proxy and our gitlab-mcp-custom deployment pattern.

OAuth (v5.0.0)

  • New src/oauth/entra-proxy.ts: self-contained, BookStack-agnostic OAuth proxy. Hosts RFC 9728 protected-resource metadata, RFC 8414 AS metadata, local DCR (RFC 7591), and /authorize /callback /token, brokering login upstream to Entra. Stateless per-request bearer validation via jose remote JWKS (issuer, audience, expiry).
  • Two shared service tokens (read-only + write-capable). Write is gated per session by an Entra app role (OAUTH_WRITE_ROLE, default Writer); non-writers are bound to the read-only token, so a gating bug still cannot write.
  • index.ts mounts the OAuth surface when MCP_OAUTH_ENABLE=true, enforces the bearer on /mcp (401 + WWW-Authenticate bootstraps discovery), and selects the per-session credential/tool set by role. stdio and no-auth HTTP are unchanged and backward compatible.

Image + CI

  • Dockerfile (node:20-alpine, multi-stage, non-root, dumb-init, HEALTHCHECK on /health).
  • ci.yml: per-PR docker-preview job → ghcr.io/ttpears/bookstack-mcp:branch-<slug> (this PR: :branch-feat-m365-oauth-connector) for testing on ceres.
  • release.yml: on v* tags → :X.Y.Z, :X.Y, :latest (amd64+arm64).

Docs

  • docs/SETUP.md: step-by-step Entra app registration, env reference, two-token BookStack setup, internal LibreChat deploy on ceres, and the public OAuth connector deploy. Design spec under docs/superpowers/specs/.

Verification

  • type-check + build clean; docker build/run green.
  • Smoke-tested: PRM + AS metadata, DCR /register, /authorize → Entra redirect (PKCE preserved), unauthenticated /mcp → 401 with WWW-Authenticate, container HEALTHCHECK healthy, and no-auth mode still initializes a session.
  • Pending: live end-to-end against the tenant once the Entra app exists.

ttpears added 3 commits June 27, 2026 23:12
Adds an HTTP auth mode that lets bookstack-mcp be added as a remote Claude
Connector gated by Microsoft 365 / Entra ID login, with no API key or client
credential entry by the user. Modeled on zereight/gitlab-mcp's OAuth proxy.

- New src/oauth/entra-proxy.ts: self-contained, BookStack-agnostic OAuth proxy.
  Hosts RFC 9728 protected-resource metadata, RFC 8414 AS metadata, local DCR
  (RFC 7591), and /authorize /callback /token, brokering login upstream to Entra.
  Stateless per-request bearer validation via jose remote JWKS (issuer, audience,
  expiry). Short-lived in-memory stores for DCR clients, pending auth, and codes.
- Two shared service tokens: read-only (BOOKSTACK_TOKEN_*) and write-capable
  (BOOKSTACK_WRITE_TOKEN_*). Write is gated per session by an Entra app role
  (OAUTH_WRITE_ROLE); non-writers' sessions are bound to the read-only token, so
  a gating bug still cannot write.
- index.ts: startHttp mounts the OAuth surface when MCP_OAUTH_ENABLE=true,
  enforces the bearer on /mcp (401 with WWW-Authenticate bootstraps discovery),
  and selects the per-session credential/tool set by role. stdio and no-auth
  HTTP modes are unchanged and backward compatible.
- New env: MCP_OAUTH_ENABLE, MCP_SERVER_URL, MCP_TRUST_PROXY, OAUTH_TENANT_ID,
  OAUTH_CLIENT_ID/SECRET, OAUTH_AUDIENCE, OAUTH_SCOPES, OAUTH_WRITE_ROLE,
  BOOKSTACK_WRITE_TOKEN_ID/SECRET.
- Add jose as a direct dependency; design spec under docs/superpowers/specs.
Ships a container image and the deployment/setup documentation needed to test
the M365/Entra OAuth connector on ceres.

- Dockerfile (node:20-alpine, multi-stage, non-root, dumb-init, HEALTHCHECK on
  /health) binding 0.0.0.0:8080; npm ci --ignore-scripts so the prepare hook
  doesn't run the build before src/ is copied. Add .dockerignore.
- ci.yml: docker-preview job builds and pushes ghcr.io/ttpears/bookstack-mcp:
  branch-<slug> for every PR (GITHUB_TOKEN, amd64) so a branch can be pulled and
  deployed on ceres before it ships — mirrors the gitlab-mcp-custom pattern.
- release.yml: docker job builds and pushes :X.Y.Z, :X.Y, :latest (amd64+arm64)
  on version tags.
- docs/SETUP.md: full runbook — step-by-step Entra app registration (single
  tenant, expose-api api://<client-id>, Writer app role, v2 tokens, assignment
  required + role assignment), env var reference, two-token BookStack setup,
  internal LibreChat deploy on ceres, and the public OAuth connector deploy.
- README: link to the runbook and note the GHCR image + preview tags.

Verified: docker build + run green; /health 200, PRM 200, unauthenticated /mcp
401, container HEALTHCHECK healthy. Workflow YAML parses.
@ttpears
ttpears force-pushed the feat/m365-oauth-connector branch from 145f018 to 35c6ae8 Compare June 28, 2026 04:15
ttpears and others added 5 commits June 28, 2026 16:28
…ence (#8)

Move the entra-proxy's in-memory clients/pending/codes maps behind an async
KvStore (in-memory default, Redis when REDIS_URL is set), so DCR clients and
in-flight auth flows survive redeploys and can be shared across replicas. Keys
namespaced by issuer host. Mirrors ttpears/gitlab-mcp.

Co-authored-by: Tim Pearson <ttpearso@gleim.com>
Adds four content-oriented MCP prompts surfaced as one-click actions in Claude's
connector menu (prompts capability), driving the existing read tools:
- answer_from_wiki (question) — search + read top pages + cited answer
- find_pages (topic) — ranked search results with previews + links
- summarize_book (book name/id) — structured book summary with section links
- recent_changes (days, default 30) — grouped summary of recent edits

New src/prompts.ts registers them via server.registerPrompt (auto-advertises the
capability + prompts/list,get); wired into buildServer. Registered in all modes
(read-only, no per-user identity). Verified via local prompts/list + prompts/get.
plugin.json was stranded at 3.5.0 and package-lock.json at 5.0.0 while
package.json moved to 5.1.0. Sync both so the marketplace and lockfile
match the release. The plugin manifest version is what Claude Code's
plugin cache keys on.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ttpears
ttpears merged commit f9a5c8a into main Jun 29, 2026
3 checks passed
@ttpears
ttpears deleted the feat/m365-oauth-connector branch June 29, 2026 04:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant