From b11db64757ee46db7a1157556323ced85f28e0c4 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Tue, 10 Jun 2025 14:31:34 +0200 Subject: [PATCH 1/5] add context toolset and adjust readme --- README.md | 9 ++++++--- internal/ghmcp/server.go | 2 -- pkg/github/search.go | 1 + pkg/github/tools.go | 16 ++++++---------- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 9dba301d..7fc55e1f 100644 --- a/README.md +++ b/README.md @@ -147,11 +147,14 @@ The following sets of tools are available (all are on by default): | Toolset | Description | | ----------------------- | ------------------------------------------------------------- | -| `repos` | Repository-related tools (file operations, branches, commits) | +| `context` | **Strongly recommended**: Tools that provide context about the current user and GitHub context you are operating in | +| `code_security` | Code scanning alerts and security features | | `issues` | Issue-related tools (create, read, update, comment) | -| `users` | Anything relating to GitHub Users | +| `notifications` | GitHub Notifications related tools | | `pull_requests` | Pull request operations (create, merge, review) | -| `code_security` | Code scanning alerts and security features | +| `repos` | Repository-related tools (file operations, branches, commits) | +| `secret_protection` | Secret protection related tools, such as GitHub Secret Scanning | +| `users` | Anything relating to GitHub Users | | `experiments` | Experimental features (not considered stable) | #### Specifying Toolsets diff --git a/internal/ghmcp/server.go b/internal/ghmcp/server.go index 593411ae..29a6e7ec 100644 --- a/internal/ghmcp/server.go +++ b/internal/ghmcp/server.go @@ -120,12 +120,10 @@ func NewMCPServer(cfg MCPServerConfig) (*server.MCPServer, error) { return nil, fmt.Errorf("failed to enable toolsets: %w", err) } - context := github.InitContextToolset(getClient, cfg.Translator) github.RegisterResources(ghServer, getClient, cfg.Translator) // Register the tools with the server tsg.RegisterTools(ghServer) - context.RegisterTools(ghServer) if cfg.DynamicToolsets { dynamic := github.InitDynamicToolset(ghServer, tsg, cfg.Translator) diff --git a/pkg/github/search.go b/pkg/github/search.go index 157675c1..34c04dad 100644 --- a/pkg/github/search.go +++ b/pkg/github/search.go @@ -209,6 +209,7 @@ func SearchUsers(getClient GetClientFn, t translations.TranslationHelperFunc) (t } client, err := getClient(ctx) + if err != nil { return nil, fmt.Errorf("failed to get GitHub client: %w", err) } diff --git a/pkg/github/tools.go b/pkg/github/tools.go index 550adddd..337204b1 100644 --- a/pkg/github/tools.go +++ b/pkg/github/tools.go @@ -106,7 +106,13 @@ func DefaultToolsetGroup(readOnly bool, getClient GetClientFn, getGQLClient GetG // Keep experiments alive so the system doesn't error out when it's always enabled experiments := toolsets.NewToolset("experiments", "Experimental features that are not considered stable yet") + contextTools := toolsets.NewToolset("context", "Tools that provide context about the current user and GitHub context you are operating in"). + AddReadTools( + toolsets.NewServerTool(GetMe(getClient, t)), + ) + // Add toolsets to the group + tsg.AddToolset(contextTools) tsg.AddToolset(repos) tsg.AddToolset(issues) tsg.AddToolset(users) @@ -119,16 +125,6 @@ func DefaultToolsetGroup(readOnly bool, getClient GetClientFn, getGQLClient GetG return tsg } -func InitContextToolset(getClient GetClientFn, t translations.TranslationHelperFunc) *toolsets.Toolset { - // Create a new context toolset - contextTools := toolsets.NewToolset("context", "Tools that provide context about the current user and GitHub context you are operating in"). - AddReadTools( - toolsets.NewServerTool(GetMe(getClient, t)), - ) - contextTools.Enabled = true - return contextTools -} - // InitDynamicToolset creates a dynamic toolset that can be used to enable other toolsets, and so requires the server and toolset group as arguments func InitDynamicToolset(s *server.MCPServer, tsg *toolsets.ToolsetGroup, t translations.TranslationHelperFunc) *toolsets.Toolset { // Create a new dynamic toolset From 1969f25e90a3604b4a859e643665255a8d71d7bb Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Tue, 10 Jun 2025 14:57:56 +0200 Subject: [PATCH 2/5] move resources to a toolset --- internal/ghmcp/server.go | 2 -- pkg/github/resources.go | 14 -------------- pkg/github/tools.go | 7 +++++++ pkg/toolsets/toolsets.go | 41 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 48 insertions(+), 16 deletions(-) delete mode 100644 pkg/github/resources.go diff --git a/internal/ghmcp/server.go b/internal/ghmcp/server.go index 29a6e7ec..50c09e6e 100644 --- a/internal/ghmcp/server.go +++ b/internal/ghmcp/server.go @@ -120,8 +120,6 @@ func NewMCPServer(cfg MCPServerConfig) (*server.MCPServer, error) { return nil, fmt.Errorf("failed to enable toolsets: %w", err) } - github.RegisterResources(ghServer, getClient, cfg.Translator) - // Register the tools with the server tsg.RegisterTools(ghServer) diff --git a/pkg/github/resources.go b/pkg/github/resources.go deleted file mode 100644 index 774261e9..00000000 --- a/pkg/github/resources.go +++ /dev/null @@ -1,14 +0,0 @@ -package github - -import ( - "github.com/github/github-mcp-server/pkg/translations" - "github.com/mark3labs/mcp-go/server" -) - -func RegisterResources(s *server.MCPServer, getClient GetClientFn, t translations.TranslationHelperFunc) { - s.AddResourceTemplate(GetRepositoryResourceContent(getClient, t)) - s.AddResourceTemplate(GetRepositoryResourceBranchContent(getClient, t)) - s.AddResourceTemplate(GetRepositoryResourceCommitContent(getClient, t)) - s.AddResourceTemplate(GetRepositoryResourceTagContent(getClient, t)) - s.AddResourceTemplate(GetRepositoryResourcePrContent(getClient, t)) -} diff --git a/pkg/github/tools.go b/pkg/github/tools.go index 337204b1..0a3e7245 100644 --- a/pkg/github/tools.go +++ b/pkg/github/tools.go @@ -38,6 +38,13 @@ func DefaultToolsetGroup(readOnly bool, getClient GetClientFn, getGQLClient GetG toolsets.NewServerTool(CreateBranch(getClient, t)), toolsets.NewServerTool(PushFiles(getClient, t)), toolsets.NewServerTool(DeleteFile(getClient, t)), + ). + AddResourceTemplates( + toolsets.NewServerResourceTemplate(GetRepositoryResourceContent(getClient, t)), + toolsets.NewServerResourceTemplate(GetRepositoryResourceBranchContent(getClient, t)), + toolsets.NewServerResourceTemplate(GetRepositoryResourceCommitContent(getClient, t)), + toolsets.NewServerResourceTemplate(GetRepositoryResourceTagContent(getClient, t)), + toolsets.NewServerResourceTemplate(GetRepositoryResourcePrContent(getClient, t)), ) issues := toolsets.NewToolset("issues", "GitHub Issues related tools"). AddReadTools( diff --git a/pkg/toolsets/toolsets.go b/pkg/toolsets/toolsets.go index fcb5e93b..33549e7b 100644 --- a/pkg/toolsets/toolsets.go +++ b/pkg/toolsets/toolsets.go @@ -33,6 +33,19 @@ func NewServerTool(tool mcp.Tool, handler server.ToolHandlerFunc) server.ServerT return server.ServerTool{Tool: tool, Handler: handler} } +func NewServerResourceTemplate(resourceTemplate mcp.ResourceTemplate, handler server.ResourceTemplateHandlerFunc) ServerResourceTemplate { + return ServerResourceTemplate{ + resourceTemplate: resourceTemplate, + handler: handler, + } +} + +// ServerResource represents a resource that can be registered with the MCP server. +type ServerResourceTemplate struct { + resourceTemplate mcp.ResourceTemplate + handler server.ResourceTemplateHandlerFunc +} + type Toolset struct { Name string Description string @@ -40,6 +53,9 @@ type Toolset struct { readOnly bool writeTools []server.ServerTool readTools []server.ServerTool + // resources are not tools, but the community seems to be moving towards namespaces as a broader concept + // and in order to have multiple servers running concurrently, we want to avoid overlapping resources too. + resourceTemplates []ServerResourceTemplate } func (t *Toolset) GetActiveTools() []server.ServerTool { @@ -73,6 +89,31 @@ func (t *Toolset) RegisterTools(s *server.MCPServer) { } } +func (t *Toolset) AddResourceTemplates(templates ...ServerResourceTemplate) *Toolset { + t.resourceTemplates = append(t.resourceTemplates, templates...) + return t +} + +func (t *Toolset) GetActiveResourceTemplates() []ServerResourceTemplate { + if !t.Enabled { + return nil + } + return t.resourceTemplates +} + +func (t *Toolset) GetAvailableResourceTemplates() []ServerResourceTemplate { + return t.resourceTemplates +} + +func (t *Toolset) RegisterResourcesTemplates(s *server.MCPServer) { + if !t.Enabled { + return + } + for _, resource := range t.resourceTemplates { + s.AddResourceTemplate(resource.resourceTemplate, resource.handler) + } +} + func (t *Toolset) SetReadOnly() { // Set the toolset to read-only t.readOnly = true From b674ad789ebdb32fcd76d83bd20691dad3902772 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Tue, 10 Jun 2025 15:10:45 +0200 Subject: [PATCH 3/5] add resource registration as a toolset concern --- internal/ghmcp/server.go | 4 ++-- pkg/toolsets/toolsets.go | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/internal/ghmcp/server.go b/internal/ghmcp/server.go index 50c09e6e..9a9c7392 100644 --- a/internal/ghmcp/server.go +++ b/internal/ghmcp/server.go @@ -120,8 +120,8 @@ func NewMCPServer(cfg MCPServerConfig) (*server.MCPServer, error) { return nil, fmt.Errorf("failed to enable toolsets: %w", err) } - // Register the tools with the server - tsg.RegisterTools(ghServer) + // Register all mcp functionality with the server + tsg.RegisterAll(ghServer) if cfg.DynamicToolsets { dynamic := github.InitDynamicToolset(ghServer, tsg, cfg.Translator) diff --git a/pkg/toolsets/toolsets.go b/pkg/toolsets/toolsets.go index 33549e7b..ad444c05 100644 --- a/pkg/toolsets/toolsets.go +++ b/pkg/toolsets/toolsets.go @@ -40,12 +40,13 @@ func NewServerResourceTemplate(resourceTemplate mcp.ResourceTemplate, handler se } } -// ServerResource represents a resource that can be registered with the MCP server. +// ServerResourceTemplate represents a resource template that can be registered with the MCP server. type ServerResourceTemplate struct { resourceTemplate mcp.ResourceTemplate handler server.ResourceTemplateHandlerFunc } +// Toolset represents a collection of MCP functionality that can be enabled or disabled as a group. type Toolset struct { Name string Description string @@ -220,9 +221,10 @@ func (tg *ToolsetGroup) EnableToolset(name string) error { return nil } -func (tg *ToolsetGroup) RegisterTools(s *server.MCPServer) { +func (tg *ToolsetGroup) RegisterAll(s *server.MCPServer) { for _, toolset := range tg.Toolsets { toolset.RegisterTools(s) + toolset.RegisterResourcesTemplates(s) } } From 26b5f783c740226b02f6dc3221dc8898004e9a99 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Tue, 10 Jun 2025 15:17:55 +0200 Subject: [PATCH 4/5] add a note about broadening of toolsets --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 7fc55e1f..b37e923c 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,8 @@ If you don't have Docker, you can use `go build` to build the binary in the The GitHub MCP Server supports enabling or disabling specific groups of functionalities via the `--toolsets` flag. This allows you to control which GitHub API capabilities are available to your AI tools. Enabling only the toolsets that you need can help the LLM with tool choice and reduce the context size. +_Toolsets are not limited to Tools. Relevent MCP Resources and Prompts are also included where applicable._ + ### Available Toolsets The following sets of tools are available (all are on by default): From cd2242247cf3d122df497c41801f6dc7915e9c2e Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Tue, 10 Jun 2025 15:32:15 +0200 Subject: [PATCH 5/5] Apply suggestion from @SamMorrowDrums --- pkg/github/search.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/github/search.go b/pkg/github/search.go index 34c04dad..157675c1 100644 --- a/pkg/github/search.go +++ b/pkg/github/search.go @@ -209,7 +209,6 @@ func SearchUsers(getClient GetClientFn, t translations.TranslationHelperFunc) (t } client, err := getClient(ctx) - if err != nil { return nil, fmt.Errorf("failed to get GitHub client: %w", err) }