@@ -11,7 +11,9 @@ import (
1111 "github.com/github/github-mcp-server/pkg/http/middleware"
1212 "github.com/github/github-mcp-server/pkg/http/oauth"
1313 "github.com/github/github-mcp-server/pkg/inventory"
14+ "github.com/github/github-mcp-server/pkg/scopes"
1415 "github.com/github/github-mcp-server/pkg/translations"
16+ "github.com/github/github-mcp-server/pkg/utils"
1517 "github.com/go-chi/chi/v5"
1618 "github.com/modelcontextprotocol/go-sdk/mcp"
1719)
@@ -24,20 +26,29 @@ type Handler struct {
2426 config * ServerConfig
2527 deps github.ToolDependencies
2628 logger * slog.Logger
29+ apiHosts utils.APIHostResolver
2730 t translations.TranslationHelperFunc
2831 githubMcpServerFactory GitHubMCPServerFactoryFunc
2932 inventoryFactoryFunc InventoryFactoryFunc
3033 oauthCfg * oauth.Config
34+ scopeFetcher scopes.FetcherInterface
3135}
3236
3337type HandlerOptions struct {
3438 GitHubMcpServerFactory GitHubMCPServerFactoryFunc
3539 InventoryFactory InventoryFactoryFunc
3640 OAuthConfig * oauth.Config
41+ ScopeFetcher scopes.FetcherInterface
3742}
3843
3944type HandlerOption func (* HandlerOptions )
4045
46+ func WithScopeFetcher (f scopes.FetcherInterface ) HandlerOption {
47+ return func (o * HandlerOptions ) {
48+ o .ScopeFetcher = f
49+ }
50+ }
51+
4152func WithGitHubMCPServerFactory (f GitHubMCPServerFactoryFunc ) HandlerOption {
4253 return func (o * HandlerOptions ) {
4354 o .GitHubMcpServerFactory = f
@@ -62,6 +73,7 @@ func NewHTTPMcpHandler(
6273 deps github.ToolDependencies ,
6374 t translations.TranslationHelperFunc ,
6475 logger * slog.Logger ,
76+ apiHost utils.APIHostResolver ,
6577 options ... HandlerOption ) * Handler {
6678 opts := & HandlerOptions {}
6779 for _ , o := range options {
@@ -75,29 +87,39 @@ func NewHTTPMcpHandler(
7587
7688 inventoryFactory := opts .InventoryFactory
7789 if inventoryFactory == nil {
78- inventoryFactory = DefaultInventoryFactory (cfg , t , nil )
90+ inventoryFactory = DefaultInventoryFactory (cfg , t , nil , opts .ScopeFetcher )
91+ }
92+
93+ scopeFetcher := opts .ScopeFetcher
94+ if scopeFetcher == nil {
95+ scopeFetcher = scopes .NewFetcher (scopes.FetcherOptions {
96+ APIHost : apiHost ,
97+ })
7998 }
8099
81100 return & Handler {
82101 ctx : ctx ,
83102 config : cfg ,
84103 deps : deps ,
85104 logger : logger ,
105+ apiHosts : apiHost ,
86106 t : t ,
87107 githubMcpServerFactory : githubMcpServerFactory ,
88108 inventoryFactoryFunc : inventoryFactory ,
89109 oauthCfg : opts .OAuthConfig ,
110+ scopeFetcher : scopeFetcher ,
90111 }
91112}
92113
93114func (h * Handler ) RegisterMiddleware (r chi.Router ) {
94115 r .Use (
95116 middleware .ExtractUserToken (h .oauthCfg ),
96117 middleware .WithRequestConfig ,
97- middleware .WithScopeChallenge (h .oauthCfg ),
98118 )
99119
100- r .Use (middleware .WithScopeChallenge (h .oauthCfg ))
120+ if h .config .ScopeChallenge {
121+ r .Use (middleware .WithScopeChallenge (h .oauthCfg , h .scopeFetcher ))
122+ }
101123}
102124
103125// RegisterRoutes registers the routes for the MCP server
@@ -159,7 +181,7 @@ func DefaultGitHubMCPServerFactory(r *http.Request, deps github.ToolDependencies
159181 return github .NewMCPServer (r .Context (), cfg , deps , inventory )
160182}
161183
162- func DefaultInventoryFactory (_ * ServerConfig , t translations.TranslationHelperFunc , staticChecker inventory.FeatureFlagChecker ) InventoryFactoryFunc {
184+ func DefaultInventoryFactory (cfg * ServerConfig , t translations.TranslationHelperFunc , staticChecker inventory.FeatureFlagChecker , scopeFetcher scopes. FetcherInterface ) InventoryFactoryFunc {
163185 return func (r * http.Request ) (* inventory.Inventory , error ) {
164186 b := github .NewInventory (t ).WithDeprecatedAliases (github .DeprecatedToolAliases )
165187
@@ -170,6 +192,11 @@ func DefaultInventoryFactory(_ *ServerConfig, t translations.TranslationHelperFu
170192 }
171193
172194 b = InventoryFiltersForRequest (r , b )
195+
196+ if cfg .ScopeChallenge {
197+ b = b .WithFilter (ScopeChallengeFilter (r , scopeFetcher ))
198+ }
199+
173200 b .WithServerInstructions ()
174201
175202 return b .Build ()
@@ -198,3 +225,26 @@ func InventoryFiltersForRequest(r *http.Request, builder *inventory.Builder) *in
198225
199226 return builder
200227}
228+
229+ func ScopeChallengeFilter (r * http.Request , fetcher scopes.FetcherInterface ) inventory.ToolFilter {
230+ ctx := r .Context ()
231+
232+ tokenInfo , ok := ghcontext .GetTokenInfo (ctx )
233+ if ! ok || tokenInfo == nil {
234+ return nil
235+ }
236+
237+ // Fetch token scopes for scope-based tool filtering (PAT tokens only)
238+ // Only classic PATs (ghp_ prefix) return OAuth scopes via X-OAuth-Scopes header.
239+ // Fine-grained PATs and other token types don't support this, so we skip filtering.
240+ if tokenInfo .TokenType == utils .TokenTypePersonalAccessToken {
241+ scopesList , err := fetcher .FetchTokenScopes (ctx , tokenInfo .Token )
242+ if err != nil {
243+ return nil
244+ }
245+
246+ return github .CreateToolScopeFilter (scopesList )
247+ }
248+
249+ return nil
250+ }
0 commit comments