🌐 AI搜索 & 代理 主页
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions pkg/registry/builder.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package registry

import (
"context"
"sort"
"strings"
)

// 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)

// Builder builds a Registry with the specified configuration.
// Use NewBuilder to create a builder, chain configuration methods,
// then call Build() to create the final Registry.
Expand All @@ -19,6 +24,7 @@ import (
// WithReadOnly(true).
// WithToolsets([]string{"repos", "issues"}).
// WithFeatureChecker(checker).
// WithFilter(myFilter).
// Build()
type Builder struct {
tools []ServerTool
Expand All @@ -32,6 +38,7 @@ type Builder struct {
toolsetIDsIsNil bool // tracks if nil was passed (nil = defaults)
additionalTools []string // raw input, processed at Build()
featureChecker FeatureFlagChecker
filters []ToolFilter // filters to apply to all tools
}

// NewBuilder creates a new Builder.
Expand Down Expand Up @@ -111,6 +118,15 @@ func (b *Builder) WithFeatureChecker(checker FeatureFlagChecker) *Builder {
return b
}

// 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.
// Returns self for chaining.
func (b *Builder) WithFilter(filter ToolFilter) *Builder {
b.filters = append(b.filters, filter)
return b
}

// Build creates the final Registry with all configuration applied.
// This processes toolset filtering, tool name resolution, and sets up
// the registry for use. The returned Registry is ready for use with
Expand All @@ -123,6 +139,7 @@ func (b *Builder) Build() *Registry {
deprecatedAliases: b.deprecatedAliases,
readOnly: b.readOnly,
featureChecker: b.featureChecker,
filters: b.filters,
}

// Process toolsets and pre-compute metadata in a single pass
Expand Down
54 changes: 48 additions & 6 deletions pkg/registry/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,20 +51,48 @@ func (r *Registry) isFeatureFlagAllowed(ctx context.Context, enableFlag, disable
}

// isToolEnabled checks if a specific tool is enabled based on current filters.
// Filter evaluation order:
// 1. Tool.Enabled (tool self-filtering)
// 2. FeatureFlagEnable/FeatureFlagDisable
// 3. Read-only filter
// 4. Builder filters (via WithFilter)
// 5. Toolset/additional tools
func (r *Registry) isToolEnabled(ctx context.Context, tool *ServerTool) bool {
// Check read-only filter first (applies to all tools)
if r.readOnly && !tool.IsReadOnly() {
return false
// 1. Check tool's own Enabled function first
if tool.Enabled != nil {
enabled, err := tool.Enabled(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, "Tool.Enabled check error for %q: %v\n", tool.Tool.Name, err)
return false
}
if !enabled {
return false
}
}
// Check feature flags
// 2. Check feature flags
if !r.isFeatureFlagAllowed(ctx, tool.FeatureFlagEnable, tool.FeatureFlagDisable) {
return false
}
// Check if tool is in additionalTools (bypasses toolset filter)
// 3. Check read-only filter (applies to all tools)
if r.readOnly && !tool.IsReadOnly() {
return false
}
// 4. Apply builder filters
for _, filter := range r.filters {
allowed, err := filter(ctx, tool)
if err != nil {
fmt.Fprintf(os.Stderr, "Builder filter error for tool %q: %v\n", tool.Tool.Name, err)
return false
}
if !allowed {
return false
}
}
// 5. Check if tool is in additionalTools (bypasses toolset filter)
if r.additionalTools != nil && r.additionalTools[tool.Tool.Name] {
return true
}
// Check toolset filter
// 5. Check toolset filter
if !r.isToolsetEnabled(tool.Toolset.ID) {
return false
}
Expand Down Expand Up @@ -245,3 +273,17 @@ func (r *Registry) EnabledToolsetIDs() []ToolsetID {
sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] })
return ids
}

// FilteredTools returns tools filtered by the Enabled function and builder filters.
// This provides an explicit API for accessing filtered tools, currently implemented
// as an alias for AvailableTools.
//
// The error return is currently always nil but is included for future extensibility.
// Library consumers (e.g., remote server implementations) may need to surface
// recoverable filter errors rather than silently logging them. Having the error
// return in the API now avoids breaking changes later.
//
// The context is used for Enabled function evaluation and builder filter checks.
func (r *Registry) FilteredTools(ctx context.Context) ([]ServerTool, error) {
return r.AvailableTools(ctx), nil
}
4 changes: 4 additions & 0 deletions pkg/registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ type Registry struct {
// Takes context and flag name, returns (enabled, error). If error, log and treat as false.
// If checker is nil, all flag checks return false.
featureChecker FeatureFlagChecker
// filters are functions that will be applied to all tools during filtering.
// If any filter returns false or an error, the tool is excluded.
filters []ToolFilter
// unrecognizedToolsets holds toolset IDs that were requested but don't match any registered toolsets
unrecognizedToolsets []string
}
Expand Down Expand Up @@ -107,6 +110,7 @@ func (r *Registry) ForMCPRequest(method string, itemName string) *Registry {
enabledToolsets: r.enabledToolsets, // shared, not modified
additionalTools: r.additionalTools, // shared, not modified
featureChecker: r.featureChecker,
filters: r.filters, // shared, not modified
unrecognizedToolsets: r.unrecognizedToolsets,
}

Expand Down
Loading