MCP server for LogSeq

import os import logging from . import logseq from mcp.types import Tool, TextContent logger = logging.getLogger("mcp-logseq") api_key = os.getenv("LOGSEQ_API_TOKEN", "") if api_key == "": raise ValueError("LOGSEQ_API_TOKEN environment variable required") else: logger.info("Found LOGSEQ_API_TOKEN in environment") logger.debug(f"API Token starts with: {api_key[:5]}...") class ToolHandler(): def __init__(self, tool_name: str): self.name = tool_name def get_tool_description(self) -> Tool: raise NotImplementedError() def run_tool(self, args: dict) -> list[TextContent]: raise NotImplementedError() class CreatePageToolHandler(ToolHandler): def __init__(self): super().__init__("create_page") def get_tool_description(self): return Tool( name=self.name, description="Create a new page in LogSeq.", inputSchema={ "type": "object", "properties": { "title": { "type": "string", "description": "Title of the new page" }, "content": { "type": "string", "description": "Content of the new page" } }, "required": ["title", "content"] } ) def run_tool(self, args: dict) -> list[TextContent]: logger.info(f"Creating page with args: {args}") if "title" not in args or "content" not in args: logger.error("Missing required arguments") raise RuntimeError("title and content arguments required") try: api = logseq.LogSeq(api_key=api_key) result = api.create_page(args["title"], args["content"]) logger.info("Successfully created page") logger.debug(f"API response: {result}") return [ TextContent( type="text", text=f"Successfully created page '{args['title']}'" ) ] except Exception as e: logger.error(f"Failed to create page: {str(e)}") raise class ListPagesToolHandler(ToolHandler): def __init__(self): super().__init__("list_pages") def get_tool_description(self): return Tool( name=self.name, description="Lists all pages in a LogSeq graph.", inputSchema={ "type": "object", "properties": { "include_journals": { "type": "boolean", "description": "Whether to include journal/daily notes in the list", "default": False } }, "required": [] } ) def run_tool(self, args: dict) -> list[TextContent]: logger.info("Listing pages") include_journals = args.get("include_journals", False) try: api = logseq.LogSeq(api_key=api_key) logger.info("Created LogSeq client") result = api.list_pages() logger.debug(f"Raw API response: {result}") # Format pages for display pages_info = [] for page in result: # Skip if it's a journal page and we don't want to include those is_journal = page.get('journal?', False) if is_journal and not include_journals: continue # Get page information name = page.get('originalName') or page.get('name', '<unknown>') tags = page.get('tags', []) properties = page.get('properties', {}) # Build page info string info_parts = [f"- {name}"] if tags: info_parts.append(f"(tags: {', '.join(tags)})") if properties: props = [f"{k}: {v}" for k, v in properties.items()] info_parts.append(f"[{', '.join(props)}]") if is_journal: info_parts.append("[journal]") pages_info.append(" ".join(info_parts)) # Sort alphabetically by page name pages_info.sort() # Build response count_msg = f"\nTotal pages: {len(pages_info)}" journal_msg = " (excluding journal pages)" if not include_journals else " (including journal pages)" response = "LogSeq Pages:\n\n" + "\n".join(pages_info) + count_msg + journal_msg logger.info(f"Found {len(pages_info)} pages") return [TextContent(type="text", text=response)] except Exception as e: logger.error(f"Failed to list pages: {str(e)}") raise