Skip to main content
Glama
convocatorias-mcp

convocatorias-mcp

by Adrada03

convocatorias-mcp

Servidor MCP que vigila los boletines oficiales españoles y encuentra convocatorias que encajan con tu perfil: becas, ayudas, empleo público y subvenciones. El BOE publica a diario y es inabarcable a mano, así que la gente pierde convocatorias que le aplicaban.

Se usa conversando con Claude (Desktop o Code) en lenguaje natural, y trae un dashboard estático para consultar sin instalar nada.

El dashboard filtrando convocatorias por palabra clave y por watchlist, y abriendo después el texto oficial de una de ellas en el BOE

El dashboard, filtrando por palabra y por watchlist. Termina donde tiene que terminar: en el texto oficial del BOE, que es la única fuente válida. Pruébalo en convocatorias.alvaroadrada.dev.

Estado: en construcción. Funciona end-to-end contra el BOE real: el índice local tiene unas 15.000 convocatorias de los últimos 90 días, con servidor MCP de ocho tools, ingesta incremental, ranking por watchlists y dashboard publicado en convocatorias.alvaroadrada.dev. Pendiente: publicar la extensión .mcpb en una release.

Lo que esta herramienta no hace

No te dice si cumples los requisitos de una convocatoria. Resume qué pide, señala coincidencias y discrepancias con tu perfil y enlaza siempre al texto oficial, que es la única fuente válida. Equivocarse sobre si alguien puede optar a una ayuda tiene consecuencias reales, así que esa afirmación no la hace el programa.

Esto no es un descargo de responsabilidad decorativo: está en el modelo de datos (AVISO_NO_ELEGIBILIDAD) para que no se pueda omitir al escribir la descripción de un tool.

Related MCP server: BOE & EUR-Lex MCP Server

Cómo funciona: matching en dos niveles

El trabajo se reparte entre lo que un programa hace bien y lo que un modelo hace bien:

Nivel 1, en código. Un filtro barato y determinista sobre el índice local: palabras clave y sección del boletín. Rápido, repetible y sin llamadas a ningún modelo. Devuelve candidatas.

Nivel 2, en el modelo. get_convocatoria(id) descarga el texto íntegro y se lo entrega a Claude para que lea los requisitos. Renta, edad, unidad familiar, empadronamiento y méritos vienen en prosa jurídica con formatos infinitos: no se parsean con expresiones regulares. Esa comprensión es exactamente para lo que sirve MCP.

Instalación y uso

Hace falta Python 3.11 o superior.

git clone https://github.com/Adrada03/convocatorias-mcp
cd convocatorias-mcp
python -m venv .venv
.venv/Scripts/activate        # en Linux o macOS: source .venv/bin/activate
pip install -e .

Puebla el índice local y comprueba que todo está en su sitio:

convocatorias-mcp ingest
convocatorias-mcp doctor

Conectarlo a Claude Code

claude mcp add --transport stdio --scope user convocatorias -- /ruta/a/convocatorias-mcp/.venv/Scripts/convocatorias-mcp.exe

En macOS o Linux el ejecutable está en .venv/bin/convocatorias-mcp en vez de .venv/Scripts/convocatorias-mcp.exe. Comprueba la conexión con:

claude mcp list

convocatorias debe aparecer como Connected.

Conectarlo a Claude Desktop

Las versiones recientes de Claude Desktop ya no leen claude_desktop_config.json a mano: instalan servidores MCP locales como extensiones de escritorio (.mcpb), de un solo clic.

El .mcpb todavía no está publicado en una release. El bundle ya se construye y se ha verificado desempaquetándolo fuera de la carpeta donde se genera (el primer intento solo arrancaba en la máquina que lo construyó, y se retiró en vez de dejarlo descargable; la causa está en Construir el .mcpb). Hasta que se publique, se construye con las instrucciones de esa sección, o se usa Claude Code, arriba, que no necesita empaquetado.

Una vez tengas el .mcpb, se instala con cualquiera de estas tres opciones:

  • Haz doble clic en el archivo .mcpb.

  • Arrástralo a la ventana de Claude Desktop.

  • Settings → Extensions → Advanced settings → Install Extension… y selecciónalo.

Irá sin firmar (no hay certificado de desarrollador detrás), así que Claude Desktop avisará de ello al instalarlo.

Si usas una versión de Claude Desktop anterior a las extensiones .mcpb (antes de 2025), todavía puede valer la configuración manual en claude_desktop_config.json (%APPDATA%\Claude\ en Windows, ~/Library/Application Support/Claude/ en macOS):

{
  "mcpServers": {
    "convocatorias": {
      "command": "C:\\ruta\\a\\convocatorias-mcp\\.venv\\Scripts\\convocatorias-mcp.exe"
    }
  }
}

En versiones nuevas este archivo simplemente no se lee: si lo editas y no pasa nada, es señal de que te hace falta el .mcpb de arriba, no de que lo hiciste mal.

Una vez conectado (por Code o por Desktop), pregúntale en lenguaje natural: "¿qué convocatorias de oposiciones hay en el índice?" o "búscame subvenciones publicadas esta semana".

Es un servidor stdio local: no hay servicio alojado, ni backend, ni claves. Lo arranca tu Claude en tu máquina y lee un índice SQLite de tu disco. Cualquiera puede usarlo con su propia cuenta.

Construir el .mcpb: por qué un venv no vale

El .mcpb no se versiona en el repo ni se genera a mano, y el primer intento —empaquetar un venv y apuntar el manifest a su convocatorias-mcp.exe— resultó estar roto de una forma que no se ve: funcionaba en la máquina donde se construyó y en ninguna otra. Merece la pena contarlo porque el fallo es silencioso.

Un venv no es relocalizable. Dos cosas lo atan a su sitio:

  1. El .exe del entry point lleva una ruta absoluta. pip genera los console-scripts con un shebang embebido apuntando al intérprete del venv que los creó:

    #!C:\...\build\mcpb\server\venv\Scripts\python.exe

    Instalado en otra ruta, el .exe reenvía igualmente al directorio de build. En la máquina donde se construyó, eso existe y arranca; fuera, no.

  2. El venv no contiene el runtime. Ni Lib/ ni python3xx.dll viven dentro: pyvenv.cfg apunta a la instalación base de Python de esa máquina, que no viaja en el bundle.

El efecto combinado es que la extensión instalada no ejecutaba ni siquiera su propia copia de site-packages, sino la del directorio de build. Se detectó mirando el árbol de procesos, no los logs, porque nada fallaba.

