Skip to content

Commit 341c2c1

Browse files
authored
Merge branch 'main' into chore/improve-mcp-server-json
2 parents 9aebb73 + 70cb737 commit 341c2c1

35 files changed

Lines changed: 1912 additions & 1315 deletions

.github/workflows/code-scanning.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ jobs:
3838
uses: actions/checkout@v5
3939

4040
- name: Initialize CodeQL
41-
uses: github/codeql-action/init@v3
41+
uses: github/codeql-action/init@v4
4242
with:
4343
languages: ${{ matrix.language }}
4444
build-mode: ${{ matrix.build-mode }}
@@ -52,13 +52,13 @@ jobs:
5252
threat-models: [ ]
5353
- name: Setup proxy for registries
5454
id: proxy
55-
uses: github/codeql-action/start-proxy@v3
55+
uses: github/codeql-action/start-proxy@v4
5656
with:
5757
registries_credentials: ${{ secrets.GITHUB_REGISTRIES_PROXY }}
5858
language: ${{ matrix.language }}
5959

6060
- name: Configure
61-
uses: github/codeql-action/resolve-environment@v3
61+
uses: github/codeql-action/resolve-environment@v4
6262
id: resolve-environment
6363
with:
6464
language: ${{ matrix.language }}
@@ -70,10 +70,10 @@ jobs:
7070
cache: false
7171

7272
- name: Autobuild
73-
uses: github/codeql-action/autobuild@v3
73+
uses: github/codeql-action/autobuild@v4
7474

7575
- name: Perform CodeQL Analysis
76-
uses: github/codeql-action/analyze@v3
76+
uses: github/codeql-action/analyze@v4
7777
env:
7878
CODEQL_PROXY_HOST: ${{ steps.proxy.outputs.proxy_host }}
7979
CODEQL_PROXY_PORT: ${{ steps.proxy.outputs.proxy_port }}

.github/workflows/docker-publish.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ jobs:
4646
# https://github.com/sigstore/cosign-installer
4747
- name: Install cosign
4848
if: github.event_name != 'pull_request'
49-
uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 #v3.10.0
49+
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad #v4.0.0
5050
with:
5151
cosign-release: "v2.2.4"
5252

.github/workflows/moderator.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: AI Moderator
2+
on:
3+
issues:
4+
types: [opened]
5+
issue_comment:
6+
types: [created]
7+
pull_request_review_comment:
8+
types: [created]
9+
10+
jobs:
11+
spam-detection:
12+
runs-on: ubuntu-latest
13+
permissions:
14+
issues: write
15+
pull-requests: write
16+
models: read
17+
contents: read
18+
steps:
19+
- uses: actions/checkout@v4
20+
- uses: github/ai-moderator@v1
21+
with:
22+
token: ${{ secrets.GITHUB_TOKEN }}
23+
spam-label: 'spam'
24+
ai-label: 'ai-generated'
25+
minimize-detected-comments: true
26+
enable-spam-detection: true
27+
enable-link-spam-detection: true
28+
enable-ai-detection: true

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM golang:1.25.1-alpine AS build
1+
FROM golang:1.25.3-alpine AS build
22
ARG VERSION="dev"
33

44
# Set the working directory

README.md

Lines changed: 88 additions & 78 deletions
Large diffs are not rendered by default.

cmd/github-mcp-server/main.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@ var (
4545
return fmt.Errorf("failed to unmarshal toolsets: %w", err)
4646
}
4747

48+
// No passed toolsets configuration means we enable the default toolset
4849
if len(enabledToolsets) == 0 {
49-
enabledToolsets = github.GetDefaultToolsetIDs()
50+
enabledToolsets = []string{github.ToolsetMetadataDefault.ID}
5051
}
5152

