"""
LibreOffice MCP Extension - MCP Server Module
This module implements an embedded MCP server that integrates with LibreOffice
via the UNO API, providing real-time document manipulation capabilities.
"""
import asyncio
import json
import logging
from typing import Dict, Any, Optional, List
from .uno_bridge import UNOBridge
# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class LibreOfficeMCPServer:
"""Embedded MCP server for LibreOffice plugin"""
def __init__(self):
"""Initialize the MCP server"""
self.uno_bridge = UNOBridge()
self.tools = {}
self._register_tools()
logger.info("LibreOffice MCP Server initialized")
def _register_tools(self):
"""Register all available MCP tools"""
# Document creation tools
self.tools["create_document_live"] = {
"description": "Create a new document in LibreOffice",
"parameters": {
"type": "object",
"properties": {
"doc_type": {
"type": "string",
"enum": ["writer", "calc", "impress", "draw"],
"description": "Type of document to create",
"default": "writer"
}
}
},
"handler": self.create_document_live
}
# Text manipulation tools
self.tools["insert_text_live"] = {
"description": "Insert text into the currently active document",
"parameters": {
"type": "object",
"properties": {
"text": {
"type": "string",
"description": "Text to insert"
},
"position": {
"type": "integer",
"description": "Position to insert at (optional, defaults to cursor position)"
}
},
"required": ["text"]
},
"handler": self.insert_text_live
}
# Document info tools
self.tools["get_document_info_live"] = {
"description": "Get information about the currently active document",
"parameters": {
"type": "object",
"properties": {}
},
"handler": self.get_document_info_live
}
# Text formatting tools
self.tools["format_text_live"] = {
"description": "Apply formatting to selected text in active document",
"parameters": {
"type": "object",
"properties": {
"bold": {
"type": "boolean",
"description": "Apply bold formatting"
},
"italic": {
"type": "boolean",
"description": "Apply italic formatting"
},
"underline": {
"type": "boolean",
"description": "Apply underline formatting"
},
"font_size": {
"type": "number",
"description": "Font size in points"
},
"font_name": {
"type": "string",
"description": "Font family name"
}
}
},
"handler": self.format_text_live
}
# Document saving tools
self.tools["save_document_live"] = {
"description": "Save the currently active document",
"parameters": {
"type": "object",
"properties": {
"file_path": {
"type": "string",
"description": "Path to save document to (optional, saves to current location if not specified)"
}
}
},
"handler": self.save_document_live
}
# Document export tools
self.tools["export_document_live"] = {
"description": "Export the currently active document to a different format",
"parameters": {
"type": "object",
"properties": {
"export_format": {
"type": "string",
"enum": ["pdf", "docx", "doc", "odt", "txt", "rtf", "html"],
"description": "Format to export to"
},
"file_path": {
"type": "string",
"description": "Path to export document to"
}
},
"required": ["export_format", "file_path"]
},
"handler": self.export_document_live
}
# Content reading tools
self.tools["get_text_content_live"] = {
"description": "Get the text content of the currently active document",
"parameters": {
"type": "object",
"properties": {}
},
"handler": self.get_text_content_live
}
# Document list tools
self.tools["list_open_documents"] = {
"description": "List all currently open documents in LibreOffice",
"parameters": {
"type": "object",
"properties": {}
},
"handler": self.list_open_documents
}
logger.info(f"Registered {len(self.tools)} MCP tools")
async def execute_tool(self, tool_name: str, parameters: Dict[str, Any]) -> Dict[str, Any]:
"""
Execute an MCP tool
Args:
tool_name: Name of the tool to execute
parameters: Parameters for the tool
Returns:
Result dictionary
"""
try:
if tool_name not in self.tools:
return {
"success": False,
"error": f"Unknown tool: {tool_name}",
"available_tools": list(self.tools.keys())
}
tool = self.tools[tool_name]
handler = tool["handler"]
# Execute the tool handler
result = handler(**parameters)
logger.info(f"Executed tool '{tool_name}' successfully")
return result
except Exception as e:
logger.error(f"Error executing tool '{tool_name}': {e}")
return {
"success": False,
"error": str(e),
"tool": tool_name,
"parameters": parameters
}
def get_tool_list(self) -> List[Dict[str, Any]]:
"""Get list of available tools with their descriptions"""
return [
{
"name": name,
"description": tool["description"],
"parameters": tool["parameters"]
}
for name, tool in self.tools.items()
]
# Tool handler methods
def create_document_live(self, doc_type: str = "writer") -> Dict[str, Any]:
"""Create a new document in LibreOffice"""
try:
doc = self.uno_bridge.create_document(doc_type)
doc_info = self.uno_bridge.get_document_info(doc)
return {
"success": True,
"message": f"Created new {doc_type} document",
"document_info": doc_info
}
except Exception as e:
return {"success": False, "error": str(e)}
def insert_text_live(self, text: str, position: Optional[int] = None) -> Dict[str, Any]:
"""Insert text into the currently active document"""
return self.uno_bridge.insert_text(text, position)
def get_document_info_live(self) -> Dict[str, Any]:
"""Get information about the currently active document"""
doc_info = self.uno_bridge.get_document_info()
if "error" in doc_info:
return {"success": False, **doc_info}
else:
return {"success": True, "document_info": doc_info}
def format_text_live(self, **formatting) -> Dict[str, Any]:
"""Apply formatting to selected text"""
return self.uno_bridge.format_text(formatting)
def save_document_live(self, file_path: Optional[str] = None) -> Dict[str, Any]:
"""Save the currently active document"""
return self.uno_bridge.save_document(file_path=file_path)
def export_document_live(self, export_format: str, file_path: str) -> Dict[str, Any]:
"""Export the currently active document"""
return self.uno_bridge.export_document(export_format, file_path)
def get_text_content_live(self) -> Dict[str, Any]:
"""Get text content of the currently active document"""
return self.uno_bridge.get_text_content()
def list_open_documents(self) -> Dict[str, Any]:
"""List all open documents in LibreOffice"""
try:
desktop = self.uno_bridge.desktop
documents = []
# Get all open documents
frames = desktop.getFrames()
for i in range(frames.getCount()):
frame = frames.getByIndex(i)
controller = frame.getController()
if controller:
doc = controller.getModel()
if doc:
doc_info = self.uno_bridge.get_document_info(doc)
documents.append(doc_info)
return {
"success": True,
"documents": documents,
"count": len(documents)
}
except Exception as e:
return {"success": False, "error": str(e)}
# Global instance
mcp_server = None
def get_mcp_server() -> LibreOfficeMCPServer:
"""Get or create the global MCP server instance"""
global mcp_server
if mcp_server is None:
mcp_server = LibreOfficeMCPServer()
return mcp_server