Skip to content

Commit 5baf02b

Browse files
Copilotomgitsads
andcommitted
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>
1 parent 8887c93 commit 5baf02b

File tree

5 files changed

+434
-107
lines changed

5 files changed

+434
-107
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"annotations": {
3+
"readOnlyHint": true,
4+
"title": "Enable a toolset"
5+
},
6+
"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",
7+
"inputSchema": {
8+
"type": "object",
9+
"required": [
10+
"toolset"
11+
],
12+
"properties": {
13+
"toolset": {
14+
"type": "string",
15+
"description": "The name of the toolset to enable",
16+
"enum": [
17+
"mock-toolset"
18+
]
19+
}
20+
}
21+
},
22+
"name": "enable_toolset"
23+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"annotations": {
3+
"readOnlyHint": true,
4+
"title": "List all tools in a toolset"
5+
},
6+
"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",
7+
"inputSchema": {
8+
"type": "object",
9+
"required": [
10+
"toolset"
11+
],
12+
"properties": {
13+
"toolset": {
14+
"type": "string",
15+
"description": "The name of the toolset you want to get the tools for",
16+
"enum": [
17+
"mock-toolset"
18+
]
19+
}
20+
}
21+
},
22+
"name": "get_toolset_tools"
23+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"annotations": {
3+
"readOnlyHint": true,
4+
"title": "List available toolsets"
5+
},
6+
"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",
7+
"inputSchema": {
8+
"type": "object"
9+
},
10+
"name": "list_available_toolsets"
11+
}

pkg/github/dynamic_tools.go

Lines changed: 136 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
//go:build ignore
2-
31
package github
42

