Skip to main content
Glama
server.py7.74 kB
#!/home/borjigin/dev/bookstack-mcp/venv/bin/python """ Minimal BookStack MCP Server for Cursor Provides essential tools for managing BookStack documentation. """ import os import asyncio import httpx from typing import Optional, Literal from fastmcp import FastMCP from dotenv import load_dotenv # Load environment variables load_dotenv() BS_URL = os.getenv("BS_URL", "http://192.168.1.193:6875") BS_TOKEN_ID = os.getenv("BS_TOKEN_ID") BS_TOKEN_SECRET = os.getenv("BS_TOKEN_SECRET") # Initialize FastMCP server mcp = FastMCP("bookstack-mcp") class BookStackClient: """Simple BookStack API client""" def __init__(self, base_url: str, token_id: str, token_secret: str): self.base_url = base_url.rstrip("/") self.headers = { "Authorization": f"Token {token_id}:{token_secret}", "Content-Type": "application/json" } async def request(self, method: str, endpoint: str, **kwargs): """Make an HTTP request to BookStack API""" url = f"{self.base_url}/api/{endpoint.lstrip('/')}" async with httpx.AsyncClient(timeout=30.0) as client: response = await client.request(method, url, headers=self.headers, **kwargs) response.raise_for_status() return response.json() def get_client() -> BookStackClient: """Get configured BookStack client""" if not BS_TOKEN_ID or not BS_TOKEN_SECRET: raise ValueError("BS_TOKEN_ID and BS_TOKEN_SECRET must be set in .env file") return BookStackClient(BS_URL, BS_TOKEN_ID, BS_TOKEN_SECRET) @mcp.tool() async def bookstack_list_content( entity_type: Literal["books", "bookshelves", "chapters", "pages"], count: int = 20, offset: int = 0, filter_name: Optional[str] = None ) -> str: """ List BookStack content entities (books, bookshelves, chapters, or pages). Args: entity_type: Type of entity to list count: Number of items to return (default 20) offset: Pagination offset (default 0) filter_name: Optional filter by name (substring match) Returns: JSON string with list of entities """ client = get_client() params = {"count": count, "offset": offset} if filter_name: params["filter[name]"] = filter_name result = await client.request("GET", entity_type, params=params) return str(result) @mcp.tool() async def bookstack_search( query: str, page: int = 1, count: int = 20 ) -> str: """ Search across all BookStack content. Args: query: Search query string page: Page number for pagination (default 1) count: Number of results per page (default 20) Returns: JSON string with search results """ client = get_client() params = {"query": query, "page": page, "count": count} result = await client.request("GET", "search", params=params) return str(result) @mcp.tool() async def bookstack_get_page(page_id: int) -> str: """ Get detailed information about a specific page including its content. Args: page_id: The ID of the page to retrieve Returns: JSON string with page details including HTML and Markdown content """ client = get_client() result = await client.request("GET", f"pages/{page_id}") return str(result) @mcp.tool() async def bookstack_create_page( book_id: int, name: str, html: Optional[str] = None, markdown: Optional[str] = None, chapter_id: Optional[int] = None, tags: Optional[str] = None ) -> str: """ Create a new page in BookStack. Args: book_id: ID of the book to create the page in name: Name/title of the page html: HTML content (use either html or markdown, not both) markdown: Markdown content (use either html or markdown, not both) chapter_id: Optional chapter ID to place the page in tags: Optional comma-separated tags (e.g., "tag1=value1,tag2=value2") Returns: JSON string with created page details """ client = get_client() data = { "book_id": book_id, "name": name } if html: data["html"] = html elif markdown: data["markdown"] = markdown if chapter_id: data["chapter_id"] = chapter_id if tags: data["tags"] = [{"name": tag.split("=")[0], "value": tag.split("=")[1] if "=" in tag else ""} for tag in tags.split(",")] result = await client.request("POST", "pages", json=data) return str(result) @mcp.tool() async def bookstack_update_page( page_id: int, name: Optional[str] = None, html: Optional[str] = None, markdown: Optional[str] = None, tags: Optional[str] = None ) -> str: """ Update an existing page in BookStack. Args: page_id: ID of the page to update name: New name/title (optional) html: New HTML content (optional) markdown: New Markdown content (optional) tags: Optional comma-separated tags (e.g., "tag1=value1,tag2=value2") Returns: JSON string with updated page details """ client = get_client() data = {} if name: data["name"] = name if html: data["html"] = html if markdown: data["markdown"] = markdown if tags: data["tags"] = [{"name": tag.split("=")[0], "value": tag.split("=")[1] if "=" in tag else ""} for tag in tags.split(",")] result = await client.request("PUT", f"pages/{page_id}", json=data) return str(result) @mcp.tool() async def bookstack_delete_page(page_id: int) -> str: """ Delete a page from BookStack. Args: page_id: ID of the page to delete Returns: Success message """ client = get_client() await client.request("DELETE", f"pages/{page_id}") return f"Page {page_id} deleted successfully" @mcp.tool() async def bookstack_create_book( name: str, description: Optional[str] = None, tags: Optional[str] = None ) -> str: """ Create a new book in BookStack. Args: name: Name of the book description: Optional description tags: Optional comma-separated tags Returns: JSON string with created book details """ client = get_client() data = {"name": name} if description: data["description"] = description if tags: data["tags"] = [{"name": tag.split("=")[0], "value": tag.split("=")[1] if "=" in tag else ""} for tag in tags.split(",")] result = await client.request("POST", "books", json=data) return str(result) @mcp.tool() async def bookstack_create_chapter( book_id: int, name: str, description: Optional[str] = None, tags: Optional[str] = None ) -> str: """ Create a new chapter in a book. Args: book_id: ID of the book to create the chapter in name: Name of the chapter description: Optional description tags: Optional comma-separated tags Returns: JSON string with created chapter details """ client = get_client() data = { "book_id": book_id, "name": name } if description: data["description"] = description if tags: data["tags"] = [{"name": tag.split("=")[0], "value": tag.split("=")[1] if "=" in tag else ""} for tag in tags.split(",")] result = await client.request("POST", "chapters", json=data) return str(result) if __name__ == "__main__": # Run the MCP server mcp.run()

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/lborjigi/bookstack-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server