diff --git a/docs/remote-server.md b/docs/remote-server.md index be91009148..be9b155b87 100644 --- a/docs/remote-server.md +++ b/docs/remote-server.md @@ -131,12 +131,12 @@ The Remote GitHub MCP server supports the following URL path patterns: - `/x/all/readonly` - All available toolsets in read-only mode - `/x/all/insiders` - All available toolsets with insiders mode enabled - `/x/all/readonly/insiders` - All available toolsets in read-only mode with insiders mode enabled -- `/x/{toolset}` - Single specific toolset -- `/x/{toolset}/readonly` - Single specific toolset in read-only mode -- `/x/{toolset}/insiders` - Single specific toolset with insiders mode enabled -- `/x/{toolset}/readonly/insiders` - Single specific toolset in read-only mode with insiders mode enabled +- `/x/{toolsets}` - One or more comma-separated toolsets +- `/x/{toolsets}/readonly` - One or more toolsets in read-only mode +- `/x/{toolsets}/insiders` - One or more toolsets with insiders mode enabled +- `/x/{toolsets}/readonly/insiders` - One or more toolsets in read-only mode with insiders mode enabled -Note: `{toolset}` can only be a single toolset, not a comma-separated list. To combine multiple toolsets, use the `X-MCP-Toolsets` header instead. Path modifiers like `/readonly` and `/insiders` can be combined with the `X-MCP-Insiders` or `X-MCP-Readonly` headers. +The `{toolsets}` path segment accepts the same comma-separated values as the `X-MCP-Toolsets` header. For example, `/x/default,actions,projects` enables exactly that bundle. URL-based toolsets take precedence over `X-MCP-Toolsets` when both are present. Path modifiers like `/readonly` and `/insiders` can be combined with the `X-MCP-Insiders` or `X-MCP-Readonly` headers. Example: diff --git a/docs/server-configuration.md b/docs/server-configuration.md index 25c641153e..0ec080945a 100644 --- a/docs/server-configuration.md +++ b/docs/server-configuration.md @@ -7,7 +7,7 @@ We currently support the following ways in which the GitHub MCP Server can be co | Configuration | Remote Server | Local Server | |---------------|---------------|--------------| -| Toolsets | `X-MCP-Toolsets` header or `/x/{toolset}` URL | `--toolsets` flag or `GITHUB_TOOLSETS` env var | +| Toolsets | `X-MCP-Toolsets` header or `/x/{toolsets}` URL | `--toolsets` flag or `GITHUB_TOOLSETS` env var | | Individual Tools | `X-MCP-Tools` header | `--tools` flag or `GITHUB_TOOLS` env var | | Exclude Tools | `X-MCP-Exclude-Tools` header | `--exclude-tools` flag or `GITHUB_EXCLUDE_TOOLS` env var | | Read-Only Mode | `X-MCP-Readonly` header or `/readonly` URL | `--read-only` flag or `GITHUB_READ_ONLY` env var | @@ -87,6 +87,8 @@ The examples below use VS Code configuration format to illustrate the concepts. **Best for:** Users who want to enable multiple related toolsets. +Remote clients that cannot set custom headers can put the same comma-separated toolset list in the URL path, for example `/mcp/x/issues,pull_requests`. URL-based toolsets take precedence if both the path and `X-MCP-Toolsets` are provided. + diff --git a/pkg/http/handler.go b/pkg/http/handler.go index 1aad3ed01d..680b56118b 100644 --- a/pkg/http/handler.go +++ b/pkg/http/handler.go @@ -183,11 +183,12 @@ func withReadonly(next http.Handler) http.Handler { }) } -// withToolset is middleware that extracts the toolset from the URL and sets it in the request context +// withToolset is middleware that extracts one or more comma-separated toolsets +// from the URL and sets them in the request context. func withToolset(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - toolset := chi.URLParam(r, "toolset") - ctx := ghcontext.WithToolsets(r.Context(), []string{toolset}) + toolsets := headers.ParseCommaSeparated(chi.URLParam(r, "toolset")) + ctx := ghcontext.WithToolsets(r.Context(), toolsets) next.ServeHTTP(w, r.WithContext(ctx)) }) } diff --git a/pkg/http/handler_test.go b/pkg/http/handler_test.go index c9fa1de095..8fe8725fd6 100644 --- a/pkg/http/handler_test.go +++ b/pkg/http/handler_test.go @@ -230,6 +230,11 @@ func TestHTTPHandlerRoutes(t *testing.T) { path: "/x/issues", expectedTools: []string{"list_issues", "create_issue"}, }, + { + name: "toolset path supports multiple toolsets", + path: "/x/repos,issues", + expectedTools: []string{"get_file_contents", "create_repository", "hidden_by_holdback", "list_issues", "create_issue"}, + }, { name: "toolset readonly path filters to readonly tools in toolset", path: "/x/repos/readonly", @@ -240,6 +245,11 @@ func TestHTTPHandlerRoutes(t *testing.T) { path: "/x/issues/readonly", expectedTools: []string{"list_issues"}, }, + { + name: "multiple toolsets compose with readonly path", + path: "/x/repos,issues/readonly", + expectedTools: []string{"get_file_contents", "hidden_by_holdback", "list_issues"}, + }, { name: "X-MCP-Tools header filters to specific tools", path: "/", @@ -288,6 +298,14 @@ func TestHTTPHandlerRoutes(t *testing.T) { }, expectedTools: []string{"list_issues", "create_issue"}, }, + { + name: "multiple URL toolsets take precedence over header toolset", + path: "/x/repos,issues", + headers: map[string]string{ + headers.MCPToolsetsHeader: "pull_requests", + }, + expectedTools: []string{"get_file_contents", "create_repository", "hidden_by_holdback", "list_issues", "create_issue"}, + }, { name: "URL readonly takes precedence over header", path: "/readonly",
Remote ServerLocal Server