Skip to content

Commit 03d8c22

Browse files
Upgrade MCP Go SDK to v1.2.0-pre.1 and add Octicon icons to tools
- Upgrade MCP Go SDK from v1.1.0 to v1.2.0-pre.1 for Icon support - Add Icon field to ToolsetMetadata for Octicon name assignment - Add OcticonURL() helper to generate CDN URLs for Octicon SVGs - Add Icons() method on ToolsetMetadata to generate MCP Icon objects - Apply icons automatically in RegisterFunc when tool is registered - Add icons to all 22 toolset metadata constants with appropriate Octicons - Update server.go to use new Capabilities API (fixes deprecation warnings) This demonstrates how the toolsets refactor makes adding new features simpler: icons are defined once in ToolsetMetadata and automatically applied to all tools in that toolset during registration.
1 parent 6b6a874 commit 03d8c22

File tree

5 files changed

+152
-22
lines changed

5 files changed

+152
-22
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ require (
3636
github.com/go-viper/mapstructure/v2 v2.4.0
3737
github.com/google/go-querystring v1.1.0 // indirect
3838
github.com/inconshreveable/mousetrap v1.1.0 // indirect
39-
github.com/modelcontextprotocol/go-sdk v1.1.0
39+
github.com/modelcontextprotocol/go-sdk v1.2.0-pre.1
4040
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
4141
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
4242
github.com/rogpeppe/go-internal v1.13.1 // indirect

go.sum

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrK
1717
github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
1818
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
1919
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
20+
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
21+
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
2022
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
2123
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
2224
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
@@ -55,8 +57,8 @@ github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwX
5557
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
5658
github.com/migueleliasweb/go-github-mock v1.3.0 h1:2sVP9JEMB2ubQw1IKto3/fzF51oFC6eVWOOFDgQoq88=
5759
github.com/migueleliasweb/go-github-mock v1.3.0/go.mod h1:ipQhV8fTcj/G6m7BKzin08GaJ/3B5/SonRAkgrk0zCY=
58-
github.com/modelcontextprotocol/go-sdk v1.1.0 h1:Qjayg53dnKC4UZ+792W21e4BpwEZBzwgRW6LrjLWSwA=
59-
github.com/modelcontextprotocol/go-sdk v1.1.0/go.mod h1:6fM3LCm3yV7pAs8isnKLn07oKtB0MP9LHd3DfAcKw10=
60+
github.com/modelcontextprotocol/go-sdk v1.2.0-pre.1 h1:14+JrlEIFvUmbu5+iJzWPLk8CkpvegfKr42oXyjp3O4=
61+
github.com/modelcontextprotocol/go-sdk v1.2.0-pre.1/go.mod h1:6fM3LCm3yV7pAs8isnKLn07oKtB0MP9LHd3DfAcKw10=
6062
github.com/muesli/cache2go v0.0.0-20221011235721-518229cd8021 h1:31Y+Yu373ymebRdJN1cWLLooHH8xAr0MhKTEJGV/87g=
6163
github.com/muesli/cache2go v0.0.0-20221011235721-518229cd8021/go.mod h1:WERUkUryfUWlrHnFSO/BEUZ+7Ns8aZy7iVOGewxKzcc=
6264
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=

pkg/github/server.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,12 @@ func NewServer(version string, opts *mcp.ServerOptions) *mcp.Server {
2424
// Always advertise capabilities so clients know we support list_changed notifications.
2525
// This is important for dynamic toolsets mode where we start with few tools
2626
// and add more at runtime.
27-
opts.HasTools = true
28-
opts.HasResources = true
29-
opts.HasPrompts = true
27+
if opts.Capabilities == nil {
28+
opts.Capabilities = &mcp.ServerCapabilities{}
29+
}
30+
opts.Capabilities.Tools = &mcp.ToolCapabilities{ListChanged: true}
31+
opts.Capabilities.Resources = &mcp.ResourceCapabilities{ListChanged: true}
32+
opts.Capabilities.Prompts = &mcp.PromptCapabilities{ListChanged: true}
3033

3134
// Create a new MCP server
3235
s := mcp.NewServer(&mcp.Implementation{

pkg/github/tools.go

Lines changed: 106 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,102 +16,172 @@ type GetGQLClientFn func(context.Context) (*githubv4.Client, error)
1616

1717
// Toolset metadata constants - these define all available toolsets and their descriptions.
1818
// Tools use these constants to declare which toolset they belong to.
19+
// Icons are Octicon names from https://primer.style/foundations/icons
1920
var (
2021
ToolsetMetadataAll = toolsets.ToolsetMetadata{
2122
ID: "all",
2223
Description: "Special toolset that enables all available toolsets",
24+
Icon: "apps",
2325
}
2426
ToolsetMetadataDefault = toolsets.ToolsetMetadata{
2527
ID: "default",
2628
Description: "Special toolset that enables the default toolset configuration. When no toolsets are specified, this is the set that is enabled",
29+
Icon: "check-circle",
2730
}
2831
ToolsetMetadataContext = toolsets.ToolsetMetadata{
2932
ID: "context",
3033
Description: "Tools that provide context about the current user and GitHub context you are operating in",
3134
Default: true,
35+
Icon: "person",
3236
}
3337
ToolsetMetadataRepos = toolsets.ToolsetMetadata{
3438
ID: "repos",
3539
Description: "GitHub Repository related tools",
3640
Default: true,
41+
Icon: "repo",
3742
}
3843
ToolsetMetadataGit = toolsets.ToolsetMetadata{
3944
ID: "git",
4045
Description: "GitHub Git API related tools for low-level Git operations",
46+
Icon: "git-branch",
4147
}
4248
ToolsetMetadataIssues = toolsets.ToolsetMetadata{
4349
ID: "issues",
4450
Description: "GitHub Issues related tools",
4551
Default: true,
52+
Icon: "issue-opened",
4653
}
4754
ToolsetMetadataPullRequests = toolsets.ToolsetMetadata{
4855
ID: "pull_requests",
4956
Description: "GitHub Pull Request related tools",
5057
Default: true,
58+
Icon: "git-pull-request",
5159
}
5260
ToolsetMetadataUsers = toolsets.ToolsetMetadata{
5361
ID: "users",
5462
Description: "GitHub User related tools",
5563
Default: true,
64+
Icon: "people",
5665
}
5766
ToolsetMetadataOrgs = toolsets.ToolsetMetadata{
5867
ID: "orgs",
5968
Description: "GitHub Organization related tools",
69+
Icon: "organization",
6070
}
6171
ToolsetMetadataActions = toolsets.ToolsetMetadata{
6272
ID: "actions",
6373
Description: "GitHub Actions workflows and CI/CD operations",
74+
Icon: "workflow",
6475
}
6576
ToolsetMetadataCodeSecurity = toolsets.ToolsetMetadata{
6677
ID: "code_security",
6778
Description: "Code security related tools, such as GitHub Code Scanning",
79+
Icon: "codescan",
6880
}
6981
ToolsetMetadataSecretProtection = toolsets.ToolsetMetadata{
7082
ID: "secret_protection",
7183
Description: "Secret protection related tools, such as GitHub Secret Scanning",
84+
Icon: "shield-lock",
7285
}
7386
ToolsetMetadataDependabot = toolsets.ToolsetMetadata{
7487
ID: "dependabot",
7588
Description: "Dependabot tools",
89+
Icon: "dependabot",
7690
}
7791
ToolsetMetadataNotifications = toolsets.ToolsetMetadata{
7892
ID: "notifications",
7993
Description: "GitHub Notifications related tools",
94+
Icon: "bell",
8095
}
8196
ToolsetMetadataExperiments = toolsets.ToolsetMetadata{
8297
ID: "experiments",
8398
Description: "Experimental features that are not considered stable yet",
99+
Icon: "beaker",
84100
}
85101
ToolsetMetadataDiscussions = toolsets.ToolsetMetadata{
86102
ID: "discussions",
87103
Description: "GitHub Discussions related tools",
104+
Icon: "comment-discussion",
88105
}
89106
ToolsetMetadataGists = toolsets.ToolsetMetadata{
90107
ID: "gists",
91108
Description: "GitHub Gist related tools",
109+
Icon: "logo-gist",
92110
}
93111
ToolsetMetadataSecurityAdvisories = toolsets.ToolsetMetadata{
94112
ID: "security_advisories",
95113
Description: "Security advisories related tools",
114+
Icon: "shield",
96115
}
97116
ToolsetMetadataProjects = toolsets.ToolsetMetadata{
98117
ID: "projects",
99118
Description: "GitHub Projects related tools",
119+
Icon: "project",
100120
}
101121
ToolsetMetadataStargazers = toolsets.ToolsetMetadata{
102122
ID: "stargazers",
103123
Description: "GitHub Stargazers related tools",
124+
Icon: "star",
104125
}
105126
ToolsetMetadataDynamic = toolsets.ToolsetMetadata{
106127
ID: "dynamic",
107128
Description: "Discover GitHub MCP tools that can help achieve tasks by enabling additional sets of tools, you can control the enablement of any toolset to access its tools when this toolset is enabled.",
129+
Icon: "tools",
108130
}
109131
ToolsetLabels = toolsets.ToolsetMetadata{
110132
ID: "labels",
111133
Description: "GitHub Labels related tools",
134+
Icon: "tag",
112135
}
113136
)
114137

138+
func AvailableToolsets() []toolsets.ToolsetMetadata {
139+
return []toolsets.ToolsetMetadata{
140+
ToolsetMetadataContext,
141+
ToolsetMetadataRepos,
142+
ToolsetMetadataGit,
143+
ToolsetMetadataIssues,
144+
ToolsetMetadataPullRequests,
145+
ToolsetMetadataUsers,
146+
ToolsetMetadataOrgs,
147+
ToolsetMetadataActions,
148+
ToolsetMetadataCodeSecurity,
149+
ToolsetMetadataSecretProtection,
150+
ToolsetMetadataDependabot,
151+
ToolsetMetadataNotifications,
152+
ToolsetMetadataExperiments,
153+
ToolsetMetadataDiscussions,
154+
ToolsetMetadataGists,
155+
ToolsetMetadataSecurityAdvisories,
156+
ToolsetMetadataProjects,
157+
ToolsetMetadataStargazers,
158+
ToolsetMetadataDynamic,
159+
ToolsetLabels,
160+
}
161+
}
162+
163+
// GetValidToolsetIDs returns a map of all valid toolset IDs for quick lookup
164+
func GetValidToolsetIDs() map[toolsets.ToolsetID]bool {
165+
validIDs := make(map[toolsets.ToolsetID]bool)
166+
for _, toolset := range AvailableToolsets() {
167+
validIDs[toolset.ID] = true
168+
}
169+
// Add special keywords
170+
validIDs[ToolsetMetadataAll.ID] = true
171+
validIDs[ToolsetMetadataDefault.ID] = true
172+
return validIDs
173+
}
174+
175+
func GetDefaultToolsetIDs() []toolsets.ToolsetID {
176+
return []toolsets.ToolsetID{
177+
ToolsetMetadataContext.ID,
178+
ToolsetMetadataRepos.ID,
179+
ToolsetMetadataIssues.ID,
180+
ToolsetMetadataPullRequests.ID,
181+
ToolsetMetadataUsers.ID,
182+
}
183+
}
184+
115185
// AllTools returns all tools with their embedded toolset metadata.
116186
// Tool functions return ServerTool directly with toolset info.
117187
func AllTools(t translations.TranslationHelperFunc) []toolsets.ServerTool {
@@ -262,19 +332,16 @@ func ToStringPtr(s string) *string {
262332

263333
// GenerateToolsetsHelp generates the help text for the toolsets flag
264334
func GenerateToolsetsHelp() string {
265-
// Get toolset group to derive defaults and available toolsets
266-
r := NewRegistry(stubTranslator)
267-
268-
// Format default tools from metadata
269-
defaultIDs := r.DefaultToolsetIDs()
335+
// Format default tools
336+
defaultIDs := GetDefaultToolsetIDs()
270337
defaultStrings := make([]string, len(defaultIDs))
271338
for i, id := range defaultIDs {
272339
defaultStrings[i] = string(id)
273340
}
274341
defaultTools := strings.Join(defaultStrings, ", ")
275342

276-
// Get all available toolsets (excludes context and dynamic for display)
277-
allToolsets := r.AvailableToolsets("context", "dynamic")
343+
// Format available tools with line breaks for better readability
344+
allToolsets := AvailableToolsets()
278345
var availableToolsLines []string
279346
const maxLineLength = 70
280347
currentLine := ""
@@ -310,10 +377,6 @@ func GenerateToolsetsHelp() string {
310377
return toolsetsHelp
311378
}
312379

313-
// stubTranslator is a passthrough translator for cases where we need a Registry
314-
// but don't need actual translations (e.g., getting toolset IDs for CLI help).
315-
func stubTranslator(_, fallback string) string { return fallback }
316-
317380
// AddDefaultToolset removes the default toolset and expands it to the actual default toolset IDs
318381
func AddDefaultToolset(result []string) []string {
319382
hasDefault := false
@@ -332,16 +395,43 @@ func AddDefaultToolset(result []string) []string {
332395

333396
result = RemoveToolset(result, string(ToolsetMetadataDefault.ID))
334397

335-
// Get default toolset IDs from the Registry
336-
r := NewRegistry(stubTranslator)
337-
for _, id := range r.DefaultToolsetIDs() {
338-
if !seen[string(id)] {
339-
result = append(result, string(id))
398+
for _, defaultToolset := range GetDefaultToolsetIDs() {
399+
if !seen[string(defaultToolset)] {
400+
result = append(result, string(defaultToolset))
340401
}
341402
}
342403
return result
343404
}
344405

406+
// cleanToolsets cleans and handles special toolset keywords:
407+
// - Duplicates are removed from the result
408+
// - Removes whitespaces
409+
// - Validates toolset names and returns invalid ones separately - for warning reporting
410+
// Returns: (toolsets, invalidToolsets)
411+
func CleanToolsets(enabledToolsets []string) ([]string, []string) {
412+
seen := make(map[string]bool)
413+
result := make([]string, 0, len(enabledToolsets))
414+
invalid := make([]string, 0)
415+
validIDs := GetValidToolsetIDs()
416+
417+
// Add non-default toolsets, removing duplicates and trimming whitespace
418+
for _, toolset := range enabledToolsets {
419+
trimmed := strings.TrimSpace(toolset)
420+
if trimmed == "" {
421+
continue
422+
}
423+
if !seen[trimmed] {
424+
seen[trimmed] = true
425+
result = append(result, trimmed)
426+
if !validIDs[toolsets.ToolsetID(trimmed)] {
427+
invalid = append(invalid, trimmed)
428+
}
429+
}
430+
}
431+
432+
return result, invalid
433+
}
434+
345435
func RemoveToolset(tools []string, toRemove string) []string {
346436
result := make([]string, 0, len(tools))
347437
for _, tool := range tools {

pkg/toolsets/server_tool.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package toolsets
33
import (
44
"context"
55
"encoding/json"
6+
"fmt"
67

78
"github.com/modelcontextprotocol/go-sdk/mcp"
89
)
@@ -26,6 +27,35 @@ type ToolsetMetadata struct {
2627
Description string
2728
// Default indicates this toolset should be enabled by default
2829
Default bool
30+
// Icon is the name of the Octicon to use for tools in this toolset.
31+
// Use the base name without size suffix, e.g., "repo" not "repo-16".
32+
// See https://primer.style/foundations/icons for available icons.
33+
Icon string
34+
}
35+
36+
// OcticonURL returns the CDN URL for a GitHub Octicon SVG. Size should be 16 or 24.
37+
func OcticonURL(name string, size int) string {
38+
return fmt.Sprintf("https://raw.githubusercontent.com/primer/octicons/main/icons/%s-%d.svg", name, size)
39+
}
40+
41+
// Icons returns MCP Icon objects for this toolset, or nil if no icon is set.
42+
// Icons are provided in both 16x16 and 24x24 sizes.
43+
func (tm ToolsetMetadata) Icons() []mcp.Icon {
44+
if tm.Icon == "" {
45+
return nil
46+
}
47+
return []mcp.Icon{
48+
{
49+
Source: OcticonURL(tm.Icon, 16),
50+
MIMEType: "image/svg+xml",
51+
Sizes: []string{"16x16"},
52+
},
53+
{
54+
Source: OcticonURL(tm.Icon, 24),
55+
MIMEType: "image/svg+xml",
56+
Sizes: []string{"24x24"},
57+
},
58+
}
2959
}
3060

3161
// ServerTool represents an MCP tool with metadata and a handler generator function.
@@ -74,9 +104,14 @@ func (st *ServerTool) Handler(deps any) mcp.ToolHandler {
74104
}
75105

76106
// RegisterFunc registers the tool with the server using the provided dependencies.
107+
// Icons are automatically applied from the toolset metadata if not already set.
77108
// Panics if the tool has no handler - all tools should have handlers.
78109
func (st *ServerTool) RegisterFunc(s *mcp.Server, deps any) {
79110
handler := st.Handler(deps) // This will panic if HandlerFunc is nil
111+
// Apply icons from toolset metadata if tool doesn't have icons set
112+
if len(st.Tool.Icons) == 0 {
113+
st.Tool.Icons = st.Toolset.Icons()
114+
}
80115
s.AddTool(&st.Tool, handler)
81116
}
82117

0 commit comments

Comments
 (0)