-
Notifications
You must be signed in to change notification settings - Fork 3.2k
refactor: Separate ServerTool with HandlerFunc pattern and ToolDependencies #1589
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
- Extract ServerTool struct into pkg/toolsets/server_tool.go - Add ToolDependencies struct for passing common dependencies to handlers - HandlerFunc allows lazy handler generation from Tool definitions - NewServerTool for new dependency-based tools - NewServerToolLegacy for backward compatibility with existing handlers - Update toolsets.go to store and pass dependencies - Update all call sites to use NewServerToolLegacy Co-authored-by: Adam Holt <4619+omgitsads@users.noreply.github.com>
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
This PR refactors the ServerTool architecture to separate tool definitions from handler generation, introducing a dependency injection pattern through ToolDependencies and a HandlerFunc type. This enables static tool definitions that can be passed around before dependencies are available, with handlers generated on-demand during registration.
Key Changes:
- New
ToolDependenciesstruct centralizes all shared dependencies (GitHub clients, translation functions, caches, flags) HandlerFuncpattern allows lazy handler generation with dependencies injected at registration timeNewServerToolLegacyprovides backward compatibility during the migrationSetDependencies()method onToolsetenables fluent API for dependency configuration
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
pkg/toolsets/server_tool.go |
New file defining ToolDependencies, HandlerFunc, and refactored ServerTool struct with constructor functions |
pkg/toolsets/toolsets.go |
Adds deps field to Toolset, implements SetDependencies() method, updates RegisterTools to use stored deps, modifies RegisterSpecificTools signature |
pkg/toolsets/toolsets_test.go |
Updates mockTool to use new API (NewServerToolFromHandler), adds InputSchema to mock tools, passes deps to RegisterSpecificTools |
pkg/github/tools.go |
Creates ToolDependencies struct and calls SetDependencies() on all toolsets, converts all tools to use NewServerToolLegacy |
pkg/github/dynamic_tools.go |
Simplifies EnableToolset to use toolset.RegisterTools(s) instead of manual loop |
internal/ghmcp/server.go |
Updates RegisterSpecificTools call to include ToolDependencies{} parameter and imports toolsets package |
internal/ghmcp/server.go
Outdated
|
|
||
| // Register the specified tools (additive to any toolsets already enabled) | ||
| err = tsg.RegisterSpecificTools(ghServer, enabledTools, cfg.ReadOnly) | ||
| err = tsg.RegisterSpecificTools(ghServer, enabledTools, cfg.ReadOnly, toolsets.ToolDependencies{}) |
Copilot
AI
Dec 13, 2025
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.
An empty ToolDependencies{} is being passed to RegisterSpecificTools, which means the registered tools won't have access to the GitHub clients, translation functions, or other dependencies they need. The dependencies were already created and set on the toolsets at lines 167-180 via SetDependencies(deps), but RegisterSpecificTools bypasses the toolset's stored dependencies and uses the deps parameter instead.
The solution is to either:
- Remove the deps parameter from
RegisterSpecificToolsand have it use the dependencies stored in each toolset (viaSetDependencies), or - Pass the actual deps struct here instead of an empty one
Option 1 would be more consistent with how RegisterAll works (which calls toolset.RegisterTools(s) that uses the stored deps), while option 2 would require exposing the deps or reconstructing it here.
| err = tsg.RegisterSpecificTools(ghServer, enabledTools, cfg.ReadOnly, toolsets.ToolDependencies{}) | |
| err = tsg.RegisterSpecificTools(ghServer, enabledTools, cfg.ReadOnly) |
pkg/toolsets/server_tool.go
Outdated
| // RegisterFunc registers the tool with the server using the provided dependencies. | ||
| func (st *ServerTool) RegisterFunc(s *mcp.Server, deps ToolDependencies) { |
Copilot
AI
Dec 13, 2025
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.
The RegisterFunc method registers a tool with a potentially nil handler (if HandlerFunc is nil, Handler() returns nil). While this may be intentional to support partial construction, it could lead to runtime errors when the MCP server tries to call the handler. Consider either:
- Adding validation to prevent nil handlers from being registered
- Documenting when and why HandlerFunc might be nil
- Making HandlerFunc non-optional in the ServerTool struct
| // RegisterFunc registers the tool with the server using the provided dependencies. | |
| func (st *ServerTool) RegisterFunc(s *mcp.Server, deps ToolDependencies) { | |
| // RegisterFunc registers the tool with the server using the provided dependencies. | |
| // RegisterFunc registers the tool with the server using the provided dependencies. | |
| // Panics if HandlerFunc is nil, as registering a tool with a nil handler is a programming error. | |
| func (st *ServerTool) RegisterFunc(s *mcp.Server, deps ToolDependencies) { | |
| if st.HandlerFunc == nil { | |
| panic("toolsets: cannot register tool '" + st.Tool.Name + "' with nil HandlerFunc") | |
| } |
- Move ToolDependencies to pkg/github/dependencies.go with proper types - Use 'any' in toolsets package to avoid circular dependencies - Add NewTool/NewToolFromHandler helpers that isolate type assertion - Tool implementations will be fully typed with no assertions scattered - Infrastructure ready for incremental tool migration
790b435 to
187a8b6
Compare
Migration Progress UpdateAdded the first tool file migration: search.go Changes in this commit (b29546e)
Migration Pattern DemonstratedOld signature: func SearchRepositories(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[...])New signature: func SearchRepositories(t translations.TranslationHelperFunc) toolsets.ServerToolThe key benefit: no type assertions in tool code. The single type assertion is isolated in the |
Migrate search.go tools (SearchRepositories, SearchCode, SearchUsers, SearchOrgs) to use the new NewTool helper and ToolDependencies pattern. - Functions now take only TranslationHelperFunc and return ServerTool - Handler generation uses ToolDependencies for typed access to clients - Update tools.go call sites to remove getClient parameter - Update tests to use new Handler(deps) pattern This demonstrates the migration pattern for additional tool files. Co-authored-by: Adam Holt <omgitsads@users.noreply.github.com>
b29546e to
43acefe
Compare
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.
Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.
Summary
This PR refactors the
ServerTooltype to separate tool definitions from handler generation, enabling:HandlerFuncwhen tools are registeredToolDependenciesstructArchitecture
Avoiding Circular Dependencies
The
toolsetspackage usesanyfor dependencies to stay generic and avoid importingpkg/github. The type safety is achieved through:pkg/github/dependencies.go- DefinesToolDependencieswith proper typesNewTool/NewToolFromHandlerhelpers - Isolate the single type assertionChanges
New file:
pkg/github/dependencies.goToolDependenciesstruct with properly typed fieldsNewTool[In, Out]- typed helper for standard tool handlersNewToolFromHandler- typed helper for raw handlersUpdated:
pkg/toolsets/server_tool.goHandlerFuncusesanyfor deps to avoid circular importsNewServerTool/NewServerToolFromHandleracceptanyNewServerToolLegacyfor backward compatibility with existing handlersUpdated:
pkg/toolsets/toolsets.godepsfield typed asanySetDependencies(any)methodUpdated:
internal/ghmcp/server.gogithub.ToolDependenciesand passes to toolsetsBenefits
NewToolhelperNext Steps
Individual tool files can be incrementally migrated from
NewServerToolLegacytoNewToolin stacked PRs.Testing