53
import (
@@ -9,132 +7,163 @@ import (
97

108
"github.com/github/github-mcp-server/pkg/toolsets"
119
"github.com/github/github-mcp-server/pkg/translations"
12-
"github.com/mark3labs/mcp-go/mcp"
13-
"github.com/mark3labs/mcp-go/server"
10+
"github.com/github/github-mcp-server/pkg/utils"
11+
"github.com/google/jsonschema-go/jsonschema"
12+
"github.com/modelcontextprotocol/go-sdk/mcp"
1413
)
1514

16-
func ToolsetEnum(toolsetGroup *toolsets.ToolsetGroup) mcp.PropertyOption {
17-
toolsetNames := make([]string, 0, len(toolsetGroup.Toolsets))
15+
func ToolsetEnum(toolsetGroup *toolsets.ToolsetGroup) []any {
16+
toolsetNames := make([]any, 0, len(toolsetGroup.Toolsets))
1817
for name := range toolsetGroup.Toolsets {
1918
toolsetNames = append(toolsetNames, name)
2019
}
21-
return mcp.Enum(toolsetNames...)
20+
return toolsetNames
2221
}
2322

24-
func EnableToolset(s *server.MCPServer, toolsetGroup *toolsets.ToolsetGroup, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
25-
return mcp.NewTool("enable_toolset",
26-
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")),
27-
mcp.WithToolAnnotation(mcp.ToolAnnotation{
28-
Title: t("TOOL_ENABLE_TOOLSET_USER_TITLE", "Enable a toolset"),
29-
// Not modifying GitHub data so no need to show a warning
30-
ReadOnlyHint: ToBoolPtr(true),
31-
}),
32-
mcp.WithString("toolset",
33-
mcp.Required(),
34-
mcp.Description("The name of the toolset to enable"),
35-
ToolsetEnum(toolsetGroup),
36-
),
37-
),
38-
func(_ context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
39-
// We need to convert the toolsets back to a map for JSON serialization
40-
toolsetName, err := RequiredParam[string](request, "toolset")
41-
if err != nil {
42-
return mcp.NewToolResultError(err.Error()), nil
43-
}
44-
toolset := toolsetGroup.Toolsets[toolsetName]
45-
if toolset == nil {
46-
return mcp.NewToolResultError(fmt.Sprintf("Toolset %s not found", toolsetName)), nil
47-
}
48-
if toolset.Enabled {
49-
return mcp.NewToolResultText(fmt.Sprintf("Toolset %s is already enabled", toolsetName)), nil
50-
}
23+
func EnableToolset(s *mcp.Server, toolsetGroup *toolsets.ToolsetGroup, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[map[string]any, any]) {
24+
tool := mcp.Tool{
25+
Name: "enable_toolset",
26+
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"),
27+
Annotations: &mcp.ToolAnnotations{
28+
Title: t("TOOL_ENABLE_TOOLSET_USER_TITLE", "Enable a toolset"),
29+
// Not modifying GitHub data so no need to show a warning
30+
ReadOnlyHint: true,
31+
},
32+
InputSchema: &jsonschema.Schema{
33+
Type: "object",
34+
Properties: map[string]*jsonschema.Schema{
35+
"toolset": {
36+
Type: "string",
37+
Description: "The name of the toolset to enable",
38+
Enum: ToolsetEnum(toolsetGroup),
39+
},
40+
},
41+
Required: []string{"toolset"},
42+
},
43+
}
5144

52-
toolset.Enabled = true
45+
handler := mcp.ToolHandlerFor[map[string]any, any](func(_ context.Context, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
46+
// We need to convert the toolsets back to a map for JSON serialization
47+
toolsetName, err := RequiredParam[string](args, "toolset")
48+
if err != nil {
49+
return utils.NewToolResultError(err.Error()), nil, nil
50+
}
51+
toolset := toolsetGroup.Toolsets[toolsetName]
52+
if toolset == nil {
53+
return utils.NewToolResultError(fmt.Sprintf("Toolset %s not found", toolsetName)), nil, nil
54+
}
55+
if toolset.Enabled {
56+
return utils.NewToolResultText(fmt.Sprintf("Toolset %s is already enabled", toolsetName)), nil, nil
57+
}
5358

54-
// caution: this currently affects the global tools and notifies all clients:
55-
//
56-
// Send notification to all initialized sessions
57-
// s.sendNotificationToAllClients("notifications/tools/list_changed", nil)
58-
s.AddTools(toolset.GetActiveTools()...)
59+
toolset.Enabled = true
5960

60-
return mcp.NewToolResultText(fmt.Sprintf("Toolset %s enabled", toolsetName)), nil
61+
// caution: this currently affects the global tools and notifies all clients:
62+
//
63+
// Send notification to all initialized sessions
64+
// s.sendNotificationToAllClients("notifications/tools/list_changed", nil)
65+
for _, serverTool := range toolset.GetActiveTools() {
66+
serverTool.RegisterFunc(s)
6167
}
68+
69+
return utils.NewToolResultText(fmt.Sprintf("Toolset %s enabled", toolsetName)), nil, nil
70+
})
71+
72+
return tool, handler
6273
}
6374

64-
func ListAvailableToolsets(toolsetGroup *toolsets.ToolsetGroup, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
65-
return mcp.NewTool("list_available_toolsets",
66-
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")),
67-
mcp.WithToolAnnotation(mcp.ToolAnnotation{
68-
Title: t("TOOL_LIST_AVAILABLE_TOOLSETS_USER_TITLE", "List available toolsets"),
69-
ReadOnlyHint: ToBoolPtr(true),
70-
}),
71-
),
72-
func(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
73-
// We need to convert the toolsetGroup back to a map for JSON serialization
74-
75-
payload := []map[string]string{}
76-
77-
for name, ts := range toolsetGroup.Toolsets {
78-
{
79-
t := map[string]string{
80-
"name": name,
81-
"description": ts.Description,
82-
"can_enable": "true",
83-
"currently_enabled": fmt.Sprintf("%t", ts.Enabled),
84-
}
85-
payload = append(payload, t)
86-
}
87-
}
75+
func ListAvailableToolsets(toolsetGroup *toolsets.ToolsetGroup, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[map[string]any, any]) {
76+
tool := mcp.Tool{
77+
Name: "list_available_toolsets",
78+
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"),
79+
Annotations: &mcp.ToolAnnotations{
80+
Title: t("TOOL_LIST_AVAILABLE_TOOLSETS_USER_TITLE", "List available toolsets"),
81+
ReadOnlyHint: true,
82+
},
83+
InputSchema: &jsonschema.Schema{
84+
Type: "object",
85+
Properties: map[string]*jsonschema.Schema{},
86+
},
87+
}
88+
89+
handler := mcp.ToolHandlerFor[map[string]any, any](func(_ context.Context, _ *mcp.CallToolRequest, _ map[string]any) (*mcp.CallToolResult, any, error) {
90+
// We need to convert the toolsetGroup back to a map for JSON serialization
91+
92+
payload := []map[string]string{}
8893

89-
r, err := json.Marshal(payload)
90-
if err != nil {
91-
return nil, fmt.Errorf("failed to marshal features: %w", err)
94+
for name, ts := range toolsetGroup.Toolsets {
95+
{
96+
t := map[string]string{
97+
"name": name,
98+
"description": ts.Description,
99+
"can_enable": "true",
100+
"currently_enabled": fmt.Sprintf("%t", ts.Enabled),
101+
}
102+
payload = append(payload, t)
92103
}
104+
}
93105

94-
return mcp.NewToolResultText(string(r)), nil
106+
r, err := json.Marshal(payload)
107+
if err != nil {
108+
return nil, nil, fmt.Errorf("failed to marshal features: %w", err)
95109
}
110+
111+
return utils.NewToolResultText(string(r)), nil, nil
112+
})
113+
114+
return tool, handler
96115
}
97116

98-
func GetToolsetsTools(toolsetGroup *toolsets.ToolsetGroup, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
99-
return mcp.NewTool("get_toolset_tools",
100-
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")),
101-
mcp.WithToolAnnotation(mcp.ToolAnnotation{
102-
Title: t("TOOL_GET_TOOLSET_TOOLS_USER_TITLE", "List all tools in a toolset"),
103-
ReadOnlyHint: ToBoolPtr(true),
104-
}),
105-
mcp.WithString("toolset",
106-
mcp.Required(),
107-
mcp.Description("The name of the toolset you want to get the tools for"),
108-
ToolsetEnum(toolsetGroup),
109-
),
110-
),
111-
func(_ context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
112-
// We need to convert the toolsetGroup back to a map for JSON serialization
113-
toolsetName, err := RequiredParam[string](request, "toolset")
114-
if err != nil {
115-
return mcp.NewToolResultError(err.Error()), nil
116-
}
117-
toolset := toolsetGroup.Toolsets[toolsetName]
118-
if toolset == nil {
119-
return mcp.NewToolResultError(fmt.Sprintf("Toolset %s not found", toolsetName)), nil
120-
}
121-
payload := []map[string]string{}
122-
123-
for _, st := range toolset.GetAvailableTools() {
124-
tool := map[string]string{
125-
"name": st.Tool.Name,
126-
"description": st.Tool.Description,
127-
"can_enable": "true",
128-
"toolset": toolsetName,
129-
}
130-
payload = append(payload, tool)
131-
}
117+
func GetToolsetsTools(toolsetGroup *toolsets.ToolsetGroup, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[map[string]any, any]) {
118+
tool := mcp.Tool{
119+
Name: "get_toolset_tools",
120+
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"),
121+
Annotations: &mcp.ToolAnnotations{
122+
Title: t("TOOL_GET_TOOLSET_TOOLS_USER_TITLE", "List all tools in a toolset"),
123+
ReadOnlyHint: true,
124+
},
125+
InputSchema: &jsonschema.Schema{
126+
Type: "object",
127+
Properties: map[string]*jsonschema.Schema{
128+
"toolset": {
129+
Type: "string",
130+
Description: "The name of the toolset you want to get the tools for",
131+
Enum: ToolsetEnum(toolsetGroup),
132+
},
133+
},
134+
Required: []string{"toolset"},
135+
},
136+
}
132137

133-
r, err := json.Marshal(payload)
134-
if err != nil {
135-
return nil, fmt.Errorf("failed to marshal features: %w", err)
138+
handler := mcp.ToolHandlerFor[map[string]any, any](func(_ context.Context, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
139+
// We need to convert the toolsetGroup back to a map for JSON serialization
140+
toolsetName, err := RequiredParam[string](args, "toolset")
141+
if err != nil {
142+
return utils.NewToolResultError(err.Error()), nil, nil
143+
}
144+
toolset := toolsetGroup.Toolsets[toolsetName]
145+
if toolset == nil {
146+
return utils.NewToolResultError(fmt.Sprintf("Toolset %s not found", toolsetName)), nil, nil
147+
}
148+
payload := []map[string]string{}
149+
150+
for _, st := range toolset.GetAvailableTools() {
151+
tool := map[string]string{
152+
"name": st.Tool.Name,
153+
"description": st.Tool.Description,
154+
"can_enable": "true",
155+
"toolset": toolsetName,
136156
}
157+
payload = append(payload, tool)
158+
}
137159

138-
return mcp.NewToolResultText(string(r)), nil
160+
r, err := json.Marshal(payload)
161+
if err != nil {
162+
return nil, nil, fmt.Errorf("failed to marshal features: %w", err)
139163
}
164+
165+
return utils.NewToolResultText(string(r)), nil, nil
166+
})
167+
168+
return tool, handler
140169
}

0 commit comments

Comments
 (0)