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.
¿Qué se puede vigilar?
Sección titulada «¿Qué se puede vigilar?»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):
| Grupo | Recursos suscribibles |
|---|---|
| CI/CD | Pipeline, lista de jobs del pipeline, último pipeline, job concreto, deployment, environment |
| Colaboración | Merge request (con sus discusiones y notas), issue |
| Releases y refs | Release, tag, rama, feature flag |
| Planificación | Milestone y label (de proyecto o grupo), board |
| Contenido | Página wiki, archivo del repositorio, snippet (de proyecto o personal) |
| Contenedores | Proyecto, 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.
¿Con qué rapidez, y a qué coste?
Sección titulada «¿Con qué rapidez, y a qué coste?»La cadencia de sondeo se adapta al recurso:
| Estado del recurso | Intervalo | Ejemplo |
|---|---|---|
| Trabajo en curso | 5s (suelo) | un pipeline running, un merge request opened |
| Asentado | 60s | un pipeline success, un issue closed |
| Sin campo de ciclo | 15s | una página wiki, un archivo, un label |
| Concesión degradada | 10min | 30 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.
Ciclo de vida y renovación
Sección titulada «Ciclo de vida y renovación»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 } } }}Por qué terminó una suscripción
Sección titulada «Por qué terminó una suscripción»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" } } }}reason | Qué ocurrió | Qué hacer |
|---|---|---|
credential_evicted | La presión de tamaño se llevó la entrada del pool de esta credencial | Reconecta y vuelve a suscribirte ya; la credencial sigue siendo válida |
credential_reset | La entrada se reclamó por inactividad, antigüedad o una reconstrucción | Reconecta y vuelve a suscribirte; la credencial no tiene nada malo |
credential_revoked | GitLab rechazó la credencial | Reautentícate primero; el mismo token se rechazará de nuevo |
resource_gone | El recurso vigilado respondió 401, 403 o 404 | Revisa el acceso; vuelve a suscribirte solo si el recurso reaparece |
lifetime_reached | Se agotó el tope absoluto de vida de 24 horas | Vuelve a suscribirte; es lo esperado en un cliente de larga duración |
watcher_evicted | Un vigilante tuyo, degradado, se detuvo en el tope de vigilantes | Vuelve a suscribirte y mantén la sesión activa |
shutdown | El servidor se está deteniendo | Reconecta; 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 tú 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.
Notas de transporte
Sección titulada «Notas de transporte»- stdio: funciona tal cual en ambas generaciones del protocolo.
- HTTP, modo stateless (el predeterminado): la petición legada
resources/subscribese 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/listenmantiene 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/subscribeincluso a servidores que anunciansubscribe: false; el cliente del SDK de Go disparasubscriptions/listensin esperar la respuesta, así que un rechazo puede no aflorar nunca en el cliente.
Códigos de error
Sección titulada «Códigos de error»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:
| Rechazo | Có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.