Ir al contenido

Docker

La imagen se publica como ghcr.io/jmrplens/gitlab-mcp-server y se replica como docker.io/jmrplens/gitlab-mcp-server en Docker Hub, con una etiqueta <versión> y latest en ambos registros, para linux/amd64 y linux/arm64. Cada release la construye con atestaciones de procedencia y SBOM y la firma por digest con Cosign. Las dos variantes de plataforma se arrancan y se les pide su versión antes de publicar nada, así que ninguna puede salir incapaz de arrancar, que es lo que le pasó a la imagen linux/arm64 de v2.7.5, cuyo binario pedía un cargador dinámico que el runtime Alpine no trae. Dentro: una base Alpine con certificados CA y datos de zona horaria, el binario en /usr/local/bin/gitlab-mcp-server, un usuario no-root appuser (UID 10001), el puerto 8080 expuesto y un HEALTHCHECK que ejecuta gitlab-mcp-server --probe, que lee el listener de los propios flags del servidor en marcha y pregunta por su /health, así que sigue a un puerto cambiado, a un socket unix o a TLS en el listener (la imagen 2.7.5 lanza wget contra http://localhost:8080/health en su lugar, y da por no sano cualquier otro listener).

El binario de la imagen se compila sin -trimpath, a diferencia de los binarios del release, así que su información de compilación registra el release con el que se compiló y el SBOM de la imagen lista el servidor con esa versión, con una URL de paquete versionada que un escáner de vulnerabilidades puede casar. Las imágenes hasta la 3.1.0 incluida lo listan sin versión. A partir del primer release posterior a 3.1.0, la imagen lleva además LICENSE y THIRD_PARTY_NOTICES en /usr/share/licenses/gitlab-mcp-server/: los textos de licencia, aviso y patente de todos los módulos que enlaza ese binario, generados durante la compilación de la imagen a partir de los módulos del propio binario y de los paquetes que enlaza.

Ventana de terminal
docker run -i --rm -e GITLAB_TOKEN ghcr.io/jmrplens/gitlab-mcp-server:latest

-e GITLAB_TOKEN sin valor reenvía la variable desde el entorno que el cliente le da a docker, de modo que el token vive en el bloque env del cliente y no en la línea de comandos. Docker descarga la imagen en la primera ejecución. La entrada de cliente que lanza este comando está en Configura tu cliente más abajo; con Claude Code el registro es esa misma línea de comandos, y tampoco lleva token:

Ventana de terminal
# Pide el token sin mostrarlo, así que queda fuera del historial de la shell
printf 'GitLab token: ' && read -rs GITLAB_TOKEN && export GITLAB_TOKEN && echo
claude mcp add gitlab --transport stdio \
-- docker run -i --rm -e GITLAB_TOKEN ghcr.io/jmrplens/gitlab-mcp-server:latest

Claude Code le pasa su propio entorno a docker, que es de donde -e GITLAB_TOKEN toma el valor, así que expórtalo en la shell desde la que lanzas el cliente (tu perfil de shell lo hace duradero) o añádelo después al bloque env de la entrada. Un contenedor tiene su propio sistema de archivos y nunca lee ~/.gitlab-mcp-server.env, que es como el resto de canales aportan el token; el reenvío es el equivalente del contenedor.

Los botones de un clic para VS Code, Cursor, LM Studio y Kiro del Inicio rápido registran exactamente esta entrada, y el manifiesto de Plugins de agente también la ejecuta.

En modo stdio cada ajuste es una variable de entorno, y cada una que uses necesita su propio -e NOMBRE para que Docker la copie dentro del contenedor. La lista que reenvía la configuración del plugin incluido es una buena plantilla: GITLAB_URL, GITLAB_TOKEN, GITLAB_MCP_SKIP_TLS_VERIFY, GITLAB_MCP_TOOL_SURFACE, GITLAB_MCP_CAPABILITY_SURFACE, GITLAB_MCP_META_PARAM_SCHEMA, GITLAB_MCP_TIER, GITLAB_MCP_READ_ONLY, GITLAB_MCP_SAFE_MODE, GITLAB_MCP_EMBEDDED_RESOURCES, GITLAB_MCP_EXCLUDE_TOOLS, GITLAB_MCP_IGNORE_SCOPES, GITLAB_MCP_UPLOAD_MAX_FILE_SIZE, GITLAB_MCP_ALLOWED_IMPORT_DIRS, GITLAB_MCP_RATE_LIMIT_RPS, GITLAB_MCP_RATE_LIMIT_BURST, GITLAB_MCP_CLIENT_COMPAT y GITLAB_MCP_LOG_LEVEL. Qué hace cada una está en la página de Configuración. La entrada de la imagen en el MCP Registry (server.json) declara diecinueve variables, las que un cliente del registro ofrece rellenar: esta lista sin GITLAB_MCP_CLIENT_COMPAT, más GITLAB_MCP_ALLOWED_UPLOAD_DIRS y GITLAB_MCP_ALLOWED_DOWNLOAD_DIRS.

Arrancado sin -i, el contenedor no tiene una entrada estándar por la que hablar y escucha en el puerto 8080, pero no arranca hasta que le digas a qué GitLab sirve. El comando por defecto no lleva ninguna instancia, así que un docker run -p 8080:8080 ghcr.io/jmrplens/gitlab-mcp-server:latest a secas termina al momento con --gitlab-url is required in HTTP mode. La forma más sencilla conserva el comando por defecto y nombra la instancia en una variable, que rellena la lista sin reemplazar el comando:

Ventana de terminal
docker run --rm -p 8080:8080 -e GITLAB_URL=https://gitlab.com ghcr.io/jmrplens/gitlab-mcp-server:latest

Un GITLAB_URL separado por comas publica varias instancias, entre las que elige después la cabecera GITLAB-URL. Para nombrar la instancia en la línea de comandos, pasa los flags de forma explícita; cualquier argumento tras el nombre de la imagen reemplaza el comando por defecto, así que --http y --http-addr vuelven con ellos en lugar de deducirse:

Ventana de terminal
docker run --rm -p 8080:8080 ghcr.io/jmrplens/gitlab-mcp-server:latest \
--http --http-addr=0.0.0.0:8080 --gitlab-url=https://gitlab.com

Un despliegue que no nombra ninguna instancia haría peticiones al host que quien llama ponga en la cabecera GITLAB-URL, con el token que esa misma persona enviara, así que se rechaza al arrancar. --allow-any-gitlab-url lo acepta para un despliegue local de un único usuario y avisa, pero solo si --http-addr liga loopback o un socket unix, así que dentro de un contenedor que liga 0.0.0.0 no sirve: publica las instancias. Cada cliente envía además su propio token de GitLab en cada petición, y el servidor no guarda ninguno. Para endurecer el contenedor como hace el despliegue de referencia:

Ventana de terminal
docker run -d \
--name gitlab-mcp \
--read-only \
--tmpfs /tmp:rw,size=64m \
--cap-drop=ALL \
--security-opt=no-new-privileges:true \
-p 8080:8080 \
ghcr.io/jmrplens/gitlab-mcp-server:latest \
--http \
--http-addr=0.0.0.0:8080 \
--gitlab-url=https://gitlab.com

Un cliente HTTP apunta entonces a http://localhost:8080/mcp con type: "http" y su propio token en una cabecera PRIVATE-TOKEN (el modo de autenticación legacy, que es el predeterminado), y http://localhost:8080/health responde sin credencial:

Ventana de terminal
curl -X POST http://localhost:8080/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "PRIVATE-TOKEN: glpat-tu-token" \
-d '{"jsonrpc":"2.0","method":"tools/list","id":1}'

En modo HTTP la configuración viene de flags y no de variables (--tool-surface, --tier, --read-only, --safe-mode, --skip-tls-verify, etcétera), que es lo que el docker-compose.yml del repositorio pasa en su línea command. El modo OAuth, la limitación de tasa, TLS en el propio listener, el dimensionado y la configuración de cliente para este transporte están en la página del Servidor HTTP.

  • latest sigue a la release más nueva; 3.2.0 y cualquier otra etiqueta de versión se quedan fijas. Fija una etiqueta de versión en producción, o fija el digest para una inmutabilidad total: ghcr.io/jmrplens/gitlab-mcp-server:3.2.0@sha256:<digest> es la forma que usa el server.json del MCP Registry para la release actual, con el digest que el workflow de release anotó al publicar la imagen. curl -fsSL https://raw.githubusercontent.com/jmrplens/gitlab-mcp-server/main/server.json | jq -r '.packages[] | select(.registryType == "oci") | .identifier' imprime esa referencia para los dos registros.
  • ghcr.io/jmrplens/gitlab-mcp-server y docker.io/jmrplens/gitlab-mcp-server llevan las mismas imágenes: cada release sube un mismo índice a los dos, así que una etiqueta de versión y latest resuelven al mismo digest en cada uno. La documentación usa ghcr.io; usa el nombre de Docker Hub si ese es el registro que permite tu red.
  • El docker-compose.yml del repositorio nombra su imagen como ${DOCKER_REGISTRY:-ghcr.io/jmrplens/gitlab-mcp-server}:${VERSION:-latest}, así que DOCKER_REGISTRY y VERSION eligen el registro y la etiqueta sin editar el archivo.
  • La imagen lleva la etiqueta io.modelcontextprotocol.server.name=io.github.jmrplens/gitlab-mcp-server, que es como el MCP Registry valida su propiedad.

La entrada stdio para un cliente de escritorio es el comando docker run de arriba, repartido entre command y args, con el token en env:

{
"mcpServers": {
"gitlab": {
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "GITLAB_TOKEN", "ghcr.io/jmrplens/gitlab-mcp-server:latest"],
"env": {
"GITLAB_TOKEN": "glpat-xxxxxxxxxxxxxxxxxxxx"
}
}
}
}

