OpenCode's MCP support is configured through its TOML config rather than a dedicated mcp add subcommand, which means setup is a bit more manual than Claude Code but also more transparent — you can see the entire server definition in one file without a CLI abstraction hiding it. This topic covers config, day-to-day MR/issue workflows, a full walkthrough of creating and reviewing an MR, and the rough edges you'll actually hit.
Installing and Connecting GitLab MCP to OpenCode
OpenCode reads MCP server definitions from opencode.json (project-level) or ~/.config/opencode/opencode.json (global). Add the GitLab MCP server under the mcp key:
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"gitlab": {
"type": "local",
"command": ["npx", "-y", "@gitlab-org/mcp-server"],
"environment": {
"GITLAB_PERSONAL_ACCESS_TOKEN": "{env:GITLAB_PERSONAL_ACCESS_TOKEN}",
"GITLAB_API_URL": "https://gitlab.com/api/v4"
},
"enabled": true
}
}
}
OpenCode's {env:VAR_NAME} interpolation syntax pulls from your shell environment at server start — make sure GITLAB_PERSONAL_ACCESS_TOKEN is exported in the shell OpenCode launches from, not just set in a .env file OpenCode doesn't automatically load.
For self-managed GitLab, swap the API URL and confirm your instance is reachable from wherever OpenCode runs (a remote dev container will need the internal DNS name, not localhost):
{
"mcp": {
"gitlab": {
"type": "local",
"command": ["npx", "-y", "@gitlab-org/mcp-server"],
"environment": {
"GITLAB_PERSONAL_ACCESS_TOKEN": "{env:GITLAB_PAT_SELFHOSTED}",
"GITLAB_API_URL": "https://gitlab.internal.company.com/api/v4"
},
"enabled": true
}
}
}
Confirm the server loaded by running opencode and checking the tool list with /tools inside a session, or by running a trivial read command:
List the first 5 projects I have access to on GitLab.
If nothing comes back, check OpenCode's log output (opencode --log-level debug on the next launch) — a silently misconfigured environment block is the most common cause, since OpenCode won't always surface a clear "env var not found" error at startup.
Tips
- Double-check{env:VAR_NAME}resolves in the exact shell/session OpenCode is launched from — a var exported only in an interactive shell won't reach a background-launched OpenCode process.
- Use project-levelopencode.jsonfor repo-specificGITLAB_API_URLoverrides (self-managed instances), global config only for defaults that apply everywhere.
- Run with--log-level debugonce after any config change to confirm the server actually started before trusting silence as success.
Managing Merge Requests and Issues from OpenCode
OpenCode's tool-calling loop is generally more verbose about what it's doing than Claude Code's, which is useful for learning the tool surface but means prompts benefit from being a bit more directive to avoid excessive intermediate narration.
Listing MRs awaiting your review:
Show me merge requests in my-group/backend-services where I'm listed as
a reviewer and the MR is still open. Sort by oldest first.
Creating an issue from a bug you just found while working:
Create an issue in my-group/backend-services titled "Rate limiter allows
burst beyond configured threshold under concurrent load". Description:
reproduced with 50 concurrent requests against /api/orders, threshold
configured at 100/min but observed 140 requests passing through in the
first 10 seconds. Label it "bug" and "performance".
OpenCode maps this cleanly to create_issue with labels: ["bug", "performance"] — verify the labels already exist in the project though; GitLab's API will silently create net-new labels with default gray color if they don't exist yet, which clutters your label list over time.
Updating MR labels and milestone in bulk:
For all open MRs in my-group/backend-services targeting the "v2.4"
milestone, add the label "release-candidate" if it isn't already there.
This requires a list_merge_requests (filtered by milestone) followed by conditional update_merge_request calls — a good test of whether your OpenCode + model combination handles multi-step conditional loops well. Weaker models sometimes apply the label unconditionally rather than checking for existing labels first; watch the first run.
Tips
- Pre-verify label names exist in the project before asking the agent to apply them — GitLab's API creates missing labels silently, which is rarely what you want.
- For bulk conditional updates, run it once on a small filtered set first (e.g., a specific milestone) before trusting it against "all open MRs."
- OpenCode narrates tool calls more verbosely by default — if that's noisy, most OpenCode versions let you collapse tool-call detail in the session view (check/helpfor the current toggle).
Practical Example: Creating and Reviewing an MR in OpenCode
Here's an end-to-end pass: you've just finished a small feature on a branch and want OpenCode + GitLab MCP to handle the MR lifecycle from creation through addressing review comments.
Step 1 — create the MR from the current branch:
I'm on branch feature/add-request-timeout in my-group/backend-services,
based off main. Create a merge request for it. Look at the actual diff
first, then write a description covering what changed and why. Title:
"Add configurable request timeout to HTTP client".
OpenCode calls get_merge_request_changes (or the equivalent diff-fetch after MR creation with an initial title) to ground the description, then update_merge_request to fill in the description. Confirm the resulting MR reads correctly — auto-generated descriptions are a good draft, not a final one; skim it before telling a human reviewer it's ready.
Step 2 — a teammate leaves review comments; pull them in:
Pull all unresolved discussion threads on MR !231 in
my-group/backend-services and list them grouped by file.
Step 3 — address one comment and respond:
The reviewer commented on http_client.py line 42 asking why the timeout
default is 30 seconds instead of matching the platform-wide 10-second
standard. Read that file, check if there's a documented reason in
comments or the codebase, and draft a reply to that specific discussion
thread explaining the reasoning (or agreeing to change it if there's no
good reason).
This is where create_merge_request_discussion (replying to an existing thread, not opening a new top-level comment) matters — a new top-level comment loses the threading context and makes the MR harder for the human reviewer to follow. Confirm your OpenCode session is calling the reply-to-thread variant, not just posting a fresh note.
Step 4 — once approved, merge:
Check if MR !231 has been approved and its pipeline is green. If both
are true, merge it with squash enabled and delete the source branch.
merge_merge_request takes squash and should_remove_source_branch as parameters — spell out both explicitly in your prompt since defaults vary between the official and community servers (the official server defaults should_remove_source_branch to false, which surprises people expecting GitHub's more aggressive default cleanup behavior).
Tips
- Always fetch the real diff before generating an MR description — a description guessed from the branch name reads noticeably worse to reviewers and it shows.
- Reply into existing discussion threads (create_merge_request_discussionreply, not a fresh note) when addressing specific review comments — preserves threading for the human reviewer.
- Spell outsquashandshould_remove_source_branchexplicitly in merge prompts — defaults differ between GitLab MCP server implementations and silently not matching your team's convention.
Known Limitations for GitLab MCP in OpenCode
Be upfront with yourself about where this setup falls short, because working around a known gap is faster than debugging a mystery.
No native diff rendering in the OpenCode TUI. Unlike VS Code, OpenCode's terminal interface shows diff content as raw text output from the tool call, not a syntax-highlighted, side-by-side view. For large diffs this is genuinely harder to review than the GitLab web UI — use OpenCode for triage and summarization, and pop open the actual GitLab MR page in a browser for the final visual review pass before approving.
Approval rule visibility is weak. As noted in the earlier topic, most GitLab MCP servers don't expose a clean tool for "required approvers" or "approval rules" — OpenCode inherits this gap. Asking "is this MR mergeable per our approval policy" often gets an incomplete answer because the agent can only see approval counts, not the configured rules. Don't rely on an agent's "yes it's approved" as the final word on branch-protection compliance — GitLab's own merge button will still block the merge if rules aren't satisfied, which is your real safety net.
Self-managed instance version drift. OpenCode itself has no opinion here, but because OpenCode is popular with teams running self-hosted GitLab (cost-conscious teams tend to self-host both), you'll hit GraphQL field mismatches on older GitLab versions more often than GitLab.com users will. If a tool call errors with something like Field 'approvalsRequired' doesn't exist on type 'MergeRequest', that's a version gap, not an OpenCode bug — check your instance's GitLab version against the MCP server's changelog.
Token refresh isn't automatic. OpenCode doesn't proactively warn you that a PAT is nearing expiry the way some IDE-integrated tools do. An expired token surfaces as a generic 401 mid-session, which can look like a broken MCP server. Set yourself a calendar reminder tied to your token's expiration date rather than relying on OpenCode to catch it.
Tips
- Use OpenCode for MR triage, summaries, and comment drafting; do the final visual diff review in the GitLab web UI for anything non-trivial.
- Don't treat an agent's "approved, ready to merge" as authoritative on approval-rule compliance — let GitLab's actual merge button be the enforcement point.
- Set a manual reminder for PAT expiration — OpenCode won't warn you proactively, and a 401 mid-session is a confusing way to find out.
Tips
Tips
- Keepopencode.jsonGitLab config in the project repo so the whole team shares the same server definition — token stays in env, config stays in git.
- Run--log-level debugafter any MCP config change; OpenCode's default logging under-reports connection failures.
- For anything merge-button-adjacent, treat OpenCode's read as advisory and GitLab's own UI/branch-protection as the final authority.