Proyecto · Open source · Go

MCP para tramites.gub.uy

Servidor Model Context Protocol open source en Go que usa los datos abiertos de AGESIC para que agentes de IA — Claude, Claude Code, otros clientes compatibles — consulten trámites del Estado uruguayo con fuentes verificables.

En 2015 implementé la norma UNIT 1215 de Accesibilidad Web en el portal tramites.gub.uy desde AGESIC. Diez años después, este MCP permite a agentes de IA operar ese mismo portal usando los datos abiertos publicados por AGESIC. Mismo trámite, distinta capa.

Por qué importa

Hay una pregunta recurrente cuando uno trabaja con modelos de lenguaje: ¿cómo lograr que respondan con información útil, verificable y actualizada, sin depender de lo que "recuerdan"?

Para trámites del Estado uruguayo, esa pregunta es especialmente importante. Un trámite puede cambiar de requisitos, costos, oficinas, canales de atención o enlaces oficiales. Si un asistente responde desde memoria, puede sonar convincente y estar mal. Para este tipo de casos, el camino razonable es conectar el modelo a una fuente de datos concreta.

Este MCP es esa conexión. Los datos vienen del dataset abierto Guía de Trámites de AGESIC (catalogodatos.gub.uy), publicado bajo licencia CC BY 4.0. El código del servidor está bajo MIT.

Qué expone el servidor

Transporte: HTTP Streamable en /mcp. 4 tools y 1 resource:

  • search_uruguay_government_procedures(query, organismo?, limit?) — búsqueda de trámites con FTS5 o híbrida si hay embeddings.
  • get_uruguay_government_procedure(id) — detalle completo de un trámite por ID, con URL oficial, última actualización, atribución a AGESIC y licencia de los datos.
  • list_uruguay_government_organismos() — listado de organismos del Estado que publican trámites.
  • find_uruguay_procedure_offices(procedure_id, departamento?) — oficinas físicas donde se gestiona un trámite, con filtro opcional por departamento.
  • Resource: tramite://{id} — para que el LLM traiga el trámite completo como recurso MCP.

Cada respuesta incluye url_oficial, last_updated_at, atribución a AGESIC y licencia de los datos. Esto permite que el asistente cite el enlace oficial y advierta sobre la frescura de la información.

Arquitectura

El proyecto tiene una arquitectura deliberadamente simple:

CKAN API → XML mensual → ingest streaming → SQLite → FTS5 / embeddings → MCP HTTP

Stack

  • Go + Gin (HTTP routing).
  • SQLite con modernc.org/sqlite (driver Go puro, sin CGO).
  • FTS5 para búsqueda léxica (tokenizer unicode61 remove_diacritics 2).
  • OpenAI embeddings para búsqueda semántica.
  • mcp-go para el servidor MCP.
  • Docker Compose para deploy con volumen persistente.

Búsqueda en 3 modos

  • FTS5: búsqueda léxica rápida con SQLite.
  • Vector: embeddings en memoria con similitud coseno.
  • Híbrida: combinación de FTS + vector con Reciprocal Rank Fusion.

Por defecto el servidor usa FTS5. Si hay embeddings cargados y OPENAI_API_KEY configurada, intenta búsqueda híbrida. Si no puede, cae a FTS5 y lo declara en la respuesta.

Ingest reproducible

El ingest descarga el XML real de AGESIC, lo parsea en streaming con encoding/xml, calcula un content_hash por trámite y hace upsert solo cuando detecta cambios. Si un trámite cambia, se resetea su embedding para regenerarlo. Si un trámite desaparece del XML actual, se marca con soft delete.

Cómo se instala y se prueba

Claude Desktop o Claude.ai (custom connector remoto)

El endpoint debe ser público y accesible por HTTPS. Ejemplo: https://tramites.example.com/mcp. En Claude Desktop o Claude.ai:

Settings → Customize → Connectors → Add custom connector

Claude Code (desarrollo local)

claude mcp add --transport http tramites-gub-uy http://localhost:8080/mcp

Ejemplo: buscar trámites de pasaporte vía cURL

