Skip to content

Commit fc503f3

Browse files
authored
Merge branch 'main' into main
2 parents ac306cb + bd47e63 commit fc503f3

102 files changed

Lines changed: 6225 additions & 1697 deletions

File tree

Some content is hidden

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

.github/workflows/code-scanning.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ jobs:
4444
uses: actions/checkout@v7
4545

4646
- name: Initialize CodeQL
47-
uses: github/codeql-action/init@v4
47+
uses: github/codeql-action/init@v4.37.4
4848
with:
4949
languages: ${{ matrix.language }}
5050
build-mode: ${{ matrix.build-mode }}
@@ -61,13 +61,13 @@ jobs:
6161
threat-models: [ ]
6262
- name: Setup proxy for registries
6363
id: proxy
64-
uses: github/codeql-action/start-proxy@v4
64+
uses: github/codeql-action/start-proxy@v4.37.4
6565
with:
6666
registries_credentials: ${{ secrets.GITHUB_REGISTRIES_PROXY }}
6767
language: ${{ matrix.language }}
6868

6969
- name: Configure
70-
uses: github/codeql-action/resolve-environment@v4
70+
uses: github/codeql-action/resolve-environment@v4.37.4
7171
id: resolve-environment
7272
with:
7373
language: ${{ matrix.language }}
@@ -91,10 +91,10 @@ jobs:
9191
uses: ./.github/actions/build-ui
9292

9393
- name: Autobuild
94-
uses: github/codeql-action/autobuild@v4
94+
uses: github/codeql-action/autobuild@v4.37.4
9595

9696
- name: Perform CodeQL Analysis
97-
uses: github/codeql-action/analyze@v4
97+
uses: github/codeql-action/analyze@v4.37.4
9898
env:
9999
CODEQL_PROXY_HOST: ${{ steps.proxy.outputs.proxy_host }}
100100
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
@@ -54,7 +54,7 @@ jobs:
5454
# multi-platform images and export cache
5555
# https://github.com/docker/setup-buildx-action
5656
- name: Set up Docker Buildx
57-
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
57+
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
5858

5959
# Login against a Docker registry except on PR
6060
# https://github.com/docker/login-action

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.13-alpine@sha256:1e0126852075c9c60731c8ba49088448b91f63e2aed97ca9d1a9791622a05946 AS build
10+
FROM golang:1.27.0-alpine@sha256:4c9fe60190a2a3350ddc51de80d0224b8a6698d12bdfc999fee45ea9d6c46dbc 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:76b3162a31477bca4a245b836c624f4c4a1a3705e99b9003907d992bec2c4bca
38+
FROM gcr.io/distroless/base-debian12@sha256:fabbf1c0c357a3d42550111351daed089b20a2c954df13ee2fcff60602515e84
3939

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

README.md

Lines changed: 92 additions & 116 deletions
Large diffs are not rendered by default.

cmd/github-mcp-server/generate_docs.go