El empaquetado actual no depende de nada de la máquina: el intérprete va dentro.

pip install --target mcpb/server/lib .              # dependencias, arbol plano
curl -O https://www.python.org/ftp/python/3.12.10/python-3.12.10-embed-amd64.zip
unzip python-3.12.10-embed-amd64.zip -d mcpb/server/python
npm install -g @anthropic-ai/mcpb
mcpb pack mcpb dist/convocatorias-mcp.mcpb

pip install --target produce un árbol de paquetes sin rutas absolutas, y el manifest lanza el Python embebido del propio bundle (${__dirname}/server/python/python.exe -m convocatorias_mcp.cli) en vez de un console-script, que es lo que embebía el shebang.

Por qué el intérprete tiene que ir dentro

La versión intermedia usaba el python del sistema y el PYTHONPATH. Parecía razonable —hay Python 3.12 en el PATH, python --version lo confirma— y fallaba igualmente con un error que no dice lo que pasa:

ImportError: cannot import name 'ArgsKwargs' from 'pydantic_core._pydantic_core'

Claude Desktop construye su propio PATH y antepone la instalación de Python que detecta, que no tiene por qué ser la que gana en el PATH del sistema. El log de la extensión lo enseña:

Using MCP server command: ...\Programs\Python\Python314\python.exe
paths: [ '...\Programs\Python\Python314',   <- antepuesta por la app
         ...
         'C:\Users\<usuario>\',             <- el 3.12 del PATH, por detrás

Los wheels compilados del bundle (lxml, pydantic_core, cryptography) son cp312, así que bajo 3.14 no cargan. No es un problema de configuración del usuario: con dos Pythons instalados, cuál se usa no lo decide el usuario ni el manifest. La única forma de que el ABI case siempre es llevar el intérprete dentro.

Eso tiene un precio que conviene saber: el bundle pasa de 20 a 31 MB y queda atado a una plataforma. compatibility.platforms dice win32 porque tanto los wheels como el Python embebible son de Windows; dar soporte a macOS y Linux es construir un .mcpb por sistema, en CI.

El bundle se prueba desempaquetándolo en otra carpeta y arrancándolo allí con un PATH hostil (Python 3.14 el primero, y también sin ningún Python). Si solo se prueba donde se construyó y con el PATH del desarrollador, ninguno de estos dos fallos aparece.

Dos detalles más que solo se ven al ejecutarlo:

  • pip install --target no procesa los ficheros .pth, y pywin32 —que llega como dependencia de mcp— reparte pywintypes por win32/lib. Sin esas carpetas en el sys.path, el servidor muere al importar. Van en el python312._pth del intérprete embebido, que además ignora el PYTHONPATH del entorno: otra cosa menos de la que depender.

  • Las watchlists no viajan en el bundle, ni deben: son datos personales. El manifest las pide como user_config de tipo file y las pasa en CONVOCATORIAS_WATCHLISTS_PATH, así que Claude Desktop genera la casilla al instalar. Sin ella el servidor funciona, pero ordena por fecha sin puntuar.

compatibility.runtimes.python no se declara, y ya no hace falta: no hay requisito externo que declarar. Cuando lo había, ese campo resultó ser una puerta y no documentación —Claude Desktop deshabilitaba el botón de instalar si no encontraba Python donde lo busca, aunque estuviese instalado y funcionando—, lo que dejaba fuera precisamente a quien cumplía el requisito.

Tools que expone

Tool

Qué hace

search_convocatorias

Filtro de nivel 1 sobre el índice: palabras clave, sección, fechas y watchlist. Devuelve metadatos y enlaces, no textos.

get_convocatoria

Una convocatoria con su texto oficial completo, para que el modelo lea los requisitos (nivel 2). La primera vez lo descarga del boletín y lo cachea; solo bajo demanda, nunca las 15.000 por adelantado.

explain_match

Por qué una convocatoria encaja (o no) con cada watchlist, con el desglose del score por dimensiones.

deadlines_soon

Lo que vence en los próximos N días, de las convocatorias con plazo determinado.

mark_solicitada

Anota que la has solicitado, con notas y fecha. Es tu registro de solicitudes.

discard

Descarta una convocatoria, con su motivo. Desaparece de búsquedas y avisos.

list_solicitudes

Lo que has solicitado, con sus notas. Opcionalmente también lo descartado.

source_health

Hasta dónde llega el índice, su antigüedad y si tiene huecos. Sirve para distinguir "no hay nada que encaje" de "esas fechas no están ingeridas".

Falta run_digest, que llega con el digest por email. No está registrado: un tool declarado que no funciona es peor que uno que no existe, porque el modelo lo intentaría y se llevaría un error.

Toda respuesta lleva el aviso de no elegibilidad y la cita que exige la licencia del BOE. No es decorativo: hay tests de que ninguna respuesta puede salir sin ellos.

El ranking

Con watchlists configuradas, los resultados se ordenan por afinidad. El score sale de cuatro dimensiones, con estos pesos por defecto:

Dimensión

Peso

Qué mira

Título

0,55

Palabras clave de la watchlist en el título. La señal más directa.

Sección

0,25

Que la sección sea una de las que vigila, o al menos una de las que traen convocatorias.

Departamento

0,15

Palabras clave en el departamento o el epígrafe. Suma cuando acierta, no resta cuando no.

Ámbito

—

Inactiva. El BOE no publica el campo: está vacío en las 14.935. No resta (solo puntúa cuando ambos lados lo traen), pero nunca suma.

Tres decisiones que importan:

  • El scoring no mira el texto completo. Puntuar con el texto obligaría a descargar 15.000 documentos y rompería el reparto en dos niveles: el score ordena candidatas, y la profundidad la pone el modelo sobre las pocas que suben.

  • Una sección no vigilada es una exclusión, no un cero. Si vigilas II.B y III, una licitación de V.A no es "poco afín": es que no la has pedido.

  • Las keywords de una watchlist son un OR. Un perfil con cuatro intereses no busca convocatorias que los mezclen todos; en una búsqueda explícita, en cambio, las palabras que escribes se exigen todas.

Los pesos viven en ranking.Pesos para poder afinarlos con el uso sin tocar la lógica. Y explain_match enseña el desglose, que es la forma de entender por qué sale lo que sale:

empleo-publico-tic: score 0.5416
  titulo         peso=0.55  cumple=0.3333 -> 0.1833
    1 de 3 palabras clave en el título: sistemas.
  departamento   peso=0.15  cumple=0.0    -> 0.0
    Ninguna de las 3 palabras clave aparece en el departamento o el epígrafe.
  seccion        peso=0.25  cumple=1.0    -> 0.25
    La sección II.B está entre las que vigila esta watchlist (II.B).

El score mide afinidad temática, no elegibilidad. Que algo puntúe 1,0 no dice nada sobre si cumples sus requisitos.

Los plazos, y por qué muchos faltan

plazo_fin se extrae del texto oficial cuando se descarga, y solo si el texto lo dice de forma explícita e inequívoca: la frase tiene que hablar de un plazo y de presentar solicitudes, y dar una sola fecha. Si no, queda en desconocido y se dice con palabras.

Sobre una muestra real de 18 extractos de convocatorias de la sección V.B:

  • 5 declaran una fecha absoluta y se extraen bien.

  • 2 usan plazos relativos ("veinte días hábiles desde la publicación"), que no se calculan: haría falta el calendario de festivos nacionales, autonómicos y locales, y equivocarse en un día es equivocarse.

  • 11 no declaran plazo en el extracto, porque remiten a las bases.

Esa proporción es la razón de que deadlines_soon avise de que su lista no es exhaustiva. Un plazo equivocado es peor que no tener plazo, porque alguien se fiaría de él.

Durante el desarrollo el extractor sí se equivocó una vez, y merece la pena contarlo: cogió el 31 de diciembre de 2028 de la frase "la beca no podrá superar la fecha límite de 31 de diciembre de 2028". Era la duración de la beca, no su plazo de solicitud. Por eso ahora se exige que la frase hable de presentar algo, y por eso hay un test con ese texto exacto.

El índice se pone al día solo

Trabajar con datos viejos sin saberlo es peor que esperar unos segundos, así que el servidor comprueba la frescura del índice en cada consulta:

  • Hasta 7 días de atraso (configurable con CONVOCATORIAS_AUTO_INGEST_DIAS): ingiere los días que falten antes de responder. Siete días son como mucho siete peticiones.

  • Más de 7 días: no bloquea la consulta. Responde con lo que haya e incluye un aviso de que el índice está desactualizado y de que hay que ejecutar convocatorias-mcp ingest, porque un backfill de semanas no cabe en el tiempo de una respuesta.

  • Solo se intenta una vez por sesión: si la red está caída, no se hace esperar al usuario en cada consulta.

Y todas las respuestas, estén al día o no, dicen indice_cubre_hasta y dias_de_atraso. El objetivo es que el modelo nunca afirme que no hay convocatorias cuando lo que pasa es que no ha mirado.

El vocabulario del boletín

Los títulos del BOE están en lenguaje administrativo. Una oposición no se titula "oposiciones": se titula "Resolución de 8 de octubre de 2026, de la Subsecretaría, por la que se convoca proceso selectivo para ingreso...". Buscar "oposiciones" sobre 2.255 convocatorias de la sección II.B devolvía cero, y era correcto.

La solución principal está en la descripción del tool, que enseña al modelo qué contiene cada sección y cómo traducir la intención del usuario: el cliente es un LLM, y eso es su trabajo, no el de un filtro LIKE. Como red de seguridad hay además una tabla corta de sinónimos (oposiciones → proceso selectivo, pruebas selectivas...), ampliable con un sinonimos.yaml propio:

cp sinonimos.example.yaml sinonimos.yaml

Cada clave del fichero sustituye la de serie, así que se puede corregir una entrada y no solo añadir.

Comandos

Comando

Qué hace

convocatorias-mcp

Arranca el servidor MCP por stdio. Es como lo invoca Claude Desktop.

convocatorias-mcp ingest [--backfill N] [--fuente X] [--forzar]

Puebla el índice con los sumarios que falten. Incremental: no repite fechas ya resueltas, pero sí reintenta las que fallaron. Imprime una línea por día.

convocatorias-mcp export [--dias N] [--todo] [--salida RUTA]

Genera el JSON que consume el dashboard.

convocatorias-mcp doctor [--check-api]

Revisa la instalación.

La fuente de desarrollo

Además del BOE hay una fuente fixture que lee sumarios guardados en tests/fixtures/. No toca la red, así que sirve para desarrollar y para que cualquiera pruebe el proyecto entero sin depender de que el BOE publique hoy:

convocatorias-mcp ingest --fuente fixture

Sus ids empiezan por fixture: en vez de boe:, porque el prefijo de la fuente forma parte del id. Si habías poblado el índice con ella, bórralo antes de ingerir el BOE real para no quedarte con la misma convocatoria bajo dos ids. La ruta del índice la dice convocatorias-mcp doctor.

Diseño en una frase

Cada boletín es una fuente que implementa la interfaz BoletinSource (async fetch_dia(fecha) -> list[Convocatoria]). La ingesta, el índice y el servidor MCP no conocen ningún boletín concreto, así que añadir uno es crear un fichero, implementar fetch_dia() y registrarlo.

El detalle completo —capas, invariantes, ingesta, ranking, plazos y empaquetado, con diagramas— está en docs/ARQUITECTURA.md.

El pivote: de ofertas de empleo a convocatorias oficiales

Este repo empezó siendo jobscout-mcp, un agregador de ofertas de empleo de portales españoles. El historial de commits lo conserva entero, porque la razón del cambio es la parte interesante.

Al verificar las fuentes una por una, ninguna resultó viable con el criterio de consentimiento explícito que se describe más abajo:

  • Tecnoempleo se podía leer técnicamente, pero su robots.txt excluye por nombre a ClaudeBot, AnthropicBot y otros agentes de IA. Pasar por la regla * habría sido una tecnicalidad que no respeta la intención del operador, y menos en una herramienta pensada para que la instale cualquiera.

  • InfoJobs exige aprobación previa como partner.

  • LinkedIn, Indeed y Glassdoor prohíben el scraping en sus términos.

  • Adzuna sí tenía API pública y términos compatibles, pero necesita registro y credenciales para poder confirmar siquiera que cubre España.

Los boletines oficiales son el caso opuesto: datos abiertos por ley, con licencia de reutilización explícita y una API pública sin autenticación. En vez de pelear por acceder a datos que alguien no quiere compartir, el proyecto se mudó a datos que existen precisamente para ser reutilizados. Y el problema que resuelve es más real: una oferta de empleo la ves en cinco portales, pero una beca publicada en el BOE un martes no la ve nadie.

La arquitectura sobrevivió al cambio sin tocarse: la interfaz de fuente, el SourceError, el rate limiting y el patrón de fuentes → normalización → índice → ranking → digest son los mismos. Solo cambió el dominio.

Fuentes de datos

Fuente

Estado

Método

Consentimiento

BOE (Boletín Oficial del Estado)

Funcionando

API de datos abiertos

Datos abiertos con licencia de reutilización

Boletines autonómicos (BOCM, BOJA, DOGC…)

Pendiente

Por verificar

Por verificar

Los autonómicos no están implementados: antes hay que comprobar si ofrecen API de datos abiertos, igual que se verificó el BOE. Es la limitación conocida más importante — una convocatoria de vivienda protegida de la Comunidad de Madrid no aparece en el BOE, así que hoy este programa no la encuentra.

API del BOE

GET https://www.boe.es/datosabiertos/api/boe/sumario/{AAAAMMDD}
Accept: application/json

Sin autenticación, sin clave y sin registro. Devuelve sumarios por fecha y no permite buscar: de ahí que haga falta un índice local.

Verificado con peticiones reales el 19/10/2026, con el User-Agent de este proyecto:

  • El sumario del 17/10/2026 trae 271 items en 7 secciones (313 KB de JSON).

  • Los domingos responden normalmente 404, con cuerpo XML aunque pidas JSON. Eso no es un error: es ausencia de publicación, y se registra como tal para no ensuciar la salud de la fuente ni reintentar la fecha para siempre.

  • Pero no todos los domingos. En el backfill de 90 días, 11 domingos devolvieron 404 y 2 devolvieron 200 con contenido: el BOE publicó ediciones extraordinarias el 26/07 y el 09/08/2026, por la emergencia de incendios y por controles fronterizos.

  • Esos dos sumarios llegan además con otra forma: un nivel texto extra entre seccion y departamento, y entre departamento y epigrafe. El parser maneja las dos, y resultó que el envoltorio también aparecía en los sumarios normales (en la sección V.C), donde estaba perdiendo un anuncio en silencio.

  • robots.txt (494 KB) permite el acceso a la API y no excluye a ningún agente de IA.

  • Solo admite GET; un POST devuelve 403.

Licencia de reutilización

Los datos vienen de la sede electrónica de la Agencia Estatal Boletín Oficial del Estado, y su reutilización se rige por las condiciones de reutilización del BOE, la licencia tipo aprobada por Resolución de la Agencia de 27 de junio de 2024 al amparo de la Ley 37/2007 sobre reutilización de la información del sector público.

Esa licencia permite el uso comercial y no comercial, y obliga a dos cosas que este proyecto cumple:

  1. Citar la fuente con un enlace a https://www.boe.es. La cita que exige la licencia es "Fuente de los datos: Agencia Estatal Boletín Oficial del Estado", y aparece en el digest, en el dashboard y en las respuestas del servidor. La declara la propia fuente (cita_requerida), no la plantilla de cada salida, para que cumplirla no dependa de que nadie se acuerde.

  2. No desnaturalizar el sentido de la información. Por eso el programa no interpreta requisitos ni reescribe convocatorias: enlaza al texto oficial y deja que lo lea quien tiene que decidir.

Esta herramienta no es un servicio oficial ni está respaldada por el BOE.

Política de fuentes

Este proyecto solo consulta fuentes que han dado consentimiento explícito para que un tercero lea sus datos. No es una limitación técnica: casi todas las fuentes descartadas aquí se podrían leer igualmente. Es una decisión de diseño, y está documentada porque la considero parte del trabajo.

Por orden de preferencia:

  1. Datos abiertos con licencia de reutilización. El operador publica los datos para que se reutilicen y dice bajo qué condiciones.

  2. API pública documentada para terceros.

  3. Feed RSS o JSON publicado para ser consumido.

  4. Scraping solo si robots.txt permite nuestro caso de uso y no excluye agentes de IA.

Y una regla que manda sobre las cuatro:

Si una fuente excluye agentes de IA por nombre, queda descartada, aunque técnicamente pudiéramos pasar.

Esta herramienta canaliza información hacia un asistente de IA. Que nuestro User-Agent no coincida con los que el operador ha bloqueado es una tecnicalidad que no respeta su intención. Es un problema de consentimiento, no de carga: bajar el ritmo de peticiones no lo resolvería.

Fuentes descartadas

Fuente

Motivo

Verificado

Tecnoempleo

Su robots.txt excluye con Disallow: / a ClaudeBot, AnthropicBot, anthropic-ai, Claude, Claude-Web, Claude-SearchBot y GPTBot.

2026-10-19, petición real

LinkedIn, Indeed, Glassdoor

Sus términos de servicio prohíben el scraping.

Decisión de diseño

InfoJobs

Su API requiere aprobación previa como partner.

—

Adzuna

API y términos compatibles, pero exige registro y credenciales. Descartada al pivotar, no por su política.

2026-10-19

Nota de diseño: robots.txt no gobierna una API

robots.txt se dirige a rastreadores que descubren contenido siguiendo enlaces. No es el documento que regula a un cliente de una API publicada para terceros ni a un reutilizador de datos abiertos. Dos ejemplos verificados: api.adzuna.com/robots.txt es User-agent: * / Disallow: /, y api.ashbyhq.com/robots.txt responde 401 Unauthorized.

Aplicar ahí la comprobación dejaría el proyecto sin fuentes, cumpliendo la letra de un documento que no va dirigido a nosotros mientras se ignora el contrato que sí lo está. Así que cada fuente declara de dónde viene su consentimiento (Consentimiento.DATOS_ABIERTOS, API_PUBLICA, FEED_PUBLICO o ROBOTS) y la comprobación de robots.txt se aplica solo a las que se apoyan en él. Lo que no cambia en ninguna fuente: el User-Agent identificado, el rate limiting y la caché.

Configuración

Watchlists

El ranking es opcional. Copia la plantilla y edítala:

cp watchlists.example.yaml watchlists.yaml
watchlists:
  - nombre: empleo-publico
    keywords: [proceso selectivo, pruebas selectivas]
    secciones: [II.B]
  - nombre: becas
    keywords: [beca, becas, matrícula]
    secciones: [III, V.B]
notificacion:
  avisar_plazo_dias: 3

Las keywords van en el vocabulario del boletín, no en el tuyo: sobre un índice real de 14.935 convocatorias, analista y programador aparecen 0 veces, y proceso selectivo 262. Y no hay min_score en el ejemplo a propósito: su defecto es 0, y subirlo es lo que más resultados hace desaparecer sin avisar (ver abajo).

convocatorias-mcp doctor cuenta cuántas casa cada watchlist contra tu índice y avisa si alguna casa casi nada, distinguiendo si la culpa es de las keywords o del min_score. Una watchlist mal configurada no da ningún error: callada, se confunde con "no hay nada que te encaje".

Hay varias watchlists, y no un perfil único, porque los criterios de una beca de informática no se parecen a los de una vivienda protegida: mezclarlos daría un score sin significado. El digest agrupa por watchlist.

Secciones que traen convocatorias: II.B oposiciones y concursos, III bases reguladoras de becas y subvenciones, V.A licitaciones públicas y V.B otros anuncios oficiales. Se aceptan tanto la etiqueta romana (III) como el código interno del BOE (3).

watchlists.yaml está en .gitignore: tus criterios son tuyos y el repo solo versiona la plantilla. Sin el fichero el servidor funciona igual, ordenando por fecha.

El comando doctor

Este servidor lo lanza Claude Desktop en segundo plano, así que cuando algo está mal configurado no hay ningún mensaje de error a la vista. doctor es el sitio donde mirar:

convocatorias-mcp doctor --check-api
[ ok ] Python: 3.12.10
[ ok ] watchlists.yaml: 2: becas-informatica (4 keywords, 2 secciones), empleo-publico-tic (3 keywords, 1 sección)
[ ok ] índice: C:\Users\...\convocatorias-mcp\convocatorias-index.sqlite3
[warn] contenido del índice: vacío; ejecuta 'convocatorias-mcp ingest' para poblarlo
[ ok ] API del BOE: sumario del 2026-10-19: HTTP 200, 371.771 bytes

Revisa las watchlists (y avisa si tienen un typo, en vez de ignorarlo), el estado del índice con sus huecos y, con --check-api, la conectividad real con el BOE. Devuelve código de salida 1 si algo impide funcionar.

Nunca imprime credenciales ni datos personales, ni completos ni parciales: su salida está pensada para pegarse en un issue. De las claves dice la longitud, de las watchlists cuenta las keywords sin listarlas, y la dirección del digest no se imprime ni enmascarada.

El dashboard

Una página HTML estática, sin backend y sin framework, para consultar el índice sin instalar nada. Se genera así:

convocatorias-mcp export
python -m http.server -d dashboard 8000

Y se abre en localhost:8000. Hace falta el servidor porque abriendo el fichero con doble clic el navegador bloquea la lectura de data.json; si pasa, la propia página lo explica y da el comando.

Permite buscar por texto, filtrar por watchlist, sección, estado y rango de fechas, y ordenar por fecha, por plazo o por afinidad. Lo que vence en siete días o menos va marcado en rojo, y lo del mes, en ámbar. Funciona en móvil y sigue el modo claro u oscuro del sistema.

Qué se exporta, y qué no

Solo metadatos. Nunca textos completos: el texto oficial vive en el BOE y la página enlaza a él. Ni siquiera se exporta la URL, porque se deriva del identificador; eran 940 KB de repetir la misma cadena 15.000 veces.

Los tamaños, medidos sobre el índice real de 14.935 convocatorias:

Variante

Crudo

Gzip

Todo, claves legibles

7,31 MB

926 KB

Todo, claves cortas y sin campos vacíos

4,42 MB

747 KB

30 días + coincidencias (por defecto)

1,66 MB

~290 KB

Por defecto se recorta a los últimos 30 días más todo lo que coincida con alguna watchlist, y la página lo dice en su cabecera junto a la fecha hasta la que llegan los datos. Una beca de hace dos meses que encaja con tu perfil es exactamente lo que no se puede perder por recortar.

El recorte no es por la red —gzipped el índice entero son 747 KB y GitHub Pages comprime— sino porque 4,4 MB de JSON son 15.000 objetos que un móvil tiene que parsear y mantener en memoria. Con --todo se exporta completo, que en escritorio va sobrado.

Las watchlists del sitio publicado

El dashboard desplegado usa watchlists.demo.yaml, con intereses inventados (investigación, empleo público TIC, ayudas al estudio y vivienda), para que se vea el ranking y los chips de afinidad funcionando sobre datos reales. La propia página avisa de que son de ejemplo.

Las watchlists de verdad viven en watchlists.yaml, que no se versiona: los criterios de cada uno dicen bastante de uno mismo, y eso no se publica.

Una de ellas lleva min_score: 0.6 a propósito. Sus keywords incluyen "becas", que tira de los sinónimos y arrastra "ayuda" y "subvención", palabras que inundan las secciones III y V.B: sin ese mínimo, esa watchlist casaría una de cada seis convocatorias del BOE. Es el ejemplo de para qué sirve min_score.

Despliegue

.github/workflows/pages.yml está escrito pero no activo: solo se puede lanzar a mano, y publicar requiere además activar Pages en el repositorio. El sitio irá en convocatorias.alvaroadrada.dev vía CNAME.

Antes de activarlo hay una decisión pendiente, y está anotada en el propio workflow: dashboard/data.json está en .gitignore porque se genera desde el índice local, así que hay que elegir si se versiona, se genera en CI con una ingesta programada o se sube como artefacto.

Acceso educado a las fuentes

Verificado con tests, no solo documentado:

  • User-Agent identificado y honesto: convocatorias-mcp/0.1.0 (+URL del repo). No se imita ningún navegador.

  • Retardo mínimo de 1,5 s entre peticiones al mismo dominio, compartido entre fuentes y no por instancia, para que un backfill concurrente no pueda duplicar el ritmo.

  • Si un sitio declara un Crawl-delay mayor, gana el del sitio.

  • Índice local agresivo: una fecha ya ingerida no se vuelve a pedir, y un texto completo ya descargado tampoco.

  • Fechas en zona horaria Europe/Madrid, que es cuando publica el BOE.

Desarrollo

python -m venv .venv
.venv/Scripts/activate        # en Linux o macOS: source .venv/bin/activate
pip install -e ".[dev]"
pytest
ruff check .

Ningún test toca la red: las fuentes HTTP se prueban con respx y los parsers con sumarios reales del BOE guardados en tests/fixtures/.

Licencia

MIT. Ver LICENSE. Los datos del BOE se reutilizan bajo sus condiciones de reutilización.

Available Tools

8 tools
deadlines_soonPlazos que vencenA
Read-onlyIdempotent

Convocatorias con el plazo abierto que cierran dentro de N días.

Úsalo para "¿qué se me acaba pronto?" o para un repaso semanal.

IMPORTANTE: solo aparecen las convocatorias cuyo plazo se ha podido determinar leyendo su texto. Muchas lo expresan como "veinte días hábiles desde la publicación", que no se puede calcular sin el calendario de festivos, y esas no salen aquí. Esta lista no es exhaustiva: no le digas al usuario que no le vence nada más.

Args: dias: Ventana en días naturales desde hoy. Por defecto 7. limite: Máximo de resultados.

NameRequiredDescriptionDefault
diasNo
limiteNo

Output Schema

NameRequiredDescription

No output parameters

TDQS

A4.3/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/closed-world, and the description adds the single most important behavioral trait: results are partial, because deadlines expressed as 'veinte días hábiles desde la publicación' cannot be computed and are silently absent. It even warns against telling the user nothing else is due, which prevents a real downstream error.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose and the two usage scenarios come first, then the critical caveat, then the Args block. The IMPORTANT paragraph is long but earns its place; the Args block mildly repeats schema defaults but compensates for the empty schema titles.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists so return shape needn't be described; what remains — scope, non-exhaustiveness, and both parameter meanings — is fully covered. An agent can call this correctly and interpret the results without guessing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0% — parameters are bare 'Dias'/'Limite' integers — so the description carries the full burden, and it does: dias is a window in natural days from today, limite caps results. Both are explained; only limite lacks a note on truncation behavior.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a concrete resource (convocatorias con plazo abierto) and a precise filter (cierre dentro de N días), so the agent knows exactly what it returns. It does not name search_convocatorias as the broader alternative, so sibling differentiation is left implicit.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly frames the use case — '¿qué se me acaba pronto?' or a weekly review — which is a clear when-to-use signal. It stops short of saying when not to use it or pointing to search_convocatorias for the full list.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

discardDescartarA
Idempotent

Descarta una convocatoria para que no vuelva a aparecer.

Llámalo cuando el usuario diga que algo no le interesa o no le aplica. Las descartadas se omiten de las búsquedas y de los avisos de plazo.

Guarda el motivo si el usuario lo da: sirve para entender después por qué se descartó, y para afinar sus watchlists.

Args: id: El campo "id" de un resultado de search_convocatorias. motivo: Por qué no le interesa, con las palabras del usuario.

NameRequiredDescriptionDefault
idYes
motivoNo

Output Schema

NameRequiredDescription

No output parameters

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare non-destructive, idempotent, closed-world behavior, so the bar is lower; the description nevertheless adds real consequence beyond them — discarded items are hidden from searches and deadline alerts, and the motivo is persisted to tune watchlists later. It does not say whether discarding can be undone, which is the main remaining gap.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the one-line purpose, then when-to-call, then effect, then argument semantics. Every block earns its place; the Args section is slightly redundant with the schema titles but adds semantic detail rather than repeating types.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present and annotations covering the safety profile, the description supplies what's missing: trigger condition, visibility effect, and parameter provenance. Only the reversibility/undo story and any relationship to mark_solicitada are absent.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must carry the semantics, and it does: id is identified as the 'id' field from a search_convocatorias result, and motivo is explained as the user's own words about why it was discarded, plus its downstream use. Both parameters gain meaning not present in the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Descarta una convocatoria') plus the scope of the effect ('para que no vuelva a aparecer'). It also ties the id back to sibling tool search_convocatorias, so an agent can distinguish this from get_convocatoria or mark_solicitada.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives an explicit trigger condition: call it when the user says something doesn't interest them or doesn't apply. It also states the consequence (excluded from searches and deadline notices), which clarifies the intended context. It stops short of naming alternatives or a when-not case, so not a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

explain_matchPor qué encajaA
Read-onlyIdempotent

Explica por qué una convocatoria encaja (o no) con las watchlists.

Úsalo cuando el usuario pregunte "¿por qué me sale esto?" o "¿por qué no me sale lo que busco?". Devuelve el desglose del score por dimensiones: palabras clave en el título, sección, departamento y ámbito.

Ojo con lo que significa: el score mide afinidad temática, no elegibilidad. Que puntúe alto no dice nada sobre si el usuario cumple los requisitos; eso está en el texto y se lee con get_convocatoria.

Args: id: El campo "id" de un resultado de search_convocatorias. watchlist: Para explicar solo una. Sin esto se explican todas.

NameRequiredDescriptionDefault
idYes
watchlistNo

Output Schema

NameRequiredDescription

No output parameters

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/closed-world, so the safety profile is covered. The description adds genuinely new behavioral context: the return shape (score breakdown by title keywords, section, department, ámbito) and the crucial caveat that the score measures thematic affinity, not eligibility. It doesn't cover error behavior for an invalid id, keeping it below a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the purpose, then usage triggers, then the important semantic caveat, then Args. Every section earns its place, though the Args block partially duplicates semantics already implied and adds some length.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Output schema exists, yet the description still usefully summarizes what is returned (per-dimension score breakdown). With annotations covering safety, parameters explained, and eligibility caveat stated, an agent has everything needed to call and interpret this correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description must carry the load, and it does: 'id' is identified as the field from a search_convocatorias result (linking it to the sibling that produces it), and 'watchlist' is explained as an optional filter whose omission explains all matches. This fully compensates for the empty schema; only the id's exact format is left implicit.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource: explains why a convocatoria matches or not against watchlists, and names the score-breakdown dimensions. Clearly distinguishable from search_convocatorias (finding items) and get_convocatoria (reading requirements).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly names the triggering questions ('¿por qué me sale esto?', '¿por qué no me sale lo que busco?') and states the exclusion: eligibility is not covered here and must be read with get_convocatoria. Both when-to-use and the alternative are given.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_convocatoriaLeer una convocatoriaA
Read-onlyIdempotent

Devuelve una convocatoria con su TEXTO OFICIAL COMPLETO.

Llámalo cuando una convocatoria de search_convocatorias parezca encajar y haga falta leer sus requisitos: plazos, titulación, renta, méritos, baremos. Esos requisitos vienen en prosa jurídica y hay que leerlos; no se deducen del título.

La primera vez descarga el texto del boletín y lo guarda, así que la siguiente llamada es instantánea. Pídelo solo de las convocatorias que de verdad interesen: son 15.000 en el índice y descargarlas todas sería abusar de una fuente pública.

Al resumir: di qué pide la convocatoria, señala qué coincide y qué no con lo que sepas del perfil del usuario, y enlaza siempre al texto oficial. No afirmes que cumple o no cumple los requisitos: esa conclusión no es tuya, y equivocarse tiene consecuencias reales.

Args: id: El campo "id" de un resultado de search_convocatorias, con el formato "boe:BOE-A-2026-21635".

NameRequiredDescriptionDefault
idYes

Output Schema

NameRequiredDescription

No output parameters

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well past the readOnly/idempotent annotations by disclosing caching behavior ("la primera vez descarga el texto del boletín y lo guarda, así que la siguiente llamada es instantánea") and the cost/politeness implication of hitting a public source. It also warns that requirements are in legal prose and must be read rather than inferred.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core behavior, then usage, then caching, then the arg format. The summarizing/eligibility block is somewhat long, but it is high-stakes guidance rather than filler; a slightly tighter phrasing would earn a 5.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present, the description needn't describe return structure, and it covers everything else: what to call it for, the id format, caching cost, and a hard boundary on drawing eligibility conclusions. Nothing needed to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description must carry the parameter, and it does: id is the "id" field of a search_convocatorias result, with the concrete format "boe:BOE-A-2026-21635". That is exactly the missing information an agent needs to construct a valid call.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ("Devuelve una convocatoria con su TEXTO OFICIAL COMPLETO") and immediately separates it from the sibling search_convocatorias, which only finds candidates. An agent knows this is the read-the-full-text step, not a search.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit trigger ("cuando una convocatoria de search_convocatorias parezca encajar y haga falta leer sus requisitos") plus an explicit restraint ("Pídelo solo de las convocatorias que de verdad interesen... descargarlas todas sería abusar de una fuente pública"). It even lists what to look for: plazos, titulación, renta, méritos, baremos.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_solicitudesSolicitudes presentadasA
Read-onlyIdempotent

Lista las convocatorias que el usuario ha marcado como solicitadas.

Úsalo para "¿a qué me he apuntado?" o para repasar lo presentado. Devuelve también las notas que se guardaron con cada una.

Args: incluir_descartadas: Añade además las descartadas, con su motivo.

NameRequiredDescriptionDefault
incluir_descartadasNo

Output Schema

NameRequiredDescription

No output parameters

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare read-only, idempotent, and closed-world behavior. The description adds useful scope beyond that: it says saved notes are returned and that incluir_descartadas adds discarded entries with their reason.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is short, front-loaded with the main purpose, and uses a brief usage note plus an Args section without wasted prose.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple read-only list tool with one optional parameter and an output schema, the description covers purpose, usage, returned notes, and the optional discarded-inclusion behavior. Nothing critical is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must carry parameter meaning. It explains that incluir_descartadas adds discarded items and includes their reason, which is useful beyond the bare schema, though it does not restate the default behavior.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: listing convocatorias that the user marked as solicitadas. This is clear, but it does not explicitly differentiate the tool from siblings such as search_convocatorias or get_convocatoria.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives a clear usage context with examples such as '¿a qué me he apuntado?' and 'repasar lo presentado'. It does not describe exclusions or explicitly name alternative tools for related tasks.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

mark_solicitadaMarcar como solicitadaA
Idempotent

Anota que el usuario ha solicitado una convocatoria.

Sirve de registro de solicitudes, que después se consulta con list_solicitudes. Llámalo cuando el usuario diga que la ha pedido, que la ha presentado o que ya la ha entregado.

Args: id: El campo "id" de un resultado de search_convocatorias. notas: Lo que el usuario quiera recordar: por dónde la presentó, qué documentación envió, un número de registro. fecha: Fecha de la solicitud en formato AAAA-MM-DD. Por defecto hoy.

NameRequiredDescriptionDefault
idYes
fechaNo
notasNo

Output Schema

NameRequiredDescription

No output parameters

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=false, idempotentHint=true, destructiveHint=false, so the safety profile is covered. The description adds real context beyond that: the write is a persistent record that will be read back later via list_solicitudes. It doesn't clarify behavior on duplicate submissions or whether the call disappears from search results, but the annotation set carries most of the burden.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose and trigger conditions are front-loaded, and the Args block is compact. Slightly verbose in the notas explanation, but every line carries usable information and nothing is filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values needn't be described, and annotations cover the safety profile; the mutation semantics and parameter provenance are well covered. The only gap is whether marking affects the convocatoria's visibility in search_convocatorias, which is useful but not essential for calling the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate, and it does for all three parameters: it tells the agent that 'id' comes from a search_convocatorias result (essential provenance), explains what belongs in 'notas', and gives the AAAA-MM-DD format plus the today default for 'fecha'. This is more than the schema conveys.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Anota que el usuario ha solicitado una convocatoria') and frames the result as a registry entry that is later consulted with list_solicitudes, which distinguishes it from the read-side sibling. An agent can tell this is the write counterpart to the solicitudes listing without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit trigger conditions: call it when the user says they have requested, submitted, or delivered the application. It also names the downstream consumer (list_solicitudes). It does not state when not to use it versus discard, which keeps it from a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_convocatoriasBuscar convocatoriasA
Read-onlyIdempotent

