- server.py: MCP wrapper para API REST slide.descomplicar.pt - requirements.txt: fastmcp, httpx - .gitignore: Python standard
171 lines
4.5 KiB
Python
171 lines
4.5 KiB
Python
"""
|
|
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()
|