Error Handling
GitLab MCP Server provides structured error handling that classifies errors, extracts actionable details from GitLab API responses, and suggests corrective actions to the AI assistant.
Error classification
Section titled “Error classification”Every error from the GitLab API is classified by HTTP status code into an actionable message:
| Status Code | Classification | Message |
|---|---|---|
| 400 | Bad request | Check your input parameters |
| 401 | Authentication | GITLAB_TOKEN may be invalid or expired |
| 403 | Permissions | Your token lacks the required permissions |
| 404 | Not found | Missing, inaccessible, or above your GitLab tier |
| 405 | Not allowed | The action does not apply to the resource’s current state |
| 409 | Conflict | The resource already exists or there is a state conflict |
| 422 | Validation | GitLab rejected the request due to invalid data |
| 429 | Rate limit | Too many requests — wait before retrying |
| 500 | Server error | GitLab internal server error |
| 502 | Bad gateway | GitLab is temporarily unavailable |
| 503 | Maintenance | GitLab is under maintenance or overloaded |
Network-level errors are also classified:
| Error Type | Message |
|---|---|
| Connection refused | GitLab server is unreachable |
| DNS failure | GitLab server hostname could not be resolved |
| Timeout | Request to GitLab timed out |
| TLS/SSL | TLS/SSL handshake failed |
Error wrapping functions
Section titled “Error wrapping functions”The server uses five error handling helpers, chosen based on the operation type:
Read-only operations — Used for list, get, and search operations. Classifies the error and wraps it with the operation name:
list_issues: authentication failed — GITLAB_TOKEN may be invalid or expiredMutating operations — Used for create, update, and delete operations. Includes the specific error detail extracted from the GitLab API response:
fileCreate: bad request — A file with this name already exists: POST .../files: 400The server extracts the detailed error message from GitLab’s API response, handling nested formats like {message: {base: [text]}}, and truncates at 300 characters.
Known corrective actions — Used when the corrective action for a specific error is known. Appends an actionable suggestion:
branchProtect: conflict — Protected branch rule already exists.Suggestion: use gitlab_protected_branch_get to view current rulesDecision tree
Section titled “Decision tree”| Scenario | Function |
|---|---|
| Read-only operation (list, get, search) | WrapErr |
| Mutating operation (create, update, delete) | WrapErrWithMessage |
| Specific error with known fix | WrapErrWithHint |
| Status-specific hint (single code) | WrapErrWithStatusHint |
| Get operation returning 404 | NotFoundResult |
NotFoundResult — Informational 404 responses
Section titled “NotFoundResult — Informational 404 responses”For get handlers, 404 errors are treated as informational rather than failures. Instead of returning an opaque Go error (logged at ERROR level), the handler returns a CallToolResult with IsError: true and domain-specific hints:
## ❓ Branch Not Found
Branch `feature/old` was not found in the project.
💡 **Next steps:**
- Use `gitlab_branch_list` to see available branches- Check branch name spelling and case sensitivityThis pattern is applied through a shared formatter in each of 19 domains, so every get handler of a domain answers the same way. It logs at INFO level (expected outcome) and provides the AI assistant with actionable next steps.
Corrective hints
Section titled “Corrective hints”Where the fix for an error is known, the handler attaches it at the call site: WrapErrWithStatusHint appends the hint only when the response carries one HTTP status (a 422 validation failure, a 404, a 409), falling back to WrapErrWithMessage for every other status, and WrapErrWithHint always appends it, which is what GraphQL errors need since they carry no status code. The suggestion travels in the error text as Suggestion: ..., so the AI assistant can correct its request without additional API calls.
Error response format
Section titled “Error response format”When a handler answers with an error result rather than a Go error, the response is a Markdown block with structured diagnostic fields:
## ❌ Error: branch/list
**Message**: authentication failed: GITLAB_TOKEN may be invalid or expired**HTTP Status**: 401 (authentication failed: GITLAB_TOKEN may be invalid or expired)**Details**: GET https://gitlab.example.com/api/v4/projects/42/repository/branches: 401 (401 Unauthorized)**Request ID**: `abc123def456`The structured response includes:
| Field | Description |
|---|---|
| Heading | The domain and action that failed |
| Message | The classified error message |
| HTTP Status | GitLab’s status code and its classification, when there was one |
| Details | The request line and the message GitLab returned, when present |
| Request ID | GitLab’s X-Request-Id for support tickets |
Transient vs permanent errors
Section titled “Transient vs permanent errors”The server classifies errors as transient (retryable) or permanent:
| Type | Status Codes | Behavior |
|---|---|---|
| Transient | 429, 5xx, timeouts, connection refused | Safe to retry after a delay |
| Permanent | 4xx (except 429) | Do not retry — fix the input or configuration |
Example error scenarios
Section titled “Example error scenarios”Invalid token
Section titled “Invalid token”❌ list_projects: authentication failed — GITLAB_TOKEN may be invalid or expired💡 Generate a new token with api scope at GitLab → Preferences → Access TokensPermission denied
Section titled “Permission denied”❌ create_issue: access denied — your token lacks the required permissionsDetail: 403 Forbidden💡 Ensure the token has api scope and you have Developer+ access to the projectResource conflict
Section titled “Resource conflict”❌ branchProtect: conflict — Protected branch rule already exists💡 Use gitlab_protected_branch_get to view current rules, or gitlab_protected_branch_update to modifyValidation error
Section titled “Validation error”❌ create_issue: validation failed — title is too long (maximum is 255 characters)💡 Shorten the title to 255 characters or lessFrequently asked questions
How does the server classify GitLab API errors?
GitLab MCP Server classifies every API error by HTTP status code into an actionable message: 401 signals an invalid or expired GITLAB_TOKEN, 403 signals missing permissions, 404 a missing resource or lack of access, 422 a validation failure, and 429 a rate limit. Network-level failures — connection refused, DNS failure, timeout, and TLS handshake errors — are classified separately. The classification drives both the wrapped error message and whether the failure is treated as retryable.
Which error wrapping function is used for which operation?
Read-only operations (list, get, search) use WrapErr. Mutating operations (create, update, delete) use WrapErrWithMessage, which includes the specific detail extracted from the GitLab response. When a corrective action is known, WrapErrWithHint appends a suggestion, and WrapErrWithStatusHint scopes that hint to a single HTTP status code. A get operation returning 404 uses NotFoundResult instead of returning an error.
Why does a 404 not appear as an error?
For get handlers, 404 responses are treated as informational rather than failures. Instead of returning an opaque Go error logged at ERROR level, the handler returns a CallToolResult with IsError: true and domain-specific hints, logged at INFO level. This NotFoundResult pattern is applied through a shared formatter in each of 19 domains and gives the AI assistant actionable next steps, such as listing available resources or checking the resource name.
Should the AI assistant retry a failed operation?
It depends on whether the error is transient or permanent. Transient errors — 429, 5xx, timeouts, and connection refused — are safe to retry after a delay. Permanent errors — 4xx except 429 — indicate a problem with the request itself, so retrying produces the same result. For permanent errors, fix the input parameters or configuration instead of retrying.