5253
stdioServerConfig := ghmcp.StdioServerConfig{
@@ -99,6 +100,7 @@ func init() {
99100
func initConfig() {
100101
// Initialize Viper configuration
101102
viper.SetEnvPrefix("github")
103+
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
102104
viper.AutomaticEnv()
103105

104106
}

docs/installation-guides/install-gemini-cli.md

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ For security, avoid hardcoding your token. Create or update `~/.gemini/.env` (wh
1414

1515
```bash
1616
# ~/.gemini/.env
17-
GITHUB_PAT=your_token_here
17+
GITHUB_MCP_PAT=your_token_here
1818
```
1919

2020
</details>
@@ -30,26 +30,34 @@ After securely storing your PAT, you can add the GitHub MCP server configuration
3030

3131
> **Note:** For the most up-to-date configuration options, see the [main README.md](../../README.md).
3232
33-
### Method 1: Remote Server (Recommended)
33+
### Method 1: Gemini Extension (Recommended)
3434

35-
The simplest way is to use GitHub's hosted MCP server:
35+
The simplest way is to use GitHub's hosted MCP server via our gemini extension.
36+
37+
`gemini extensions install https://github.com/github/github-mcp-server`
38+
39+
> [!NOTE]
40+
> You will still need to have a personal access token with the appropriate scopes called `GITHUB_MCP_PAT` in your environment.
41+
42+
### Method 2: Remote Server
43+
44+
You can also connect to the hosted MCP server directly. After securely storing your PAT, configure Gemini CLI with:
3645

3746
```json
3847
// ~/.gemini/settings.json
3948
{
4049
"mcpServers": {
4150
"github": {
4251
"httpUrl": "https://api.githubcopilot.com/mcp/",
43-
"trust": true,
4452
"headers": {
45-
"Authorization": "Bearer $GITHUB_PAT"
53+
"Authorization": "Bearer $GITHUB_MCP_PAT"
4654
}
4755
}
4856
}
4957
}
5058
```
5159

52-
### Method 2: Local Docker
60+
### Method 3: Local Docker
5361

5462
With docker running, you can run the GitHub MCP server in a container:
5563

@@ -68,14 +76,14 @@ With docker running, you can run the GitHub MCP server in a container:
6876
"ghcr.io/github/github-mcp-server"
6977
],
7078
"env": {
71-
"GITHUB_PERSONAL_ACCESS_TOKEN": "$GITHUB_PAT"
79+
"GITHUB_PERSONAL_ACCESS_TOKEN": "$GITHUB_MCP_PAT"
7280
}
7381
}
7482
}
7583
}
7684
```
7785

78-
### Method 3: Binary
86+
### Method 4: Binary
7987

8088
You can download the latest binary release from the [GitHub releases page](https://github.com/github/github-mcp-server/releases) or build it from source by running `go build -o github-mcp-server ./cmd/github-mcp-server`.
8189

@@ -89,7 +97,7 @@ Then, replacing `/path/to/binary` with the actual path to your binary, configure
8997
"command": "/path/to/binary",
9098
"args": ["stdio"],
9199
"env": {
92-
"GITHUB_PERSONAL_ACCESS_TOKEN": "$GITHUB_PAT"
100+
"GITHUB_PERSONAL_ACCESS_TOKEN": "$GITHUB_MCP_PAT"
93101
}
94102
}
95103
}
@@ -122,6 +130,10 @@ To verify that the GitHub MCP server has been configured, start Gemini CLI in yo
122130
List my GitHub repositories
123131
```
124132
133+
## Additional Configuration
134+
135+
You can find more MCP configuration options for Gemini CLI here: [MCP Configuration Structure](https://google-gemini.github.io/gemini-cli/docs/tools/mcp-server.html#configuration-structure). For example, bypassing tool confirmations or excluding specific tools.
136+
125137
## Troubleshooting
126138
127139
### Local Server Issues

docs/remote-server.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ These toolsets are only available in the remote GitHub MCP Server and are not in
4646

4747
| Name | Description | API URL | 1-Click Install (VS Code) | Read-only Link | 1-Click Read-only Install (VS Code) |
4848
| -------------------- | --------------------------------------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
49-
| Copilot coding agent | Perform task with GitHub Copilot coding agent | https://api.githubcopilot.com/mcp/x/copilot | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/copilot/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot%2Freadonly%22%7D) |
49+
| Copilot | Copilot related tools | https://api.githubcopilot.com/mcp/x/copilot | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/copilot/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot%2Freadonly%22%7D) |
50+
| Copilot Spaces | Copilot Spaces tools | https://api.githubcopilot.com/mcp/x/copilot_spaces | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot_spaces&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot_spaces%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/copilot_spaces/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot_spaces&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot_spaces%2Freadonly%22%7D) |
51+
| GitHub support docs search | Retrieve documentation to answer GitHub product and support questions. Topics include: GitHub Actions Workflows, Authentication, ... | https://api.githubcopilot.com/mcp/x/github_support_docs_search | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-support&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgithub_support_docs_search%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/github_support_docs_search/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-support&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgithub_support_docs_search%2Freadonly%22%7D) |
5052

5153
### Optional Headers
5254

@@ -74,13 +76,16 @@ Example:
7476

7577
### URL Path Parameters
7678

77-
The Remote GitHub MCP server also supports the URL path parameters:
79+
The Remote GitHub MCP server supports the following URL path patterns:
7880

79-
- `/x/{toolset}`
80-
- `/x/{toolset}/readonly`
81-
- `/readonly`
81+
- `/` - Default toolset (see ["default" toolset](../README.md#default-toolset))
82+
- `/readonly` - Default toolset in read-only mode
83+
- `/x/all` - All available toolsets
84+
- `/x/all/readonly` - All available toolsets in read-only mode
85+
- `/x/{toolset}` - Single specific toolset
86+
- `/x/{toolset}/readonly` - Single specific toolset in read-only mode
8287

83-
Note: `{toolset}` can only been a single toolset, not a comma-separated list. To combine multiple toolsets, use the `X-MCP-Toolsets` header instead.
88+
Note: `{toolset}` can only be a single toolset, not a comma-separated list. To combine multiple toolsets, use the `X-MCP-Toolsets` header instead.
8489

8590
Example:
8691

gemini-extension.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name": "github",
3+
"version": "1.0.0",
4+
"mcpServers": {
5+
"github": {
6+
"description": "Connect AI assistants to GitHub - manage repos, issues, PRs, and workflows through natural language.",
7+
"httpUrl": "https://api.githubcopilot.com/mcp/",
8+
"headers": {
9+
"Authorization": "Bearer $GITHUB_MCP_PAT"
10+
}
11+
}
12+
}
13+
}

internal/ghmcp/server.go

Lines changed: 64 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"os/signal"
1313
"strings"
1414
"syscall"
15+
"time"
1516

1617
"github.com/github/github-mcp-server/pkg/errors"
1718
"github.com/github/github-mcp-server/pkg/github"
@@ -106,14 +107,26 @@ func NewMCPServer(cfg MCPServerConfig) (*server.MCPServer, error) {
106107
}
107108

108109
enabledToolsets := cfg.EnabledToolsets
110+
111+
// If dynamic toolsets are enabled, remove "all" from the enabled toolsets
109112
if cfg.DynamicToolsets {
110-
// filter "all" from the enabled toolsets
111-
enabledToolsets = make([]string, 0, len(cfg.EnabledToolsets))
112-
for _, toolset := range cfg.EnabledToolsets {
113-
if toolset != "all" {
114-
enabledToolsets = append(enabledToolsets, toolset)
115-
}
116-
}
113+
enabledToolsets = github.RemoveToolset(enabledToolsets, github.ToolsetMetadataAll.ID)
114+
}
115+
116+
// Clean up the passed toolsets
117+
enabledToolsets, invalidToolsets := github.CleanToolsets(enabledToolsets)
118+
119+
// If "all" is present, override all other toolsets
120+
if github.ContainsToolset(enabledToolsets, github.ToolsetMetadataAll.ID) {
121+
enabledToolsets = []string{github.ToolsetMetadataAll.ID}
122+
}
123+
// If "default" is present, expand to real toolset IDs
124+
if github.ContainsToolset(enabledToolsets, github.ToolsetMetadataDefault.ID) {
125+
enabledToolsets = github.AddDefaultToolset(enabledToolsets)
126+
}
127+
128+
if len(invalidToolsets) > 0 {
129+
fmt.Fprintf(os.Stderr, "Invalid toolsets ignored: %s\n", strings.Join(invalidToolsets, ", "))
117130
}
118131

119132
// Generate instructions based on enabled toolsets
@@ -363,11 +376,30 @@ func newGHESHost(hostname string) (apiHost, error) {
363376
return apiHost{}, fmt.Errorf("failed to parse GHES GraphQL URL: %w", err)
364377
}
365378

366-
uploadURL, err := url.Parse(fmt.Sprintf("%s://%s/api/uploads/", u.Scheme, u.Hostname()))
379+
// Check if subdomain isolation is enabled
380+
// See https://docs.github.com/en/enterprise-server@3.17/admin/configuring-settings/hardening-security-for-your-enterprise/enabling-subdomain-isolation#about-subdomain-isolation
381+
hasSubdomainIsolation := checkSubdomainIsolation(u.Scheme, u.Hostname())
382+
383+
var uploadURL *url.URL
384+
if hasSubdomainIsolation {
385+
// With subdomain isolation: https://uploads.hostname/
386+
uploadURL, err = url.Parse(fmt.Sprintf("%s://uploads.%s/", u.Scheme, u.Hostname()))
387+
} else {
388+
// Without subdomain isolation: https://hostname/api/uploads/
389+
uploadURL, err = url.Parse(fmt.Sprintf("%s://%s/api/uploads/", u.Scheme, u.Hostname()))
390+
}
367391
if err != nil {
368392
return apiHost{}, fmt.Errorf("failed to parse GHES Upload URL: %w", err)
369393
}
370-
rawURL, err := url.Parse(fmt.Sprintf("%s://%s/raw/", u.Scheme, u.Hostname()))
394+
395+
var rawURL *url.URL
396+
if hasSubdomainIsolation {
397+
// With subdomain isolation: https://raw.hostname/
398+
rawURL, err = url.Parse(fmt.Sprintf("%s://raw.%s/", u.Scheme, u.Hostname()))
399+
} else {
400+
// Without subdomain isolation: https://hostname/raw/
401+
rawURL, err = url.Parse(fmt.Sprintf("%s://%s/raw/", u.Scheme, u.Hostname()))
402+
}
371403
if err != nil {
372404
return apiHost{}, fmt.Errorf("failed to parse GHES Raw URL: %w", err)
373405
}
@@ -380,6 +412,29 @@ func newGHESHost(hostname string) (apiHost, error) {
380412
}, nil
381413
}
382414

415+
// checkSubdomainIsolation detects if GitHub Enterprise Server has subdomain isolation enabled
416+
// by attempting to ping the raw.<host>/_ping endpoint on the subdomain. The raw subdomain must always exist for subdomain isolation.
417+
func checkSubdomainIsolation(scheme, hostname string) bool {
418+
subdomainURL := fmt.Sprintf("%s://raw.%s/_ping", scheme, hostname)
419+
420+
client := &http.Client{
421+
Timeout: 5 * time.Second,
422+
// Don't follow redirects - we just want to check if the endpoint exists
423+
//nolint:revive // parameters are required by http.Client.CheckRedirect signature
424+
CheckRedirect: func(req *http.Request, via []*http.Request) error {
425+
return http.ErrUseLastResponse
426+
},
427+
}
428+
429+
resp, err := client.Get(subdomainURL)
430+
if err != nil {
431+
return false
432+
}
433+
defer resp.Body.Close()
434+
435+
return resp.StatusCode == http.StatusOK
436+
}
437+
383438
// Note that this does not handle ports yet, so development environments are out.
384439
func parseAPIHost(s string) (apiHost, error) {
385440
if s == "" {

0 commit comments

Comments
 (0)