curl -sS -X POST https://tramites.example.com/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"search_uruguay_government_procedures","arguments":{"query":"pasaporte","limit":3}}}'

Deploy con Docker / Dokploy

Pensado para correr en Docker simple. El primer arranque puede cargar la base automáticamente con BOOTSTRAP_INGEST=true: si la tabla de trámites está vacía, el entrypoint corre /app/ingest --if-empty y después levanta el server. La base vive en un volumen persistente (/data/tramites.db), así que reinicios posteriores arrancan rápido.

Hardening y operación

  • Rate limit en memoria: 60 requests/min por IP.
  • Logs JSON estructurados, con eventos específicos de búsquedas MCP (event=mcp_query).
  • Healthcheck HTTP para orquestadores.
  • Dockerfile multi-stage.
  • GitHub Actions con go build, go test, go vet y staticcheck.
  • Cron de ejemplo para refrescar datos el día 5 de cada mes (cuando AGESIC publica el dump).

Decisiones de diseño

SQLite alcanza perfectamente para este volumen de datos. FTS5 resuelve muy bien la búsqueda textual. Para búsqueda semántica, cargar los embeddings en memoria es más simple que meter una base vectorial adicional, y para unos miles de trámites funciona sin drama.

El contenedor arranca con una DB funcional en el primer deploy. En un proyecto como este, la experiencia de despliegue importa: si alguien lo corre en Dokploy, debería poder levantar el servicio y empezar a consultar datos sin ejecutar cinco comandos manuales.

El proyecto es deliberadamente sobrio. No es una plataforma enorme. Es una pieza chica, mantenible y desplegable.

Próximos pasos posibles

  • Endpoint administrativo para recargar el índice vectorial después de un ingest.
  • Métricas Prometheus.
  • Autenticación opcional para deployments públicos.
  • Panel web simple para explorar trámites.
  • Caché de respuestas MCP frecuentes.
  • Job mensual integrado en el propio contenedor (hoy es cron externo).

La base ya está: datos abiertos, ingest reproducible, búsqueda en 3 modos, MCP y deploy reproducible.

Preguntas frecuentes

Sobre MCP y este proyecto

Es un estándar abierto creado por Anthropic en noviembre de 2024 que define cómo un modelo de lenguaje (Claude, GPT, Gemini) puede conectarse a herramientas externas y recursos. En lugar de pedirle al modelo que recuerde información, el modelo invoca herramientas y trae datos verificables de la fuente.
Del dataset abierto "Guía de Trámites" publicado por AGESIC en catalogodatos.gub.uy bajo licencia Creative Commons Attribution 4.0 (CC BY 4.0). El servidor descarga el dump XML mensual, lo importa a SQLite y expone tools MCP para consultarlo.
Tres modos: FTS5 (búsqueda léxica con SQLite, tokenizer unicode61 sin diacríticos), vector search (embeddings OpenAI con similitud coseno), e híbrida (combinación con Reciprocal Rank Fusion). Por defecto FTS5; si hay embeddings cargados y OPENAI_API_KEY, intenta híbrida.
Como custom connector remoto vía HTTPS: Settings → Customize → Connectors → Add custom connector, apuntando a https://tu-dominio.com/mcp. Para desarrollo local con Claude Code: claude mcp add --transport http tramites-gub-uy http://localhost:8080/mcp.
Sí. La arquitectura (Go + SQLite + FTS5 + MCP HTTP Streamable) es replicable para cualquier dataset interno. Hago esto como servicio: ver "Desarrollo de agentes y MCPs".
Datos: AGESIC, bajo Creative Commons Attribution 4.0 International (CC BY 4.0). Código del servidor: MIT. La intención es que sea infraestructura común para cualquiera que quiera construir sobre los datos abiertos del Estado uruguayo.

¿Tu organismo necesita un MCP a medida?

Si tu organización quiere conectar agentes de IA con sus datos o sistemas internos — trámites, bases de datos, APIs, workflows — construyo MCPs a medida con foco en producción, no demo. Misma arquitectura: chico, mantenible, desplegable.