Skip to content

Commit fb49a7d

Browse files
authored
Merge branch 'main' into kerobbi/fix-search-issues-ghes-field-values
2 parents 23b9eeb + 316b8ef commit fb49a7d

58 files changed

Lines changed: 3747 additions & 194 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/docker-publish.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ jobs:
6060
# https://github.com/docker/login-action
6161
- name: Log into registry ${{ env.REGISTRY }}
6262
if: github.event_name != 'pull_request'
63-
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
63+
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
6464
with:
6565
registry: ${{ env.REGISTRY }}
6666
username: ${{ github.actor }}

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ COPY ui/ ./ui/
77
RUN mkdir -p ./pkg/github/ui_dist && \
88
cd ui && npm run build
99

10-
FROM golang:1.25.12-alpine@sha256:56961d79ea8129efddcc0b8643fd8a5416b4e6228cfd477e3fd61deb2672c587 AS build
10+
FROM golang:1.25.13-alpine@sha256:1e0126852075c9c60731c8ba49088448b91f63e2aed97ca9d1a9791622a05946 AS build
1111
ARG VERSION="dev"
1212

1313
# Set the working directory
@@ -35,7 +35,7 @@ RUN --mount=type=cache,target=/go/pkg/mod \
3535
-o /bin/github-mcp-server ./cmd/github-mcp-server
3636

3737
# Make a stage to run the app
38-
FROM gcr.io/distroless/base-debian12@sha256:348dac1808083ccc3366399d6db835875b4eaf7c9b694783f5a3f353c4b58a28
38+
FROM gcr.io/distroless/base-debian12@sha256:76b3162a31477bca4a245b836c624f4c4a1a3705e99b9003907d992bec2c4bca
3939

4040
# Add required MCP server annotation
4141
LABEL io.modelcontextprotocol.server.name="io.github.github/github-mcp-server"

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ To keep your GitHub PAT secure and reusable across different MCP hosts:
247247
The flag `--gh-host` and the environment variable `GITHUB_HOST` can be used to set
248248
the hostname for GitHub Enterprise Server or GitHub Enterprise Cloud with data residency.
249249

250-
- For GitHub Enterprise Server, prefix the hostname with the `https://` URI scheme, as it otherwise defaults to `http://`, which GitHub Enterprise Server does not support.
250+
- For GitHub Enterprise Server, prefix the hostname with the `https://` URI scheme. HTTPS is required and enforced: non-HTTPS hosts are refused so that credentials are never sent over cleartext (the only exception is a loopback host such as `http://localhost` for local development).
251251
- For GitHub Enterprise Cloud with data residency, use `https://YOURSUBDOMAIN.ghe.com` as the hostname.
252252

