Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "bookstack-mcp",
"description": "BookStack wiki MCP server — search, read, create, and manage documentation",
"version": "3.5.0",
"version": "5.1.0",
"author": {
"name": "Tim Pearson"
},
Expand Down
12 changes: 12 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
node_modules
dist
dist-mcpb
.git
.gitignore
*.md
docs
.env*
.DS_Store
*.log
mcpb
assets
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,10 @@ PORT=8007
# WARNING: Only enable if you trust the AI with write access to BookStack
BOOKSTACK_ENABLE_WRITE=false

# Optional (OAuth proxy mode): back the broker state (DCR clients, pending auth
# flows, issued codes) with Redis so it survives redeploys and can run >1 replica.
# Unset = in-memory (single replica). Keys are namespaced by issuer host;
# REDIS_KEY_PREFIX (default "bookstack-mcp") sets the base.
# REDIS_URL=redis://:password@redis:6379
# REDIS_KEY_PREFIX=bookstack-mcp

31 changes: 31 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,37 @@ jobs:
- run: npm ci
- run: npm run build

# Build and push a throwaway GHCR image for every PR so the branch can be
# deployed somewhere (e.g. an internal LibreChat host) before it ships. Uses the built-in
# GITHUB_TOKEN (no PAT) and amd64 only. Tag: branch-<slug>. Never publishes npm.
docker-preview:
needs: build
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- id: meta
run: |
slug=$(echo "${GITHUB_HEAD_REF}" | tr '[:upper:]/' '[:lower:]-' | tr -cs 'a-z0-9_.-' '-' | sed 's/^-*//;s/-*$//')
echo "tag=branch-${slug}" >> "$GITHUB_OUTPUT"
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64
push: true
tags: ghcr.io/ttpears/bookstack-mcp:${{ steps.meta.outputs.tag }}
cache-from: type=gha
cache-to: type=gha,mode=max

tag:
needs: build
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
Expand Down
44 changes: 44 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,47 @@ jobs:
gh release create "${GITHUB_REF_NAME}" \
--generate-notes \
"bookstack-mcp-${VERSION}.mcpb#BookStack MCP — Claude Desktop / MCPB bundle"

docker:
name: Build and push container image
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Extract version from tag
id: version
run: |
VERSION="${GITHUB_REF_NAME#v}"
MAJOR_MINOR=$(echo "$VERSION" | awk -F. '{print $1"."$2}')
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "major_minor=$MAJOR_MINOR" >> "$GITHUB_OUTPUT"

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: |
ghcr.io/ttpears/bookstack-mcp:${{ steps.version.outputs.version }}
ghcr.io/ttpears/bookstack-mcp:${{ steps.version.outputs.major_minor }}
ghcr.io/ttpears/bookstack-mcp:latest
cache-from: type=gha
cache-to: type=gha,mode=max
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,6 @@ Thumbs.db
.dockerignore

# Local scripts with credentials
start-mcp.sh
start-mcp.sh
# Internal AI-session design docs
docs/superpowers/
42 changes: 42 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# BookStack MCP Server — runtime image
# Built from the repo's source. Pushed to ghcr.io/ttpears/bookstack-mcp.

FROM node:20-alpine AS builder
WORKDIR /app

# --ignore-scripts: the package's `prepare` hook runs the build, but src/ isn't
# present yet at install time — build explicitly once sources are copied.
COPY package.json package-lock.json ./
RUN npm ci --ignore-scripts

COPY tsconfig.json ./
COPY src ./src
RUN npm run build

RUN npm prune --omit=dev --ignore-scripts

FROM node:20-alpine AS runtime
RUN apk add --no-cache dumb-init wget
WORKDIR /app

COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./

RUN addgroup -g 1001 -S mcpuser && adduser -S mcpuser -u 1001
USER mcpuser

# HTTP transport by default; bind all interfaces so traefik / the LibreChat
# Docker network can reach it (the server defaults to 127.0.0.1 for stdio safety).
ENV NODE_ENV=production \
MCP_TRANSPORT=http \
MCP_HTTP_HOST=0.0.0.0 \
MCP_HTTP_PORT=8080

EXPOSE 8080

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget -qO- http://localhost:8080/health || exit 1

ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "dist/index.js"]
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,17 @@ When binding to `0.0.0.0` (e.g. inside a container reachable from other services

Restart LibreChat after config changes.

### Remote hosting & Claude Connector (Docker + Entra OAuth)

A container image is published to `ghcr.io/ttpears/bookstack-mcp` (released `:X.Y.Z`/`:latest`,
plus per-PR preview tags `:branch-<slug>`). The server can run as a public **Claude Connector**
gated by Microsoft 365 / Entra ID login — users sign in, no API key or client credential to
paste; write tools are unlocked per session by an Entra app role.

See **[docs/SETUP.md](docs/SETUP.md)** for the full runbook: Entra app registration steps,
the environment-variable reference, internal LibreChat deployment, and the public OAuth
connector setup.

### Claude Code (CLI)

The recommended path is the `ttpears/claude-plugins` marketplace, which ships this repo's plugin manifest (`.claude-plugin/plugin.json`):
Expand Down
Loading
Loading