@@ -10,7 +10,9 @@ import (
1010 "github.com/github/github-mcp-server/pkg/http/middleware"
1111 "github.com/github/github-mcp-server/pkg/http/oauth"
1212 "github.com/github/github-mcp-server/pkg/inventory"
13+ "github.com/github/github-mcp-server/pkg/scopes"
1314 "github.com/github/github-mcp-server/pkg/translations"
15+ "github.com/github/github-mcp-server/pkg/utils"
1416 "github.com/go-chi/chi/v5"
1517 "github.com/modelcontextprotocol/go-sdk/mcp"
1618)
@@ -23,21 +25,30 @@ type Handler struct {
2325 config * ServerConfig
2426 deps github.ToolDependencies
2527 logger * slog.Logger
28+ apiHosts utils.APIHostResolver
2629 t translations.TranslationHelperFunc
2730 githubMcpServerFactory GitHubMCPServerFactoryFunc
2831 inventoryFactoryFunc InventoryFactoryFunc
2932 oauthCfg * oauth.Config
33+ scopeFetcher scopes.FetcherInterface
3034}
3135
3236type HandlerOptions struct {
3337 GitHubMcpServerFactory GitHubMCPServerFactoryFunc
3438 InventoryFactory InventoryFactoryFunc
3539 OAuthConfig * oauth.Config
40+ ScopeFetcher scopes.FetcherInterface
3641 FeatureChecker inventory.FeatureFlagChecker
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
@@ -68,6 +79,7 @@ func NewHTTPMcpHandler(
6879 deps github.ToolDependencies ,
6980 t translations.TranslationHelperFunc ,
7081 logger * slog.Logger ,
82+ apiHost utils.APIHostResolver ,
7183 options ... HandlerOption ) * Handler {
7284 opts := & HandlerOptions {}
7385 for _ , o := range options {
@@ -79,28 +91,40 @@ func NewHTTPMcpHandler(
7991 githubMcpServerFactory = DefaultGitHubMCPServerFactory
8092 }
8193
94+ scopeFetcher := opts .ScopeFetcher
95+ if scopeFetcher == nil {
96+ scopeFetcher = scopes .NewFetcher (apiHost , scopes.FetcherOptions {})
97+ }
98+
8299 inventoryFactory := opts .InventoryFactory
83100 if inventoryFactory == nil {
84- inventoryFactory = DefaultInventoryFactory (cfg , t , opts .FeatureChecker )
101+ inventoryFactory = DefaultInventoryFactory (cfg , t , opts .FeatureChecker , scopeFetcher )
85102 }
86103
87104 return & Handler {
88105 ctx : ctx ,
89106 config : cfg ,
90107 deps : deps ,
91108 logger : logger ,
109+ apiHosts : apiHost ,
92110 t : t ,
93111 githubMcpServerFactory : githubMcpServerFactory ,
94112 inventoryFactoryFunc : inventoryFactory ,
95113 oauthCfg : opts .OAuthConfig ,
114+ scopeFetcher : scopeFetcher ,
96115 }
97116}
98117
99118func (h * Handler ) RegisterMiddleware (r chi.Router ) {
100119 r .Use (
101120 middleware .ExtractUserToken (h .oauthCfg ),
102121 middleware .WithRequestConfig ,
122+ middleware .WithMCPParse (),
103123 )
124+
125+ if h .config .ScopeChallenge {
126+ r .Use (middleware .WithScopeChallenge (h .oauthCfg , h .scopeFetcher ))
127+ }
104128}
105129
106130// RegisterRoutes registers the routes for the MCP server
@@ -145,22 +169,38 @@ func withInsiders(next http.Handler) http.Handler {
145169}
146170
147171func (h * Handler ) ServeHTTP (w http.ResponseWriter , r * http.Request ) {
148- inventory , err := h .inventoryFactoryFunc (r )
172+ inv , err := h .inventoryFactoryFunc (r )
149173 if err != nil {
150174 w .WriteHeader (http .StatusInternalServerError )
151175 return
152176 }
153177
154- ghServer , err := h .githubMcpServerFactory (r , h .deps , inventory , & github.MCPServerConfig {
178+ invToUse := inv
179+ if methodInfo , ok := ghcontext .MCPMethod (r .Context ()); ok && methodInfo != nil {
180+ invToUse = inv .ForMCPRequest (methodInfo .Method , methodInfo .ItemName )
181+ }
182+
183+ ghServer , err := h .githubMcpServerFactory (r , h .deps , invToUse , & github.MCPServerConfig {
155184 Version : h .config .Version ,
156185 Translator : h .t ,
157186 ContentWindowSize : h .config .ContentWindowSize ,
158187 Logger : h .logger ,
159188 RepoAccessTTL : h .config .RepoAccessCacheTTL ,
189+ // Explicitly set empty capabilities. inv.ForMCPRequest currently returns nothing for Initialize.
190+ ServerOptions : []github.MCPServerOption {
191+ func (so * mcp.ServerOptions ) {
192+ so .Capabilities = & mcp.ServerCapabilities {
193+ Tools : & mcp.ToolCapabilities {},
194+ Resources : & mcp.ResourceCapabilities {},
195+ Prompts : & mcp.PromptCapabilities {},
196+ }
197+ },
198+ },
160199 })
161200
162201 if err != nil {
163202 w .WriteHeader (http .StatusInternalServerError )
203+ return
164204 }
165205
166206 mcpHandler := mcp .NewStreamableHTTPHandler (func (_ * http.Request ) * mcp.Server {
@@ -177,13 +217,15 @@ func DefaultGitHubMCPServerFactory(r *http.Request, deps github.ToolDependencies
177217}
178218
179219// DefaultInventoryFactory creates the default inventory factory for HTTP mode
180- func DefaultInventoryFactory (_ * ServerConfig , t translations.TranslationHelperFunc , featureChecker inventory.FeatureFlagChecker ) InventoryFactoryFunc {
220+ func DefaultInventoryFactory (_ * ServerConfig , t translations.TranslationHelperFunc , featureChecker inventory.FeatureFlagChecker , scopeFetcher scopes. FetcherInterface ) InventoryFactoryFunc {
181221 return func (r * http.Request ) (* inventory.Inventory , error ) {
182222 b := github .NewInventory (t ).
183223 WithDeprecatedAliases (github .DeprecatedToolAliases ).
184224 WithFeatureChecker (featureChecker )
185225
186226 b = InventoryFiltersForRequest (r , b )
227+ b = PATScopeFilter (b , r , scopeFetcher )
228+
187229 b .WithServerInstructions ()
188230
189231 return b .Build ()
@@ -215,3 +257,29 @@ func InventoryFiltersForRequest(r *http.Request, builder *inventory.Builder) *in
215257
216258 return builder
217259}
260+
261+ func PATScopeFilter (b * inventory.Builder , r * http.Request , fetcher scopes.FetcherInterface ) * inventory.Builder {
262+ ctx := r .Context ()
263+
264+ tokenInfo , ok := ghcontext .GetTokenInfo (ctx )
265+ if ! ok || tokenInfo == nil {
266+ return b
267+ }
268+
269+ // Fetch token scopes for scope-based tool filtering (PAT tokens only)
270+ // Only classic PATs (ghp_ prefix) return OAuth scopes via X-OAuth-Scopes header.
271+ // Fine-grained PATs and other token types don't support this, so we skip filtering.
272+ if tokenInfo .TokenType == utils .TokenTypePersonalAccessToken {
273+ scopesList , err := fetcher .FetchTokenScopes (ctx , tokenInfo .Token )
274+ if err != nil {
275+ return b
276+ }
277+
278+ // Store fetched scopes in context for downstream use
279+ ghcontext .SetTokenScopes (ctx , scopesList )
280+
281+ return b .WithFilter (github .CreateToolScopeFilter (scopesList ))
282+ }
283+
284+ return b
285+ }
0 commit comments