Ir al contenido

Suscripciones a Recursos

Las suscripciones a recursos permiten a un cliente decir «avísame cuando esto cambie» en lugar de releer un recurso por intuición. El cliente envía resources/subscribe para una URI como gitlab://project/42/pipelines/latest, y GitLab MCP Server envía notifications/resources/updated cada vez que el contenido de ese recurso cambia.

El servidor atiende las suscripciones mediante sondeo: un vigilante relee la URI suscrita a través del mismo handler al que despacha resources/read, compara un SHA-256 del contenido y notifica solo ante un cambio real. «El contenido cambió» significa por tanto exactamente «lo que tú leerías cambió» — nunca una suposición. La decisión y sus límites están registrados en ADR-0015.

Las suscripciones se anuncian con GITLAB_MCP_CAPABILITY_SURFACE=full (el valor por defecto). La superficie minimal no registra los recursos de datos de GitLab a los que aplica una suscripción, así que tampoco anuncia la capacidad.

26 tipos de recurso: objetos únicos con un ciclo de vida que merece seguirse, más tres listas de un solo padre (los jobs de un pipeline, las discusiones de un merge request y sus notas):

GrupoRecursos suscribibles
CI/CDPipeline, lista de jobs del pipeline, último pipeline, job concreto, deployment, environment
ColaboraciónMerge request (con sus discusiones y notas), issue
Releases y refsRelease, tag, rama, feature flag
PlanificaciónMilestone y label (de proyecto o grupo), board
ContenidoPágina wiki, archivo del repositorio, snippet (de proyecto o personal)
ContenedoresProyecto, grupo, deploy key

Las colecciones están excluidas deliberadamente. Suscribirse a gitlab://project/42/issues se dispararía con cada cambio de cualquier issue del proyecto y costaría una página completa por sondeo — una suscripción a una colección se rechaza. La lista completa legible por máquina viaja en el manifiesto gitlab://tools bajo subscriptions.subscribable_uri_templates.

La cadencia de sondeo se adapta al recurso:

Estado del recursoIntervaloEjemplo
Trabajo en curso5s (suelo)un pipeline running, un merge request opened
Asentado60sun pipeline success, un issue closed
Sin campo de ciclo15suna página wiki, un archivo, un label
Concesión degradada10min30 minutos sin ninguna petición en la sesión

Los vigilantes usan el token de GitLab del propio suscriptor, con un tope de 10 vigilantes por credencial (una entrada del pool por token+URL en modo HTTP) y de 512 por proceso. El peor caso — diez vigilantes al suelo de 5s — son 120 peticiones/minuto; diez degradados cuestan una. Un 429 de GitLab pausa todos los vigilantes con retroceso exponencial (30s doblando hasta 5 minutos). El techo del proceso rechaza en lugar de parar el vigilante de nadie, y rechaza antes de que una credencial en su propio tope gaste un vigilante degradado para hacer sitio, así que un rechazo nunca te cuesta una suscripción que ya tenías.

El tope por proceso existe porque acuñar una credencial es una llamada a la API, así que un número por credencial se multiplica por cuantas tenga quien llama. Rechaza en lugar de hacer sitio: detener el vigilante de una credencial para arrancar el de otra es un intercambio que este servidor no hace. Ninguno de los dos topes es configurable, y ambos rechazos dicen cuál se alcanzó.

Nada se retira por estar «terminado»: GitLab no tiene estado terminal — un pipeline reintentado reutiliza su ID y vuelve a ejecutarse, un issue cerrado se reabre — así que un vigilante solo termina cuando el cliente se desuscribe, su sesión se desconecta, el recurso devuelve 401/403/404, se alcanza el tope de vida de 24 horas, es desalojado para hacer sitio en el tope de vigilantes, o el pool suelta la entrada de la credencial por presión de --max-http-clients. Este último caso prefiere una entrada que no esté atendiendo una suscripción y se lleva una ocupada solo cuando todas las entradas del pool lo están; cuando ocurre, los vigilantes se detienen, las peticiones subscriptions/listen abiertas se completan y basta con reconectar, porque la credencial no tiene nada malo.

Un vigilante sin renovar no muere en silencio — se ralentiza. Tras 30 minutos sin tráfico en la sesión suscriptora, el vigilante baja a un sondeo de 10 minutos; cualquier llamada a herramienta o lectura de recurso en esa sesión restaura la velocidad completa automáticamente. Cada notificación lleva el estado del vigilante en _meta bajo io.github.jmrplens/watch:

{
"method": "notifications/resources/updated",
"params": {
"uri": "gitlab://project/42/pipeline/99",
"_meta": {
"io.github.jmrplens/watch": {
"state": "active",
"renewBy": "2026-08-25T19:30:00Z",
"pollIntervalMs": 5000,
"renewedByActivity": true
}
}
}
}

En el protocolo 2026-07-28 una suscripción es una petición subscriptions/listen que el cliente deja abierta, y el servidor la termina respondiéndola. Todo final que inicia el servidor dice cuál fue, en el _meta del resultado, junto al identificador de suscripción que el SDK estampa ahí:

