-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Open
Description
Summary
Add two generic mechanisms to the registry package for flexible tool filtering without coupling to specific domain concepts:
Enabledfunction onServerTool- Allows tools to self-filter based on request contextWithFiltermethod onBuilder- Allows cross-cutting filters to be applied to all tools
Motivation
Consumers of the registry (like github-mcp-server-remote) need to filter tools based on various conditions:
- User permissions and policies
- Request context (e.g., which client is calling)
- Feature flags with complex logic (AND/OR combinations)
- Runtime availability of dependencies
Currently, consumers must do this filtering externally before passing tools to the registry, which leads to complex orchestration code. By adding generic filter hooks, consumers can encapsulate their domain-specific logic while the registry remains agnostic.
Implementation
1. Add Enabled field to ServerTool struct in pkg/registry/tool.go:
type ServerTool struct {
Tool mcp.Tool
Toolset ToolsetMetadata
HandlerFunc HandlerFunc
FeatureFlagEnable string
FeatureFlagDisable string
// Enabled is an optional function called at build/filter time to determine
// if this tool should be available. If nil, the tool is considered enabled
// (subject to FeatureFlagEnable/FeatureFlagDisable checks).
// The context carries request-scoped information for the consumer to use.
// Returns (enabled, error). On error, the tool should be treated as disabled.
Enabled func(ctx context.Context) (bool, error)
}2. Add ToolFilter type and WithFilter method to Builder in pkg/registry/registry.go:
// ToolFilter is a function that determines if a tool should be included.
// Returns true if the tool should be included, false to exclude it.
type ToolFilter func(ctx context.Context, tool *ServerTool) (bool, error)
// WithFilter adds a filter function that will be applied to all tools.
// Multiple filters can be added and are evaluated in order.
// If any filter returns false or an error, the tool is excluded.
func (b *Builder) WithFilter(filter ToolFilter) *Builder {
b.filters = append(b.filters, filter)
return b
}3. Update isToolEnabled in the builder to check these in order:
- Check tool's own
Enabledfunction (if set) - Check
FeatureFlagEnable(existing behavior) - Check
FeatureFlagDisable(existing behavior) - Apply builder filters (new)
4. Add a context-aware method to get filtered tools:
// FilteredTools returns tools filtered by the Enabled function and builder filters.
// This should be called per-request with the request context.
func (r *Registry) FilteredTools(ctx context.Context) ([]ServerTool, error)Example Usage
// Tool with self-filtering logic
tool := registry.ServerTool{
Tool: myTool,
Toolset: myToolset,
HandlerFunc: myHandler,
Enabled: func(ctx context.Context) (bool, error) {
// Consumer's domain-specific logic here
user := mypackage.UserFromContext(ctx)
return user != nil && user.HasPermission("use_tool"), nil
},
}
// Builder with cross-cutting filter
reg := registry.NewBuilder().
SetTools(tools).
WithFilter(func(ctx context.Context, tool *registry.ServerTool) (bool, error) {
// Cross-cutting concern like scope filtering
return !shouldHideForCurrentAuth(ctx, tool), nil
}).
Build()
// Get filtered tools for a request
enabledTools, err := reg.FilteredTools(ctx)Testing
Add tests for:
Enabledfunction returning true/false/errorWithFilterwith single and multiple filters- Interaction between
Enabled, feature flags, and filters FilteredToolsmethod with various filter combinations
Backwards Compatibility
- All new fields are optional with nil/empty defaults
- Existing code continues to work unchanged
FeatureFlagEnable/FeatureFlagDisableremain fully functional
Metadata
Metadata
Assignees
Labels
No labels