0b8fe7b2df
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>
69 lines
2.2 KiB
Python
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,
|
|
)
|