Busca convocatorias en el índice local de boletines oficiales.

Devuelve metadatos y enlaces, no el texto completo: para leer los requisitos de una convocatoria concreta, llama después a get_convocatoria.

IMPORTANTE: los títulos del BOE están escritos en lenguaje administrativo, no coloquial. Una oposición nunca se titula "oposiciones": se titula "Resolución de 8 de octubre de 2026, de la Subsecretaría, por la que se convoca proceso selectivo para ingreso en el Cuerpo...". Traducir la intención del usuario a ese vocabulario es tu trabajo, y el filtro más potente casi siempre es la sección, no las palabras.

Qué contiene cada sección:

  • "II.B": empleo público. Procesos selectivos, pruebas selectivas, concursos-oposición, concursos de traslados, listas de admitidos y excluidos, tribunales. Aquí está todo lo que la gente llama "oposiciones".

  • "III": bases reguladoras y convocatorias de becas, ayudas, subvenciones, premios y convenios. Aquí están las becas de verdad, con sus requisitos.

  • "V.B": extractos de convocatorias de subvenciones y otros anuncios oficiales. Suele ser la versión corta de algo publicado en III, y es la sección con más volumen.

  • "V.A": contratación pública. Licitaciones, adjudicaciones y formalizaciones de contratos. Útil para autónomos y empresas, no para quien busca empleo o una beca.

  • "I", "II.A", "IV", "V.C": normativa general, nombramientos, administración de justicia y anuncios particulares. Rara vez son convocatorias a las que alguien pueda optar.

