Skip to content

Add dynamic tools support to MCP server #401

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
85 changes: 85 additions & 0 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ type ToolHandlerMiddleware func(ToolHandlerFunc) ToolHandlerFunc
// ToolFilterFunc is a function that filters tools based on context, typically using session information.
type ToolFilterFunc func(ctx context.Context, tools []mcp.Tool) []mcp.Tool

// dynamicTools holds configuration for dynamic tool generation
type dynamicTools struct {
enabled bool
listFunc func(ctx context.Context, request mcp.ListToolsRequest) ([]mcp.Tool, error)
handlerFunc func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error)
validateFunc func(ctx context.Context, toolName string) bool
}

// ServerTool combines a Tool with its ToolHandlerFunc.
type ServerTool struct {
Tool mcp.Tool
Expand Down Expand Up @@ -155,6 +163,7 @@ type MCPServer struct {
tools map[string]ServerTool
toolHandlerMiddlewares []ToolHandlerMiddleware
toolFilters []ToolFilterFunc
dynamicTools *dynamicTools
notificationHandlers map[string]NotificationHandlerFunc
capabilities serverCapabilities
paginationLimit *int
Expand Down Expand Up @@ -288,6 +297,32 @@ func WithInstructions(instructions string) ServerOption {
}
}

func WithDynamicTools(
enabled bool,
listFunc func(ctx context.Context, request mcp.ListToolsRequest) ([]mcp.Tool, error),
handlerFunc func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error),
validateFunc func(ctx context.Context, toolName string) bool,
) ServerOption {
return func(s *MCPServer) {
// If enabled, all functions must be non-nil
if enabled && (listFunc == nil || handlerFunc == nil || validateFunc == nil) {
panic("WithDynamicTools: when enabled=true, all functions (listFunc, handlerFunc, validateFunc) must be non-nil")
}

s.dynamicTools = &dynamicTools{
enabled: enabled,
listFunc: listFunc,
handlerFunc: handlerFunc,
validateFunc: validateFunc,
}

// Ensure capabilities.tool is not nil so that clients know tools exist
if enabled {
s.implicitlyRegisterToolCapabilities()
}
}
}

// NewMCPServer creates a new MCP server instance with the given name, version and options
func NewMCPServer(
name, version string,
Expand Down Expand Up @@ -944,6 +979,44 @@ func (s *MCPServer) handleListTools(
}
}

// Add dynamic tools if enabled
if s.dynamicTools != nil && s.dynamicTools.enabled {
dynamicTools, err := s.dynamicTools.listFunc(ctx, request)
if err != nil {
return nil, &requestError{
id: id,
code: mcp.INTERNAL_ERROR,
err: fmt.Errorf("dynamic tools list function failed: %w", err),
}
}

if len(dynamicTools) > 0 {
// Create a map to merge tools properly
toolMap := make(map[string]mcp.Tool)

// Add dynamic tools first (lowest priority)
for _, tool := range dynamicTools {
toolMap[tool.Name] = tool
}

// Then add existing tools (they override dynamic tools)
for _, tool := range tools {
toolMap[tool.Name] = tool
}

// Convert back to slice
tools = make([]mcp.Tool, 0, len(toolMap))
for _, tool := range toolMap {
tools = append(tools, tool)
}

// Sort again to maintain consistent ordering
sort.Slice(tools, func(i, j int) bool {
return tools[i].Name < tools[j].Name
})
}
}

// Apply tool filters if any are defined
s.toolFiltersMu.RLock()
if len(s.toolFilters) > 0 {
Expand Down Expand Up @@ -1006,6 +1079,18 @@ func (s *MCPServer) handleToolCall(
s.toolsMu.RUnlock()
}

// If still not found, try dynamic tool handler
if !ok && s.dynamicTools != nil && s.dynamicTools.enabled {
// Check if this tool is valid using the validation function
if s.dynamicTools.validateFunc(ctx, request.Params.Name) {
// Create a ServerTool with the dynamic handler
tool = ServerTool{
Handler: s.dynamicTools.handlerFunc,
}
ok = true
}
}

if !ok {
return nil, &requestError{
id: id,
Expand Down