Skip to content

Commit 6572d38

Browse files
Add generic tool filtering mechanisms to registry package
- Add Enabled field to ServerTool for self-filtering based on context - Add ToolFilter type and WithFilter method to Builder for cross-cutting filters - Update isToolEnabled to check Enabled function and builder filters in order: 1. Tool's Enabled function 2. Feature flags (FeatureFlagEnable/FeatureFlagDisable) 3. Read-only filter 4. Builder filters 5. Toolset/additional tools check - Add FilteredTools method to Registry as alias for AvailableTools - Add comprehensive tests for all new functionality - All tests pass and linter is clean Closes #1618 Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com>
1 parent 8e014a5 commit 6572d38

File tree

5 files changed

+529
-3
lines changed

5 files changed

+529
-3
lines changed

pkg/registry/builder.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
package registry
22

33
import (
4+
"context"
45
"sort"
56
"strings"
67
)
78

9+
// ToolFilter is a function that determines if a tool should be included.
10+
// Returns true if the tool should be included, false to exclude it.
11+
type ToolFilter func(ctx context.Context, tool *ServerTool) (bool, error)
12+
813
// Builder builds a Registry with the specified configuration.
914
// Use NewBuilder to create a builder, chain configuration methods,
1015
// then call Build() to create the final Registry.
@@ -19,6 +24,7 @@ import (
1924
// WithReadOnly(true).
2025
// WithToolsets([]string{"repos", "issues"}).
2126
// WithFeatureChecker(checker).
27+
// WithFilter(myFilter).
2228
// Build()
2329
type Builder struct {
2430
tools []ServerTool
@@ -32,6 +38,7 @@ type Builder struct {
3238
toolsetIDsIsNil bool // tracks if nil was passed (nil = defaults)
3339
additionalTools []string // raw input, processed at Build()
3440
featureChecker FeatureFlagChecker
41+
filters []ToolFilter // filters to apply to all tools
3542
}
3643

3744
// NewBuilder creates a new Builder.
@@ -111,6 +118,15 @@ func (b *Builder) WithFeatureChecker(checker FeatureFlagChecker) *Builder {
111118
return b
112119
}
113120

121+
// WithFilter adds a filter function that will be applied to all tools.
122+
// Multiple filters can be added and are evaluated in order.
123+
// If any filter returns false or an error, the tool is excluded.
124+
// Returns self for chaining.
125+
func (b *Builder) WithFilter(filter ToolFilter) *Builder {
126+
b.filters = append(b.filters, filter)
127+
return b
128+
}
129+
114130
// Build creates the final Registry with all configuration applied.
115131
// This processes toolset filtering, tool name resolution, and sets up
116132
// the registry for use. The returned Registry is ready for use with
@@ -123,6 +139,7 @@ func (b *Builder) Build() *Registry {
123139
deprecatedAliases: b.deprecatedAliases,
124140
readOnly: b.readOnly,
125141
featureChecker: b.featureChecker,
142+
filters: b.filters,
126143
}
127144

128145
// Process toolsets and pre-compute metadata in a single pass

pkg/registry/filters.go

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,36 @@ func (r *Registry) isFeatureFlagAllowed(ctx context.Context, enableFlag, disable
5252

5353
// isToolEnabled checks if a specific tool is enabled based on current filters.
5454
func (r *Registry) isToolEnabled(ctx context.Context, tool *ServerTool) bool {
55-
// Check read-only filter first (applies to all tools)
56-
if r.readOnly && !tool.IsReadOnly() {
57-
return false
55+
// Check tool's own Enabled function first
56+
if tool.Enabled != nil {
57+
enabled, err := tool.Enabled(ctx)
58+
if err != nil {
59+
fmt.Fprintf(os.Stderr, "Tool.Enabled check error for %q: %v\n", tool.Tool.Name, err)
60+
return false
61+
}
62+
if !enabled {
63+
return false
64+
}
5865
}
5966
// Check feature flags
6067
if !r.isFeatureFlagAllowed(ctx, tool.FeatureFlagEnable, tool.FeatureFlagDisable) {
6168
return false
6269
}
70+
// Check read-only filter (applies to all tools)
71+
if r.readOnly && !tool.IsReadOnly() {
72+
return false
73+
}
74+
// Apply builder filters
75+
for _, filter := range r.filters {
76+
allowed, err := filter(ctx, tool)
77+
if err != nil {
78+
fmt.Fprintf(os.Stderr, "Builder filter error for tool %q: %v\n", tool.Tool.Name, err)
79+
return false
80+
}
81+
if !allowed {
82+
return false
83+
}
84+
}
6385
// Check if tool is in additionalTools (bypasses toolset filter)
6486
if r.additionalTools != nil && r.additionalTools[tool.Tool.Name] {
6587
return true
@@ -245,3 +267,10 @@ func (r *Registry) EnabledToolsetIDs() []ToolsetID {
245267
sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] })
246268
return ids
247269
}
270+
271+
// FilteredTools returns tools filtered by the Enabled function and builder filters.
272+
// This is an alias for AvailableTools for clarity when focusing on filtering behavior.
273+
// The context is used for Enabled function evaluation and builder filter checks.
274+
func (r *Registry) FilteredTools(ctx context.Context) ([]ServerTool, error) {
275+
return r.AvailableTools(ctx), nil
276+
}

pkg/registry/registry.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ type Registry struct {
5353
// Takes context and flag name, returns (enabled, error). If error, log and treat as false.
5454
// If checker is nil, all flag checks return false.
5555
featureChecker FeatureFlagChecker
56+
// filters are functions that will be applied to all tools during filtering.
57+
// If any filter returns false or an error, the tool is excluded.
58+
filters []ToolFilter
5659
// unrecognizedToolsets holds toolset IDs that were requested but don't match any registered toolsets
5760
unrecognizedToolsets []string
5861
}
@@ -107,6 +110,7 @@ func (r *Registry) ForMCPRequest(method string, itemName string) *Registry {
107110
enabledToolsets: r.enabledToolsets, // shared, not modified
108111
additionalTools: r.additionalTools, // shared, not modified
109112
featureChecker: r.featureChecker,
113+
filters: r.filters, // shared, not modified
110114
unrecognizedToolsets: r.unrecognizedToolsets,
111115
}
112116

0 commit comments

Comments
 (0)