diff --git a/pkg/github/dynamic_tools.go b/pkg/github/dynamic_tools.go index 45a481576..c65510246 100644 --- a/pkg/github/dynamic_tools.go +++ b/pkg/github/dynamic_tools.go @@ -1,5 +1,3 @@ -//go:build ignore - package github import ( @@ -9,44 +7,52 @@ import ( "github.com/github/github-mcp-server/pkg/toolsets" "github.com/github/github-mcp-server/pkg/translations" - "github.com/mark3labs/mcp-go/mcp" - "github.com/mark3labs/mcp-go/server" + "github.com/github/github-mcp-server/pkg/utils" + "github.com/google/jsonschema-go/jsonschema" + "github.com/modelcontextprotocol/go-sdk/mcp" ) -func ToolsetEnum(toolsetGroup *toolsets.ToolsetGroup) mcp.PropertyOption { - toolsetNames := make([]string, 0, len(toolsetGroup.Toolsets)) +func ToolsetEnum(toolsetGroup *toolsets.ToolsetGroup) []any { + toolsetNames := make([]any, 0, len(toolsetGroup.Toolsets)) for name := range toolsetGroup.Toolsets { toolsetNames = append(toolsetNames, name) } - return mcp.Enum(toolsetNames...) + return toolsetNames } -func EnableToolset(s *server.MCPServer, toolsetGroup *toolsets.ToolsetGroup, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { - return mcp.NewTool("enable_toolset", - mcp.WithDescription(t("TOOL_ENABLE_TOOLSET_DESCRIPTION", "Enable one of the sets of tools the GitHub MCP server provides, use get_toolset_tools and list_available_toolsets first to see what this will enable")), - mcp.WithToolAnnotation(mcp.ToolAnnotation{ +func EnableToolset(s *mcp.Server, toolsetGroup *toolsets.ToolsetGroup, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[map[string]any, any]) { + return mcp.Tool{ + Name: "enable_toolset", + Description: t("TOOL_ENABLE_TOOLSET_DESCRIPTION", "Enable one of the sets of tools the GitHub MCP server provides, use get_toolset_tools and list_available_toolsets first to see what this will enable"), + Annotations: &mcp.ToolAnnotations{ Title: t("TOOL_ENABLE_TOOLSET_USER_TITLE", "Enable a toolset"), // Not modifying GitHub data so no need to show a warning - ReadOnlyHint: ToBoolPtr(true), - }), - mcp.WithString("toolset", - mcp.Required(), - mcp.Description("The name of the toolset to enable"), - ToolsetEnum(toolsetGroup), - ), - ), - func(_ context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + ReadOnlyHint: true, + }, + InputSchema: &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "toolset": { + Type: "string", + Description: "The name of the toolset to enable", + Enum: ToolsetEnum(toolsetGroup), + }, + }, + Required: []string{"toolset"}, + }, + }, + mcp.ToolHandlerFor[map[string]any, any](func(_ context.Context, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { // We need to convert the toolsets back to a map for JSON serialization - toolsetName, err := RequiredParam[string](request, "toolset") + toolsetName, err := RequiredParam[string](args, "toolset") if err != nil { - return mcp.NewToolResultError(err.Error()), nil + return utils.NewToolResultError(err.Error()), nil, nil } toolset := toolsetGroup.Toolsets[toolsetName] if toolset == nil { - return mcp.NewToolResultError(fmt.Sprintf("Toolset %s not found", toolsetName)), nil + return utils.NewToolResultError(fmt.Sprintf("Toolset %s not found", toolsetName)), nil, nil } if toolset.Enabled { - return mcp.NewToolResultText(fmt.Sprintf("Toolset %s is already enabled", toolsetName)), nil + return utils.NewToolResultText(fmt.Sprintf("Toolset %s is already enabled", toolsetName)), nil, nil } toolset.Enabled = true @@ -55,21 +61,28 @@ func EnableToolset(s *server.MCPServer, toolsetGroup *toolsets.ToolsetGroup, t t // // Send notification to all initialized sessions // s.sendNotificationToAllClients("notifications/tools/list_changed", nil) - s.AddTools(toolset.GetActiveTools()...) + for _, serverTool := range toolset.GetActiveTools() { + serverTool.RegisterFunc(s) + } - return mcp.NewToolResultText(fmt.Sprintf("Toolset %s enabled", toolsetName)), nil - } + return utils.NewToolResultText(fmt.Sprintf("Toolset %s enabled", toolsetName)), nil, nil + }) } -func ListAvailableToolsets(toolsetGroup *toolsets.ToolsetGroup, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { - return mcp.NewTool("list_available_toolsets", - mcp.WithDescription(t("TOOL_LIST_AVAILABLE_TOOLSETS_DESCRIPTION", "List all available toolsets this GitHub MCP server can offer, providing the enabled status of each. Use this when a task could be achieved with a GitHub tool and the currently available tools aren't enough. Call get_toolset_tools with these toolset names to discover specific tools you can call")), - mcp.WithToolAnnotation(mcp.ToolAnnotation{ +func ListAvailableToolsets(toolsetGroup *toolsets.ToolsetGroup, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[map[string]any, any]) { + return mcp.Tool{ + Name: "list_available_toolsets", + Description: t("TOOL_LIST_AVAILABLE_TOOLSETS_DESCRIPTION", "List all available toolsets this GitHub MCP server can offer, providing the enabled status of each. Use this when a task could be achieved with a GitHub tool and the currently available tools aren't enough. Call get_toolset_tools with these toolset names to discover specific tools you can call"), + Annotations: &mcp.ToolAnnotations{ Title: t("TOOL_LIST_AVAILABLE_TOOLSETS_USER_TITLE", "List available toolsets"), - ReadOnlyHint: ToBoolPtr(true), - }), - ), - func(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) { + ReadOnlyHint: true, + }, + InputSchema: &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{}, + }, + }, + mcp.ToolHandlerFor[map[string]any, any](func(_ context.Context, _ *mcp.CallToolRequest, _ map[string]any) (*mcp.CallToolResult, any, error) { // We need to convert the toolsetGroup back to a map for JSON serialization payload := []map[string]string{} @@ -88,35 +101,42 @@ func ListAvailableToolsets(toolsetGroup *toolsets.ToolsetGroup, t translations.T r, err := json.Marshal(payload) if err != nil { - return nil, fmt.Errorf("failed to marshal features: %w", err) + return nil, nil, fmt.Errorf("failed to marshal features: %w", err) } - return mcp.NewToolResultText(string(r)), nil - } + return utils.NewToolResultText(string(r)), nil, nil + }) } -func GetToolsetsTools(toolsetGroup *toolsets.ToolsetGroup, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { - return mcp.NewTool("get_toolset_tools", - mcp.WithDescription(t("TOOL_GET_TOOLSET_TOOLS_DESCRIPTION", "Lists all the capabilities that are enabled with the specified toolset, use this to get clarity on whether enabling a toolset would help you to complete a task")), - mcp.WithToolAnnotation(mcp.ToolAnnotation{ +func GetToolsetsTools(toolsetGroup *toolsets.ToolsetGroup, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[map[string]any, any]) { + return mcp.Tool{ + Name: "get_toolset_tools", + Description: t("TOOL_GET_TOOLSET_TOOLS_DESCRIPTION", "Lists all the capabilities that are enabled with the specified toolset, use this to get clarity on whether enabling a toolset would help you to complete a task"), + Annotations: &mcp.ToolAnnotations{ Title: t("TOOL_GET_TOOLSET_TOOLS_USER_TITLE", "List all tools in a toolset"), - ReadOnlyHint: ToBoolPtr(true), - }), - mcp.WithString("toolset", - mcp.Required(), - mcp.Description("The name of the toolset you want to get the tools for"), - ToolsetEnum(toolsetGroup), - ), - ), - func(_ context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + ReadOnlyHint: true, + }, + InputSchema: &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "toolset": { + Type: "string", + Description: "The name of the toolset you want to get the tools for", + Enum: ToolsetEnum(toolsetGroup), + }, + }, + Required: []string{"toolset"}, + }, + }, + mcp.ToolHandlerFor[map[string]any, any](func(_ context.Context, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { // We need to convert the toolsetGroup back to a map for JSON serialization - toolsetName, err := RequiredParam[string](request, "toolset") + toolsetName, err := RequiredParam[string](args, "toolset") if err != nil { - return mcp.NewToolResultError(err.Error()), nil + return utils.NewToolResultError(err.Error()), nil, nil } toolset := toolsetGroup.Toolsets[toolsetName] if toolset == nil { - return mcp.NewToolResultError(fmt.Sprintf("Toolset %s not found", toolsetName)), nil + return utils.NewToolResultError(fmt.Sprintf("Toolset %s not found", toolsetName)), nil, nil } payload := []map[string]string{} @@ -132,9 +152,9 @@ func GetToolsetsTools(toolsetGroup *toolsets.ToolsetGroup, t translations.Transl r, err := json.Marshal(payload) if err != nil { - return nil, fmt.Errorf("failed to marshal features: %w", err) + return nil, nil, fmt.Errorf("failed to marshal features: %w", err) } - return mcp.NewToolResultText(string(r)), nil - } + return utils.NewToolResultText(string(r)), nil, nil + }) } diff --git a/pkg/github/tools.go b/pkg/github/tools.go index 11d148695..cb7b47505 100644 --- a/pkg/github/tools.go +++ b/pkg/github/tools.go @@ -390,9 +390,9 @@ func InitDynamicToolset(s *mcp.Server, tsg *toolsets.ToolsetGroup, t translation // Need to add the dynamic toolset last so it can be used to enable other toolsets dynamicToolSelection := toolsets.NewToolset(ToolsetMetadataDynamic.ID, ToolsetMetadataDynamic.Description). AddReadTools( - // toolsets.NewServerTool(ListAvailableToolsets(tsg, t)), - // toolsets.NewServerTool(GetToolsetsTools(tsg, t)), - // toolsets.NewServerTool(EnableToolset(s, tsg, t)), + toolsets.NewServerTool(ListAvailableToolsets(tsg, t)), + toolsets.NewServerTool(GetToolsetsTools(tsg, t)), + toolsets.NewServerTool(EnableToolset(s, tsg, t)), ) dynamicToolSelection.Enabled = true