MCP server for LogSeq
by ergut
- mcp-logseq-server
- src
- mcp_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