{
"jsonrpc": "2.0",
"id": 1,
"result": {
"_meta": {
"io.modelcontextprotocol/subscriptionId": 1,
"io.github.jmrplens/watch-end": {
"reason": "credential_evicted",
"detail": "the server released this credential's pooled entry under capacity pressure; reconnect and subscribe again, the credential itself is still valid"
}
}
}
}
reasonQué ocurrióQué hacer
credential_evictedLa presión de tamaño se llevó la entrada del pool de esta credencialReconecta y vuelve a suscribirte ya; la credencial sigue siendo válida
credential_resetLa entrada se reclamó por inactividad, antigüedad o una reconstrucciónReconecta y vuelve a suscribirte; la credencial no tiene nada malo
credential_revokedGitLab rechazó la credencialReautentícate primero; el mismo token se rechazará de nuevo
resource_goneEl recurso vigilado respondió 401, 403 o 404Revisa el acceso; vuelve a suscribirte solo si el recurso reaparece
lifetime_reachedSe agotó el tope absoluto de vida de 24 horasVuelve a suscribirte; es lo esperado en un cliente de larga duración
watcher_evictedUn vigilante tuyo, degradado, se detuvo en el tope de vigilantesVuelve a suscribirte y mantén la sesión activa
shutdownEl servidor se está deteniendoReconecta; tras un balanceador te atenderá otra instancia

detail es una frase de consejo, para que un cliente que no reconozca la palabra del motivo pueda actuar igualmente. El campo status aparece solo en resource_gone, y se retransmite sin interpretarlo: GitLab responde 404 a un recurso que quizá no puedas ver, así que un 404 aquí no significa «borrado». El vocabulario es cerrado y se publica en subscriptions.end_reasons de la tarjeta del servidor, que solo se sirve en modo HTTP: en stdio no hay endpoint del que leerla, y los cuatro motivos que allí pueden darse (resource_gone, lifetime_reached, watcher_evicted y shutdown) se documentan en esta página. Trata un valor desconocido como «un final que este cliente no conoce», nunca como «cualquier final».

Un final que causas no lleva motivo, precisamente porque lo causaste tú. Tampoco lo lleva un resources/subscribe de la era de sesiones con --stateless=false: no mantiene ninguna petición abierta, así que no hay nada que responder, y terminar la sesión es el único final que el protocolo le ofrece. Usa subscriptions/listen si quieres el motivo.

  • stdio: funciona tal cual en ambas generaciones del protocolo.
  • HTTP, modo stateless (el predeterminado): la petición legada resources/subscribe se rechaza con un error explicativo — cada POST stateless recibe su propia sesión que se cierra con la respuesta, así que una suscripción aceptada nunca podría notificarse. Los clientes con protocolo 2026-07-28 no se ven afectados: subscriptions/listen mantiene la petición abierta, y eso el modo stateless sí lo soporta. Los suscriptores legados necesitan --stateless=false.
  • Particularidades de clientes: VS Code se suscribe a cada recurso que lee y encamina las actualizaciones a su pipeline de cambios de archivo; Cursor envía resources/subscribe incluso a servidores que anuncian subscribe: false; el cliente del SDK de Go dispara subscriptions/listen sin esperar la respuesta, así que un rechazo puede no aflorar nunca en el cliente.

Un resources/subscribe heredado rechazado lleva un código JSON-RPC deliberado — nunca el code: 0 accidental con el que se serializaría un error plano:

RechazoCódigo
URI deliberadamente no suscribible-32602 (parámetros inválidos)
Recurso ilegible en la lectura de autorización (401/403/404)-32602 — el mismo código con el que el SDK responde un resources/read desconocido
Límite de peticiones, cupo de watchers sin ningún watch desalojable, o servidor apagándose-32000 (servidor ocupado — transitorio, reintente más tarde)
Vigilantes por proceso en 512-32000, con server-wide en el mensaje
Fallo transitorio de GitLab en la primera lectura-32603 (error interno)
resources/subscribe en HTTP sin estado-32600 — use subscriptions/listen en su lugar

Preguntas frecuentes

¿Qué son las suscripciones a recursos MCP?

Un cliente pide al servidor vigilar un recurso con resources/subscribe, y el servidor envía notifications/resources/updated cuando su contenido cambia. GitLab MCP Server lo atiende mediante sondeo: un vigilante relee la URI suscrita a través del mismo handler que usa resources/read y notifica solo cuando el contenido cambió de verdad. GitLab no ofrece ningún canal push que un servidor local pueda consumir, así que el sondeo con límites explícitos es la implementación honesta.

¿A qué recursos se puede suscribir?

26 tipos de recurso, objetos únicos más tres listas de un solo padre: un proyecto o grupo; un pipeline, su lista de jobs, el último pipeline, un job concreto; un merge request con sus discusiones y notas; un issue; un deployment, environment o feature flag; una release, tag o rama; un milestone o label (de proyecto o grupo); un board; una deploy key; un snippet; una página wiki; y un archivo del repositorio. Las colecciones se rechazan — suscribirse a una lista de issues notificaría cambios que el suscriptor nunca pidió.

¿Con qué rapidez llegan las notificaciones?

La latencia es la cadencia de sondeo: 5 segundos mientras el recurso está activo (un pipeline en ejecución, un merge request abierto), 15 segundos por defecto, 60 segundos una vez asentado y 10 minutos tras degradarse la concesión por 30 minutos de inactividad de la sesión. Cualquier petición en la sesión restaura la velocidad completa. El _meta de cada notificación informa del estado del vigilante y su cadencia actual.

¿Cuánto cuesta vigilar contra mi límite de peticiones de GitLab?

Los vigilantes usan el token del propio suscriptor, con un tope de 10 vigilantes por credencial. El peor caso — los diez al suelo de 5 segundos — son 120 peticiones por minuto, el 6% del límite por usuario de GitLab.com. Diez vigilantes degradados cuestan una petición por minuto. Un 429 de GitLab pausa todos los vigilantes con retroceso exponencial.