From 2a56e61b28b122bb684f45a4334896d66a78c154 Mon Sep 17 00:00:00 2001 From: tommaso-moro Date: Tue, 11 Nov 2025 18:02:44 +0000 Subject: [PATCH 01/13] add enabledTools to StdioServerConfig --- cmd/github-mcp-server/main.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cmd/github-mcp-server/main.go b/cmd/github-mcp-server/main.go index 647ec1d19..7a59f8362 100644 --- a/cmd/github-mcp-server/main.go +++ b/cmd/github-mcp-server/main.go @@ -50,11 +50,18 @@ var ( enabledToolsets = []string{github.ToolsetMetadataDefault.ID} } + // Parse tools (similar to toolsets) + var enabledTools []string + if err := viper.UnmarshalKey("tools", &enabledTools); err != nil { + return fmt.Errorf("failed to unmarshal tools: %w", err) + } + stdioServerConfig := ghmcp.StdioServerConfig{ Version: version, Host: viper.GetString("host"), Token: token, EnabledToolsets: enabledToolsets, + EnabledTools: enabledTools, DynamicToolsets: viper.GetBool("dynamic_toolsets"), ReadOnly: viper.GetBool("read-only"), ExportTranslations: viper.GetBool("export-translations"), @@ -75,6 +82,7 @@ func init() { // Add global flags that will be shared by all commands rootCmd.PersistentFlags().StringSlice("toolsets", nil, github.GenerateToolsetsHelp()) + rootCmd.PersistentFlags().StringSlice("tools", nil, "Comma-separated list of specific tools to enable (takes priority over toolsets)") rootCmd.PersistentFlags().Bool("dynamic-toolsets", false, "Enable dynamic toolsets") rootCmd.PersistentFlags().Bool("read-only", false, "Restrict the server to read-only operations") rootCmd.PersistentFlags().String("log-file", "", "Path to log file") @@ -85,6 +93,7 @@ func init() { // Bind flag to viper _ = viper.BindPFlag("toolsets", rootCmd.PersistentFlags().Lookup("toolsets")) + _ = viper.BindPFlag("tools", rootCmd.PersistentFlags().Lookup("tools")) _ = viper.BindPFlag("dynamic_toolsets", rootCmd.PersistentFlags().Lookup("dynamic-toolsets")) _ = viper.BindPFlag("read-only", rootCmd.PersistentFlags().Lookup("read-only")) _ = viper.BindPFlag("log-file", rootCmd.PersistentFlags().Lookup("log-file")) From 7bbd6c4d93b329a22c42853f75a1a5b08f71dd19 Mon Sep 17 00:00:00 2001 From: tommaso-moro Date: Tue, 11 Nov 2025 18:03:32 +0000 Subject: [PATCH 02/13] add EnabledTools to MCPServerConfig, and logic to bypass toolset config if present --- internal/ghmcp/server.go | 51 +++++++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/internal/ghmcp/server.go b/internal/ghmcp/server.go index 5e1ee58c8..642e61320 100644 --- a/internal/ghmcp/server.go +++ b/internal/ghmcp/server.go @@ -39,6 +39,10 @@ type MCPServerConfig struct { // See: https://github.com/github/github-mcp-server?tab=readme-ov-file#tool-configuration EnabledToolsets []string + // EnabledTools is a list of specific tools to enable (takes priority over toolsets) + // When specified, only these tools will be registered, bypassing toolset enablement + EnabledTools []string + // Whether to enable dynamic toolsets // See: https://github.com/github/github-mcp-server?tab=readme-ov-file#dynamic-tool-discovery DynamicToolsets bool @@ -155,18 +159,42 @@ func NewMCPServer(cfg MCPServerConfig) (*server.MCPServer, error) { // Create default toolsets tsg := github.DefaultToolsetGroup(cfg.ReadOnly, getClient, getGQLClient, getRawClient, cfg.Translator, cfg.ContentWindowSize) - err = tsg.EnableToolsets(enabledToolsets, nil) - if err != nil { - return nil, fmt.Errorf("failed to enable toolsets: %w", err) - } + // PRIORITY: If specific tools are configured, use tool-level registration + if len(cfg.EnabledTools) > 0 { + // Clean and validate tool names + enabledTools, invalidTools := github.CleanTools(cfg.EnabledTools) + + if len(invalidTools) > 0 { + fmt.Fprintf(os.Stderr, "Invalid tools ignored: %s\n", strings.Join(invalidTools, ", ")) + } - // Register all mcp functionality with the server - tsg.RegisterAll(ghServer) + // Register only the specified tools (bypasses toolset enablement) + err = tsg.RegisterSpecificTools(ghServer, enabledTools, cfg.ReadOnly) + if err != nil { + return nil, fmt.Errorf("failed to register tools: %w", err) + } - if cfg.DynamicToolsets { - dynamic := github.InitDynamicToolset(ghServer, tsg, cfg.Translator) - dynamic.RegisterTools(ghServer) + // Still register resources and prompts from all toolsets to maintain functionality + for _, toolset := range tsg.Toolsets { + toolset.RegisterResourcesTemplates(ghServer) + toolset.RegisterPrompts(ghServer) + } + } else { + // EXISTING FLOW: Use toolset-based registration + err = tsg.EnableToolsets(enabledToolsets, nil) + if err != nil { + return nil, fmt.Errorf("failed to enable toolsets: %w", err) + } + + // Register all mcp functionality with the server + tsg.RegisterAll(ghServer) + + // Dynamic toolsets only work if no specific tools configured + if cfg.DynamicToolsets { + dynamic := github.InitDynamicToolset(ghServer, tsg, cfg.Translator) + dynamic.RegisterTools(ghServer) + } } return ghServer, nil @@ -186,6 +214,10 @@ type StdioServerConfig struct { // See: https://github.com/github/github-mcp-server?tab=readme-ov-file#tool-configuration EnabledToolsets []string + // EnabledTools is a list of specific tools to enable (takes priority over toolsets) + // When specified, only these tools will be registered, bypassing toolset enablement + EnabledTools []string + // Whether to enable dynamic toolsets // See: https://github.com/github/github-mcp-server?tab=readme-ov-file#dynamic-tool-discovery DynamicToolsets bool @@ -220,6 +252,7 @@ func RunStdioServer(cfg StdioServerConfig) error { Host: cfg.Host, Token: cfg.Token, EnabledToolsets: cfg.EnabledToolsets, + EnabledTools: cfg.EnabledTools, DynamicToolsets: cfg.DynamicToolsets, ReadOnly: cfg.ReadOnly, Translator: t, From 1dc9c55d0b3f62feb7c594f7bf55403e64226d11 Mon Sep 17 00:00:00 2001 From: tommaso-moro Date: Tue, 11 Nov 2025 18:06:12 +0000 Subject: [PATCH 03/13] add logic to register specific tools --- pkg/github/tools.go | 27 ++++++++++++++++++ pkg/toolsets/toolsets.go | 59 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/pkg/github/tools.go b/pkg/github/tools.go index 4296aaa72..06b1c0ed7 100644 --- a/pkg/github/tools.go +++ b/pkg/github/tools.go @@ -516,3 +516,30 @@ func ContainsToolset(tools []string, toCheck string) bool { } return false } + +// CleanTools validates and cleans tool names: +// - Duplicates are removed from the result +// - Removes whitespaces +// - Validation of tool existence is done during registration +// Returns: (cleaned tools, invalid tools) +// Note: Invalid tools are identified during registration, so this function only cleans and deduplicates. +func CleanTools(toolNames []string) ([]string, []string) { + seen := make(map[string]bool) + result := make([]string, 0, len(toolNames)) + invalid := make([]string, 0) + + // Remove duplicates and trim whitespace + for _, tool := range toolNames { + trimmed := strings.TrimSpace(tool) + if trimmed == "" { + continue + } + if !seen[trimmed] { + seen[trimmed] = true + result = append(result, trimmed) + } + } + + // Validation will happen during registration, so we return empty invalid list here + return result, invalid +} diff --git a/pkg/toolsets/toolsets.go b/pkg/toolsets/toolsets.go index 96f1fc3ca..3603b3e3b 100644 --- a/pkg/toolsets/toolsets.go +++ b/pkg/toolsets/toolsets.go @@ -263,3 +263,62 @@ func (tg *ToolsetGroup) GetToolset(name string) (*Toolset, error) { } return toolset, nil } + +type ToolDoesNotExistError struct { + Name string +} + +func (e *ToolDoesNotExistError) Error() string { + return fmt.Sprintf("tool %s does not exist", e.Name) +} + +func NewToolDoesNotExistError(name string) *ToolDoesNotExistError { + return &ToolDoesNotExistError{Name: name} +} + +// FindToolByName searches all toolsets (enabled or disabled) for a tool by name. +// Returns the tool, its parent toolset name, and an error if not found. +func (tg *ToolsetGroup) FindToolByName(toolName string) (*server.ServerTool, string, error) { + for toolsetName, toolset := range tg.Toolsets { + // Check read tools + for _, tool := range toolset.readTools { + if tool.Tool.Name == toolName { + return &tool, toolsetName, nil + } + } + // Check write tools + for _, tool := range toolset.writeTools { + if tool.Tool.Name == toolName { + return &tool, toolsetName, nil + } + } + } + return nil, "", NewToolDoesNotExistError(toolName) +} + +// RegisterSpecificTools registers only the specified tools, bypassing toolset enablement. +// Respects read-only mode (skips write tools if readOnly=true). +// Returns error if any tool is not found. +func (tg *ToolsetGroup) RegisterSpecificTools(s *server.MCPServer, toolNames []string, readOnly bool) error { + for _, toolName := range toolNames { + tool, toolsetName, err := tg.FindToolByName(toolName) + if err != nil { + return fmt.Errorf("tool %s not found: %w", toolName, err) + } + + // Check if it's a write tool and we're in read-only mode + // ReadOnlyHint should always be set, but add defensive check + if tool.Tool.Annotations.ReadOnlyHint != nil { + isWriteTool := !*tool.Tool.Annotations.ReadOnlyHint + if isWriteTool && readOnly { + // Skip write tools in read-only mode + continue + } + } + + // Register the tool + s.AddTool(tool.Tool, tool.Handler) + _ = toolsetName // toolsetName is available for potential future use (logging, etc.) + } + return nil +} From dd11e3ec0d59cfc6969534232d355ef0b88f614b Mon Sep 17 00:00:00 2001 From: tommaso-moro Date: Tue, 11 Nov 2025 18:06:20 +0000 Subject: [PATCH 04/13] update readme --- README.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/README.md b/README.md index 2e896cea8..d01321ac9 100644 --- a/README.md +++ b/README.md @@ -343,6 +343,28 @@ To specify toolsets you want available to the LLM, you can pass an allow-list in The environment variable `GITHUB_TOOLSETS` takes precedence over the command line argument if both are provided. +#### Specifying Individual Tools + +You can also configure specific tools instead of entire toolsets using the `--tools` flag. When tools are specified, they take priority over toolsets configuration, read-only mode, and dynamic toolsets. + +1. **Using Command Line Argument**: + + ```bash + github-mcp-server --tools get_file_contents,issue_read,create_pull_request + ``` + +2. **Using Environment Variable**: + ```bash + GITHUB_TOOLS="get_file_contents,issue_read,create_pull_request" ./github-mcp-server + ``` + +**Important Notes:** +- When `--tools` is specified, only the listed tools are registered, bypassing toolset enablement +- Read-only mode is still respected: write tools are skipped if `--read-only` is set, even if explicitly requested +- Dynamic toolsets are disabled when specific tools are configured +- Resources and prompts from all toolsets are still registered to maintain functionality +- Tool names must match exactly (e.g., `get_file_contents`, not `getFileContents`) + ### Using Toolsets With Docker When using Docker, you can pass the toolsets as environment variables: @@ -354,6 +376,17 @@ docker run -i --rm \ ghcr.io/github/github-mcp-server ``` +### Using Tools With Docker + +When using Docker, you can pass specific tools as environment variables: + +```bash +docker run -i --rm \ + -e GITHUB_PERSONAL_ACCESS_TOKEN= \ + -e GITHUB_TOOLS="get_file_contents,issue_read,create_pull_request" \ + ghcr.io/github/github-mcp-server +``` + ### Special toolsets #### "all" toolset From 475a747557379f585c757120cdce9717f7cc801b Mon Sep 17 00:00:00 2001 From: tommaso-moro Date: Tue, 18 Nov 2025 22:53:02 +0000 Subject: [PATCH 05/13] Update to be consistent with: https://docs.google.com/document/d/1tOOBJ4y9xY61QVrO18ymuVt4SO9nV-z2B4ckaL2f9IU/edit?tab=t.0#heading=h.ffto4e5dwzlf specifically - allow for --tools and dynamic toolset mode together - allow for --tools and --toolsets together --- README.md | 32 ++++++++++++++++---- cmd/github-mcp-server/main.go | 10 +++---- internal/ghmcp/server.go | 55 +++++++++++++++-------------------- pkg/github/tools.go | 14 +++------ pkg/toolsets/toolsets.go | 10 +++++++ 5 files changed, 69 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 7555489a8..486cccd3f 100644 --- a/README.md +++ b/README.md @@ -345,7 +345,7 @@ The environment variable `GITHUB_TOOLSETS` takes precedence over the command lin #### Specifying Individual Tools -You can also configure specific tools instead of entire toolsets using the `--tools` flag. When tools are specified, they take priority over toolsets configuration, read-only mode, and dynamic toolsets. +You can also configure specific tools using the `--tools` flag. Tools can be used independently or combined with toolsets and dynamic toolsets discovery for fine-grained control. 1. **Using Command Line Argument**: @@ -358,12 +358,24 @@ You can also configure specific tools instead of entire toolsets using the `--to GITHUB_TOOLS="get_file_contents,issue_read,create_pull_request" ./github-mcp-server ``` +3. **Combining with Toolsets** (additive): + ```bash + github-mcp-server --toolsets repos,issues --tools get_file_contents,search_code + ``` + This registers all tools from `repos` and `issues` toolsets, plus `get_file_contents` and `search_code`. + +4. **Combining with Dynamic Toolsets** (additive): + ```bash + github-mcp-server --tools get_file_contents --dynamic-toolsets + ``` + This registers `get_file_contents` plus the dynamic toolset tools (`enable_toolset`, `list_available_toolsets`, `get_toolset_tools`). + **Important Notes:** -- When `--tools` is specified, only the listed tools are registered, bypassing toolset enablement -- Read-only mode is still respected: write tools are skipped if `--read-only` is set, even if explicitly requested -- Dynamic toolsets are disabled when specific tools are configured -- Resources and prompts from all toolsets are still registered to maintain functionality +- Tools, toolsets, and dynamic toolsets can all be used together - they are additive +- Read-only mode takes priority: write tools are skipped if `--read-only` is set, even if explicitly requested via `--tools` +- When write tools are skipped due to read-only mode, a warning is logged to stderr - Tool names must match exactly (e.g., `get_file_contents`, not `getFileContents`) +- Invalid tool names will cause the server to fail at startup with an error message ### Using Toolsets With Docker @@ -378,13 +390,21 @@ docker run -i --rm \ ### Using Tools With Docker -When using Docker, you can pass specific tools as environment variables: +When using Docker, you can pass specific tools as environment variables. You can also combine tools with toolsets: ```bash +# Tools only docker run -i --rm \ -e GITHUB_PERSONAL_ACCESS_TOKEN= \ -e GITHUB_TOOLS="get_file_contents,issue_read,create_pull_request" \ ghcr.io/github/github-mcp-server + +# Tools combined with toolsets (additive) +docker run -i --rm \ + -e GITHUB_PERSONAL_ACCESS_TOKEN= \ + -e GITHUB_TOOLSETS="repos,issues" \ + -e GITHUB_TOOLS="search_code,search_repositories" \ + ghcr.io/github/github-mcp-server ``` ### Special toolsets diff --git a/cmd/github-mcp-server/main.go b/cmd/github-mcp-server/main.go index 50160e7be..25424fa20 100644 --- a/cmd/github-mcp-server/main.go +++ b/cmd/github-mcp-server/main.go @@ -45,17 +45,17 @@ var ( return fmt.Errorf("failed to unmarshal toolsets: %w", err) } - // No passed toolsets configuration means we enable the default toolset - if len(enabledToolsets) == 0 { - enabledToolsets = []string{github.ToolsetMetadataDefault.ID} - } - // Parse tools (similar to toolsets) var enabledTools []string if err := viper.UnmarshalKey("tools", &enabledTools); err != nil { return fmt.Errorf("failed to unmarshal tools: %w", err) } + // If neither toolset config nor tools config is passed we enable the default toolset + if len(enabledToolsets) == 0 && len(enabledTools) == 0 { + enabledToolsets = []string{github.ToolsetMetadataDefault.ID} + } + stdioServerConfig := ghmcp.StdioServerConfig{ Version: version, Host: viper.GetString("host"), diff --git a/internal/ghmcp/server.go b/internal/ghmcp/server.go index 0c58cc609..f977856ca 100644 --- a/internal/ghmcp/server.go +++ b/internal/ghmcp/server.go @@ -39,8 +39,8 @@ type MCPServerConfig struct { // See: https://github.com/github/github-mcp-server?tab=readme-ov-file#tool-configuration EnabledToolsets []string - // EnabledTools is a list of specific tools to enable (takes priority over toolsets) - // When specified, only these tools will be registered, bypassing toolset enablement + // EnabledTools is a list of specific tools to enable (additive to toolsets) + // When specified, these tools are registered in addition to any specified toolset tools EnabledTools []string // Whether to enable dynamic toolsets @@ -171,43 +171,36 @@ func NewMCPServer(cfg MCPServerConfig) (*server.MCPServer, error) { github.FeatureFlags{LockdownMode: cfg.LockdownMode}, ) - // PRIORITY: If specific tools are configured, use tool-level registration - if len(cfg.EnabledTools) > 0 { - // Clean and validate tool names - enabledTools, invalidTools := github.CleanTools(cfg.EnabledTools) - - if len(invalidTools) > 0 { - fmt.Fprintf(os.Stderr, "Invalid tools ignored: %s\n", strings.Join(invalidTools, ", ")) - } - - // Register only the specified tools (bypasses toolset enablement) - err = tsg.RegisterSpecificTools(ghServer, enabledTools, cfg.ReadOnly) - if err != nil { - return nil, fmt.Errorf("failed to register tools: %w", err) - } - - // Still register resources and prompts from all toolsets to maintain functionality - for _, toolset := range tsg.Toolsets { - toolset.RegisterResourcesTemplates(ghServer) - toolset.RegisterPrompts(ghServer) - } - } else { - // EXISTING FLOW: Use toolset-based registration + // Enable and register toolsets if configured + // This always happens if toolsets are specified, regardless of whether tools are also specified + if len(enabledToolsets) > 0 { err = tsg.EnableToolsets(enabledToolsets, nil) if err != nil { return nil, fmt.Errorf("failed to enable toolsets: %w", err) } - // Register all mcp functionality with the server + // Register all mcp functionality with the server (tools, resources, prompts) tsg.RegisterAll(ghServer) + } + + // Register specific tools if configured (additive to toolsets) + if len(cfg.EnabledTools) > 0 { + // Clean and validate tool names + enabledTools := github.CleanTools(cfg.EnabledTools) - // Dynamic toolsets only work if no specific tools configured - if cfg.DynamicToolsets { - dynamic := github.InitDynamicToolset(ghServer, tsg, cfg.Translator) - dynamic.RegisterTools(ghServer) + // Register the specified tools (additive to any toolsets already enabled) + err = tsg.RegisterSpecificTools(ghServer, enabledTools, cfg.ReadOnly) + if err != nil { + return nil, fmt.Errorf("failed to register tools: %w", err) } } + // Register dynamic toolsets if configured (additive to toolsets and tools) + if cfg.DynamicToolsets { + dynamic := github.InitDynamicToolset(ghServer, tsg, cfg.Translator) + dynamic.RegisterTools(ghServer) + } + return ghServer, nil } @@ -225,8 +218,8 @@ type StdioServerConfig struct { // See: https://github.com/github/github-mcp-server?tab=readme-ov-file#tool-configuration EnabledToolsets []string - // EnabledTools is a list of specific tools to enable (takes priority over toolsets) - // When specified, only these tools will be registered, bypassing toolset enablement + // EnabledTools is a list of specific tools to enable (additive to toolsets) + // When specified, these tools are registered in addition to any specifiedtoolset tools EnabledTools []string // Whether to enable dynamic toolsets diff --git a/pkg/github/tools.go b/pkg/github/tools.go index a0cc70439..1756f4bc8 100644 --- a/pkg/github/tools.go +++ b/pkg/github/tools.go @@ -524,16 +524,11 @@ func ContainsToolset(tools []string, toCheck string) bool { return false } -// CleanTools validates and cleans tool names: -// - Duplicates are removed from the result -// - Removes whitespaces -// - Validation of tool existence is done during registration -// Returns: (cleaned tools, invalid tools) -// Note: Invalid tools are identified during registration, so this function only cleans and deduplicates. -func CleanTools(toolNames []string) ([]string, []string) { +// CleanTools cleans tool names by removing duplicates and trimming whitespace. +// Validation of tool existence is done during registration. +func CleanTools(toolNames []string) []string { seen := make(map[string]bool) result := make([]string, 0, len(toolNames)) - invalid := make([]string, 0) // Remove duplicates and trim whitespace for _, tool := range toolNames { @@ -547,6 +542,5 @@ func CleanTools(toolNames []string) ([]string, []string) { } } - // Validation will happen during registration, so we return empty invalid list here - return result, invalid + return result } diff --git a/pkg/toolsets/toolsets.go b/pkg/toolsets/toolsets.go index 3603b3e3b..01f98a85b 100644 --- a/pkg/toolsets/toolsets.go +++ b/pkg/toolsets/toolsets.go @@ -2,6 +2,8 @@ package toolsets import ( "fmt" + "os" + "strings" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" @@ -300,6 +302,7 @@ func (tg *ToolsetGroup) FindToolByName(toolName string) (*server.ServerTool, str // Respects read-only mode (skips write tools if readOnly=true). // Returns error if any tool is not found. func (tg *ToolsetGroup) RegisterSpecificTools(s *server.MCPServer, toolNames []string, readOnly bool) error { + var skippedTools []string for _, toolName := range toolNames { tool, toolsetName, err := tg.FindToolByName(toolName) if err != nil { @@ -312,6 +315,7 @@ func (tg *ToolsetGroup) RegisterSpecificTools(s *server.MCPServer, toolNames []s isWriteTool := !*tool.Tool.Annotations.ReadOnlyHint if isWriteTool && readOnly { // Skip write tools in read-only mode + skippedTools = append(skippedTools, toolName) continue } } @@ -320,5 +324,11 @@ func (tg *ToolsetGroup) RegisterSpecificTools(s *server.MCPServer, toolNames []s s.AddTool(tool.Tool, tool.Handler) _ = toolsetName // toolsetName is available for potential future use (logging, etc.) } + + // Log skipped write tools if any + if len(skippedTools) > 0 { + fmt.Fprintf(os.Stderr, "Write tools skipped due to read-only mode: %s\n", strings.Join(skippedTools, ", ")) + } + return nil } From e2f9e8e17651c0fadcfa8bd139e0e64e75b39fc8 Mon Sep 17 00:00:00 2001 From: tommaso-moro Date: Wed, 19 Nov 2025 13:03:49 +0000 Subject: [PATCH 06/13] go mod tidy --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 02b9ad252..b88e1dc1c 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 - github.com/google/go-querystring v1.1.0 + github.com/google/go-querystring v1.1.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect From 065fbdc3b8b56b6e90cd1c0031dff8cc9a8be05e Mon Sep 17 00:00:00 2001 From: tommaso-moro Date: Thu, 20 Nov 2025 09:51:09 +0000 Subject: [PATCH 07/13] update --- cmd/github-mcp-server/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/github-mcp-server/main.go b/cmd/github-mcp-server/main.go index 25424fa20..a559e878a 100644 --- a/cmd/github-mcp-server/main.go +++ b/cmd/github-mcp-server/main.go @@ -83,7 +83,7 @@ func init() { // Add global flags that will be shared by all commands rootCmd.PersistentFlags().StringSlice("toolsets", nil, github.GenerateToolsetsHelp()) - rootCmd.PersistentFlags().StringSlice("tools", nil, "Comma-separated list of specific tools to enable (takes priority over toolsets)") + rootCmd.PersistentFlags().StringSlice("tools", nil, "Comma-separated list of specific tools to enable") rootCmd.PersistentFlags().Bool("dynamic-toolsets", false, "Enable dynamic toolsets") rootCmd.PersistentFlags().Bool("read-only", false, "Restrict the server to read-only operations") rootCmd.PersistentFlags().String("log-file", "", "Path to log file") From a40c966d44c49bad1987395cd7079269e768882d Mon Sep 17 00:00:00 2001 From: tommaso-moro Date: Thu, 20 Nov 2025 10:00:59 +0000 Subject: [PATCH 08/13] clean up comment --- internal/ghmcp/server.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/ghmcp/server.go b/internal/ghmcp/server.go index f977856ca..ef0ed75d5 100644 --- a/internal/ghmcp/server.go +++ b/internal/ghmcp/server.go @@ -179,11 +179,11 @@ func NewMCPServer(cfg MCPServerConfig) (*server.MCPServer, error) { return nil, fmt.Errorf("failed to enable toolsets: %w", err) } - // Register all mcp functionality with the server (tools, resources, prompts) + // Register all mcp functionality with the server tsg.RegisterAll(ghServer) } - // Register specific tools if configured (additive to toolsets) + // Register specific tools if configured if len(cfg.EnabledTools) > 0 { // Clean and validate tool names enabledTools := github.CleanTools(cfg.EnabledTools) From 7466d5441e88f52acc0c6b14f4c014f4a29b429e Mon Sep 17 00:00:00 2001 From: tommaso-moro Date: Thu, 20 Nov 2025 10:39:57 +0000 Subject: [PATCH 09/13] fix --- internal/ghmcp/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/ghmcp/server.go b/internal/ghmcp/server.go index ef0ed75d5..cb617a78f 100644 --- a/internal/ghmcp/server.go +++ b/internal/ghmcp/server.go @@ -219,7 +219,7 @@ type StdioServerConfig struct { EnabledToolsets []string // EnabledTools is a list of specific tools to enable (additive to toolsets) - // When specified, these tools are registered in addition to any specifiedtoolset tools + // When specified, these tools are registered in addition to any specified toolset tools EnabledTools []string // Whether to enable dynamic toolsets From 5d587bf7f585fa5c9db317b63ea6a49311b787c3 Mon Sep 17 00:00:00 2001 From: tommaso-moro Date: Thu, 20 Nov 2025 10:41:47 +0000 Subject: [PATCH 10/13] fix --- pkg/toolsets/toolsets.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/toolsets/toolsets.go b/pkg/toolsets/toolsets.go index 01f98a85b..9104e6fff 100644 --- a/pkg/toolsets/toolsets.go +++ b/pkg/toolsets/toolsets.go @@ -298,7 +298,7 @@ func (tg *ToolsetGroup) FindToolByName(toolName string) (*server.ServerTool, str return nil, "", NewToolDoesNotExistError(toolName) } -// RegisterSpecificTools registers only the specified tools, bypassing toolset enablement. +// RegisterSpecificTools registers only the specified tools. // Respects read-only mode (skips write tools if readOnly=true). // Returns error if any tool is not found. func (tg *ToolsetGroup) RegisterSpecificTools(s *server.MCPServer, toolNames []string, readOnly bool) error { From 413303aa9f27197ff9a4a1c3d4483bc4ffbece2f Mon Sep 17 00:00:00 2001 From: tommaso-moro Date: Thu, 20 Nov 2025 13:01:37 +0000 Subject: [PATCH 11/13] updte --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 486cccd3f..c43e14fea 100644 --- a/README.md +++ b/README.md @@ -360,9 +360,9 @@ You can also configure specific tools using the `--tools` flag. Tools can be use 3. **Combining with Toolsets** (additive): ```bash - github-mcp-server --toolsets repos,issues --tools get_file_contents,search_code + github-mcp-server --toolsets repos,issues --tools get_gist ``` - This registers all tools from `repos` and `issues` toolsets, plus `get_file_contents` and `search_code`. + This registers all tools from `repos` and `issues` toolsets, plus `get_gist`. 4. **Combining with Dynamic Toolsets** (additive): ```bash @@ -371,11 +371,9 @@ You can also configure specific tools using the `--tools` flag. Tools can be use This registers `get_file_contents` plus the dynamic toolset tools (`enable_toolset`, `list_available_toolsets`, `get_toolset_tools`). **Important Notes:** -- Tools, toolsets, and dynamic toolsets can all be used together - they are additive +- Tools, toolsets, and dynamic toolsets can all be used together - Read-only mode takes priority: write tools are skipped if `--read-only` is set, even if explicitly requested via `--tools` -- When write tools are skipped due to read-only mode, a warning is logged to stderr -- Tool names must match exactly (e.g., `get_file_contents`, not `getFileContents`) -- Invalid tool names will cause the server to fail at startup with an error message +- Tool names must match exactly (e.g., `get_file_contents`, not `getFileContents`). Invalid tool names will cause the server to fail at startup with an error message ### Using Toolsets With Docker From 9aa7ed51ad1235115d3089ca28f3be1eb7243e20 Mon Sep 17 00:00:00 2001 From: tommaso-moro Date: Thu, 20 Nov 2025 13:02:19 +0000 Subject: [PATCH 12/13] update --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c43e14fea..44472458b 100644 --- a/README.md +++ b/README.md @@ -401,7 +401,7 @@ docker run -i --rm \ docker run -i --rm \ -e GITHUB_PERSONAL_ACCESS_TOKEN= \ -e GITHUB_TOOLSETS="repos,issues" \ - -e GITHUB_TOOLS="search_code,search_repositories" \ + -e GITHUB_TOOLS="get_gist" \ ghcr.io/github/github-mcp-server ``` From 671b1793f372f5641ad689e4fa9290c5751d8b42 Mon Sep 17 00:00:00 2001 From: tommaso-moro Date: Thu, 20 Nov 2025 13:05:31 +0000 Subject: [PATCH 13/13] clean up --- pkg/toolsets/toolsets.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/toolsets/toolsets.go b/pkg/toolsets/toolsets.go index 9104e6fff..ba68649e3 100644 --- a/pkg/toolsets/toolsets.go +++ b/pkg/toolsets/toolsets.go @@ -304,13 +304,12 @@ func (tg *ToolsetGroup) FindToolByName(toolName string) (*server.ServerTool, str func (tg *ToolsetGroup) RegisterSpecificTools(s *server.MCPServer, toolNames []string, readOnly bool) error { var skippedTools []string for _, toolName := range toolNames { - tool, toolsetName, err := tg.FindToolByName(toolName) + tool, _, err := tg.FindToolByName(toolName) if err != nil { return fmt.Errorf("tool %s not found: %w", toolName, err) } // Check if it's a write tool and we're in read-only mode - // ReadOnlyHint should always be set, but add defensive check if tool.Tool.Annotations.ReadOnlyHint != nil { isWriteTool := !*tool.Tool.Annotations.ReadOnlyHint if isWriteTool && readOnly { @@ -322,7 +321,6 @@ func (tg *ToolsetGroup) RegisterSpecificTools(s *server.MCPServer, toolNames []s // Register the tool s.AddTool(tool.Tool, tool.Handler) - _ = toolsetName // toolsetName is available for potential future use (logging, etc.) } // Log skipped write tools if any