Cómo construir una buena consulta:

  • "quiero oposiciones de informática" -> seccion="II.B" y keywords="informatica" (o "sistemas", "telecomunicaciones"). No pases "oposiciones" como keyword: no aparece en los títulos.

  • "becas para estudiar un máster" -> seccion="III" y keywords="becas".

  • "¿qué ha salido esta semana?" -> solo desde, sin keywords.

  • "ayudas para autónomos en Madrid" -> seccion="III" y keywords="autonomos", y revisa el ámbito en los resultados en vez de filtrar por él, que no siempre viene.

  • Si una búsqueda con varias palabras no devuelve nada, prueba con menos: las keywords se exigen TODAS.

Hay una tabla pequeña de sinónimos que traduce algunos términos coloquiales ("oposiciones" -> "proceso selectivo"), pero es una red de seguridad y no cubre el idioma entero: no te apoyes en ella.

Args: keywords: Palabras que deben aparecer TODAS en el título, sin distinguir mayúsculas ni acentos. Usa el vocabulario del boletín, no el del usuario. seccion: Sección del boletín, como "II.B" o "III". Es el filtro más útil; úsalo siempre que la intención del usuario esté clara. desde: Fecha de publicación mínima, en formato AAAA-MM-DD. hasta: Fecha de publicación máxima, en formato AAAA-MM-DD. watchlist: Nombre de una watchlist del usuario para quedarte solo con lo que ella hace coincidir. Si el usuario dice "lo de siempre" o "mis becas", mira primero qué watchlists tiene en la respuesta de source_health. include_vistas: Por defecto se omiten las ya enviadas en un digest. Ponlo a true si el usuario quiere revisar también esas. limite: Máximo de resultados, entre 1 y 200.