253253
``` json
@@ -894,7 +894,7 @@ The following sets of tools are available:
894894
- **add_issue_comment** - Add comment to issue or pull request
895895
- **Required OAuth Scopes**: `repo`
896896
- `body`: Comment content. Required unless reaction is provided. (string, optional)
897-
- `comment_id`: The numeric ID of the issue or pull request comment to react to. Use this for reactions to comments; omit it to react to the issue or pull request itself. Cannot be combined with body. (number, optional)
897+
- `comment_id`: The numeric ID of the issue or pull request comment to react to. Use this for reactions to comments; omit it to react to the issue or pull request itself. Cannot be combined with body. (integer, optional)
898898
- `issue_number`: Issue or pull request number to comment on or react to. (number, required)
899899
- `owner`: Repository owner (string, required)
900900
- `reaction`: Emoji reaction to add. Required unless body is provided. (string, optional)
@@ -1308,6 +1308,11 @@ The following sets of tools are available:
13081308
- `path`: Path to the file to delete (string, required)
13091309
- `repo`: Repository name (string, required)
13101310

1311+
- **delete_repository** - Delete repository
1312+
- **Required OAuth Scopes (all required)**: `delete_repo`, `repo`
1313+
- `owner`: Repository owner (username or organization) (string, required)
1314+
- `repo`: Repository name (string, required)
1315+
13111316
- **fork_repository** - Fork repository
13121317
- **Required OAuth Scopes**: `repo`
13131318
- `organization`: Organization to fork to (string, optional)

cmd/github-mcp-server/generate_docs.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -221,13 +221,13 @@ func writeToolDoc(buf *strings.Builder, tool inventory.ServerTool) {
221221

222222
// OAuth scopes if present
223223
if len(tool.RequiredScopes) > 0 {
224-
// Scope filtering uses "any of" semantics (see scopes.HasRequiredScopes),
225-
// so when multiple required scopes are listed, render them as alternatives
226-
// rather than implying all are required.
227224
scopeList := "`" + strings.Join(tool.RequiredScopes, "`, `") + "`"
228-
if len(tool.RequiredScopes) > 1 {
225+
switch {
226+
case len(tool.RequiredScopeGroups) > 1:
227+
fmt.Fprintf(buf, " - **Required OAuth Scopes (all required)**: %s\n", scopeList)
228+
case len(tool.RequiredScopes) > 1:
229229
fmt.Fprintf(buf, " - **Required OAuth Scopes (any of)**: %s\n", scopeList)
230-
} else {
230+
default:
231231
fmt.Fprintf(buf, " - **Required OAuth Scopes**: %s\n", scopeList)
232232
}
233233

cmd/github-mcp-server/main.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,11 @@ var (
128128
}
129129

130130
// When no static token is provided, log in via OAuth using the given
131-
// client. The requested scopes default to the full supported set
132-
// (which filters out no tools); an explicit, narrower --oauth-scopes
133-
// both narrows the grant and hides tools needing other scopes.
131+
// client. The requested scopes default to the standard set; high-risk
132+
// scopes such as delete_repo require explicit --oauth-scopes opt-in.
133+
// The requested set also filters tools needing other scopes.
134134
if token == "" && !appAuthRequested {
135-
scopes := ghoauth.SupportedScopes
135+
scopes := ghoauth.DefaultScopes
136136
if viper.IsSet("oauth-scopes") {
137137
if err := viper.UnmarshalKey("oauth-scopes", &scopes); err != nil {
138138
return fmt.Errorf("failed to unmarshal oauth-scopes: %w", err)
@@ -217,6 +217,7 @@ var (
217217
EnabledFeatures: enabledFeatures,
218218
InsidersMode: viper.GetBool("insiders"),
219219
TrustProxyHeaders: viper.GetBool("trust-proxy-headers"),
220+
MRTRStateKey: os.Getenv(ghhttp.MRTRStateKeyEnv),
220221
}
221222

222223
return ghhttp.RunHTTPServer(httpConfig)

cmd/github-mcp-server/main_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ package main
33
import (
44
"os"
55
"path/filepath"
6+
"strings"
67
"testing"
78

9+
"github.com/github/github-mcp-server/pkg/inventory"
810
"github.com/google/jsonschema-go/jsonschema"
11+
"github.com/modelcontextprotocol/go-sdk/mcp"
912
"github.com/stretchr/testify/assert"
1013
"github.com/stretchr/testify/require"
1114
)
@@ -38,6 +41,40 @@ func TestGitHubAppFlagsAreStdioOnly(t *testing.T) {
3841
assert.Nil(t, httpCmd.Flags().Lookup("app-id"))
3942
}
4043

44+
func TestWriteToolDocScopeSemantics(t *testing.T) {
45+
tests := []struct {
46+
name string
47+
tool inventory.ServerTool
48+
want string
49+
}{
50+
{
51+
name: "legacy multi-scope tools use any-of",
52+
tool: inventory.ServerTool{
53+
Tool: mcp.Tool{Name: "legacy", Annotations: &mcp.ToolAnnotations{Title: "Legacy"}},
54+
RequiredScopes: []string{"repo", "read:org"},
55+
},
56+
want: "**Required OAuth Scopes (any of)**",
57+
},
58+
{
59+
name: "conjunctive scope groups use all-required",
60+
tool: inventory.ServerTool{
61+
Tool: mcp.Tool{Name: "conjunctive", Annotations: &mcp.ToolAnnotations{Title: "Conjunctive"}},
62+
RequiredScopes: []string{"delete_repo", "repo"},
63+
RequiredScopeGroups: [][]string{{"delete_repo"}, {"repo"}},
64+
},
65+
want: "**Required OAuth Scopes (all required)**",
66+
},
67+
}
68+
69+
for _, tt := range tests {
70+
t.Run(tt.name, func(t *testing.T) {
71+
var buf strings.Builder
72+
writeToolDoc(&buf, tt.tool)
73+
assert.Contains(t, buf.String(), tt.want)
74+
})
75+
}
76+
}
77+
4178
func TestSchemaTypeString(t *testing.T) {
4279
tests := []struct {
4380
name string

docs/streamable-http.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,27 @@ github-mcp-server http --scope-challenge
3232

3333
When `--scope-challenge` is enabled, requests with insufficient scopes receive a `403 Forbidden` response with a `WWW-Authenticate` header indicating the required scopes.
3434

35+
### Repository deletion and request-state encryption
36+
37+
The `delete_repository` tool uses multi-round-trip elicitation and carries its
38+
confirmed target through client-held request state. To expose this tool in HTTP
39+
mode, configure a stable 32-byte encryption key encoded with standard Base64:
40+
41+
```bash
42+
export GITHUB_MCP_SERVER_MRTR_STATE_KEY="$(openssl rand -base64 32 | tr -d '\n')"
43+
github-mcp-server http
44+
```
45+
46+
Use the same key on every replica that may handle a retry. Keep it secret and
47+
stable during deployments; changing it invalidates confirmations already in
48+
flight. If the variable is absent, `delete_repository` is not exposed by the
49+
HTTP server. If it is present but malformed, the server refuses to start.
50+
51+
This self-hosted key is independent of keys used by the hosted remote server.
52+
Integrators can provide their own request-state sealer through the exported
53+
`github.RequestStateSealer` interface and expose it from their tool dependencies
54+
through `github.RequestStateSealerProvider` without changing their key format.
55+
3556
### With OAuth Metadata Discovery
3657

3758
For use behind reverse proxies or with custom domains, expose OAuth metadata endpoints:

go.mod

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,25 @@ require (
1717
github.com/spf13/cobra v1.10.2
1818
github.com/spf13/pflag v1.0.10
1919
github.com/spf13/viper v1.21.0
20-
github.com/stretchr/testify v1.11.1
20+
github.com/stretchr/testify v1.12.0
2121
github.com/yosida95/uritemplate/v3 v3.0.2
2222
golang.org/x/oauth2 v0.36.0
2323
)
2424

2525
require (
2626
github.com/aymerick/douceur v0.2.0 // indirect
27-
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
2827
github.com/fsnotify/fsnotify v1.9.0 // indirect
2928
github.com/google/go-querystring v1.2.0 // indirect
3029
github.com/gorilla/css v1.0.1 // indirect
3130
github.com/inconshreveable/mousetrap v1.1.0 // indirect
3231
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
33-
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
3432
github.com/sagikazarmark/locafero v0.11.0 // indirect
3533
github.com/segmentio/asm v1.1.3 // indirect
3634
github.com/segmentio/encoding v0.5.4 // indirect
3735
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
3836
github.com/spf13/afero v1.15.0 // indirect
3937
github.com/spf13/cast v1.10.0 // indirect
40-
github.com/stretchr/objx v0.5.2 // indirect
38+
github.com/stretchr/objx v0.5.3 // indirect
4139
github.com/subosito/gotenv v1.6.0 // indirect
4240
go.yaml.in/yaml/v3 v3.0.4 // indirect
4341
golang.org/x/net v0.55.0 // indirect

go.sum

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
22
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
33
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
4-
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
5-
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
64
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
75
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
86
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
@@ -45,8 +43,6 @@ github.com/muesli/cache2go v0.0.0-20221011235721-518229cd8021 h1:31Y+Yu373ymebRd
4543
github.com/muesli/cache2go v0.0.0-20221011235721-518229cd8021/go.mod h1:WERUkUryfUWlrHnFSO/BEUZ+7Ns8aZy7iVOGewxKzcc=
4644
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
4745
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
48-
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
49-
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5046
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
5147
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
5248
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -73,10 +69,10 @@ github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
7369
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
7470
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
7571
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
76-
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
77-
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
78-
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
79-
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
72+
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
73+
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
74+
github.com/stretchr/testify v1.12.0 h1:K6Mr6jO9JICuend/5xzTM03ydSV3vdNRYAdPSukj8uI=
75+
github.com/stretchr/testify v1.12.0/go.mod h1:bOYBZb5qJ00vPzWfIqBUZPaxK8jWiXc6d3ErP4Ca9Gw=
8076
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
8177
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
8278
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=

internal/ghmcp/oauth.go

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,6 @@ type oauthAuthenticator interface {
103103
// delayed response from an older prompt from affecting a newer flow.
104104
const oauthElicitIDPrefix = "github_authorization:"
105105

106-
// protocolVersionNoServerElicitation is the first MCP protocol version that
107-
// forbids server-initiated JSON-RPC requests (SEP-2322): from this version on
108-
// the server may not send elicitation/create while serving a request and must
109-
// instead return an InputRequests map from the tool call (multi round-trip
110-
// requests). It mirrors the go-sdk's internal constant of the same value, which
111-
// the SDK does not export.
112-
const protocolVersionNoServerElicitation = "2026-07-28"
113-
114106
// serverMayInitiateElicitation reports whether the server is permitted to send
115107
// elicitation requests to the client itself, which the spec allows only before
116108
// protocol version 2026-07-28. A nil or un-negotiated session (only reached in
@@ -120,7 +112,7 @@ func serverMayInitiateElicitation(ss *mcp.ServerSession) bool {
120112
return true
121113
}
122114
params := ss.InitializeParams()
123-
return params == nil || params.ProtocolVersion < protocolVersionNoServerElicitation
115+
return params == nil || params.ProtocolVersion < inventory.ProtocolVersionMultiRoundTrip
124116
}
125117

126118
// createOAuthToolMiddleware returns tool-handler middleware that authorizes the

0 commit comments

Comments
 (0)