repogate is a GitHub workbench for coding agents: it wraps the GitHub REST API into a set of MCP (Model Context Protocol) tools, letting agents perform repo queries, issue management, PR creation and merging, code review, search, and other common operations directly in a conversation.
- Zero runtime dependencies: uses only built-in Node.js capabilities (
fetch,node:test); no packages need to be installed to run; - Standard MCP stdio server: works with any MCP-capable client (dsh, Claude Code, Codex, opencode, etc.);
- Built for dsh: ships a dsh bundle (
cordis.patch.yml+ a self-developed bridge plugin), connected withdsh plugin addin one step — tools appear automatically in the model's tool list (mcp__repogate__*); - Dual auth channels: personal access token (PAT) and OAuth device authorization flow, with local token caching;
- Read-only mode: blocks all write operations with one switch, ideal for research-only sessions;
- Actionable errors: rate limits, invalid tokens, insufficient permissions, rejected parameters — every error includes a Chinese fix guide;
- Slim output for models: lists/search return concise summaries instead of full JSON, saving context tokens.
# requires Node.js ≥ 18.17
REPOGATE_TOKEN=ghp_yourtoken node src/entry.jsExample config line using the official dsh bridge (also works in Claude Code / Codex MCP configs):
# dsh: insert into $DSH_HOME/profiles/<profile>/cordis.patch.yml
- insert:
- id: mcp-repogate
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: repogate
transport: stdio
command: node
args: ['/absolute/path/to/src/entry.js']
env:
REPOGATE_TOKEN: !!js process.env.REPOGATE_TOKEN ?? ''After connecting, the model sees 23 tools such as gh_issue_fetch and gh_pr_merge
(generic MCP clients see bare names; under dsh they carry the mcp__repogate__ prefix, see below).
This plugin is declared as a dsh bundle (the dsh.bundle field in package.json). In the plugin checkout directory run:
dsh plugin --profile web add .- On first use it initializes the
webprofile automatically and adds this package todsh.profile.bundles; - The
repogate/bridgeplugin defined in the package'scordis.patch.ymllaunches this MCP server directly inside the dsh process, and after the handshake registers all tools intoctx.tools— no manual config changes needed; - The token is inherited from the dsh process environment by default (
REPOGATE_TOKENorGITHUB_TOKEN); - Uninstall:
dsh plugin --profile web remove repogate.
After installing, restart dsh and simply say in a session:
"Look at the open issues of the octo/hello repo, close #12, then comment 'fixed, waiting for verification' on #12."
The corresponding tool call chain: mcp__repogate__gh_issue_browse → mcp__repogate__gh_issue_fetch →
mcp__repogate__gh_issue_edit → mcp__repogate__gh_issue_respond.
Note: dsh does not enable any MCP server by default (each server command is trusted code executed outside the sandbox); this plugin's bundle line is the "enable" action itself; only install trusted plugins.
dsh plugin --profile demo add github:JohnXu22786/github-mcpA single command installs this plugin into the dsh demo profile from the GitHub repository. Integration, auth, and lifecycle details follow in the "dsh integration" section below.
dsh uses the Cordis plugin framework; the unit of composition is a bundle: an npm package + a patch layer. The loading chain is as follows:
package.json (dsh.bundle.patch → ./cordis.patch.yml)
└─ a line in cordis.patch.yml: name: 'repogate/bridge'
└─ src/bridge/plugin.js (Cordis plugin, inject: ['tools'])
├─ spawns src/entry.js (the MCP server child process, stdio) using Node itself
├─ completes the initialize / tools/list handshake
└─ registers each tool as mcp__repogate__<tool name> into ctx.tools
- Tool interface: the tool name visible to the model =
mcp__<serverName>__<original tool name>,serverNamedefaults torepogate; - Events/skills: this plugin registers no events or skills; it only exposes capabilities through the
ctx.toolstool interface; - Lifecycle: handshake and registration happen during the plugin's
apply; on unload it kills the child process and unregisters all tools automatically (cleanup registered viactx.effect, so hot reloads/unloads leave no residue); - Two bridges to choose from: the bundle's built-in bridge
repogate/bridge(zero dependencies, works out of the box) and the official dsh@deepseek-ai/dsh-mcp-clientconfig line (seeexamples/overlay-for-dsh.yml.example); the tools are named and behave identically — pick either one, do not enable both; - Environment variables: dsh filters credential-type variables from the MCP child process environment, so the official bridge line needs the token written into the
envconfig; the built-in bridge's child process inherits the host environment, soREPOGATE_TOKENis passed through automatically.
| Symptom | Treatment |
|---|---|
| Tools don't appear in the list | Check whether the cordis.patch.yml line took effect (dsh --profile <name> --dump-config to inspect layers), confirm no errors in the startup log |
| 401 invalid token | Check the env.REPOGATE_TOKEN config; or ask the model to call mcp__repogate__gh_auth_login in the session to use OAuth |
| Want read-only | Configure args: ['--read-only'] on the bridge line, or append --read-only to the official line's args |
| pnpm ≥10 rejects prepare scripts on git installs | This plugin is pure JS with no build script, so it's not affected; install from checkout or tarball |
| Domain | Tool | Purpose | Write op |
|---|---|---|---|
| Repo | gh_repo_fetch |
Repo details: default branch, stars, language, visibility | |
| Repo | gh_repo_browse |
List user/org/own repos (paginated) | |
| Issue | gh_issue_open |
Create an issue (title required, optional labels/assignee) | ✔ |
| Issue | gh_issue_fetch |
View full issue info | |
| Issue | gh_issue_browse |
Filter by state/labels/assignee/author (pages may exclude PRs) | |
| Issue | gh_issue_edit |
Edit title/body/state/assignee/labels | ✔ |
| Issue | gh_issue_respond |
Post a comment (works in PR threads too) | ✔ |
| PR | gh_pr_open |
Create a pull request (head/base/draft) | ✔ |
| PR | gh_pr_fetch |
PR details: mergeability, changed stats, review count | |
| PR | gh_pr_browse |
Filter by state/branch, sort, paginate | |
| PR | gh_pr_edit |
Edit title/body/state/draft/base branch | ✔ |
| PR | gh_pr_merge |
Merge (method/commit message/delete source branch) | ✔ |
| Review | gh_review_submit |
Submit a full review: approve / request_changes / comment | ✔ |
| Review | gh_review_comment |
Line-level diff comments (including range comments) | ✔ |
| Review | gh_review_fetch |
List all line-level comments | |
| Review | gh_review_browse |
List submitted full reviews | |
| Search | gh_search_repos |
Search repos with GitHub search syntax | |
| Search | gh_search_issues |
Search issues/PRs (type:pr distinguishes) |
|
| Search | gh_search_code |
Search code (requires token, returns file hits) | |
| Account | gh_whoami |
Current identity, token source, read-only mode, API quota | |
| Auth | gh_auth_login |
Start OAuth device authorization (requires configured clientId) | |
| Auth | gh_auth_check |
Poll authorization result once | |
| Auth | gh_auth_logout |
Clear the local token cache |
All tools take JSON Schema inputs (name/description/inputSchema) that models can discover on their own;
write tools are intercepted with a clear message in read-only mode.
Priority: CLI flags > environment variables > config file > defaults. The config file is JSON, its path given by --config
or REPOGATE_CONFIG; see examples/repogate.config.json.example.
| Config item | Environment variable | Default |
|---|---|---|
| Access token | REPOGATE_TOKEN (also accepts GITHUB_TOKEN / GH_TOKEN) |
none |
| API base URL (enterprise instances) | REPOGATE_BASE_URL |
https://api.github.com |
| Read-only mode | REPOGATE_READ_ONLY (1/true/yes/on) |
false |
| Per-request timeout (ms) | REPOGATE_TIMEOUT_MS |
30000 |
| OAuth Client ID | REPOGATE_OAUTH_CLIENT_ID |
none |
| OAuth token cache file | REPOGATE_TOKEN_FILE |
~/.repogate/token.json when oauth.clientId is configured |
| Config file path | REPOGATE_CONFIG |
none |
| Debug logging (stderr) | REPOGATE_DEBUG |
false |
CLI flags: --config --token --read-only --base-url --timeout-ms
--oauth-client-id --token-file --debug --version --help.
Generate one in GitHub's Developer settings (check the repo permissions needed on a fine-grained token), then choose any of:
REPOGATE_TOKEN=ghp_xxx node src/entry.js # environment variable
node src/entry.js --token ghp_xxx # CLI flag
node src/entry.js --config repogate.config.json # config file (token field)Windows PowerShell 下环境变量写法:
$env:REPOGATE_TOKEN = 'ghp_xxx'
node src/entry.jsToken resolution order: --token > REPOGATE_TOKEN > GITHUB_TOKEN > GH_TOKEN > config file > cache file.
For those who'd rather not assemble a token by hand. You first need a GitHub App's Client ID (the device flow only requires a public client_id):
- Configure
oauth.clientId(config file orREPOGATE_OAUTH_CLIENT_ID); - Ask the model to call
gh_auth_login→ returns an authorization URL and a one-time code; - The user opens the URL in a browser, enters the code, and confirms;
- The model calls
gh_auth_check(may be called multiple times; each call checks once) → oncegranted, the token is written to the cache file, and all tools become available; the cached token survives process restarts; gh_auth_logoutclears the cache.
Note: the device authorization endpoint always uses github.com; for enterprise instances (custom
baseUrl) use a PAT. The token cache file is written with 0600 permissions; do not commit the cache file to version control.
node src/entry.js --read-only # or REPOGATE_READ_ONLY=1When enabled, the 8 write tools (gh_issue_open / gh_issue_edit / gh_issue_respond /
gh_pr_open / gh_pr_edit / gh_pr_merge / gh_review_submit / gh_review_comment)
are intercepted after argument validation and return a [readonly] error explaining how to turn it off; query, search, and auth tools are unaffected.
All failures are returned as structured errors (MCP isError: true + structuredContent.error) in the form
[error code] reason. Common error codes and typical scenarios:
| Error code | Scenario | Guide |
|---|---|---|
auth |
Token missing/invalid (401) | Configure a token or use OAuth device authorization |
ratelimit |
Quota exhausted (403/429) | Report reset time or Retry-After seconds |
http |
404/403/422/409 etc. | Explain the specific cause (not found/no permission/params rejected/conflict) |
validation |
Argument validation failed | Point out which argument is invalid |
readonly |
Read-only mode blocks a write | Explain how to disable |
timeout |
Request timeout | Suggest increasing timeoutMs |
network |
Network-layer failure | Check the network and baseUrl |
The gateway automatically retries once on 502/503/504 and network jitter (idempotent read requests only; writes are not retried to avoid duplicate side effects);
if a 5xx still fails after retry, it returns an http error instead of failing silently.
src/
├── entry.js entry: config parsing → assembly → start the stdio session
├── protocol/ protocol layer (MCP over stdio, line-delimited JSON-RPC 2.0)
│ ├── jsonrpc.js message encoding/decoding and classification
│ ├── transport.js stdin/stdout read/write loop (logs go to stderr only)
│ └── engine.js session engine: initialize / ping / tools/list / tools/call
├── core/ core layer
│ ├── config.js layered config merge (flag > env > config file > defaults)
│ ├── auth.js credential hub: token resolution + OAuth device auth state machine + cache
│ ├── gateway.js REST gateway: request assembly/retry/timeout/status-code mapping
│ └── errors.js unified error model with actionable hints
├── tools/ tool layer
│ ├── registry.js registry: argument validation (JSON Schema subset) + read-only gate + dispatch
│ ├── repo.js / issue.js / pull.js / review.js / search.js / account.js
│ └── index.js assembles the 23 tools
├── bridge/ dsh integration
│ ├── client.js MCP stdio client (initialize/list/call, cancellation and timeout)
│ └── plugin.js Cordis plugin: spawns the server and registers tools into ctx.tools
└── util/format.js output shaping: entity summaries, pagination detection, URL building
test/ tests (node:test, zero dependencies)
├── helpers/ fake fetch and a local mock API service
└── *.test.js protocol/gateway/config/auth/registry/tool/end-to-end (128 test cases)
Design highlights:
- Layered one-way dependencies: protocol layer → core layer → tool layer; tools don't know protocol details, and the protocol doesn't know API details;
- On-demand token resolution: OAuth authorization takes effect after completion without restart (the gateway holds a tokenResolver rather than a static token);
- One codebase, both ends:
bridge/client.jsand the server share the same JSON-RPC vocabulary, so handshake and call logic are consistent.
node --test # run all 128 tests (including real child-process end-to-end)
node src/entry.js --helpTest coverage: protocol handshake and error paths (including interception of uninitialized sessions), gateway retry (idempotent methods only) and status-code mapping, the full OAuth state machine (including expiry), config priority, argument validation, the read-only gate, request construction and output shaping for all 23 tools, and an end-to-end chain of "real child process + local mock API".
- A token has the same power as the account; do not write it into logs, commit it to version control, or leak it to untrusted conversations;
- Under dsh, MCP server commands are trusted code outside the sandbox; install this plugin only from trusted sources;
- Read-only mode significantly reduces the risk of misuse; research-only sessions are recommended to enable it.