7.2 KiB
7.2 KiB
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-gov1.0.0 (paquetesmcpyserver) con suserver.NewMCPServery transporteServeStdio. - 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-gopermite 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_URLyOUTLINE_API_KEYse leen en runtime al construirOutlineClient; 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
envpor proceso, por lo que el entorno es el canal natural.
D4: Auto-update con minio/selfupdate + API de releases de Gitea
- Elección:
GET {GiteaURL}/api/v1/repos/{RepoOwner}/{RepoName}/releases/latestpara obtener tag y assets.- Comparación semver simple: si el tag es igual o superior a
Version, no se hace nada. Los tags siguen el formatovX.Y.Z; el prefijovse 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). - Selección de asset por convención de nombres
<name>_<GOOS>_<GOARCH>[.exe](ej.outline-mcp_darwin_arm64), filtrando conruntime.GOOS/runtime.GOARCH. - 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.exeen Windows). El updater busca ese patrón.GOOS/GOARCHse usan literalmente para que el mapeo sea directo.
D6: Pipeline de release
- Elección: workflow en
.gitea/workflows/release.ymlconon: push: tags: ['v*'], runnerubuntu-latest, setup de Go 1.26 (actionactions/setup-go@v5, compatible con Gitea Actions), bucle de build sobre las 3 plataformas conCGO_ENABLED=0yldflagsinyectandomain.Version(desdegithub.ref_name),main.GiteaURL,main.RepoOwner,main.RepoName(desdegithub.server_urly el contexto del repo). Publicación con la acciónakkuman/gitea-release-actiono el CLItea release createcomo 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:1no es necesaria (la imagen ya incluye Go); se añade la featuredocker-in-dockeropcional no requerida — se omite para minimizar superficie. postCreateCommand: instalación degolangci-lintvía script oficial ygo mod downloadsi existego.mod.- Extensiones:
golang.go(Go oficial, incluye gopls),eamodio.gitlens. go.useLanguageServer: trueen settings del editor.
- Features:
- 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 /"; 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ánvX.Y.Z. - [
selfupdate.Applyno funciona en algunos entornos (ej. binario en ruta sin permiso de escritura, Windows con AV bloqueando el reemplazo)] →Applyreemplaza 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/repodefinitivos: 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.