Migrate dynamic toolset to modelcontextprotocol/go-sdk (#1450)
* Initial plan * Migrate dynamic toolset to modelcontextprotocol/go-sdk This commit migrates the dynamic toolset (enable_toolset, list_available_toolsets, get_toolset_tools) from mark3labs/mcp-go to modelcontextprotocol/go-sdk. Changes: - Removed //go:build ignore tag - Updated imports to use modelcontextprotocol/go-sdk - Migrated all tool functions to use new SDK patterns - Updated ToolsetEnum helper to return []any instead of mcp.PropertyOption - Converted DSL-based schema definitions to jsonschema.Schema structures - Updated handler signatures to use map[string]any args - Replaced old result helpers with utils package equivalents - Fixed EnableToolset to use RegisterFunc instead of AddTools - Created comprehensive test suite for all three tools - Generated toolsnaps for the new tools Related to #1428 Co-authored-by: omgitsads <4619+omgitsads@users.noreply.github.com> * Enable dynamic tools * Remove new test and toolsnaps, we can follow up with this * Just return the tool and handler directly instead of assigning to variables first. This stops copilot complaining in review that the variables are unused. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: omgitsads <4619+omgitsads@users.noreply.github.com> Co-authored-by: Adam Holt <me@adamholt.co.uk>
This commit is contained in:
+77
-57
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user