·

GitHub MCP With OpenCode

Set up GitHub MCP in OpenCode so your AI agent can manage repositories, pull requests, and issues right from your editor.

OpenCode takes a plain-JSON, no-magic approach to MCP configuration — there's no opencode mcp add wizard like Claude Code's CLI, you edit opencode.json (or ~/.config/opencode/opencode.json for a global config) directly. That's a small amount of extra friction on setup, and a large amount of extra clarity once it's running, because the entire server config is one file you can diff, review, and check into git.


Installing and Connecting GitHub MCP to OpenCode

OpenCode supports both local (stdio, spawns a process) and remote (HTTP/SSE) MCP server definitions in its config schema. For GitHub MCP, the remote hosted endpoint is again the lowest-friction option:

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "github": {
      "type": "remote",
      "url": "https://api.githubcopilot.com/mcp/",
      "enabled": true
    }
  }
}

For a local stdio setup with the official Go binary (no Docker), point OpenCode at the binary directly and pass the PAT as an env var in the server definition:

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "github": {
      "type": "local",
      "command": ["github-mcp-server", "stdio"],
      "environment": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "{env:GITHUB_PAT}",
        "GITHUB_TOOLSETS": "repos,issues,pull_requests"
      },
      "enabled": true
    }
  }
}

The {env:GITHUB_PAT} interpolation pulls from your shell environment at launch time rather than hardcoding the token in the JSON file — this is the pattern to use for any config you intend to commit to a repo.

Confirm the connection from inside a session:

opencode
/mcp

If github shows as failed to connect with a local server, the most common cause is the binary not being on PATH inside the environment OpenCode spawns from — test it standalone first:

which github-mcp-server
GITHUB_PERSONAL_ACCESS_TOKEN=$GITHUB_PAT github-mcp-server stdio

Tips
- Use {env:VAR_NAME} interpolation for tokens in opencode.json — never hardcode a PAT into a file you might commit.
- Test the github-mcp-server binary standalone before wiring it into OpenCode; a silent connection failure inside the TUI is much harder to debug than a binary that errors immediately on its own.
- Project-level opencode.json (repo root) overrides the global ~/.config/opencode/opencode.json — check both if a server behaves differently than you expect between projects.


Managing Issues, Pull Requests, and Repos from OpenCode

Once connected, OpenCode's model sees the same GitHub MCP tool surface as any other client — tool names typically show up prefixed as github_list_issues, github_create_pull_request, etc. (exact prefixing depends on OpenCode's tool-naming convention for the configured server key). Natural-language prompts work the same way:

> show me all pull requests in this repo that have been open for more than 5 days
  with no reviews
> read PR #77's diff and tell me if the new endpoint added in api/routes.go
  has a corresponding entry in api/openapi.yaml

For repo-content tasks that don't need a local clone — reading a config file from a different branch, or checking what a file looked like at a specific commit — get_file_contents avoids the checkout entirely:

> get the contents of config/feature-flags.yml from the release/2.4 branch
  and diff it conceptually against what's on main