NameRequiredDescriptionDefault
desdeNo
hastaNo
limiteNo
seccionNo
keywordsNo
watchlistNo
include_vistasNo

Output Schema

NameRequiredDescription

No output parameters

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/closed-world, and the description adds substantial behavior beyond them: results are metadata and links rather than full text, already-digested items are omitted by default via include_vistas, keywords are AND-semantics so a narrower query is the recovery path, and keywords match ignoring case and accents. It also discloses the limits of the synonym table so the agent doesn't over-rely on it.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose and the get_convocatoria hand-off before the long section taxonomy, and each block is actionable rather than filler. It is nevertheless a lengthy definition, and the section-by-section enumeration could be tightened without losing routing value.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, yet the description still tells the agent what comes back (metadata and links) and when to escalate to get_convocatoria. Combined with full parameter coverage and section semantics, an agent has everything needed to build a correct query on the first attempt.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description carries the full burden and does so for all seven parameters: keyword matching semantics, section vocabulary with real values ("II.B", "III"), date format AAAA-MM-DD, watchlist sourcing, include_vistas default behavior, and the 1-200 bound on limite. This is meaning well beyond the bare typed schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a concrete verb and resource ("Busca convocatorias en el índice local de boletines oficiales") and immediately bounds scope: returns metadata and links, not full text. It also names the sibling get_convocatoria as the tool for reading requirements, so the agent can distinguish it without opening schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit routing: use get_convocatoria for full text, use source_health to discover watchlists when the user says "lo de siempre", and set include_vistas only when the user wants already-sent items. It goes further with worked intent-to-query mappings (oposiciones→seccion II.B, becas→III) and an exclusion (don't pass "oposiciones" as a keyword).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