Para una instancia autogestionada añade "-e", "GITLAB_URL" a args y "GITLAB_URL": "https://gitlab.example.com" a env: la variable tiene que estar definida y reenviada a la vez. Las pestañas por cliente del Inicio rápido muestran dónde va este JSON en cada cliente; sustituye su command y su env por los de arriba. VS Code usa un mapa servers con "type": "stdio" en lugar de mcpServers.

El servidor nunca se actualiza a sí mismo, y dentro de un contenedor no debería: descarga la imagen más nueva y arranca un contenedor nuevo a partir de ella.

Ventana de terminal
docker pull ghcr.io/jmrplens/gitlab-mcp-server:latest

Un cliente stdio que ejecuta docker run --rm usa la imagen descargada en su siguiente arranque. Para un servicio HTTP, recrea el contenedor (docker compose pull && docker compose up -d con el archivo Compose de referencia, o docker rm -f gitlab-mcp y el docker run de arriba). Con una etiqueta de versión, cambia la etiqueta y descarga esa. Para desinstalar, para cualquier contenedor y elimina la imagen:

Ventana de terminal
docker rmi ghcr.io/jmrplens/gitlab-mcp-server:latest

y quita la entrada gitlab de la configuración de tu cliente. Son los comandos estándar de Docker; nada en la imagen los cambia.