Documentation
¶
Overview ¶
Package discovery provides tool discovery functionality for MCP servers.
This package allows you to register tools that are hidden from the main tools/list response but can be discovered via search and executed through a wrapper tool.
When to Use Discovery ¶
Use this package when you have many tools (10+) and want to:
- Reduce context window usage by not sending all tool schemas upfront
- Improve LLM performance by reducing cognitive load
- Organize tools into searchable categories with keywords
Tool Visibility Options ¶
The MCP server supports three visibility levels for tools:
ToolVisibilityVisible: Tools appear in ListTools() and are callable directly. Use for essential tools that the LLM should always know about.
ToolVisibilityHidden: Tools don't appear in ListTools() but can be called directly if the caller knows the name. Use for internal/admin tools.
ToolVisibilityOnDemand: Tools don't appear in ListTools() but ARE searchable via tool_search and callable via execute_tool. Use with this discovery package.
Basic Usage ¶
registry := discovery.NewToolRegistry()
// Register tools with keywords for better search
registry.RegisterTool(
mcp.NewTool("send_email", "Send an email message", ...),
sendEmailHandler,
"email", "send", "message", "notification", // keywords
)
// Attach to server (registers tool_search and execute_tool)
registry.Attach(server)
Keyword Best Practices ¶
Keywords significantly improve search quality. Include:
- Action verbs: "create", "delete", "update", "send", "fetch"
- Domain terms: "email", "database", "file", "user"
- Synonyms: "remove" for "delete", "retrieve" for "get"
- Common misspellings if relevant
Performance Considerations ¶
The search uses fuzzy matching with Levenshtein distance. For typical deployments (< 100 tools), performance is excellent. For larger tool sets (1000+), consider:
- Using more specific keywords to reduce search space
- Implementing a custom ToolProvider with indexed search
LLM Workflow ¶
After setup, the LLM workflow becomes:
- tool_search(query="email") -> finds tools with full schemas
- execute_tool(name="send_email", arguments={...}) -> executes the tool
Index ¶
- Constants
- Variables
- func WithRequestProviders(ctx context.Context, providers ...ToolProvider) context.Context
- type SearchResult
- type ToolMetadata
- type ToolProvider
- type ToolRegistry
- func (r *ToolRegistry) AddProvider(provider ToolProvider)
- func (r *ToolRegistry) Attach(server *mcp.Server)
- func (r *ToolRegistry) CallTool(ctx context.Context, name string, args map[string]interface{}) (*mcp.ToolResponse, error)
- func (r *ToolRegistry) GetTool(ctx context.Context, name string) (*mcp.MCPTool, error)
- func (r *ToolRegistry) ListToolMetadata(ctx context.Context) ([]ToolMetadata, error)
- func (r *ToolRegistry) RegisterMCPTool(tool *mcp.MCPTool, handler mcp.ToolHandler, keywords ...string)
- func (r *ToolRegistry) RegisterTool(tool *mcp.ToolBuilder, handler mcp.ToolHandler, keywords ...string)
- func (r *ToolRegistry) RemoveProvider(provider ToolProvider)
- func (r *ToolRegistry) Search(ctx context.Context, query string, maxResults int) []SearchResult
Constants ¶
const ( // ToolSearchName is the name of the tool search discovery tool ToolSearchName = "tool_search" // ExecuteToolName is the name of the tool execution wrapper ExecuteToolName = "execute_tool" )
Tool names used by the discovery system. These are registered with the MCP server when Attach() is called.
Variables ¶
var ErrToolNotFound = mcp.ErrUnknownTool
ErrToolNotFound is returned when a tool is not found
Functions ¶
func WithRequestProviders ¶
func WithRequestProviders(ctx context.Context, providers ...ToolProvider) context.Context
WithRequestProviders adds request-scoped tool providers to the context. These providers are only available for the duration of the request. Use this for per-user or per-tenant tool providers.
Types ¶
type SearchResult ¶
type SearchResult struct {
Name string `json:"name"`
Description string `json:"description"`
Score float64 `json:"score"`
InputSchema interface{} `json:"inputSchema,omitempty"`
}
SearchResult represents a matched tool from a search
type ToolMetadata ¶
type ToolMetadata struct {
Name string `json:"name"`
Description string `json:"description"`
Keywords []string `json:"keywords,omitempty"`
}
ToolMetadata contains searchable information about a tool
type ToolProvider ¶
type ToolProvider interface {
// ListToolMetadata returns metadata for all searchable tools from this provider
ListToolMetadata(ctx context.Context) ([]ToolMetadata, error)
// GetTool returns the full tool definition for a specific tool by name
// Returns nil, nil if the tool doesn't exist in this provider
GetTool(ctx context.Context, name string) (*mcp.MCPTool, error)
// CallTool executes a tool by name with the given arguments
// Returns ErrToolNotFound if the tool doesn't exist in this provider
CallTool(ctx context.Context, name string, args map[string]interface{}) (*mcp.ToolResponse, error)
}
ToolProvider allows external tool sources (scripts, plugins, databases, etc.)
type ToolRegistry ¶
type ToolRegistry struct {
// contains filtered or unexported fields
}
ToolRegistry manages searchable tools and provides discovery functionality. Tools registered here are hidden from tools/list but can be discovered via search.
Design Notes ¶
ToolRegistry is intentionally coupled to the mcp package. While this could be decoupled via interfaces, the discovery feature is specifically designed for MCP servers and would not make sense in isolation. The coupling is:
- Uses mcp.ToolBuilder and mcp.MCPTool for tool definitions
- Uses mcp.ToolHandler for execution callbacks
- Uses mcp.Server.RegisterTool via Attach() to add discovery endpoints
This keeps the API simple and type-safe. For alternative use cases (non-MCP tool registries), implement the ToolProvider interface with your own types.
Usage ¶
Create one instance and attach it to your MCP server:
registry := discovery.NewToolRegistry() registry.RegisterTool(myTool, myHandler, "keyword1", "keyword2") registry.Attach(server) // Adds tool_search and execute_tool endpoints
func NewToolRegistry ¶
func NewToolRegistry() *ToolRegistry
NewToolRegistry creates a new tool registry for searchable tools
func (*ToolRegistry) AddProvider ¶
func (r *ToolRegistry) AddProvider(provider ToolProvider)
AddProvider adds a dynamic tool provider
func (*ToolRegistry) Attach ¶
func (r *ToolRegistry) Attach(server *mcp.Server)
Attach registers the discovery tools (tool_search, execute_tool) with the MCP server. This method is idempotent - calling it multiple times on the same registry is safe and will only register the tools once.
func (*ToolRegistry) CallTool ¶
func (r *ToolRegistry) CallTool(ctx context.Context, name string, args map[string]interface{}) (*mcp.ToolResponse, error)
CallTool attempts to call a registered or dynamic tool
func (*ToolRegistry) ListToolMetadata ¶
func (r *ToolRegistry) ListToolMetadata(ctx context.Context) ([]ToolMetadata, error)
ListToolMetadata returns metadata for all tools registered in this registry. This implements the ToolProvider interface, allowing a ToolRegistry to be used as a request-scoped provider via WithRequestProviders.
func (*ToolRegistry) RegisterMCPTool ¶ added in v0.6.12
func (r *ToolRegistry) RegisterMCPTool(tool *mcp.MCPTool, handler mcp.ToolHandler, keywords ...string)
RegisterMCPTool registers a searchable tool from an already-built MCPTool. This is useful for registering tools from remote servers where you already have the MCPTool. Keywords are used for fuzzy search matching.
func (*ToolRegistry) RegisterTool ¶
func (r *ToolRegistry) RegisterTool(tool *mcp.ToolBuilder, handler mcp.ToolHandler, keywords ...string)
RegisterTool registers a searchable tool that won't appear in ListTools but can be discovered and called. Keywords are used for fuzzy search matching.
func (*ToolRegistry) RemoveProvider ¶
func (r *ToolRegistry) RemoveProvider(provider ToolProvider)
RemoveProvider removes a dynamic tool provider
func (*ToolRegistry) Search ¶
func (r *ToolRegistry) Search(ctx context.Context, query string, maxResults int) []SearchResult
Search performs fuzzy search across all registered and dynamic tools. If query is empty, returns all tools.