source_healthEstado del índice y las fuentesA
Read-onlyIdempotent

Informa de hasta dónde llega el índice local y si hay huecos.

Úsalo cuando una búsqueda no devuelva nada y haya que distinguir "no hay convocatorias que encajen" de "el índice no tiene esas fechas", o cuando el usuario pregunte si los datos están al día.

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription

No output parameters

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint and openWorldHint=false, so the safety profile is covered. The description adds the key behavioral fact not in annotations: the local index may be incomplete and this tool reveals coverage limits, which explains otherwise-confusing empty results.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences: the first states what it returns, the second states when to call it. No filler, and the capability is front-loaded before the usage conditions.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present, rich annotations, and no inputs, nothing an agent needs in order to call this correctly is missing. The one caveat an agent must know — that results may be empty because of index gaps — is stated.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Zero parameters, so there is nothing for the description to disambiguate; baseline 4 applies. The description correctly implies a single global status report rather than a per-source or per-date query.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: reports how far the local index reaches and whether there are gaps. That diagnostic role is obviously distinct from every sibling (search_convocatorias, get_convocatoria, deadlines_soon, etc.), though no sibling is named explicitly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives two concrete triggers: a search that returns nothing where you must distinguish 'no matching items' from 'the index lacks those dates', and user questions about data freshness. This directly routes the agent away from re-running search_convocatorias and toward this tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 8 tool updatesv0.1.0
    • First observeddeadlines_soon
    • First observeddiscard
    • First observedexplain_match
    • First observedget_convocatoria
    • First observedlist_solicitudes
    • First observedmark_solicitada
    • First observedsearch_convocatorias
    • First observedsource_health

