Skip to content

Commit 3f71ed7

Browse files
Merge branch 'main' into consolidate-project-tools
2 parents 4927b36 + 48744ca commit 3f71ed7

22 files changed

+602
-10
lines changed

README.md

Lines changed: 112 additions & 0 deletions
Large diffs are not rendered by default.

cmd/github-mcp-server/generate_docs.go

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"github.com/github/github-mcp-server/pkg/inventory"
1313
"github.com/github/github-mcp-server/pkg/translations"
1414
"github.com/google/jsonschema-go/jsonschema"
15-
"github.com/modelcontextprotocol/go-sdk/mcp"
1615
"github.com/spf13/cobra"
1716
)
1817

@@ -189,7 +188,7 @@ func generateToolsDoc(r *inventory.Inventory) string {
189188
currentToolsetID = tool.Toolset.ID
190189
currentToolsetIcon = tool.Toolset.Icon
191190
}
192-
writeToolDoc(&toolBuf, tool.Tool)
191+
writeToolDoc(&toolBuf, tool)
193192
toolBuf.WriteString("\n\n")
194193
}
195194

@@ -223,16 +222,26 @@ func formatToolsetName(name string) string {
223222
}
224223
}
225224

226-
func writeToolDoc(buf *strings.Builder, tool mcp.Tool) {
225+
func writeToolDoc(buf *strings.Builder, tool inventory.ServerTool) {
227226
// Tool name (no icon - section header already has the toolset icon)
228-
fmt.Fprintf(buf, "- **%s** - %s\n", tool.Name, tool.Annotations.Title)
227+
fmt.Fprintf(buf, "- **%s** - %s\n", tool.Tool.Name, tool.Tool.Annotations.Title)
228+
229+
// OAuth scopes if present
230+
if len(tool.RequiredScopes) > 0 {
231+
fmt.Fprintf(buf, " - **Required OAuth Scopes**: `%s`\n", strings.Join(tool.RequiredScopes, "`, `"))
232+
233+
// Only show accepted scopes if they differ from required scopes
234+
if len(tool.AcceptedScopes) > 0 && !scopesEqual(tool.RequiredScopes, tool.AcceptedScopes) {
235+
fmt.Fprintf(buf, " - **Accepted OAuth Scopes**: `%s`\n", strings.Join(tool.AcceptedScopes, "`, `"))
236+
}
237+
}
229238

230239
// Parameters
231-
if tool.InputSchema == nil {
240+
if tool.Tool.InputSchema == nil {
232241
buf.WriteString(" - No parameters required")
233242
return
234243
}
235-
schema, ok := tool.InputSchema.(*jsonschema.Schema)
244+
schema, ok := tool.Tool.InputSchema.(*jsonschema.Schema)
236245
if !ok || schema == nil {
237246
buf.WriteString(" - No parameters required")
238247
return
@@ -281,6 +290,28 @@ func writeToolDoc(buf *strings.Builder, tool mcp.Tool) {
281290
}
282291
}
283292

293+
// scopesEqual checks if two scope slices contain the same elements (order-independent)
294+
func scopesEqual(a, b []string) bool {
295+
if len(a) != len(b) {
296+
return false
297+
}
298+
299+
// Create a map for quick lookup
300+
aMap := make(map[string]bool, len(a))
301+
for _, scope := range a {
302+
aMap[scope] = true
303+
}
304+
305+
// Check if all elements in b are in a
306+
for _, scope := range b {
307+
if !aMap[scope] {
308+
return false
309+
}
310+
}
311+
312+
return true
313+
}
314+
284315
func contains(slice []string, item string) bool {
285316
for _, s := range slice {
286317
if s == item {

pkg/github/actions.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
buffer "github.com/github/github-mcp-server/pkg/buffer"
1414
ghErrors "github.com/github/github-mcp-server/pkg/errors"
1515
"github.com/github/github-mcp-server/pkg/inventory"
16+
"github.com/github/github-mcp-server/pkg/scopes"
1617
"github.com/github/github-mcp-server/pkg/translations"
1718
"github.com/github/github-mcp-server/pkg/utils"
1819
"github.com/google/go-github/v79/github"
@@ -74,6 +75,7 @@ func ListWorkflows(t translations.TranslationHelperFunc) inventory.ServerTool {
7475
Required: []string{"owner", "repo"},
7576
}),
7677
},
78+
[]scopes.Scope{scopes.Repo},
7779
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
7880
client, err := deps.GetClient(ctx)
7981
if err != nil {
@@ -200,6 +202,7 @@ func ListWorkflowRuns(t translations.TranslationHelperFunc) inventory.ServerTool
200202
Required: []string{"owner", "repo", "workflow_id"},
201203
}),
202204
},
205+
[]scopes.Scope{scopes.Repo},
203206
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
204207
client, err := deps.GetClient(ctx)
205208
if err != nil {
@@ -311,6 +314,7 @@ func RunWorkflow(t translations.TranslationHelperFunc) inventory.ServerTool {
311314
Required: []string{"owner", "repo", "workflow_id", "ref"},
312315
},
313316
},
317+
[]scopes.Scope{scopes.Repo},
314318
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
315319
client, err := deps.GetClient(ctx)
316320
if err != nil {
@@ -415,6 +419,7 @@ func GetWorkflowRun(t translations.TranslationHelperFunc) inventory.ServerTool {
415419
Required: []string{"owner", "repo", "run_id"},
416420
},
417421
},
422+
[]scopes.Scope{scopes.Repo},
418423
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
419424
client, err := deps.GetClient(ctx)
420425
if err != nil {
@@ -483,6 +488,7 @@ func GetWorkflowRunLogs(t translations.TranslationHelperFunc) inventory.ServerTo
483488
Required: []string{"owner", "repo", "run_id"},
484489
},
485490
},
491+
[]scopes.Scope{scopes.Repo},
486492
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
487493
client, err := deps.GetClient(ctx)
488494
if err != nil {
@@ -566,6 +572,7 @@ func ListWorkflowJobs(t translations.TranslationHelperFunc) inventory.ServerTool
566572
Required: []string{"owner", "repo", "run_id"},
567573
}),
568574
},
575+
[]scopes.Scope{scopes.Repo},
569576
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
570577
client, err := deps.GetClient(ctx)
571578
if err != nil {
@@ -678,6 +685,7 @@ func GetJobLogs(t translations.TranslationHelperFunc) inventory.ServerTool {
678685
Required: []string{"owner", "repo"},
679686
},
680687
},
688+
[]scopes.Scope{scopes.Repo},
681689
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
682690
client, err := deps.GetClient(ctx)
683691
if err != nil {
@@ -926,6 +934,7 @@ func RerunWorkflowRun(t translations.TranslationHelperFunc) inventory.ServerTool
926934
Required: []string{"owner", "repo", "run_id"},
927935
},
928936
},
937+
[]scopes.Scope{scopes.Repo},
929938
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
930939
client, err := deps.GetClient(ctx)
931940
if err != nil {
@@ -1001,6 +1010,7 @@ func RerunFailedJobs(t translations.TranslationHelperFunc) inventory.ServerTool
10011010
Required: []string{"owner", "repo", "run_id"},
10021011
},
10031012
},
1013+
[]scopes.Scope{scopes.Repo},
10041014
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
10051015
client, err := deps.GetClient(ctx)
10061016
if err != nil {
@@ -1076,6 +1086,7 @@ func CancelWorkflowRun(t translations.TranslationHelperFunc) inventory.ServerToo
10761086
Required: []string{"owner", "repo", "run_id"},
10771087
},
10781088
},
1089+
[]scopes.Scope{scopes.Repo},
10791090
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
10801091
client, err := deps.GetClient(ctx)
10811092
if err != nil {
@@ -1153,6 +1164,7 @@ func ListWorkflowRunArtifacts(t translations.TranslationHelperFunc) inventory.Se
11531164
Required: []string{"owner", "repo", "run_id"},
11541165
}),
11551166
},
1167+
[]scopes.Scope{scopes.Repo},
11561168
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
11571169
client, err := deps.GetClient(ctx)
11581170
if err != nil {
@@ -1233,6 +1245,7 @@ func DownloadWorkflowRunArtifact(t translations.TranslationHelperFunc) inventory
12331245
Required: []string{"owner", "repo", "artifact_id"},
12341246
},
12351247
},
1248+
[]scopes.Scope{scopes.Repo},
12361249
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
12371250
client, err := deps.GetClient(ctx)
12381251
if err != nil {
@@ -1311,6 +1324,7 @@ func DeleteWorkflowRunLogs(t translations.TranslationHelperFunc) inventory.Serve
13111324
Required: []string{"owner", "repo", "run_id"},
13121325
},
13131326
},
1327+
[]scopes.Scope{scopes.Repo},
13141328
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
13151329
client, err := deps.GetClient(ctx)
13161330
if err != nil {
@@ -1386,6 +1400,7 @@ func GetWorkflowRunUsage(t translations.TranslationHelperFunc) inventory.ServerT
13861400
Required: []string{"owner", "repo", "run_id"},
13871401
},
13881402
},
1403+
[]scopes.Scope{scopes.Repo},
13891404
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
13901405
client, err := deps.GetClient(ctx)
13911406
if err != nil {
@@ -1550,6 +1565,7 @@ Use this tool to list workflows in a repository, or list workflow runs, jobs, an
15501565
Required: []string{"method", "owner", "repo"},
15511566
},
15521567
},
1568+
[]scopes.Scope{scopes.Repo},
15531569
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
15541570
owner, err := RequiredParam[string](args, "owner")
15551571
if err != nil {
@@ -1668,6 +1684,7 @@ Use this tool to get details about individual workflows, workflow runs, jobs, an
16681684
Required: []string{"method", "owner", "repo", "resource_id"},
16691685
},
16701686
},
1687+
[]scopes.Scope{scopes.Repo},
16711688
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
16721689
owner, err := RequiredParam[string](args, "owner")
16731690
if err != nil {
@@ -1781,6 +1798,7 @@ func ActionsRunTrigger(t translations.TranslationHelperFunc) inventory.ServerToo
17811798
Required: []string{"method", "owner", "repo"},
17821799
},
17831800
},
1801+
[]scopes.Scope{scopes.Repo},
17841802
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
17851803
owner, err := RequiredParam[string](args, "owner")
17861804
if err != nil {
@@ -1895,6 +1913,7 @@ For single job logs, provide job_id. For all failed jobs in a run, provide run_i
18951913
Required: []string{"owner", "repo"},
18961914
},
18971915
},
1916+
[]scopes.Scope{scopes.Repo},
18981917
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
18991918
owner, err := RequiredParam[string](args, "owner")
19001919
if err != nil {

pkg/github/code_scanning.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
ghErrors "github.com/github/github-mcp-server/pkg/errors"
1010
"github.com/github/github-mcp-server/pkg/inventory"
11+
"github.com/github/github-mcp-server/pkg/scopes"
1112
"github.com/github/github-mcp-server/pkg/translations"
1213
"github.com/github/github-mcp-server/pkg/utils"
1314
"github.com/google/go-github/v79/github"
@@ -44,6 +45,7 @@ func GetCodeScanningAlert(t translations.TranslationHelperFunc) inventory.Server
4445
Required: []string{"owner", "repo", "alertNumber"},
4546
},
4647
},
48+
[]scopes.Scope{scopes.SecurityEvents},
4749
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
4850
owner, err := RequiredParam[string](args, "owner")
4951
if err != nil {
@@ -135,6 +137,7 @@ func ListCodeScanningAlerts(t translations.TranslationHelperFunc) inventory.Serv
135137
Required: []string{"owner", "repo"},
136138
},
137139
},
140+
[]scopes.Scope{scopes.SecurityEvents},
138141
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
139142
owner, err := RequiredParam[string](args, "owner")
140143
if err != nil {

pkg/github/context_tools.go

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

88
ghErrors "github.com/github/github-mcp-server/pkg/errors"
99
"github.com/github/github-mcp-server/pkg/inventory"
10+
"github.com/github/github-mcp-server/pkg/scopes"
1011
"github.com/github/github-mcp-server/pkg/translations"
1112
"github.com/github/github-mcp-server/pkg/utils"
1213
"github.com/google/jsonschema-go/jsonschema"
@@ -51,6 +52,7 @@ func GetMe(t translations.TranslationHelperFunc) inventory.ServerTool {
5152
// OpenAI strict mode requires the properties field to be present.
5253
InputSchema: json.RawMessage(`{"type":"object","properties":{}}`),
5354
},
55+
nil,
5456
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, _ map[string]any) (*mcp.CallToolResult, any, error) {
5557
client, err := deps.GetClient(ctx)
5658
if err != nil {
@@ -129,6 +131,7 @@ func GetTeams(t translations.TranslationHelperFunc) inventory.ServerTool {
129131
},
130132
},
131133
},
134+
[]scopes.Scope{scopes.ReadOrg},
132135
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
133136
user, err := OptionalParam[string](args, "user")
134137
if err != nil {
@@ -231,6 +234,7 @@ func GetTeamMembers(t translations.TranslationHelperFunc) inventory.ServerTool {
231234
Required: []string{"org", "team_slug"},
232235
},
233236
},
237+
[]scopes.Scope{scopes.ReadOrg},
234238
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
235239
org, err := RequiredParam[string](args, "org")
236240
if err != nil {

pkg/github/dependabot.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
ghErrors "github.com/github/github-mcp-server/pkg/errors"
1111
"github.com/github/github-mcp-server/pkg/inventory"
12+
"github.com/github/github-mcp-server/pkg/scopes"
1213
"github.com/github/github-mcp-server/pkg/translations"
1314
"github.com/github/github-mcp-server/pkg/utils"
1415
"github.com/google/go-github/v79/github"
@@ -45,6 +46,7 @@ func GetDependabotAlert(t translations.TranslationHelperFunc) inventory.ServerTo
4546
Required: []string{"owner", "repo", "alertNumber"},
4647
},
4748
},
49+
[]scopes.Scope{scopes.SecurityEvents},
4850
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
4951
owner, err := RequiredParam[string](args, "owner")
5052
if err != nil {
@@ -128,6 +130,7 @@ func ListDependabotAlerts(t translations.TranslationHelperFunc) inventory.Server
128130
Required: []string{"owner", "repo"},
129131
},
130132
},
133+
[]scopes.Scope{scopes.SecurityEvents},
131134
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
132135
owner, err := RequiredParam[string](args, "owner")
133136
if err != nil {

pkg/github/dependencies.go

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/github/github-mcp-server/pkg/inventory"
88
"github.com/github/github-mcp-server/pkg/lockdown"
99
"github.com/github/github-mcp-server/pkg/raw"
10+
"github.com/github/github-mcp-server/pkg/scopes"
1011
"github.com/github/github-mcp-server/pkg/translations"
1112
gogithub "github.com/google/go-github/v79/github"
1213
"github.com/modelcontextprotocol/go-sdk/mcp"
@@ -148,21 +149,44 @@ func (d BaseDeps) GetContentWindowSize() int { return d.ContentWindowSize }
148149
//
149150
// The handler function receives deps extracted from context via MustDepsFromContext.
150151
// Ensure ContextWithDeps is called to inject deps before any tool handlers are invoked.
151-
func NewTool[In, Out any](toolset inventory.ToolsetMetadata, tool mcp.Tool, handler func(ctx context.Context, deps ToolDependencies, req *mcp.CallToolRequest, args In) (*mcp.CallToolResult, Out, error)) inventory.ServerTool {
152-
return inventory.NewServerToolWithContextHandler(tool, toolset, func(ctx context.Context, req *mcp.CallToolRequest, args In) (*mcp.CallToolResult, Out, error) {
152+
//
153+
// requiredScopes specifies the minimum OAuth scopes needed for this tool.
154+
// AcceptedScopes are automatically derived using the scope hierarchy (e.g., if
155+
// public_repo is required, repo is also accepted since repo grants public_repo).
156+
func NewTool[In, Out any](
157+
toolset inventory.ToolsetMetadata,
158+
tool mcp.Tool,
159+
requiredScopes []scopes.Scope,
160+
handler func(ctx context.Context, deps ToolDependencies, req *mcp.CallToolRequest, args In) (*mcp.CallToolResult, Out, error),
161+
) inventory.ServerTool {
162+
st := inventory.NewServerToolWithContextHandler(tool, toolset, func(ctx context.Context, req *mcp.CallToolRequest, args In) (*mcp.CallToolResult, Out, error) {
153163
deps := MustDepsFromContext(ctx)
154164
return handler(ctx, deps, req, args)
155165
})
166+
st.RequiredScopes = scopes.ToStringSlice(requiredScopes...)
167+
st.AcceptedScopes = scopes.ExpandScopes(requiredScopes...)
168+
return st
156169
}
157170

158171
// NewToolFromHandler creates a ServerTool that retrieves ToolDependencies from context at call time.
159172
// Use this when you have a handler that conforms to mcp.ToolHandler directly.
160173
//
161174
// The handler function receives deps extracted from context via MustDepsFromContext.
162175
// Ensure ContextWithDeps is called to inject deps before any tool handlers are invoked.
163-
func NewToolFromHandler(toolset inventory.ToolsetMetadata, tool mcp.Tool, handler func(ctx context.Context, deps ToolDependencies, req *mcp.CallToolRequest) (*mcp.CallToolResult, error)) inventory.ServerTool {
164-
return inventory.NewServerToolWithRawContextHandler(tool, toolset, func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
176+
//
177+
// requiredScopes specifies the minimum OAuth scopes needed for this tool.
178+
// AcceptedScopes are automatically derived using the scope hierarchy.
179+
func NewToolFromHandler(
180+
toolset inventory.ToolsetMetadata,
181+
tool mcp.Tool,
182+
requiredScopes []scopes.Scope,
183+
handler func(ctx context.Context, deps ToolDependencies, req *mcp.CallToolRequest) (*mcp.CallToolResult, error),
184+
) inventory.ServerTool {
185+
st := inventory.NewServerToolWithRawContextHandler(tool, toolset, func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
165186
deps := MustDepsFromContext(ctx)
166187
return handler(ctx, deps, req)
167188
})
189+
st.RequiredScopes = scopes.ToStringSlice(requiredScopes...)
190+
st.AcceptedScopes = scopes.ExpandScopes(requiredScopes...)
191+
return st
168192
}

0 commit comments

Comments
 (0)