""" MCP Presenton - Wrapper API REST para slide.descomplicar.pt Gera apresentacoes AI via Presenton self-hosted. """ import os import json from typing import Optional import httpx from fastmcp import FastMCP mcp = FastMCP("presenton") BASE_URL = os.environ.get("PRESENTON_URL", "https://slide.descomplicar.pt") API_KEY = os.environ.get("PRESENTON_API_KEY", "") def _headers() -> dict: headers = { "Content-Type": "application/json", "Accept": "application/json", } if API_KEY: headers["Authorization"] = f"Bearer {API_KEY}" return headers def _api_url(path: str) -> str: return f"{BASE_URL}/api/v1{path}" async def _post(path: str, data: dict) -> dict: async with httpx.AsyncClient(timeout=120.0) as client: resp = await client.post(_api_url(path), json=data, headers=_headers()) resp.raise_for_status() return resp.json() async def _get(path: str, params: dict | None = None) -> dict: async with httpx.AsyncClient(timeout=60.0) as client: resp = await client.get(_api_url(path), params=params, headers=_headers()) resp.raise_for_status() return resp.json() @mcp.tool async def generate_presentation( content: str, n_slides: int = 10, tone: str = "professional", template: Optional[str] = None, language: str = "pt", web_search: bool = False, ) -> dict: """Gera apresentacao AI a partir de prompt de texto. Args: content: Tema ou descricao da apresentacao. n_slides: Numero de slides (default 10). tone: Tom da apresentacao (professional, casual, sales_pitch, educational). template: ID do template a usar (opcional). language: Idioma (default pt). web_search: Pesquisar web para enriquecer conteudo. Returns: Dict com presentation_id, status e detalhes. """ payload = { "content": content, "n_slides": n_slides, "tone": tone, "language": language, "web_search": web_search, } if template: payload["template"] = template return await _post("/ppt/presentation/generate", payload) @mcp.tool async def generate_from_json( slides: list[dict], template: Optional[str] = None, ) -> dict: """Gera apresentacao a partir de estrutura JSON deterministica. Args: slides: Lista de slides, cada um com title, content, layout, notes. template: ID do template (opcional). Returns: Dict com presentation_id e path. """ payload = {"slides": slides} if template: payload["template"] = template return await _post("/ppt/presentation/create", payload) @mcp.tool async def export_presentation( presentation_id: str, format: str = "pptx", ) -> dict: """Exporta apresentacao como PPTX ou PDF. Args: presentation_id: ID da apresentacao a exportar. format: Formato de exportacao (pptx ou pdf). Returns: Dict com download_url e path do ficheiro. """ return await _post("/ppt/presentation/export", { "id": presentation_id, "export_as": format, }) @mcp.tool async def list_templates() -> list[dict]: """Lista templates disponiveis (standard + AI-generated). Returns: Lista de templates com id, name, thumbnail, category. """ result = await _get("/ppt/template-management/summary") return result if isinstance(result, list) else result.get("presentations", []) @mcp.tool async def list_presentations() -> list[dict]: """Lista apresentacoes existentes. Returns: Lista de apresentacoes com id, title, created_at, slide_count. """ result = await _get("/ppt/presentation/all") return result if isinstance(result, list) else result.get("presentations", []) @mcp.tool async def upload_image(image_path: str) -> dict: """Upload imagem para biblioteca Presenton para uso em slides. Args: image_path: Caminho local do ficheiro de imagem. Returns: Dict com image_id e url da imagem no Presenton. """ async with httpx.AsyncClient(timeout=60.0) as client: with open(image_path, "rb") as f: img_headers = {} if API_KEY: img_headers["Authorization"] = f"Bearer {API_KEY}" resp = await client.post( _api_url("/ppt/images/upload"), headers=img_headers, files={"file": (os.path.basename(image_path), f)}, ) resp.raise_for_status() return resp.json() if __name__ == "__main__": mcp.run()