Skip to content

Commit d2588f0

Browse files
support tool updates
1 parent b0bcb21 commit d2588f0

File tree

5 files changed

+213
-122
lines changed

5 files changed

+213
-122
lines changed

Diff for: cmd/github-mcp-server/main.go

+18-18
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ var (
4545
if err != nil {
4646
stdlog.Fatal("Failed to initialize logger:", err)
4747
}
48-
enabledToolsets := viper.GetStringSlice("features")
49-
features, err := initToolsets(enabledToolsets)
48+
enabledToolsets := viper.GetStringSlice("toolsets")
49+
toolsets, err := initToolsets(enabledToolsets, readOnly)
5050
if err != nil {
51-
stdlog.Fatal("Failed to initialize features:", err)
51+
stdlog.Fatal("Failed to initialize toolsets:", err)
5252
}
5353

5454
logCommands := viper.GetBool("enable-command-logging")
@@ -57,7 +57,7 @@ var (
5757
logger: logger,
5858
logCommands: logCommands,
5959
exportTranslations: exportTranslations,
60-
features: features,
60+
toolsets: toolsets,
6161
}
6262
if err := runStdioServer(cfg); err != nil {
6363
stdlog.Fatal("failed to run stdio server:", err)
@@ -66,29 +66,29 @@ var (
6666
}
6767
)
6868

69-
func initToolsets(passedToolsets []string) (*toolsets.ToolsetGroup, error) {
69+
func initToolsets(passedToolsets []string, readOnly bool) (*toolsets.ToolsetGroup, error) {
7070
// Create a new toolset group
7171
fs := toolsets.NewToolsetGroup()
7272

7373
// Define all available features with their default state (disabled)
74-
fs.AddToolset("repos", "Repository related tools", false)
75-
fs.AddToolset("issues", "Issues related tools", false)
76-
fs.AddToolset("search", "Search related tools", false)
77-
fs.AddToolset("pull_requests", "Pull request related tools", false)
78-
fs.AddToolset("code_security", "Code security related tools", false)
79-
fs.AddToolset("experiments", "Experimental features that are not considered stable yet", false)
74+
fs.AddToolset("repos", "Repository related tools", readOnly, false)
75+
fs.AddToolset("issues", "Issues related tools", readOnly, false)
76+
fs.AddToolset("search", "Search related tools", readOnly, false)
77+
fs.AddToolset("pull_requests", "Pull request related tools", readOnly, false)
78+
fs.AddToolset("code_security", "Code security related tools", readOnly, false)
79+
fs.AddToolset("experiments", "Experimental features that are not considered stable yet", readOnly, false)
8080

8181
// fs.AddFeature("actions", "GitHub Actions related tools", false)
8282
// fs.AddFeature("projects", "GitHub Projects related tools", false)
8383
// fs.AddFeature("secret_protection", "Secret protection related tools", false)
8484
// fs.AddFeature("gists", "Gist related tools", false)
8585

8686
// Env gets precedence over command line flags
87-
if envFeats := os.Getenv("GITHUB_TOOLSETS"); envFeats != "" {
87+
if envToolsets := os.Getenv("GITHUB_TOOLSETS"); envToolsets != "" {
8888
passedToolsets = []string{}
8989
// Split envFeats by comma, trim whitespace, and add to the slice
90-
for _, feature := range strings.Split(envFeats, ",") {
91-
passedToolsets = append(passedToolsets, strings.TrimSpace(feature))
90+
for _, toolset := range strings.Split(envToolsets, ",") {
91+
passedToolsets = append(passedToolsets, strings.TrimSpace(toolset))
9292
}
9393
}
9494

@@ -104,15 +104,15 @@ func init() {
104104
cobra.OnInitialize(initConfig)
105105

106106
// Add global flags that will be shared by all commands
107-
rootCmd.PersistentFlags().StringSlice("features", []string{"repos", "issues", "pull_requests", "search"}, "A comma separated list of groups of tools to enable, defaults to issues/repos/search")
107+
rootCmd.PersistentFlags().StringSlice("toolsets", []string{"repos", "issues", "pull_requests", "search"}, "A comma separated list of groups of tools to enable, defaults to issues/repos/search")
108108
rootCmd.PersistentFlags().Bool("read-only", false, "Restrict the server to read-only operations")
109109
rootCmd.PersistentFlags().String("log-file", "", "Path to log file")
110110
rootCmd.PersistentFlags().Bool("enable-command-logging", false, "When enabled, the server will log all command requests and responses to the log file")
111111
rootCmd.PersistentFlags().Bool("export-translations", false, "Save translations to a JSON file")
112112
rootCmd.PersistentFlags().String("gh-host", "", "Specify the GitHub hostname (for GitHub Enterprise etc.)")
113113

114114
// Bind flag to viper
115-
_ = viper.BindPFlag("features", rootCmd.PersistentFlags().Lookup("features"))
115+
_ = viper.BindPFlag("toolsets", rootCmd.PersistentFlags().Lookup("toolsets"))
116116
_ = viper.BindPFlag("read-only", rootCmd.PersistentFlags().Lookup("read-only"))
117117
_ = viper.BindPFlag("log-file", rootCmd.PersistentFlags().Lookup("log-file"))
118118
_ = viper.BindPFlag("enable-command-logging", rootCmd.PersistentFlags().Lookup("enable-command-logging"))
@@ -151,7 +151,7 @@ type runConfig struct {
151151
logger *log.Logger
152152
logCommands bool
153153
exportTranslations bool
154-
features *toolsets.ToolsetGroup
154+
toolsets *toolsets.ToolsetGroup
155155
}
156156

157157
func runStdioServer(cfg runConfig) error {
@@ -187,7 +187,7 @@ func runStdioServer(cfg runConfig) error {
187187
return ghClient, nil // closing over client
188188
}
189189
// Create
190-
ghServer := github.NewServer(getClient, cfg.features, version, cfg.readOnly, t)
190+
ghServer := github.NewServer(getClient, cfg.toolsets, version, cfg.readOnly, t)
191191
stdioServer := server.NewStdioServer(ghServer)
192192

193193
stdLogger := stdlog.New(cfg.logger.Writer(), "stdioserver", 0)

Diff for: pkg/github/server.go

+80-67
Original file line numberDiff line numberDiff line change
@@ -29,73 +29,73 @@ func NewServer(getClient GetClientFn, toolsetGroup *toolsets.ToolsetGroup, versi
2929
// Add GitHub tools - Users
3030
s.AddTool(GetMe(getClient, t)) // GetMe is always exposed and not part of configurable features
3131

32-
if toolsetGroup.IsEnabled("repos") {
33-
// Add GitHub Repository Resources
34-
s.AddResourceTemplate(GetRepositoryResourceContent(getClient, t))
35-
s.AddResourceTemplate(GetRepositoryResourceBranchContent(getClient, t))
36-
s.AddResourceTemplate(GetRepositoryResourceCommitContent(getClient, t))
37-
s.AddResourceTemplate(GetRepositoryResourceTagContent(getClient, t))
38-
s.AddResourceTemplate(GetRepositoryResourcePrContent(getClient, t))
39-
40-
// Add GitHub tools - Repositories
41-
s.AddTool(SearchRepositories(getClient, t))
42-
s.AddTool(GetFileContents(getClient, t))
43-
s.AddTool(ListCommits(getClient, t))
44-
if !readOnly {
45-
s.AddTool(CreateOrUpdateFile(getClient, t))
46-
s.AddTool(CreateRepository(getClient, t))
47-
s.AddTool(ForkRepository(getClient, t))
48-
s.AddTool(CreateBranch(getClient, t))
49-
s.AddTool(PushFiles(getClient, t))
32+
for _, toolset := range toolsetGroup.Toolsets {
33+
switch toolset.Name {
34+
case "repos":
35+
toolset.AddTemplateResources(
36+
toolsets.NewResourceTemplate(GetRepositoryResourceContent(getClient, t)),
37+
toolsets.NewResourceTemplate(GetRepositoryResourceBranchContent(getClient, t)),
38+
toolsets.NewResourceTemplate(GetRepositoryResourceCommitContent(getClient, t)),
39+
toolsets.NewResourceTemplate(GetRepositoryResourceTagContent(getClient, t)),
40+
toolsets.NewResourceTemplate(GetRepositoryResourcePrContent(getClient, t)),
41+
).AddReadTools(
42+
toolsets.NewServerTool(SearchRepositories(getClient, t)),
43+
toolsets.NewServerTool(GetFileContents(getClient, t)),
44+
toolsets.NewServerTool(ListCommits(getClient, t)),
45+
).AddWriteTools(
46+
toolsets.NewServerTool(CreateOrUpdateFile(getClient, t)),
47+
toolsets.NewServerTool(CreateRepository(getClient, t)),
48+
toolsets.NewServerTool(ForkRepository(getClient, t)),
49+
toolsets.NewServerTool(CreateBranch(getClient, t)),
50+
toolsets.NewServerTool(PushFiles(getClient, t)),
51+
)
52+
case "issues":
53+
toolset.AddReadTools(
54+
toolsets.NewServerTool(GetIssue(getClient, t)),
55+
toolsets.NewServerTool(SearchIssues(getClient, t)),
56+
toolsets.NewServerTool(ListIssues(getClient, t)),
57+
toolsets.NewServerTool(GetIssueComments(getClient, t)),
58+
).AddWriteTools(
59+
toolsets.NewServerTool(CreateIssue(getClient, t)),
60+
toolsets.NewServerTool(AddIssueComment(getClient, t)),
61+
toolsets.NewServerTool(UpdateIssue(getClient, t)),
62+
)
63+
case "pull_requests":
64+
toolset.AddReadTools(
65+
toolsets.NewServerTool(GetPullRequest(getClient, t)),
66+
toolsets.NewServerTool(ListPullRequests(getClient, t)),
67+
toolsets.NewServerTool(GetPullRequestFiles(getClient, t)),
68+
toolsets.NewServerTool(GetPullRequestStatus(getClient, t)),
69+
toolsets.NewServerTool(GetPullRequestComments(getClient, t)),
70+
toolsets.NewServerTool(GetPullRequestReviews(getClient, t)),
71+
).AddWriteTools(
72+
toolsets.NewServerTool(MergePullRequest(getClient, t)),
73+
toolsets.NewServerTool(UpdatePullRequestBranch(getClient, t)),
74+
toolsets.NewServerTool(CreatePullRequestReview(getClient, t)),
75+
toolsets.NewServerTool(CreatePullRequest(getClient, t)),
76+
)
77+
case "search":
78+
toolset.AddReadTools(
79+
toolsets.NewServerTool(SearchCode(getClient, t)),
80+
toolsets.NewServerTool(SearchUsers(getClient, t)),
81+
)
82+
case "code_security":
83+
toolset.AddReadTools(
84+
toolsets.NewServerTool(GetCodeScanningAlert(getClient, t)),
85+
toolsets.NewServerTool(ListCodeScanningAlerts(getClient, t)),
86+
)
87+
case "experiments":
88+
toolset.AddReadTools(
89+
toolsets.NewServerTool(ListAvailableToolsets(toolsetGroup, t)),
90+
toolsets.NewServerTool(EnableToolset(s, toolsetGroup, t)),
91+
)
92+
default:
93+
panic(fmt.Sprintf("Unknown toolset: %s", toolset.Name))
5094
}
5195
}
5296

53-
if toolsetGroup.IsEnabled("issues") {
54-
// Add GitHub tools - Issues
55-
s.AddTool(GetIssue(getClient, t))
56-
s.AddTool(SearchIssues(getClient, t))
57-
s.AddTool(ListIssues(getClient, t))
58-
s.AddTool(GetIssueComments(getClient, t))
59-
if !readOnly {
60-
s.AddTool(CreateIssue(getClient, t))
61-
s.AddTool(AddIssueComment(getClient, t))
62-
s.AddTool(UpdateIssue(getClient, t))
63-
}
64-
}
65-
66-
if toolsetGroup.IsEnabled("pull_requests") {
67-
// Add GitHub tools - Pull Requests
68-
s.AddTool(GetPullRequest(getClient, t))
69-
s.AddTool(ListPullRequests(getClient, t))
70-
s.AddTool(GetPullRequestFiles(getClient, t))
71-
s.AddTool(GetPullRequestStatus(getClient, t))
72-
s.AddTool(GetPullRequestComments(getClient, t))
73-
s.AddTool(GetPullRequestReviews(getClient, t))
74-
if !readOnly {
75-
s.AddTool(MergePullRequest(getClient, t))
76-
s.AddTool(UpdatePullRequestBranch(getClient, t))
77-
s.AddTool(CreatePullRequestReview(getClient, t))
78-
s.AddTool(CreatePullRequest(getClient, t))
79-
}
80-
}
81-
82-
if toolsetGroup.IsEnabled("search") {
83-
// Add GitHub tools - Search
84-
s.AddTool(SearchCode(getClient, t))
85-
s.AddTool(SearchUsers(getClient, t))
86-
}
87-
88-
if toolsetGroup.IsEnabled("code_security") {
89-
// Add GitHub tools - Code Scanning
90-
s.AddTool(GetCodeScanningAlert(getClient, t))
91-
s.AddTool(ListCodeScanningAlerts(getClient, t))
92-
}
93-
94-
if toolsetGroup.IsEnabled("experiments") {
95-
s.AddTool(ListAvailableToolsets(toolsetGroup, t))
96-
s.AddTool(EnableToolset(s, toolsetGroup, t))
97-
}
98-
97+
// Register the tools with the server
98+
toolsetGroup.RegisterTools(s)
9999
return s
100100
}
101101

@@ -150,9 +150,22 @@ func EnableToolset(s *server.MCPServer, toolsets *toolsets.ToolsetGroup, t trans
150150
if err != nil {
151151
return mcp.NewToolResultError(err.Error()), nil
152152
}
153-
toolsets.EnableFeature(toolsetName)
154-
// TODO s.AddTool()
155-
// TODO SEND TOOL UPDATE TO CLIENT
153+
toolset := toolsets.Toolsets[toolsetName]
154+
if toolset == nil {
155+
return mcp.NewToolResultError(fmt.Sprintf("Toolset %s not found", toolsetName)), nil
156+
}
157+
if toolset.Enabled {
158+
return mcp.NewToolResultText(fmt.Sprintf("Toolset %s is already enabled", toolsetName)), nil
159+
}
160+
161+
toolset.Enabled = true
162+
163+
// caution: this currently affects the global tools and notifies all clients:
164+
//
165+
// Send notification to all initialized sessions
166+
// s.sendNotificationToAllClients("notifications/tools/list_changed", nil)
167+
s.AddTools(toolset.GetActiveTools()...)
168+
156169
return mcp.NewToolResultText(fmt.Sprintf("Toolset %s enabled", toolsetName)), nil
157170
}
158171
}

Diff for: pkg/github/server_test.go

+9-9
Original file line numberDiff line numberDiff line change
@@ -647,9 +647,9 @@ func Test_ListAvailableToolsets(t *testing.T) {
647647
toolsetGroup := toolsets.NewToolsetGroup()
648648

649649
// Add some toolsets with different states
650-
toolsetGroup.AddToolset("toolset1", "Test toolset 1", true)
651-
toolsetGroup.AddToolset("toolset2", "Test toolset 2", false)
652-
toolsetGroup.AddToolset("toolset3", "Test toolset 3", true)
650+
toolsetGroup.AddToolset("toolset1", "Test toolset 1", true, true)
651+
toolsetGroup.AddToolset("toolset2", "Test toolset 2", true, false)
652+
toolsetGroup.AddToolset("toolset3", "Test toolset 3", true, true)
653653

654654
// Test ListAvailableToolsets tool definition
655655
tool, _ := ListAvailableToolsets(toolsetGroup, translations.NullTranslationHelper)
@@ -669,9 +669,9 @@ func Test_ListAvailableToolsets(t *testing.T) {
669669
name: "regular toolset group",
670670
toolsetGroup: func() *toolsets.ToolsetGroup {
671671
fs := toolsets.NewToolsetGroup()
672-
fs.AddToolset("toolset1", "Test toolset 1", true)
673-
fs.AddToolset("toolset2", "Test toolset 2", false)
674-
fs.AddToolset("toolset3", "Test toolset 3", true)
672+
fs.AddToolset("toolset1", "Test toolset 1", true, true)
673+
fs.AddToolset("toolset2", "Test toolset 2", true, false)
674+
fs.AddToolset("toolset3", "Test toolset 3", true, true)
675675
return fs
676676
}(),
677677
requestArgs: map[string]interface{}{},
@@ -693,9 +693,9 @@ func Test_ListAvailableToolsets(t *testing.T) {
693693
name: "toolset group with everything enabled",
694694
toolsetGroup: func() *toolsets.ToolsetGroup {
695695
fs := toolsets.NewToolsetGroup()
696-
fs.AddToolset("toolset1", "Test toolset 1", false)
697-
fs.AddToolset("toolset2", "Test toolset 2", false)
698-
_ = fs.EnableFeature("everything")
696+
fs.AddToolset("toolset1", "Test toolset 1", true, false)
697+
fs.AddToolset("toolset2", "Test toolset 2", true, false)
698+
_ = fs.EnableToolset("everything")
699699
return fs
700700
}(),
701701
requestArgs: map[string]interface{}{},

0 commit comments

Comments
 (0)