Files
outiline-mcp/openspec/changes/create-outline-mcp-server/design.md
T

90 lines
7.2 KiB
Markdown

# Design: create-outline-mcp-server
## Context
El repositorio está vacío: el proyecto `outline-mcp` se construye desde cero. Es un servidor MCP (Model Context Protocol) en Go que actúa como puente entre clientes de IA y una instancia self-hosted de Outline. Restricciones clave:
- El desarrollo se realiza dentro de un contenedor (no se requiere Go local); por eso el primer entregable es el devcontainer.
- La distribución se realiza mediante releases binarios publicados en Gitea (no hay registry de contenedores para el binario final).
- El binario debe ser capaz de actualizarse a sí mismo desde la propia instancia de Gitea.
## Goals / Non-Goals
**Goals:**
- Servidor MCP funcional con transporte stdio y 4 herramientas sobre la API de Outline.
- Distribución reproducible: pipeline de Gitea Actions que publica binarios estáticos multi-plataforma.
- Auto-update del binario sin intervención manual (descarga + `selfupdate.Apply`).
- Entorno de desarrollo reproducible en contenedor con tooling de Go preconfigurado.
**Non-Goals:**
- Transporte HTTP/SSE del servidor MCP (solo stdio en esta iteración).
- Gestión avanzada de Outline (actualizar/borrar documentos, permisos, comentarios, attachments).
- Firma de binarios / notarización.
- Suite de tests de integración contra Outline real.
## Decisions
### D1: SDK de MCP — `github.com/mark3labs/mcp-go`
- **Elección**: usar `mcp-go` v1.0.0 (paquetes `mcp` y `server`) con su `server.NewMCPServer` y transporte `ServeStdio`.
- **Alternativa**: implementar el protocolo JSON-RPC manualmente. Se descarta por coste y riesgo; el SDK ya resuelve handshake, esquemas de herramientas y serialización.
- **Nota**: `mcp-go` permite declarar esquemas de entrada tipados (`mcp.WithString(...)`, `mcp.Required()`) que se traducen a JSON Schema para el cliente.
### D2: Arquitectura de un solo archivo (`main.go`)
- **Elección**: mantener todo el código en `main.go` (CLI, cliente HTTP, herramientas, auto-update) con structs y funciones bien delimitados.
- **Alternativa**: dividir en paquetes (`internal/outline`, `internal/updater`). Se descarta por ahora: el alcance es pequeño y un solo archivo facilita el bootstrap; la extracción a paquetes queda como refactor futuro sin impacto en specs.
### D3: Configuración por variables de entorno
- **Elección**: `OUTLINE_URL` y `OUTLINE_API_KEY` se leen en runtime al construir `OutlineClient`; los errores de configuración se devuelven como resultado MCP de error (no panic).
- **Alternativa**: flags de línea de comandos o archivo de config. Se descarta: los clientes MCP (ej. Claude Desktop, opencode) inyectan `env` por proceso, por lo que el entorno es el canal natural.
### D4: Auto-update con `minio/selfupdate` + API de releases de Gitea
- **Elección**:
1. `GET {GiteaURL}/api/v1/repos/{RepoOwner}/{RepoName}/releases/latest` para obtener tag y assets.
2. Comparación semver simple: si el tag es igual o superior a `Version`, no se hace nada. Los tags siguen el formato `vX.Y.Z`; el prefijo `v` se recorta antes de comparar. La comparación se realiza parseando los tres componentes numéricos (no se introducirá una librería semver externa para mantener las dependencias mínimas).
3. Selección de asset por convención de nombres `<name>_<GOOS>_<GOARCH>[.exe]` (ej. `outline-mcp_darwin_arm64`), filtrando con `runtime.GOOS`/`runtime.GOARCH`.
4. Descarga del asset y aplicación con `selfupdate.Apply` (que reemplaza atómicamente el binario y respalda el antiguo en `.old`).
- **Alternativa**: librería `go-selfupdate` (creativeprojects) con detección de assets automática. Se descarta: está orientada a GitHub y añade complejidad; la API de Gitea es compatible con el flujo simple de 4 pasos.
- **Riesgo asumido**: la convención de nombres de assets es un contrato implícito entre el pipeline y el updater; se documenta en el pipeline.
### D5: Convención de nombres de assets
- **Elección**: el pipeline nombrará los binarios como `outline-mcp_{GOOS}_{GOARCH}` (con `.exe` en Windows). El updater busca ese patrón. `GOOS`/`GOARCH` se usan literalmente para que el mapeo sea directo.
### D6: Pipeline de release
- **Elección**: workflow en `.gitea/workflows/release.yml` con `on: push: tags: ['v*']`, runner `ubuntu-latest`, setup de Go 1.26 (action `actions/setup-go@v5`, compatible con Gitea Actions), bucle de build sobre las 3 plataformas con `CGO_ENABLED=0` y `ldflags` inyectando `main.Version` (desde `github.ref_name`), `main.GiteaURL`, `main.RepoOwner`, `main.RepoName` (desde `github.server_url` y el contexto del repo). Publicación con la acción `akkuman/gitea-release-action` o el CLI `tea release create` como fallback.
- **Alternativa**: generar los binarios en Docker dentro del pipeline. Se descarta: el runner de Gitea ya provee el toolchain vía setup-go y es más rápido.
### D7: Devcontainer
- **Elección**: imagen `mcr.microsoft.com/devcontainers/go:1-trixie` (Go 1.26) con:
- Features: `ghcr.io/devcontainers/features/golang:1` no es necesaria (la imagen ya incluye Go); se añade la feature `docker-in-docker` opcional no requerida — se omite para minimizar superficie.
- `postCreateCommand`: instalación de `golangci-lint` vía script oficial y `go mod download` si existe `go.mod`.
- Extensiones: `golang.go` (Go oficial, incluye gopls), `eamodio.gitlens`.
- `go.useLanguageServer: true` en settings del editor.
- **Alternativa**: devcontainer con Dockerfile propio. Se descarta: la imagen oficial ya cubre todo el stack necesario.
## Risks / Trade-offs
- [Contrato de nombres de assets roto si alguien renombra manualmente en Gitea] → El updater devuelve error claro "asset no encontrado para <GOOS>/<GOARCH>"; convención documentada en el workflow.
- [Comparación semver casera falla con tags no numéricos (ej. `v1.0.0-rc1`)] → Si el parseo falla, se informa y no se actualiza (fail-safe); los tags del pipeline seguirán `vX.Y.Z`.
- [`selfupdate.Apply` no funciona en algunos entornos (ej. binario en ruta sin permiso de escritura, Windows con AV bloqueando el reemplazo)] → `Apply` reemplaza atómicamente y respalda en `.old`; se devuelve el error al usuario sin dejar el binario corrupto.
- [API key de Outline expuesta en el entorno del proceso] → Es el patrón estándar para servidores MCP stdio; no se loguea nunca la API key.
- [Rate limits o cambios de la API de Outline] → Los endpoints usados (`collections.list`, `documents.search`, `documents.info`, `documents.create`) son estables; los errores HTTP se propagan con el mensaje de la API para diagnosticar rápido.
- [Gitea Actions sin runners disponibles] → El release fallará visiblemente en el push del tag; el binario se puede compilar manualmente con los mismos comandos documentados en el workflow.
## Migration Plan
No aplica (proyecto nuevo). Rollback: los releases de Gitea conservan los binarios anteriores; para revertir una actualización basta reinstalar el asset de la versión previa (el updater respalda el binario anterior en `<binary>.old`).
## Open Questions
- URL exacta de la instancia de Gitea y `owner/repo` definitivos: se inyectan en tiempo de build; se usarán placeholders en el workflow derivados del contexto del repositorio.
- ¿Publicar también una imagen de contenedor del binario en el registry de Gitea? Fuera de alcance para este change; candidato para un change futuro.