-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Move resolveEnabledToolsets to Inventory #1883
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Moves enabled-toolset resolution out of internal/ghmcp/server.go and into the Inventory builder so the server receives a precomputed, fully-configured Inventory (continuing the architecture introduced in #1869).
Changes:
- Added
dynamicMode/WithDynamicMode(bool)and dynamic-mode preprocessing inpkg/inventory/builder.go. - Updated
NewMCPServerto pass raw config values into the inventory builder and enable dynamic mode viaWithDynamicMode(cfg.DynamicToolsets). - Replaced the removed
resolveEnabledToolsets()coverage with new inventory-level tests.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| pkg/inventory/builder.go | Adds dynamic toolset-mode preprocessing and integrates it into Build(). |
| pkg/inventory/registry_test.go | Adds tests covering dynamic mode and “tools without toolsets” behavior. |
| internal/ghmcp/server.go | Stops resolving toolsets in the server; delegates to inventory builder configuration. |
| internal/ghmcp/server_test.go | Removes server-level tests for resolveEnabledToolsets() after refactor. |
Comments suppressed due to low confidence (1)
pkg/inventory/builder.go:186
applyDynamicModePreprocessingcheckslen(b.additionalTools) > 0beforeWithTools()input is cleaned. IfWithTools([]string{"", " "})is used, this path forcestoolsetIDsto[](disabling defaults) even thoughcleanTools()would later drop all additional tools. Consider cleaningadditionalTools(or at least checking for any non-empty trimmed entries) before deciding to override toolset behavior.
if len(b.additionalTools) > 0 {
// When specific tools are requested but no toolsets, don't use default toolsets
// --tools=X alone registers only X
b.toolsetIDs = []string{}
b.toolsetIDsIsNil = false
return
| // applyDynamicModePreprocessing modifies toolsetIDs based on dynamicMode and additionalTools. | ||
| func (b *Builder) applyDynamicModePreprocessing() { | ||
| if b.dynamicMode && b.toolsetIDs != nil { | ||
| // In dynamic mode, remove "all" and "default" since users enable toolsets on demand | ||
| b.toolsetIDs = removeFromSlice(b.toolsetIDs, "all") | ||
| b.toolsetIDs = removeFromSlice(b.toolsetIDs, "default") |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In dynamic mode, applyDynamicModePreprocessing removes the special keywords using exact string comparison ("all" / "default") before any trimming. If callers pass values like " all " or " default " (which processToolsets() later trims), these keywords won’t be stripped and dynamic mode can unexpectedly behave like "all"/"default" were requested. Consider normalizing (e.g., strings.TrimSpace + compare) when stripping, or handling the dynamic-mode keyword suppression inside processToolsets() where trimming already occurs.
This issue also appears in the following locations of the same file:
- line 181
| // applyDynamicModePreprocessing modifies toolsetIDs based on dynamicMode and additionalTools. | |
| func (b *Builder) applyDynamicModePreprocessing() { | |
| if b.dynamicMode && b.toolsetIDs != nil { | |
| // In dynamic mode, remove "all" and "default" since users enable toolsets on demand | |
| b.toolsetIDs = removeFromSlice(b.toolsetIDs, "all") | |
| b.toolsetIDs = removeFromSlice(b.toolsetIDs, "default") | |
| // removeFromSliceTrimmed removes all occurrences of value from slice, | |
| // comparing after trimming whitespace from both the slice elements and value. | |
| func removeFromSliceTrimmed(slice []string, value string) []string { | |
| trimmedValue := strings.TrimSpace(value) | |
| result := make([]string, 0, len(slice)) | |
| for _, s := range slice { | |
| if strings.TrimSpace(s) != trimmedValue { | |
| result = append(result, s) | |
| } | |
| } | |
| return result | |
| } | |
| // applyDynamicModePreprocessing modifies toolsetIDs based on dynamicMode and additionalTools. | |
| func (b *Builder) applyDynamicModePreprocessing() { | |
| if b.dynamicMode && b.toolsetIDs != nil { | |
| // In dynamic mode, remove "all" and "default" since users enable toolsets on demand. | |
| // Use trimmed comparison so values like " all " are treated the same as "all". | |
| b.toolsetIDs = removeFromSliceTrimmed(b.toolsetIDs, "all") | |
| b.toolsetIDs = removeFromSliceTrimmed(b.toolsetIDs, "default") |
Summary
Moves the
resolveEnabledToolsetslogic frominternal/ghmcp/server.gointo the Inventory Builder (pkg/inventory/builder.go), continuing the architectural direction followed in #1869 where we give the server a configured inventory instead of creating one as part of the server creation.Tested locally.
Why
This aligns with the architecture where the Inventory holds all pre-computed configuration (the MCP server should receive a fully-configured inventory). This also makes the logic reusable for library consumers (e.g., remote server) without duplicating the toolset resolution logic.
Changes
dynamicModefield andWithDynamicMode(bool)method toBuilderapplyDynamicModePreprocessing()that handles:NewMCPServerto pass raw config values and callWithDynamicMode(cfg.DynamicToolsets)resolveEnabledToolsets()from server.goMCP impact
Prompts tested (tool changes only)
Security / limits
Tool renaming
deprecated_tool_aliases.goNote: if you're renaming tools, you must add the tool aliases. For more information on how to do so, please refer to the official docs.
Lint & tests
./script/lint./script/testDocs