renamed deploy to examples

This commit is contained in:
nick committed 2026-09-21 19:21:08 -07:00
1 parent 80a8ea097a
commit edf6756d2d
5 files changed
+217 -3

No files matched your search

+130
View File
@@ -0,0 +1,130 @@
# Authelia configuration for the BookStack MCP server.
#
# Merge these blocks into your existing configuration.yml. Tested against
# Authelia 4.39+, where ID Token claims changed (see the claims_policies note).
#
# Generate the client id and secret first:
#
# docker run --rm authelia/authelia:latest \
# authelia crypto rand --length 72 --charset rfc3986
#
# docker run --rm authelia/authelia:latest \
# authelia crypto hash generate pbkdf2 --variant sha512 \
# --random --random.length 72 --random.charset rfc3986
#
# The second command prints BOTH a "Random Password" and a "Digest".
# Digest -> client_secret below
# Random Password -> OIDC_CLIENT_SECRET in /etc/bookstack-mcp/env
# Do not put the digest in the MCP server's env file.
identity_providers:
oidc:
# -----------------------------------------------------------------------
# 1. Claims policy
#
# Authelia 4.39 stopped putting non-standard claims in the ID Token by
# default. The MCP server reads identity from the ID Token (because
# Authelia issues opaque access tokens), so groups and preferred_username
# have to be put back explicitly. Without this, MCP_REQUIRED_GROUPS will
# never match and every tool call gets denied.
# -----------------------------------------------------------------------
claims_policies:
mcp_claims:
id_token:
- 'groups'
- 'preferred_username'
- 'email'
- 'email_verified'
- 'name'
# -----------------------------------------------------------------------
# 2. Lifespan
#
# Authelia's default refresh_token lifespan is 90m. A claude.ai connector
# that sits idle longer than that has to be re-authorised by hand, which
# gets old fast. A long refresh token with a short access token is the
# right shape for a background integration.
# -----------------------------------------------------------------------
lifespans:
custom:
mcp:
access_token: '1h'
id_token: '1h'
refresh_token: '30d'
clients:
- client_id: 'REPLACE_WITH_GENERATED_ID'
client_name: 'BookStack MCP'
client_secret: 'REPLACE_WITH_PBKDF2_DIGEST'
public: false
# one_factor if you don't want to be prompted for 2FA on reconnect.
authorization_policy: 'two_factor'
claims_policy: 'mcp_claims'
lifespan: 'mcp'
# Claude re-authorises on token refresh. Explicit consent every time is
# painful; pre-configured remembers the grant for the given duration.
consent_mode: 'pre-configured'
pre_configured_consent_duration: '1 month'
require_pkce: true
pkce_challenge_method: 'S256'
redirect_uris:
- 'https://mcp.example.com/auth/callback'
# RFC 8707 resource indicators. The MCP server sends a 'resource'
# parameter identifying itself; Authelia validates it against this
# whitelist and returns 'invalid_target' if it isn't listed.
# Alternative: set OIDC_FORWARD_RESOURCE=false on the MCP server to
# stop sending it at all.
audience:
- 'https://mcp.example.com'
- 'https://mcp.example.com/mcp'
requested_audience_mode: 'implicit'
# offline_access is what gets you a refresh token. Without it the
# connector dies when the access token expires.
scopes:
- 'openid'
- 'profile'
- 'email'
- 'groups'
- 'offline_access'
grant_types:
- 'authorization_code'
- 'refresh_token'
response_types:
- 'code'
# Authelia's default and what the spec requires. Leave it alone unless
# you see 'invalid_client' at the token endpoint, in which case try
# 'client_secret_post'.
token_endpoint_auth_method: 'client_secret_basic'
# Both are already the default; stated explicitly because they matter.
# 'none' means access tokens stay opaque rather than being JWTs, which
# is exactly why the MCP server needs OIDC_VERIFY_ID_TOKEN=true.
access_token_signed_response_alg: 'none'
userinfo_signed_response_alg: 'none'
# ---------------------------------------------------------------------------
# 3. Access control bypass <-- the one everybody misses
#
# If mcp.example.com is behind Authelia's forward-auth on your reverse proxy,
# Claude will be served an HTML login page instead of the MCP endpoint and the
# connector will fail with a parse error or a redirect loop. Claude cannot do
# cookie-based forward auth; the MCP server runs its own OAuth flow against
# Authelia instead. So the host must bypass forward-auth entirely.
#
# This is not a security hole: the MCP server refuses every unauthenticated
# request itself, and Authelia is still the thing deciding who gets a token.
# ---------------------------------------------------------------------------
access_control:
rules:
- domain: 'mcp.example.com'
policy: 'bypass'
+53
View File
@@ -0,0 +1,53 @@
# Butane config for running bookstack-mcp as a container on Flatcar Container
# Linux. Convert to Ignition and hand it to the machine on first boot:
#
# butane --pretty --strict bookstack-mcp.bu > bookstack-mcp.ign
#
# Fill in the image reference below (your Gitea registry) and the values in
# the env file. systemd's EnvironmentFile= only sets the *unit's* environment
# (i.e. what the docker CLI sees), not the container's -- that's why the unit
# passes --env-file explicitly to `docker run`.
variant: flatcar
version: 1.0.0
storage:
files:
- path: /etc/bookstack-mcp.env
mode: 0600
contents:
inline: |
BOOKSTACK_URL=https://wiki.example.com
BOOKSTACK_TOKEN_ID=
BOOKSTACK_TOKEN_SECRET=
BOOKSTACK_READ_ONLY=true
MCP_HOST=0.0.0.0
MCP_PORT=8080
MCP_PATH=/mcp
MCP_AUTH_MODE=oidc+token
MCP_PUBLIC_URL=https://mcp.example.com
MCP_STATIC_TOKENS=
# See example.env at the project root for the full list of options
# (OIDC, GitHub OAuth, allowed users/groups, etc).
systemd:
units:
- name: bookstack-mcp.service
enabled: true
contents: |
[Unit]
Description=BookStack MCP Server
After=docker.service network-online.target
Requires=docker.service
Wants=network-online.target
[Service]
ExecStartPre=-/usr/bin/docker rm -f bookstack-mcp
ExecStartPre=/usr/bin/docker pull git.example.com/owner/bookstack-mcp:latest
ExecStart=/usr/bin/docker run --rm --name bookstack-mcp \
--env-file /etc/bookstack-mcp.env \
-p 8080:8080 \
git.example.com/owner/bookstack-mcp:latest
ExecStop=/usr/bin/docker stop bookstack-mcp
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
+56
View File
@@ -0,0 +1,56 @@
[Unit]
Description=BookStack MCP Server
Documentation=https://github.com/your/bookstack-mcp
After=network-online.target
Wants=network-online.target
[Service]
Type=exec
User=bookstack-mcp
Group=bookstack-mcp
EnvironmentFile=/etc/bookstack-mcp/env
WorkingDirectory=/opt/bookstack-mcp
ExecStart=/opt/bookstack-mcp/venv/bin/python -m bookstack_mcp
# ProtectSystem=strict makes everything read-only, but the MCP library writes
# to a data directory under $HOME (OAuth client registrations, caches). The
# service user's home is /opt/bookstack-mcp, which is part of the read-only
# tree -- so point HOME and the XDG dirs at directories systemd creates and
# grants write access to. Without this the service dies with
# "OSError: [Errno 30] Read-only file system: '/opt/bookstack-mcp/.local'".
StateDirectory=bookstack-mcp
CacheDirectory=bookstack-mcp
Environment=HOME=/var/lib/bookstack-mcp
Environment=XDG_DATA_HOME=/var/lib/bookstack-mcp
Environment=XDG_CONFIG_HOME=/var/lib/bookstack-mcp
Environment=XDG_CACHE_HOME=/var/cache/bookstack-mcp
Restart=on-failure
RestartSec=5s
TimeoutStopSec=20s
# --- Hardening ---
# These all work in an unprivileged Proxmox/LXD container. If the service fails
# with a 226/NAMESPACE or 227/SECCOMP status on an older kernel, comment out the
# block below and it will start fine -- it only reduces defence in depth.
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ProtectControlGroups=yes
RestrictSUIDSGID=yes
RemoveIPC=yes
LockPersonality=yes
MemoryDenyWriteExecute=yes
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
CapabilityBoundingSet=
AmbientCapabilities=
# Journald captures stdout/stderr: journalctl -u bookstack-mcp -f
StandardOutput=journal
StandardError=journal
SyslogIdentifier=bookstack-mcp
[Install]
WantedBy=multi-user.target