Initial commit: MCP server for Presenton integration

- server.py: MCP wrapper para API REST slide.descomplicar.pt
- requirements.txt: fastmcp, httpx
- .gitignore: Python standard
This commit is contained in:
2026-03-04 19:58:46 +00:00
commit f7032262ee
3 changed files with 196 additions and 0 deletions

24
.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
venv/
.venv/
ENV/
dist/
build/
*.egg-info/
.eggs/
pip-log.txt
pip-delete-this-directory.txt
.tox/
.coverage
.cache
nosetests.xml
coverage.xml
*.log
.env
.env.*
*.sqlite3

2
requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
fastmcp>=2.0.0
httpx>=0.27.0

170
server.py Normal file
View File

@@ -0,0 +1,170 @@
"""
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()