# Auto-Update
gitlab-mcp-server can automatically detect, download, and apply new releases from a GitHub repository. Updates are fetched from the GitHub Releases API, validated with a `checksums.txt` file, and applied using a **rename trick** β the running binary is renamed to `.old` and the new binary is placed at the original path. The running MCP process keeps serving; restart it to use the new binary. Old `.old` files are cleaned up automatically at startup.
> **DiΓ‘taxis type**: Explanation
> **Audience**: π€π§ All users
> **Prerequisites**: gitlab-mcp-server installed, GitHub repository with releases
> π **User documentation**: See the [Auto-update](https://jmrplens.github.io/gitlab-mcp-server/operations/auto-update/) on the documentation site for a user-friendly version.
---
## How It Works
```mermaid
sequenceDiagram
participant Server as gitlab-mcp-server
participant Update as Background update goroutine
participant GitHub as GitHub Releases API
participant FS as File System
Server->>Server: CleanupOldBinary() β remove .old from previous update
Server->>Update: Start startup check in background
Server->>Server: Continue MCP startup immediately
Update->>Update: JustUpdated()? Skip check (re-exec guard)
Update->>GitHub: GET /repos/:owner/:repo/releases/latest
GitHub-->>Update: Release metadata + asset URLs
alt Update available
Update->>GitHub: Download binary asset
GitHub-->>Update: Binary data
Update->>FS: Write to .tmp staging file
Update->>FS: Rename current β .old (rename trick)
Update->>FS: Rename .tmp β current path
Update->>Server: Log restart advisory
else Up to date
Update->>Server: Log "server is up to date"
end
```
The update mechanism uses [creativeprojects/go-selfupdate](https://github.com/creativeprojects/go-selfupdate) v1.6.0 with a GitHub source backend. Key properties:
- **GitHub Releases**: Auto-update fetches releases from the GitHub repository configured via `AUTO_UPDATE_REPO` (default: `jmrplens/gitlab-mcp-server`).
- **Rename trick**: The running binary is renamed to `.old` (allowed even on Windows where executables are locked), and the new binary is placed at the original path. This eliminates the need for deferred scripts or manual intervention.
- **Non-blocking stdio startup**: In stdio mode, release detection and downloads run in a background goroutine so MCP tool negotiation is not delayed by update I/O.
- **Next-restart activation**: The new binary is ready at the original path after a successful background update. The current MCP process keeps running the old code until it is restarted.
- **External updater integration (`--shutdown`)**: An external store app (pe-agnostic-store) can invoke `gitlab-mcp-server --shutdown` to terminate all running instances before replacing the binary on disk. This flag finds matching processes by name, sends a graceful termination signal, waits up to 5 seconds, and force-kills any remaining. Uses [gopsutil](https://github.com/shirou/gopsutil) for cross-platform process listing (Linux, macOS, Windows). No admin/root permissions required.
- **Re-exec loop prevention**: The legacy pre-start flow uses the `MCP_JUST_UPDATED=1` environment variable before re-exec and clears it on the new process startup, preventing infinite update loops.
- **Old binary cleanup**: At startup, `CleanupOldBinary()` removes any leftover `.old` file from a previous update.
- **Checksum validation**: Each release must include a `checksums.txt` asset (goreleaser format) containing SHA-256 hashes for all platform binaries.
- **Platform detection**: The library automatically selects the correct binary for the running OS and architecture (`{name}-{goos}-{goarch}`).
## Update Modes
The `AUTO_UPDATE` variable controls behaviour:
| Value | Mode | Behaviour |
| ---------------- | ---------- | ----------------------------------------------------- |
| `true` (default) | Auto | Detect and apply updates automatically |
| `check` | Check-only | Detect updates and log availability, but do not apply |
| `false` | Disabled | Skip all update checks entirely |
Accepted aliases: `1`/`yes` for true, `0`/`no` for false. The value is case-insensitive.
## Transport-Specific Behaviour
### Stdio Mode
When running as a stdio server (the default), auto-update runs as a **background startup check** with a configurable timeout (default: 60 seconds). The check is scheduled during startup, but MCP server construction and stdio transport startup continue immediately:
1. `CleanupOldBinary()` β remove leftover `.old` file from a previous update.
2. Parse `AUTO_UPDATE` mode from environment variables.
3. Check `MCP_JUST_UPDATED` β if set, skip the update check (re-exec guard) and clear the variable.
4. Launch the startup update check in a goroutine using `AUTO_UPDATE_TIMEOUT`.
5. Continue with normal MCP server startup without waiting for release detection or downloads.
6. In the background goroutine:
- If mode is `true` and a newer release exists β download and apply it, then log a restart advisory.
- If mode is `check` β log the available version without applying.
- If no update is available β log that the server is up to date.
The startup check is **non-blocking and non-fatal**: any error (network timeout, invalid token, missing releases) is logged as a warning and does not prevent the server from starting or accepting MCP requests.
```text
INFO autoupdate: server is up to date version=1.1.7
```
or (background update applied):
```text
INFO autoupdate: starting startup check in background mode=true repository=jmrplens/gitlab-mcp-server current_version=1.1.7
INFO starting MCP server transport=stdio version=1.1.7
INFO autoupdate: new version available current_version=1.1.7 latest_version=1.2.0
INFO autoupdate: update applied new_version=1.2.0
INFO autoupdate: binary updated in background β restart the server to use the new version new_version=1.2.0
```
### HTTP Mode
When running as an HTTP server (`--http`), auto-update runs as a **background periodic check**:
1. Parse `--auto-update` flag (defaults to `true`).
2. Create an `Updater` with the GitHub source, `--auto-update-repo`, and `--auto-update-interval`.
3. Launch `StartPeriodicCheck()` β a goroutine that runs every `--auto-update-interval` (default: 1 hour).
4. Each cycle:
- Check for a newer release (30-second timeout per check).
- If mode is `true` β apply the update and log a restart advisory.
- If mode is `check` β log availability only.
5. The goroutine stops when the server context is cancelled (graceful shutdown).
> **Note**: Auto-update uses the GitHub Releases API, completely independent of the user's GitLab configuration.
## Configuration Reference
### Environment Variables (Stdio Mode)
| Variable | Default | Description |
| ---------------------- | ---------------------------- | ------------------------------------------------------------ |
| `AUTO_UPDATE` | `true` | Update mode: `true`, `check`, or `false` |
| `AUTO_UPDATE_REPO` | `jmrplens/gitlab-mcp-server` | GitHub repository slug (owner/repo) for release assets |
| `AUTO_UPDATE_INTERVAL` | `1h` | Check interval (used by HTTP mode periodic checks) |
| `AUTO_UPDATE_TIMEOUT` | `60s` | Timeout for startup/background update checks (range: 5sβ10m) |
Auto-update uses the GitHub Releases API via `AUTO_UPDATE_REPO`. It does **not** use the user's `GITLAB_URL`, `GITLAB_TOKEN`, or `GITLAB_SKIP_TLS_VERIFY`.
### CLI Flags (HTTP Mode)
| Flag | Default | Description |
| ------------------------ | ---------------------------- | ------------------------------------------------------------ |
| `--auto-update` | `true` | Update mode: `true`, `check`, or `false` |
| `--auto-update-repo` | `jmrplens/gitlab-mcp-server` | GitHub repository slug (owner/repo) for release assets |
| `--auto-update-interval` | `1h` | Interval between periodic update checks |
| `--auto-update-timeout` | `60s` | Timeout for startup/background update checks (range: 5sβ10m) |
Auto-update uses the GitHub Releases API, so `--gitlab-url` and `--skip-tls-verify` do **not** affect auto-update behaviour.
### Configuration Examples
Disable auto-update entirely:
```env
AUTO_UPDATE=false
```
Check-only mode (log available updates without applying):
```env
AUTO_UPDATE=check
```
Custom repository and fast check interval:
```env
AUTO_UPDATE=true
AUTO_UPDATE_REPO=my-group/my-project
AUTO_UPDATE_INTERVAL=15m
```
Increase the download timeout for slow connections:
```env
AUTO_UPDATE_TIMEOUT=120s
```
HTTP mode with custom settings:
```bash
gitlab-mcp-server --http \
--gitlab-url=https://gitlab.example.com \
--auto-update=check \
--auto-update-interval=30m
```
## MCP Tools
When auto-update is enabled, update tools are registered as part of the `gitlab_server` meta-tool (or as individual tools in non-meta mode). These allow AI assistants to check for and apply updates on demand:
### `gitlab_server_check_update`
Check if a newer version of the MCP server is available.
**Input**: None (empty object `{}`).
**Output**:
| Field | Type | Description |
| ------------------ | ------- | ------------------------------------- |
| `update_available` | boolean | Whether a newer version exists |
| `current_version` | string | Currently running version |
| `latest_version` | string | Latest release version (if available) |
| `release_url` | string | URL to the release page |
| `release_notes` | string | Release notes content |
| `mode` | string | Current auto-update mode |
**Annotations**: Read-only (`readOnlyHint: true`, `idempotentHint: true`).
**Example response** (Markdown):
```markdown
## β¬οΈ Update Available
- **Current Version**: 1.1.7
- **Latest Version**: 1.2.0
- **Release URL**: https://github.com/jmrplens/gitlab-mcp-server/releases/tag/v1.2.0
### Release Notes
- Added new pipeline tools
- Fixed merge request approval handling
```
### `gitlab_server_apply_update`
Download and apply the latest MCP server update. The binary is replaced using the rename trick (rename current β `.old`, place new binary at original path). On all platforms a server restart is required to use the new version.
**Input**: None (empty object `{}`).
**Output**:
| Field | Type | Description |
| ------------------ | ------- | -------------------------------------------------------- |
| `applied` | boolean | Whether the update was applied (binary replaced on disk) |
| `previous_version` | string | Version before the update |
| `new_version` | string | Version after applying the update |
| `message` | string | Human-readable status message |
**Annotations**: Destructive (`destructiveHint: true`) β replaces the server binary.
**Example response** (Markdown):
```markdown
## β
Update Applied
- **Previous Version**: 1.1.7
- **New Version**: 1.2.0
> **Note**: Restart the server to use the new version.
```
> **Important**: These tools are only registered when auto-update is enabled (`AUTO_UPDATE` is not `false`) and the binary was built with version information (`-ldflags`). Development builds (`version=dev`) disable auto-update.
## Architecture
```mermaid
graph TD
subgraph "cmd/server/main.go"
A0[CleanupOldBinary] --> A
A[runStdio] -->|background startup| B[startStdioAutoUpdate]
A -->|creates| C[newUpdaterForTools]
D0[CleanupOldBinary] --> D
D[runHTTP] -->|background| E[startAutoUpdate]
end
subgraph "internal/autoupdate"
B --> F[Updater.CheckOnce]
F --> H[CheckForUpdate]
F --> I[ApplyUpdate]
E --> G[Updater.StartPeriodicCheck]
G --> H
G --> I[ApplyUpdate]
H --> J[go-selfupdate
DetectLatest]
I --> K[go-selfupdate
UpdateSelf]
I -->|Windows fallback| P
I -->|Windows fallback| Q
end
subgraph "internal/tools/serverupdate"
C --> L[ActionSpecs]
L --> M[gitlab_server_check_update]
L --> N[gitlab_server_apply_update]
M --> H
N --> I
N -->|Windows fallback| S[DownloadAndReplace]
end
J -->|GitHub API| O[(GitHub Releases)]
K -->|Download + Checksum| O
P -->|Download| O
```
### Package Responsibilities
| Package | Role |
| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `internal/autoupdate` | Core update logic: detect releases, download, rename trick replacement, optional re-exec helpers, old binary cleanup. Transport-agnostic. |
| `internal/autoupdate/exec_unix.go` | `ExecSelf()` β `syscall.Exec` to re-exec the process (same PID, same FDs). Build tag: `!windows`. |
| `internal/autoupdate/exec_windows.go` | `ExecSelf()` β stub returning an error (exec not supported). Build tag: `windows`. |
| `internal/autoupdate/prestart.go` | `PreStartUpdate()` β lower-level pre-start flow: check β download β rename β exec/log. |
| `internal/tools/serverupdate` | MCP tool wrappers exposing `Check` and `Apply` as MCP tools with Markdown formatting. |
| `cmd/server/main.go` | Wiring: calls `CleanupOldBinary()`, `startStdioAutoUpdate` (stdio), `startAutoUpdate` (HTTP), and `newUpdaterForTools` (MCP tools). |
## Release Requirements
For auto-update to work, GitHub releases must follow this structure:
1. **Tag format**: Semantic version with `v` prefix (e.g., `v1.1.0`).
2. **Binary assets**: One per supported platform, named `gitlab-mcp-server-{goos}-{goarch}[.exe]`:
- `gitlab-mcp-server-linux-amd64`
- `gitlab-mcp-server-linux-arm64`
- `gitlab-mcp-server-darwin-amd64`
- `gitlab-mcp-server-darwin-arm64`
- `gitlab-mcp-server-windows-amd64.exe`
- `gitlab-mcp-server-windows-arm64.exe`
3. **Checksum file**: A `checksums.txt` asset containing SHA-256 hashes in goreleaser format:
```text
abcdef1234567890... gitlab-mcp-server-linux-amd64
fedcba0987654321... gitlab-mcp-server-windows-amd64.exe
```
The Makefile `release` target generates the binaries and checksum file automatically.
## Security Considerations
- **Token scope**: Auto-update uses a dedicated built-in token (injected at build time), separate from the user's `GITLAB_TOKEN`. The built-in token is a GitHub token that needs read access to releases. No additional permissions are required.
- **TLS verification**: Auto-update always uses TLS verification (hardcoded `SkipTLS: false`) since it connects to GitHub (`github.com`) with a valid certificate. The user's `GITLAB_SKIP_TLS_VERIFY` setting does not affect auto-update.
- **Binary integrity**: Each downloaded binary is validated against the `checksums.txt` file before replacement. If the checksum does not match, the update is rejected.
- **Rename-and-rollback**: The old binary is renamed to `.old` before replacement. If the new binary fails to be placed, the `.old` is renamed back to the original path as a rollback.
- **Development builds**: Binaries built without `-ldflags -X main.version=...` report `version=dev` and auto-update is disabled to prevent accidental overwrites during development.
## Troubleshooting
| Symptom | Cause | Solution |
| --------------------------------------------------------------------------- | ----------------------------------------------------- | -------------------------------------------------------------------------------------- |
| `autoupdate: current version is required (binary built without -ldflags?)` | Binary built without version injection | Build with `make build` or add `-ldflags "-X main.version=1.2.0"` |
| `autoupdate: repository is required` | `AUTO_UPDATE_REPO` is empty | Set `AUTO_UPDATE_REPO` or use the default |
| `autoupdate: creating GitHub source` | Network error reaching GitHub API | Verify network connectivity to `github.com` |
| `autoupdate: detecting latest release` | No releases in repository, or token lacks permissions | Create a release or check token permissions |
| `autoupdate: startup background check failed` | Network timeout (`AUTO_UPDATE_TIMEOUT`, default 60s) | Check network connectivity or increase `AUTO_UPDATE_TIMEOUT`; the server keeps running |
| `autoupdate: could not initialize periodic updater` | Missing required config in HTTP mode | Verify `--auto-update-repo` flag and network connectivity |
| Update detected but not applied | Mode is `check` | Set `AUTO_UPDATE=true` to enable automatic application |
| Server still runs old version after background update | Binary replaced but current process keeps running | Restart the server process to use the new binary |
| `autoupdate: exec-self failed` | `syscall.Exec` failed on Unix | Server continues with old code; restart manually |
| `autoupdate: skipping startup update check (just re-executed after update)` | Normal: re-exec guard preventing loop | No action needed |
## Disabling Auto-Update
To completely disable all update-related functionality:
```env
AUTO_UPDATE=false
```
Or in HTTP mode:
```bash
gitlab-mcp-server --http --auto-update=false ...
```
This disables:
- Startup update check (stdio mode)
- Periodic background checks (HTTP mode)
- MCP tool registration (`gitlab_server_check_update` and `gitlab_server_apply_update` are not registered)