Skip to content

Commit 475a747

Browse files
committed
specifically - allow for --tools and dynamic toolset mode together - allow for --tools and --toolsets together
1 parent cef6415 commit 475a747

File tree

5 files changed

+69
-52
lines changed

5 files changed

+69
-52
lines changed

README.md

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ The environment variable `GITHUB_TOOLSETS` takes precedence over the command lin
345345

346346
#### Specifying Individual Tools
347347

348-
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.
348+
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.
349349

350350
1. **Using Command Line Argument**:
351351

@@ -358,12 +358,24 @@ You can also configure specific tools instead of entire toolsets using the `--to
358358
GITHUB_TOOLS="get_file_contents,issue_read,create_pull_request" ./github-mcp-server
359359
```
360360

361+
3. **Combining with Toolsets** (additive):
362+
```bash
363+
github-mcp-server --toolsets repos,issues --tools get_file_contents,search_code
364+
```
365+
This registers all tools from `repos` and `issues` toolsets, plus `get_file_contents` and `search_code`.
366+
367+
4. **Combining with Dynamic Toolsets** (additive):
368+
```bash
369+
github-mcp-server --tools get_file_contents --dynamic-toolsets
370+
```
371+
This registers `get_file_contents` plus the dynamic toolset tools (`enable_toolset`, `list_available_toolsets`, `get_toolset_tools`).
372+
361373
**Important Notes:**
362-
- When `--tools` is specified, only the listed tools are registered, bypassing toolset enablement
363-
- Read-only mode is still respected: write tools are skipped if `--read-only` is set, even if explicitly requested
364-
- Dynamic toolsets are disabled when specific tools are configured
365-
- Resources and prompts from all toolsets are still registered to maintain functionality
374+
- Tools, toolsets, and dynamic toolsets can all be used together - they are additive
375+
- Read-only mode takes priority: write tools are skipped if `--read-only` is set, even if explicitly requested via `--tools`
376+
- When write tools are skipped due to read-only mode, a warning is logged to stderr
366377
- Tool names must match exactly (e.g., `get_file_contents`, not `getFileContents`)
378+
- Invalid tool names will cause the server to fail at startup with an error message
367379

368380
### Using Toolsets With Docker
369381

@@ -378,13 +390,21 @@ docker run -i --rm \
378390

379391
### Using Tools With Docker
380392

381-
When using Docker, you can pass specific tools as environment variables:
393+
When using Docker, you can pass specific tools as environment variables. You can also combine tools with toolsets:
382394

383395
```bash
396+
# Tools only
384397
docker run -i --rm \
385398
-e GITHUB_PERSONAL_ACCESS_TOKEN=<your-token> \
386399
-e GITHUB_TOOLS="get_file_contents,issue_read,create_pull_request" \
387400
ghcr.io/github/github-mcp-server
401+
402+
# Tools combined with toolsets (additive)
403+
docker run -i --rm \
404+
-e GITHUB_PERSONAL_ACCESS_TOKEN=<your-token> \
405+
-e GITHUB_TOOLSETS="repos,issues" \
406+
-e GITHUB_TOOLS="search_code,search_repositories" \
407+
ghcr.io/github/github-mcp-server
388408
```
389409

390410
### Special toolsets

cmd/github-mcp-server/main.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,17 @@ var (
4545
return fmt.Errorf("failed to unmarshal toolsets: %w", err)
4646
}
4747

48-
// No passed toolsets configuration means we enable the default toolset
49-
if len(enabledToolsets) == 0 {
50-
enabledToolsets = []string{github.ToolsetMetadataDefault.ID}
51-
}
52-
5348
// Parse tools (similar to toolsets)
5449
var enabledTools []string
5550
if err := viper.UnmarshalKey("tools", &enabledTools); err != nil {
5651
return fmt.Errorf("failed to unmarshal tools: %w", err)
5752
}
5853

