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:
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal 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
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
fastmcp>=2.0.0
|
||||
httpx>=0.27.0
|
||||
170
server.py
Normal file
170
server.py
Normal 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()
|
||||
Reference in New Issue
Block a user