Lines changed: 2 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -219,22 +219,8 @@ func writeToolDoc(buf *strings.Builder, tool inventory.ServerTool) {
219219
// Tool name (no icon - section header already has the toolset icon)
220220
fmt.Fprintf(buf, "- **%s** - %s\n", tool.Tool.Name, tool.Tool.Annotations.Title)
221221

222-
// OAuth scopes if present
223-
if len(tool.RequiredScopes) > 0 {
224-
scopeList := "`" + strings.Join(tool.RequiredScopes, "`, `") + "`"
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:
229-
fmt.Fprintf(buf, " - **Required OAuth Scopes (any of)**: %s\n", scopeList)
230-
default:
231-
fmt.Fprintf(buf, " - **Required OAuth Scopes**: %s\n", scopeList)
232-
}
233-
234-
// Only show accepted scopes if they differ from required scopes
235-
if len(tool.AcceptedScopes) > 0 && !scopesEqual(tool.RequiredScopes, tool.AcceptedScopes) {
236-
fmt.Fprintf(buf, " - **Accepted OAuth Scopes**: `%s`\n", strings.Join(tool.AcceptedScopes, "`, `"))
237-
}
222+
if scopes := tool.ScopeAccess.Scopes; len(scopes) > 0 {
223+
fmt.Fprintf(buf, " - **OAuth Challenge Scopes**: `%s`\n", strings.Join(scopes, "`, `"))
238224
}
239225

240226
// MCP App UI metadata (only rendered when the remote_mcp_ui_apps flag
@@ -322,28 +308,6 @@ func schemaTypeString(schema *jsonschema.Schema) string {
322308
return strings.Join(types, " | ")
323309
}
324310

325-
// scopesEqual checks if two scope slices contain the same elements (order-independent)
326-
func scopesEqual(a, b []string) bool {
327-
if len(a) != len(b) {
328-
return false
329-
}
330-
331-
// Create a map for quick lookup
332-
aMap := make(map[string]bool, len(a))
333-
for _, scope := range a {
334-
aMap[scope] = true
335-
}
336-
337-
// Check if all elements in b are in a
338-
for _, scope := range b {
339-
if !aMap[scope] {
340-
return false
341-
}
342-
}
343-
344-
return true
345-
}
346-
347311
// indentMultilineDescription adds the specified indent to all lines after the first line.
348312
// This ensures that multi-line descriptions maintain proper markdown list formatting.
349313
func indentMultilineDescription(description, indent string) string {

cmd/github-mcp-server/list_scopes.go

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,10 @@ import (
1717

1818
// ToolScopeInfo contains scope information for a single tool.
1919
type ToolScopeInfo struct {
20-
Name string `json:"name"`
21-
Toolset string `json:"toolset"`
22-
ReadOnly bool `json:"read_only"`
23-
RequiredScopes []string `json:"required_scopes"`
24-
AcceptedScopes []string `json:"accepted_scopes,omitempty"`
20+
Name string `json:"name"`
21+
Toolset string `json:"toolset"`
22+
ReadOnly bool `json:"read_only"`
23+
ChallengeScopes []string `json:"challenge_scopes,omitempty"`
2524
}
2625

2726
// ScopesOutput is the full output structure for the list-scopes command.
@@ -36,12 +35,11 @@ type ScopesOutput struct {
3635

3736
var listScopesCmd = &cobra.Command{
3837
Use: "list-scopes",
39-
Short: "List required OAuth scopes for enabled tools",
40-
Long: `List the required OAuth scopes for all enabled tools.
38+
Short: "List OAuth scope policies for enabled tools",
39+
Long: `List the OAuth challenge scopes for all enabled tools.
4140
4241
This command creates an inventory based on the same flags as the stdio command
43-
and outputs the required OAuth scopes for each enabled tool. This is useful for
44-
determining what scopes a token needs to use specific tools.
42+
and outputs the scopes each enabled tool may request in an OAuth challenge.
4543
4644
The output format can be controlled with the --output flag:
4745
- text (default): Human-readable text output
@@ -153,30 +151,27 @@ func collectToolScopes(inv *inventory.Inventory, readOnly bool) ScopesOutput {
153151
for _, serverTool := range availableTools {
154152
tool := serverTool.Tool
155153

156-
// Get scope information directly from ServerTool
157-
requiredScopes := serverTool.RequiredScopes
158-
acceptedScopes := serverTool.AcceptedScopes
154+
challengeScopes := serverTool.ScopeAccess.Scopes
159155

160156
// Determine if tool is read-only
161157
isReadOnly := serverTool.IsReadOnly()
162158

163159
toolInfo := ToolScopeInfo{
164-
Name: tool.Name,
165-
Toolset: string(serverTool.Toolset.ID),
166-
ReadOnly: isReadOnly,
167-
RequiredScopes: requiredScopes,
168-
AcceptedScopes: acceptedScopes,
160+
Name: tool.Name,
161+
Toolset: string(serverTool.Toolset.ID),
162+
ReadOnly: isReadOnly,
163+
ChallengeScopes: challengeScopes,
169164
}
170165
tools = append(tools, toolInfo)
171166

172167
// Track unique scopes
173-
for _, s := range requiredScopes {
168+
for _, s := range challengeScopes {
174169
scopeSet[s] = true
175170
toolsByScope[s] = append(toolsByScope[s], tool.Name)
176171
}
177172

178173
// Track scopes by tool
179-
scopesByTool[tool.Name] = requiredScopes
174+
scopesByTool[tool.Name] = challengeScopes
180175
}
181176

182177
// Sort tools by name
@@ -225,7 +220,7 @@ func outputSummary(output ScopesOutput) error {
225220
return nil
226221
}
227222

228-
fmt.Println("Required OAuth scopes for enabled tools:")
223+
fmt.Println("OAuth scope policies for enabled tools:")
229224
fmt.Println()
230225
for _, scope := range output.UniqueScopes {
231226
fmt.Printf(" %s\n", formatScopeDisplay(scope))
@@ -235,8 +230,8 @@ func outputSummary(output ScopesOutput) error {
235230
}
236231

237232
func outputText(output ScopesOutput) error {
238-
fmt.Printf("OAuth Scopes for Enabled Tools\n")
239-
fmt.Printf("==============================\n\n")
233+
fmt.Printf("OAuth Challenge Scopes for Enabled Tools\n")
234+
fmt.Printf("========================================\n\n")
240235

241236
fmt.Printf("Enabled Toolsets: %s\n", strings.Join(output.EnabledToolsets, ", "))
242237
fmt.Printf("Read-Only Mode: %v\n\n", output.ReadOnly)
@@ -265,8 +260,8 @@ func outputText(output ScopesOutput) error {
265260
}
266261

267262
scopeStr := "(no scope required)"
268-
if len(tool.RequiredScopes) > 0 {
269-
scopeStr = strings.Join(tool.RequiredScopes, ", ")
263+
if len(tool.ChallengeScopes) > 0 {
264+
scopeStr = strings.Join(tool.ChallengeScopes, ", ")
270265
}
271266

272267
fmt.Printf(" %s %s: %s\n", rwIndicator, tool.Name, scopeStr)
@@ -278,9 +273,9 @@ func outputText(output ScopesOutput) error {
278273
fmt.Println("## Summary")
279274
fmt.Println()
280275
if len(output.UniqueScopes) == 0 {
281-
fmt.Println("No OAuth scopes required for enabled tools.")
276+
fmt.Println("No OAuth scopes are used by enabled tools.")
282277
} else {
283-
fmt.Println("Unique scopes required:")
278+
fmt.Println("Unique challenge scopes:")
284279
for _, scope := range output.UniqueScopes {
285280
fmt.Printf(" • %s\n", formatScopeDisplay(scope))
286281
}

cmd/github-mcp-server/main_test.go

Lines changed: 8 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"testing"
88

99
"github.com/github/github-mcp-server/pkg/inventory"
10+
"github.com/github/github-mcp-server/pkg/scopes"
1011
"github.com/google/jsonschema-go/jsonschema"
1112
"github.com/modelcontextprotocol/go-sdk/mcp"
1213
"github.com/spf13/viper"
@@ -56,38 +57,15 @@ func TestAuthorizationServerConfigurationIsHTTPOnly(t *testing.T) {
5657
assert.Equal(t, "https://oauth-proxy.example.com", viper.GetString("authorization-server"))
5758
}
5859

59-
func TestWriteToolDocScopeSemantics(t *testing.T) {
60-
tests := []struct {
61-
name string
62-
tool inventory.ServerTool
63-
want string
64-
}{
65-
{
66-
name: "legacy multi-scope tools use any-of",
67-
tool: inventory.ServerTool{
68-
Tool: mcp.Tool{Name: "legacy", Annotations: &mcp.ToolAnnotations{Title: "Legacy"}},
69-
RequiredScopes: []string{"repo", "read:org"},
70-
},
71-
want: "**Required OAuth Scopes (any of)**",
72-
},
73-
{
74-
name: "conjunctive scope groups use all-required",
75-
tool: inventory.ServerTool{
76-
Tool: mcp.Tool{Name: "conjunctive", Annotations: &mcp.ToolAnnotations{Title: "Conjunctive"}},
77-
RequiredScopes: []string{"delete_repo", "repo"},
78-
RequiredScopeGroups: [][]string{{"delete_repo"}, {"repo"}},
79-
},
80-
want: "**Required OAuth Scopes (all required)**",
81-
},
60+
func TestWriteToolDocScopes(t *testing.T) {
61+
tool := inventory.ServerTool{
62+
Tool: mcp.Tool{Name: "delete", Annotations: &mcp.ToolAnnotations{Title: "Delete"}},
63+
ScopeAccess: scopes.RequireAll(scopes.DeleteRepo, scopes.Repo),
8264
}
8365

84-
for _, tt := range tests {
85-
t.Run(tt.name, func(t *testing.T) {
86-
var buf strings.Builder
87-
writeToolDoc(&buf, tt.tool)
88-
assert.Contains(t, buf.String(), tt.want)
89-
})
90-
}
66+
var buf strings.Builder
67+
writeToolDoc(&buf, tool)
68+
assert.Contains(t, buf.String(), "**OAuth Challenge Scopes**: `delete_repo`, `repo`")
9169
}
9270

9371
func TestSchemaTypeString(t *testing.T) {

0 commit comments

Comments
 (0)