54+
// If neither toolset config nor tools config is passed we enable the default toolset
55+
if len(enabledToolsets) == 0 && len(enabledTools) == 0 {
56+
enabledToolsets = []string{github.ToolsetMetadataDefault.ID}
57+
}
58+
5959
stdioServerConfig := ghmcp.StdioServerConfig{
6060
Version: version,
6161
Host: viper.GetString("host"),

internal/ghmcp/server.go

Lines changed: 24 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ type MCPServerConfig struct {
3939
// See: https://github.com/github/github-mcp-server?tab=readme-ov-file#tool-configuration
4040
EnabledToolsets []string
4141

42-
// EnabledTools is a list of specific tools to enable (takes priority over toolsets)
43-
// When specified, only these tools will be registered, bypassing toolset enablement
42+
// EnabledTools is a list of specific tools to enable (additive to toolsets)
43+
// When specified, these tools are registered in addition to any specified toolset tools
4444
EnabledTools []string
4545

4646
// Whether to enable dynamic toolsets
@@ -171,43 +171,36 @@ func NewMCPServer(cfg MCPServerConfig) (*server.MCPServer, error) {
171171
github.FeatureFlags{LockdownMode: cfg.LockdownMode},
172172
)
173173

174-
// PRIORITY: If specific tools are configured, use tool-level registration
175-
if len(cfg.EnabledTools) > 0 {
176-
// Clean and validate tool names
177-
enabledTools, invalidTools := github.CleanTools(cfg.EnabledTools)
178-
179-
if len(invalidTools) > 0 {
180-
fmt.Fprintf(os.Stderr, "Invalid tools ignored: %s\n", strings.Join(invalidTools, ", "))
181-
}
182-
183-
// Register only the specified tools (bypasses toolset enablement)
184-
err = tsg.RegisterSpecificTools(ghServer, enabledTools, cfg.ReadOnly)
185-
if err != nil {
186-
return nil, fmt.Errorf("failed to register tools: %w", err)
187-
}
188-
189-
// Still register resources and prompts from all toolsets to maintain functionality
190-
for _, toolset := range tsg.Toolsets {
191-
toolset.RegisterResourcesTemplates(ghServer)
192-
toolset.RegisterPrompts(ghServer)
193-
}
194-
} else {
195-
// EXISTING FLOW: Use toolset-based registration
174+
// Enable and register toolsets if configured
175+
// This always happens if toolsets are specified, regardless of whether tools are also specified
176+
if len(enabledToolsets) > 0 {
196177
err = tsg.EnableToolsets(enabledToolsets, nil)
197178
if err != nil {
198179
return nil, fmt.Errorf("failed to enable toolsets: %w", err)
199180
}
200181

201-
// Register all mcp functionality with the server
182+
// Register all mcp functionality with the server (tools, resources, prompts)
202183
tsg.RegisterAll(ghServer)
184+
}
185+
186+
// Register specific tools if configured (additive to toolsets)
187+
if len(cfg.EnabledTools) > 0 {
188+
// Clean and validate tool names
189+
enabledTools := github.CleanTools(cfg.EnabledTools)
203190

204-
// Dynamic toolsets only work if no specific tools configured
205-
if cfg.DynamicToolsets {
206-
dynamic := github.InitDynamicToolset(ghServer, tsg, cfg.Translator)
207-
dynamic.RegisterTools(ghServer)
191+
// Register the specified tools (additive to any toolsets already enabled)
192+
err = tsg.RegisterSpecificTools(ghServer, enabledTools, cfg.ReadOnly)
193+
if err != nil {
194+
return nil, fmt.Errorf("failed to register tools: %w", err)
208195
}
209196
}
210197

198+
// Register dynamic toolsets if configured (additive to toolsets and tools)
199+
if cfg.DynamicToolsets {
200+
dynamic := github.InitDynamicToolset(ghServer, tsg, cfg.Translator)
201+
dynamic.RegisterTools(ghServer)
202+
}
203+
211204
return ghServer, nil
212205
}
213206

@@ -225,8 +218,8 @@ type StdioServerConfig struct {
225218
// See: https://github.com/github/github-mcp-server?tab=readme-ov-file#tool-configuration
226219
EnabledToolsets []string
227220

228-
// EnabledTools is a list of specific tools to enable (takes priority over toolsets)
229-
// When specified, only these tools will be registered, bypassing toolset enablement
221+
// EnabledTools is a list of specific tools to enable (additive to toolsets)
222+
// When specified, these tools are registered in addition to any specifiedtoolset tools
230223
EnabledTools []string
231224

232225
// Whether to enable dynamic toolsets

pkg/github/tools.go

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -524,16 +524,11 @@ func ContainsToolset(tools []string, toCheck string) bool {
524524
return false
525525
}
526526

527-
// CleanTools validates and cleans tool names:
528-
// - Duplicates are removed from the result
529-
// - Removes whitespaces
530-
// - Validation of tool existence is done during registration
531-
// Returns: (cleaned tools, invalid tools)
532-
// Note: Invalid tools are identified during registration, so this function only cleans and deduplicates.
533-
func CleanTools(toolNames []string) ([]string, []string) {
527+
// CleanTools cleans tool names by removing duplicates and trimming whitespace.
528+
// Validation of tool existence is done during registration.
529+
func CleanTools(toolNames []string) []string {
534530
seen := make(map[string]bool)
535531
result := make([]string, 0, len(toolNames))
536-
invalid := make([]string, 0)
537532

538533
// Remove duplicates and trim whitespace
539534
for _, tool := range toolNames {
@@ -547,6 +542,5 @@ func CleanTools(toolNames []string) ([]string, []string) {
547542
}
548543
}
549544

550-
// Validation will happen during registration, so we return empty invalid list here
551-
return result, invalid
545+
return result
552546
}

pkg/toolsets/toolsets.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package toolsets
22

33
import (
44
"fmt"
5+
"os"
6+
"strings"
57

68
"github.com/mark3labs/mcp-go/mcp"
79
"github.com/mark3labs/mcp-go/server"
@@ -300,6 +302,7 @@ func (tg *ToolsetGroup) FindToolByName(toolName string) (*server.ServerTool, str
300302
// Respects read-only mode (skips write tools if readOnly=true).
301303
// Returns error if any tool is not found.
302304
func (tg *ToolsetGroup) RegisterSpecificTools(s *server.MCPServer, toolNames []string, readOnly bool) error {
305+
var skippedTools []string
303306
for _, toolName := range toolNames {
304307
tool, toolsetName, err := tg.FindToolByName(toolName)
305308
if err != nil {
@@ -312,6 +315,7 @@ func (tg *ToolsetGroup) RegisterSpecificTools(s *server.MCPServer, toolNames []s
312315
isWriteTool := !*tool.Tool.Annotations.ReadOnlyHint
313316
if isWriteTool && readOnly {
314317
// Skip write tools in read-only mode
318+
skippedTools = append(skippedTools, toolName)
315319
continue
316320
}
317321
}
@@ -320,5 +324,11 @@ func (tg *ToolsetGroup) RegisterSpecificTools(s *server.MCPServer, toolNames []s
320324
s.AddTool(tool.Tool, tool.Handler)
321325
_ = toolsetName // toolsetName is available for potential future use (logging, etc.)
322326
}
327+
328+
// Log skipped write tools if any
329+
if len(skippedTools) > 0 {
330+
fmt.Fprintf(os.Stderr, "Write tools skipped due to read-only mode: %s\n", strings.Join(skippedTools, ", "))
331+
}
332+
323333
return nil
324334
}

0 commit comments

Comments
 (0)