Skip to content

Commit 5bc4bb6

Browse files
committed
Merge branch 'main' into omgitsads/go-sdk
2 parents 7c1340b + 6a57e75 commit 5bc4bb6

26 files changed

+1061
-159
lines changed

.github/agents/go-sdk-tool-migrator.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ return mcp.NewTool(
3737
mcp.WithDescription(t("TOOL_LIST_DEPENDABOT_ALERTS_DESCRIPTION", "List dependabot alerts in a GitHub repository.")),
3838
mcp.WithToolAnnotation(mcp.ToolAnnotation{
3939
Title: t("TOOL_LIST_DEPENDABOT_ALERTS_USER_TITLE", "List dependabot alerts"),
40-
ReadOnlyHint: ToBoolPtr(true),
40+
ReadOnlyHint: jsonschema.Ptr(true),
4141
}),
4242
mcp.WithString("owner",
4343
mcp.Required(),

README.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
[![Go Report Card](https://goreportcard.com/badge/github.com/github/github-mcp-server)](https://goreportcard.com/report/github.com/github/github-mcp-server)
2+
13
# GitHub MCP Server
24

35
The GitHub MCP Server connects AI tools directly to GitHub's platform. This gives AI agents, assistants, and chatbots the ability to read repositories and code files, manage issues and PRs, analyze code, and automate workflows. All through natural language interactions.
@@ -1264,7 +1266,7 @@ docker run -i --rm \
12641266

12651267
## Lockdown Mode
12661268

1267-
Lockdown mode limits the content that the server will surface from public repositories. When enabled, requests that fetch issue details will return an error if the issue was created by someone who does not have push access to the repository. Private repositories are unaffected, and collaborators can still access their own issues.
1269+
Lockdown mode limits the content that the server will surface from public repositories. When enabled, the server checks whether the author of each item has push access to the repository. Private repositories are unaffected, and collaborators keep full access to their own content.
12681270

12691271
```bash
12701272
./github-mcp-server --lockdown-mode
@@ -1279,7 +1281,20 @@ docker run -i --rm \
12791281
ghcr.io/github/github-mcp-server
12801282
```
12811283

1282-
At the moment lockdown mode applies to the issue read toolset, but it is designed to extend to additional data surfaces over time.
1284+
The behavior of lockdown mode depends on the tool invoked.
1285+
1286+
Following tools will return an error when the author lacks the push access:
1287+
1288+
- `issue_read:get`
1289+
- `pull_request_read:get`
1290+
1291+
Following tools will filter out content from users lacking the push access:
1292+
1293+
- `issue_read:get_comments`
1294+
- `issue_read:get_sub_issues`
1295+
- `pull_request_read:get_comments`
1296+
- `pull_request_read:get_review_comments`
1297+
- `pull_request_read:get_reviews`
12831298

12841299
## i18n / Overriding Descriptions
12851300

cmd/github-mcp-server/generate_docs.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"strings"
1111

1212
"github.com/github/github-mcp-server/pkg/github"
13+
"github.com/github/github-mcp-server/pkg/lockdown"
1314
"github.com/github/github-mcp-server/pkg/raw"
1415
"github.com/github/github-mcp-server/pkg/toolsets"
1516
"github.com/github/github-mcp-server/pkg/translations"
@@ -65,7 +66,8 @@ func generateReadmeDocs(readmePath string) error {
6566
t, _ := translations.TranslationHelper()
6667

6768
// Create toolset group with mock clients
68-
tsg := github.DefaultToolsetGroup(false, mockGetClient, mockGetGQLClient, mockGetRawClient, t, 5000, github.FeatureFlags{})
69+
repoAccessCache := lockdown.GetInstance(nil)
70+
tsg := github.DefaultToolsetGroup(false, mockGetClient, mockGetGQLClient, mockGetRawClient, t, 5000, github.FeatureFlags{}, repoAccessCache)
6971

7072
// Generate toolsets documentation
7173
toolsetsDoc := generateToolsetsDoc(tsg)
@@ -304,7 +306,8 @@ func generateRemoteToolsetsDoc() string {
304306
t, _ := translations.TranslationHelper()
305307

306308
// Create toolset group with mock clients
307-
tsg := github.DefaultToolsetGroup(false, mockGetClient, mockGetGQLClient, mockGetRawClient, t, 5000, github.FeatureFlags{})
309+
repoAccessCache := lockdown.GetInstance(nil)
310+
tsg := github.DefaultToolsetGroup(false, mockGetClient, mockGetGQLClient, mockGetRawClient, t, 5000, github.FeatureFlags{}, repoAccessCache)
308311

309312
// Generate table header
310313
buf.WriteString("| Name | Description | API URL | 1-Click Install (VS Code) | Read-only Link | 1-Click Read-only Install (VS Code) |\n")

cmd/github-mcp-server/main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"os"
77
"strings"
8+
"time"
89

910
"github.com/github/github-mcp-server/internal/ghmcp"
1011
"github.com/github/github-mcp-server/pkg/github"
@@ -50,6 +51,7 @@ var (
5051
enabledToolsets = []string{github.ToolsetMetadataDefault.ID}
5152
}
5253

54+
ttl := viper.GetDuration("repo-access-cache-ttl")
5355
stdioServerConfig := ghmcp.StdioServerConfig{
5456
Version: version,
5557
Host: viper.GetString("host"),
@@ -62,6 +64,7 @@ var (
6264
LogFilePath: viper.GetString("log-file"),
6365
ContentWindowSize: viper.GetInt("content-window-size"),
6466
LockdownMode: viper.GetBool("lockdown-mode"),
67+
RepoAccessCacheTTL: &ttl,
6568
}
6669
return ghmcp.RunStdioServer(stdioServerConfig)
6770
},
@@ -84,6 +87,7 @@ func init() {
8487
rootCmd.PersistentFlags().String("gh-host", "", "Specify the GitHub hostname (for GitHub Enterprise etc.)")
8588
rootCmd.PersistentFlags().Int("content-window-size", 5000, "Specify the content window size")
8689
rootCmd.PersistentFlags().Bool("lockdown-mode", false, "Enable lockdown mode")
90+
rootCmd.PersistentFlags().Duration("repo-access-cache-ttl", 5*time.Minute, "Override the repo access cache TTL (e.g. 1m, 0s to disable)")
8791

8892
// Bind flag to viper
8993
_ = viper.BindPFlag("toolsets", rootCmd.PersistentFlags().Lookup("toolsets"))
@@ -95,6 +99,7 @@ func init() {
9599
_ = viper.BindPFlag("host", rootCmd.PersistentFlags().Lookup("gh-host"))
96100
_ = viper.BindPFlag("content-window-size", rootCmd.PersistentFlags().Lookup("content-window-size"))
97101
_ = viper.BindPFlag("lockdown-mode", rootCmd.PersistentFlags().Lookup("lockdown-mode"))
102+
_ = viper.BindPFlag("repo-access-cache-ttl", rootCmd.PersistentFlags().Lookup("repo-access-cache-ttl"))
98103

99104
// Add subcommands
100105
rootCmd.AddCommand(stdioCmd)

docs/installation-guides/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ This directory contains detailed installation instructions for the GitHub MCP Se
77
- **[Claude Applications](install-claude.md)** - Installation guide for Claude Web, Claude Desktop and Claude Code CLI
88
- **[Cursor](install-cursor.md)** - Installation guide for Cursor IDE
99
- **[Google Gemini CLI](install-gemini-cli.md)** - Installation guide for Google Gemini CLI
10+
- **[OpenAI Codex](install-codex.md)** - Installation guide for OpenAI Codex
1011
- **[Windsurf](install-windsurf.md)** - Installation guide for Windsurf IDE
1112

1213
## Support by Host Application
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Install GitHub MCP Server in OpenAI Codex
2+
3+
## Prerequisites
4+
5+
1. OpenAI Codex (MCP-enabled) installed / available
6+
2. A [GitHub Personal Access Token](https://github.com/settings/personal-access-tokens/new)
7+
8+
> The remote GitHub MCP server is hosted by GitHub at `https://api.githubcopilot.com/mcp/` and supports Streamable HTTP.
9+
10+
## Remote Configuration
11+
12+
Edit `~/.codex/config.toml` (shared by CLI and IDE extension) and add:
13+
14+
```toml
15+
[mcp_servers.github]
16+
url = "https://api.githubcopilot.com/mcp/"
17+
# Replace with your real PAT (least-privilege scopes). Do NOT commit this.
18+
bearer_token_env_var = "GITHUB_PAT_TOKEN"
19+
```
20+
21+
You can also add it via the Codex CLI:
22+
23+
```cli
24+
codex mcp add github --url https://api.githubcopilot.com/mcp/
25+
```
26+
27+
<details>
28+
<summary><b>Storing Your PAT Securely</b></summary>
29+
<br>
30+
31+
For security, avoid hardcoding your token. One common approach:
32+
33+
1. Store your token in `.env` file
34+
```
35+
GITHUB_PAT_TOKEN=ghp_your_token_here
36+
```
37+
38+
2. Add to .gitignore
39+
```bash
40+
echo -e ".env" >> .gitignore
41+
```
42+
</details>
43+
44+
## Local Docker Configuration
45+
46+
Use this if you prefer a local, self-hosted instance instead of the remote HTTP server, please refer to the [OpenAI documentation for configuration](https://developers.openai.com/codex/mcp).
47+
48+
## Verification
49+
50+
After starting Codex (CLI or IDE):
51+
1. Run `/mcp` in the TUI or use the IDE MCP panel; confirm `github` shows tools.
52+
2. Ask: "List my GitHub repositories".
53+
3. If tools are missing:
54+
- Check token validity & scopes.
55+
- Confirm correct table name: `[mcp_servers.github]`.
56+
57+
## Usage
58+
59+
After setup, Codex can interact with GitHub directly. It will use the default tool set automatically but can be [configured](../../README.md#default-toolset). Try these example prompts:
60+
61+
**Repository Operations:**
62+
- "List my GitHub repositories"
63+
- "Show me recent issues in [owner/repo]"
64+
- "Create a new issue in [owner/repo] titled 'Bug: fix login'"
65+
66+
**Pull Requests:**
67+
- "List open pull requests in [owner/repo]"
68+
- "Show me the diff for PR #123"
69+
- "Add a comment to PR #123: 'LGTM, approved'"
70+
71+
**Actions & Workflows:**
72+
- "Show me recent workflow runs in [owner/repo]"
73+
- "Trigger the 'deploy' workflow in [owner/repo]"
74+
75+
**Gists:**
76+
- "Create a gist with this code snippet"
77+
- "List my gists"
78+
79+
> **Tip**: Use `/mcp` in the Codex UI to see all available GitHub tools and their descriptions.
80+
81+
## Choosing Scopes for Your PAT
82+
83+
Minimal useful scopes (adjust as needed):
84+
- `repo` (general repository operations)
85+
- `workflow` (if you want Actions workflow access)
86+
- `read:org` (if accessing org-level resources)
87+
- `project` (for classic project boards)
88+
- `gist` (if using gist tools)
89+
90+
Use the principle of least privilege: add scopes only when a tool request fails due to permission.
91+
92+
## Troubleshooting
93+
94+
| Issue | Possible Cause | Fix |
95+
|-------|----------------|-----|
96+
| Authentication failed | Missing/incorrect PAT scope | Regenerate PAT; ensure `repo` scope present |
97+
| 401 Unauthorized (remote) | Token expired/revoked | Create new PAT; update `bearer_token_env_var` |
98+
| Server not listed | Wrong table name or syntax error | Use `[mcp_servers.github]`; validate TOML |
99+
| Tools missing / zero tools | Insufficient PAT scopes | Add needed scopes (workflow, gist, etc.) |
100+
| Token in file risks leakage | Committed accidentally | Rotate token; add file to `.gitignore` |
101+
102+
## Security Best Practices
103+
1. Never commit tokens into version control
104+
3. Rotate tokens periodically
105+
4. Restrict scopes up front; expand only when required
106+
5. Remove unused PATs from your GitHub account
107+
108+
## References
109+
- Remote server URL: `https://api.githubcopilot.com/mcp/`
110+
- Release binaries: [GitHub Releases](https://github.com/github/github-mcp-server/releases)
111+
- OpenAI Codex MCP docs: https://developers.openai.com/codex/mcp
112+
- Main project README: [Advanced configuration options](../../README.md)

docs/remote-server.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ The Remote GitHub MCP server has optional headers equivalent to the Local server
6161
- `X-MCP-Readonly`: Enables only "read" tools.
6262
- Equivalent to `GITHUB_READ_ONLY` env var for Local server.
6363
- If this header is empty, "false", "f", "no", "n", "0", or "off" (ignoring whitespace and case), it will be interpreted as false. All other values are interpreted as true.
64+
- `X-MCP-Lockdown`: Enables lockdown mode, hiding public issue details created by users without push access.
65+
- Equivalent to `GITHUB_LOCKDOWN_MODE` env var for Local server.
66+
- If this header is empty, "false", "f", "no", "n", "0", or "off" (ignoring whitespace and case), it will be interpreted as false. All other values are interpreted as true.
6467

6568
Example:
6669

@@ -70,7 +73,8 @@ Example:
7073
"url": "https://api.githubcopilot.com/mcp/",
7174
"headers": {
7275
"X-MCP-Toolsets": "repos,issues",
73-
"X-MCP-Readonly": "true"
76+
"X-MCP-Readonly": "true",
77+
"X-MCP-Lockdown": "false"
7478
}
7579
}
7680
```

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ require (
99
github.com/mark3labs/mcp-go v0.36.0
1010
github.com/microcosm-cc/bluemonday v1.0.27
1111
github.com/migueleliasweb/go-github-mock v1.3.0
12+
github.com/muesli/cache2go v0.0.0-20221011235721-518229cd8021
1213
github.com/spf13/cobra v1.10.1
1314
github.com/spf13/viper v1.21.0
1415
github.com/stretchr/testify v1.11.1
@@ -38,7 +39,7 @@ require (
3839
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
3940
github.com/fsnotify/fsnotify v1.9.0 // indirect
4041
github.com/go-viper/mapstructure/v2 v2.4.0
41-
github.com/google/go-querystring v1.1.0
42+
github.com/google/go-querystring v1.1.0 // indirect
4243
github.com/google/uuid v1.6.0 // indirect
4344
github.com/inconshreveable/mousetrap v1.1.0 // indirect
4445
github.com/modelcontextprotocol/go-sdk v1.1.0

go.sum

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ github.com/migueleliasweb/go-github-mock v1.3.0 h1:2sVP9JEMB2ubQw1IKto3/fzF51oFC
6767
github.com/migueleliasweb/go-github-mock v1.3.0/go.mod h1:ipQhV8fTcj/G6m7BKzin08GaJ/3B5/SonRAkgrk0zCY=
6868
github.com/modelcontextprotocol/go-sdk v1.1.0 h1:Qjayg53dnKC4UZ+792W21e4BpwEZBzwgRW6LrjLWSwA=
6969
github.com/modelcontextprotocol/go-sdk v1.1.0/go.mod h1:6fM3LCm3yV7pAs8isnKLn07oKtB0MP9LHd3DfAcKw10=
70+
github.com/muesli/cache2go v0.0.0-20221011235721-518229cd8021 h1:31Y+Yu373ymebRdJN1cWLLooHH8xAr0MhKTEJGV/87g=
71+
github.com/muesli/cache2go v0.0.0-20221011235721-518229cd8021/go.mod h1:WERUkUryfUWlrHnFSO/BEUZ+7Ns8aZy7iVOGewxKzcc=
7072
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
7173
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
7274
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
@@ -114,8 +116,6 @@ golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0
114116
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
115117
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
116118
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
117-
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
118-
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
119119
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
120120
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
121121
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
@@ -124,6 +124,8 @@ golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
124124
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
125125
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
126126
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
127+
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
128+
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
127129
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
128130
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
129131
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

internal/ghmcp/server.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515

1616
"github.com/github/github-mcp-server/pkg/errors"
1717
"github.com/github/github-mcp-server/pkg/github"
18+
"github.com/github/github-mcp-server/pkg/lockdown"
1819
mcplog "github.com/github/github-mcp-server/pkg/log"
1920
"github.com/github/github-mcp-server/pkg/raw"
2021
"github.com/github/github-mcp-server/pkg/translations"
@@ -55,6 +56,8 @@ type MCPServerConfig struct {
5556

5657
// Logger is used for logging within the server
5758
Logger *slog.Logger
59+
// RepoAccessTTL overrides the default TTL for repository access cache entries.
60+
RepoAccessTTL *time.Duration
5861
}
5962

6063
func NewMCPServer(cfg MCPServerConfig) (*mcp.Server, error) {
@@ -79,6 +82,14 @@ func NewMCPServer(cfg MCPServerConfig) (*mcp.Server, error) {
7982
},
8083
} // We're going to wrap the Transport later in beforeInit
8184
gqlClient := githubv4.NewEnterpriseClient(apiHost.graphqlURL.String(), gqlHTTPClient)
85+
repoAccessOpts := []lockdown.RepoAccessOption{}
86+
if cfg.RepoAccessTTL != nil {
87+
repoAccessOpts = append(repoAccessOpts, lockdown.WithTTL(*cfg.RepoAccessTTL))
88+
}
89+
var repoAccessCache *lockdown.RepoAccessCache
90+
if cfg.LockdownMode {
91+
repoAccessCache = lockdown.GetInstance(gqlClient, repoAccessOpts...)
92+
}
8293

8394
enabledToolsets := cfg.EnabledToolsets
8495

@@ -140,6 +151,7 @@ func NewMCPServer(cfg MCPServerConfig) (*mcp.Server, error) {
140151
cfg.Translator,
141152
cfg.ContentWindowSize,
142153
github.FeatureFlags{LockdownMode: cfg.LockdownMode},
154+
repoAccessCache,
143155
)
144156
err = tsg.EnableToolsets(enabledToolsets, nil)
145157

@@ -194,6 +206,9 @@ type StdioServerConfig struct {
194206

195207
// LockdownMode indicates if we should enable lockdown mode
196208
LockdownMode bool
209+
210+
// RepoAccessCacheTTL overrides the default TTL for repository access cache entries.
211+
RepoAccessCacheTTL *time.Duration
197212
}
198213

199214
// RunStdioServer is not concurrent safe.
@@ -231,6 +246,7 @@ func RunStdioServer(cfg StdioServerConfig) error {
231246
ContentWindowSize: cfg.ContentWindowSize,
232247
LockdownMode: cfg.LockdownMode,
233248
Logger: logger,
249+
RepoAccessTTL: cfg.RepoAccessCacheTTL,
234250
})
235251
if err != nil {
236252
return fmt.Errorf("failed to create MCP server: %w", err)

0 commit comments

Comments
 (0)