TDQS

A4.2/5.0

Scored across 8 tools

Disambiguation4/5

Each tool has a distinct role: search returns metadata, get_convocatoria fetches full text, discard/mark_solicitada track negative/positive interest, and explain_match/source_health explain results. Minor overlap exists between list_solicitudes (with incluir_descartadas) and the discard domain, and between search_convocatorias with a watchlist filter and the watchlist-oriented tools, but descriptions resolve these well.

Naming Consistency3/5

The pattern is mixed: most tools are English verb + Spanish noun (search_convocatorias, get_convocatoria, mark_solicitada, list_solicitudes, explain_match), but source_health and deadlines_soon are bare noun phrases with no verb, and discard is a bare verb. It remains readable and predictable in spirit, but the convention is not uniform.

Tool Count5/5

Eight tools is well-scoped for a bulletin-search server, covering retrieval, detail, tracking, and diagnostics without filler. Each tool clearly earns its place in the workflow.

Completeness3/5

Core lifecycle (search → read → track/discard) is covered, but there is no undo for discard or mark_solicitada, no standalone list of discarded items (only via a flag), and watchlists are heavily referenced in search/explain_match args yet no tool creates or lists them — the description defers to source_health output. These gaps will force agents into dead ends for common follow-up requests.

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    Enables AI clients to query Spain's official BDNS subsidy and grant listings and BOE open-data publications live, with exact counts, computed deadlines, forecast calendars, and literal PDF text of grant bases. It also surfaces BOE oposiciones and licitaciones, flagging uncertainty instead of inventing data.
    10
    68 npm
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Enables Claude, Cursor, and other MCP clients to search Spanish BOE/BORME gazettes, open and forthcoming EU funding calls, and TED public procurement notices through read-only tools that return live JSON with source links.
    3
    1
    MIT