OpenCode's permission model is coarser than Claude Code's per-tool allow/ask/deny: by default it prompts for any tool call it classifies as a write, based on the tool's declared behavior in the MCP server's schema (the official github-mcp-server marks its write tools accordingly). You can pre-approve specific tools per-session via the TUI's permission prompt ("always allow this tool"), but there's no persisted fine-grained config file equivalent to Claude Code's settings.json allow-lists as of the current OpenCode release — treat every session as starting permission-neutral unless you've set "enabled": false on toolsets you never want available at all (done by omitting them from GITHUB_TOOLSETS in the local-server config, since there's no separate allow/deny layer on top).

> create an issue titled "Rate limiter drops requests under burst load"
  with a description summarizing the attached stack trace, label it "bug"

This triggers a permission prompt in the TUI before create_issue fires — approve, deny, or approve-and-remember-for-session.

Tips
- Because OpenCode's permission granularity is session-level rather than a persisted per-tool config file, be more conservative about what toolsets you even load — restrict at the GITHUB_TOOLSETS level rather than relying on prompts to catch mistakes.
- get_file_contents against a branch or commit SHA is the fastest way to answer "what does X look like on Y" questions without a local checkout — use it before reaching for a full git fetch.
- Session-level "always allow" approvals reset when you start a new OpenCode session — don't expect prior approvals to persist across restarts.


Practical Example: AI-Assisted Issue Triage in OpenCode

A concrete, repeatable workflow: an inbox of untriaged issues, and you want labels, priority, and a duplicate check, without touching each one by hand.

> list all issues with no labels, opened in the last 14 days.
  For each one:
  1. read the full body
  2. search open+closed issues for likely duplicates using search_issues
  3. propose a label from this set: bug, feature, question, docs, duplicate
  4. propose a priority: P0, P1, P2
  Show me the full list as a table before you apply anything.

OpenCode will chain list_issues → per-issue get_issuesearch_issues for duplicate detection → produce a table. Reviewing the table before approving each update_issue/add_issue_comment call is the checkpoint that keeps this safe — the duplicate-detection step in particular is heuristic (keyword-based search, not semantic), so it will occasionally flag a false match on generic titles like "Login fails" across unrelated issues.

> apply the labels for rows 1, 2, and 4 from that table.
  For row 3, don't apply — I want to check that duplicate manually first.

This selective-approval pattern — reviewing a proposed batch, then explicitly naming which rows to execute — is the difference between issue triage that saves real time and issue triage that creates a new cleanup job of mislabeled issues.

For a duplicate flagged with reasonable confidence, closing with a reference comment:

> close issue #341 as a duplicate of #298, and add a comment linking to #298
  explaining briefly why

Tips
- Treat search_issues-based duplicate detection as a first-pass filter, not a verdict — it's keyword matching against title/body, not semantic similarity, and generic issue titles produce false positives regularly.
- Always route batch triage through a "show me the table, then tell me which rows to apply" checkpoint — applying an entire proposed batch in one shot is how you end up with ten issues mislabeled the same wrong way.
- When closing as duplicate, always ask for a comment linking the canonical issue — an unexplained close reads as dismissive to whoever filed it, human-triaged or not.


Known Limitations for GitHub MCP in OpenCode

A few gaps worth knowing before you build a workflow around this combination, rather than discovering them mid-task:

  • No persisted per-tool permission config. As covered above, OpenCode's approval model is session-scoped. If you want Claude Code-style "always auto-allow reads, always ask on writes" behavior that survives restarts, you'll be re-approving read tools every session, or restricting toolsets at the config level to compensate.
  • SSE/remote transport stability. The remote HTTP server works well for short sessions but long-running OpenCode sessions (multi-hour, doing extended triage) have occasionally needed a manual /mcp reconnect after the underlying SSE connection idles out — a known class of issue with SSE-based MCP transports generally, not GitHub-specific, but you'll see it here first if you run OpenCode for extended stretches.
  • Tool-name prefixing varies with server key naming. If you name your config key something other than github (e.g., gh-work), the exposed tool names shift with it, which can break prompts you copy from documentation assuming the default github key. Keep the config key as github unless you have a specific reason not to.
  • No built-in diff-rendering UI. Unlike the Claude Code VS Code extension's inline diff cards, OpenCode's TUI renders tool call arguments and results as text — readable, but you're reading a JSON-ish PR diff in a terminal pane rather than a proper side-by-side view. For heavy visual PR review, this course's Cursor topic (Topic 5) is the better fit.
  • No GitHub App auth path documented for the local server config as of current OpenCode releases — the environment block expects a PAT-style token. If your org mandates GitHub App-based access, you'll need to front it with a short-lived-token-minting wrapper script rather than pointing OpenCode's config directly at app credentials.

Tips
- If a long OpenCode session's GitHub tools suddenly stop responding, run /mcp and reconnect before assuming the token expired — it's often just an idled SSE connection.
- Keep the MCP config key named github — tool-name prefixes derive from it, and mismatched prefixes in copied prompts are a common source of "the tool doesn't exist" confusion.
- For GitHub App-based org policies, wrap token minting in a small script that refreshes the installation token and exports it as GITHUB_PERSONAL_ACCESS_TOKEN before launching OpenCode, since there's no native app-auth field in the local server config.


Tips

Tips
- Configure GitHub MCP in opencode.json with {env:VAR} interpolation for tokens — never a literal PAT in a file you might commit.
- OpenCode's permission model is session-scoped, not persisted per-tool — compensate by restricting GITHUB_TOOLSETS up front rather than relying on approval prompts alone.
- For heavy visual PR review work, pair OpenCode's fast triage loop with a client that renders diffs visually (Cursor or the Claude Code VS Code extension) rather than reading raw diffs in a terminal pane.