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

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-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 /"; 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.