Files
simbarag/utils/bookstack_mcp.py
ryan 0b8fe7b2df Add admin-only BookStack MCP connector
Load an allowlisted subset of bookstack-mcp tools (search/read plus
create/update books, chapters, pages and comments; no deletes) over stdio
and add them to the admin agent alongside Homebox. Extract the shared
allowlist + is_admin wrapping into utils/mcp_tools.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:54:01 -04:00

69 lines
2.2 KiB
Python

"""BookStack wiki tools, loaded from the bookstack-mcp server over stdio.
Admin-only: the tools are only compiled into the admin agent, and each call
re-checks ``is_admin`` in the run config as a second line of defense.
"""
import logging
import os
from langchain_core.tools import BaseTool, StructuredTool
from utils.mcp_tools import admin_only_message, load_stdio_tools, select_tools
logger = logging.getLogger(__name__)
# Read + create/update only. The server exposes 43 tools with writes enabled;
# exposing all of them would blow the local model's 32k context and allow
# deletes, shelf/attachment management, and recycle-bin purges.
BOOKSTACK_TOOL_ALLOWLIST = (
"search_content",
"get_books",
"get_book",
"get_chapters",
"get_pages",
"get_page",
"get_recent_changes",
"create_book",
"create_chapter",
"create_page",
"update_page",
"create_comment",
)
LABEL = "BookStack"
ADMIN_ONLY_MESSAGE = admin_only_message(LABEL)
def select_bookstack_tools(mcp_tools: list[BaseTool]) -> list[StructuredTool]:
"""Keep allowlisted tools (in allowlist order) and wrap them admin-only."""
return select_tools(mcp_tools, BOOKSTACK_TOOL_ALLOWLIST, "bookstack", LABEL)
async def load_bookstack_tools() -> list[StructuredTool]:
"""Start bookstack-mcp and return the wrapped tools, or [] if unavailable."""
url = os.getenv("BOOKSTACK_BASE_URL")
token_id = os.getenv("BOOKSTACK_TOKEN_ID")
token_secret = os.getenv("BOOKSTACK_TOKEN_SECRET")
if not url or not token_id or not token_secret:
logger.info(
"BookStack MCP not configured "
"(BOOKSTACK_BASE_URL/BOOKSTACK_TOKEN_ID/BOOKSTACK_TOKEN_SECRET unset)"
)
return []
return await load_stdio_tools(
server="bookstack",
command=os.getenv("BOOKSTACK_MCP_COMMAND", "bookstack-mcp"),
env={
"BOOKSTACK_BASE_URL": url,
"BOOKSTACK_TOKEN_ID": token_id,
"BOOKSTACK_TOKEN_SECRET": token_secret,
# Server-side write gate; the allowlist above keeps deletes out.
"BOOKSTACK_ENABLE_WRITE": "true",
},
allowlist=BOOKSTACK_TOOL_ALLOWLIST,
prefix="bookstack",
label=LABEL,
)