-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Scope challenge http #1925
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: http-stack-2
Are you sure you want to change the base?
Scope challenge http #1925
Conversation
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…hub/github-mcp-server into oauth-handler-implementation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR implements OAuth scope challenge functionality for the HTTP server, adding token type detection, scope validation, and tool filtering based on token scopes. The implementation includes new middleware for parsing MCP requests and validating OAuth scopes, along with supporting infrastructure for token type identification and scope management.
Changes:
- Adds OAuth scope challenge middleware that validates token scopes and returns WWW-Authenticate challenges for insufficient permissions
- Implements token type detection for PATs, fine-grained PATs, OAuth tokens, GitHub App tokens, and IDE tokens
- Adds MCP request parsing middleware to extract method and tool information early in the request lifecycle
- Introduces tool scope mapping infrastructure to track which tools require which OAuth scopes
- Adds CLI flag
--scope-challengeto enable the scope challenge feature
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/utils/token.go | New token parsing utility with type detection for various GitHub token formats |
| pkg/utils/api.go | Adds default API host resolver factory function |
| pkg/scopes/map.go | Tool scope mapping infrastructure for tracking tool scope requirements |
| pkg/scopes/map_test.go | Tests for tool scope mapping functionality |
| pkg/scopes/fetcher.go | Updates scope fetcher to use APIHostResolver interface instead of string |
| pkg/scopes/fetcher_test.go | Updates tests to use testAPIHostResolver |
| pkg/http/middleware/scope_challenge.go | New middleware for OAuth scope validation and challenge responses |
| pkg/http/middleware/mcp_parse.go | New middleware for early MCP JSON-RPC request parsing |
| pkg/http/middleware/token.go | Refactors token extraction to use centralized parsing utility |
| pkg/http/server.go | Adds ScopeChallenge config field and initializes scope fetcher |
| pkg/http/handler.go | Integrates scope challenge and fetcher into handler lifecycle |
| pkg/http/handler_test.go | Updates tests with scope fetcher mocks |
| pkg/context/token.go | Changes token context from string to TokenInfo struct with type information |
| pkg/context/mcp_info.go | New context type for storing parsed MCP method information |
| pkg/github/server.go | Uses MCP method info from context to optimize inventory filtering |
| pkg/github/dependencies.go | Updates to extract token from new TokenInfo structure |
| internal/ghmcp/server.go | Updates scope fetcher instantiation to use APIHostResolver |
| cmd/github-mcp-server/main.go | Adds --scope-challenge CLI flag |
Comments suppressed due to low confidence (3)
pkg/http/server.go:143
- Variable name typo: "severOptions" should be "serverOptions" to match the corrected variable name.
handler := NewHTTPMcpHandler(ctx, &cfg, deps, t, logger, apiHost, append(severOptions, WithFeatureChecker(featureChecker), WithOAuthConfig(oauthCfg))...)
pkg/http/server.go:139
- Variable name typo: "severOptions" should be "serverOptions" to match the corrected variable name.
severOptions = append(severOptions, WithScopeFetcher(scopeFetcher))
pkg/http/middleware/scope_challenge.go:178
- The new
scope_challenge.gomiddleware lacks test coverage. This middleware handles critical OAuth scope validation and authorization logic, including:
- Determining when to send scope challenges
- Fetching token scopes from GitHub API
- Checking if tools have required scopes
- Building WWW-Authenticate headers
Tests should verify the middleware behavior for different token types, scope combinations, error conditions, and ensure the fallback parsing logic works correctly.
package middleware
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
ghcontext "github.com/github/github-mcp-server/pkg/context"
"github.com/github/github-mcp-server/pkg/http/oauth"
"github.com/github/github-mcp-server/pkg/scopes"
"github.com/github/github-mcp-server/pkg/utils"
)
// WithScopeChallenge creates a new middleware that determines if an OAuth request contains sufficient scopes to
// complete the request and returns a scope challenge if not.
func WithScopeChallenge(oauthCfg *oauth.Config, scopeFetcher scopes.FetcherInterface) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// Skip health check endpoints
if r.URL.Path == "/_ping" {
next.ServeHTTP(w, r)
return
}
// Get user from context
tokenInfo, ok := ghcontext.GetTokenInfo(ctx)
if !ok {
next.ServeHTTP(w, r)
return
}
// Only check OAuth tokens - scope challenge allows OAuth apps to request additional scopes
if tokenInfo.TokenType != utils.TokenTypeOAuthAccessToken {
next.ServeHTTP(w, r)
return
}
// Try to use pre-parsed MCP method info first (performance optimization)
// This avoids re-parsing the JSON body if WithMCPParse middleware ran earlier
var toolName string
if methodInfo, ok := ghcontext.MCPMethod(ctx); ok && methodInfo != nil {
// Only check tools/call requests
if methodInfo.Method != "tools/call" {
next.ServeHTTP(w, r)
return
}
toolName = methodInfo.ItemName
} else {
// Fallback: parse the request body directly
body, err := io.ReadAll(r.Body)
if err != nil {
next.ServeHTTP(w, r)
return
}
r.Body = io.NopCloser(bytes.NewReader(body))
var mcpRequest struct {
JSONRPC string `json:"jsonrpc"`
Method string `json:"method"`
Params struct {
Name string `json:"name,omitempty"`
Arguments map[string]any `json:"arguments,omitempty"`
} `json:"params"`
}
err = json.Unmarshal(body, &mcpRequest)
if err != nil {
next.ServeHTTP(w, r)
return
}
// Only check tools/call requests
if mcpRequest.Method != "tools/call" {
next.ServeHTTP(w, r)
return
}
toolName = mcpRequest.Params.Name
}
toolScopeInfo, err := scopes.GetToolScopeInfo(toolName)
if err != nil {
next.ServeHTTP(w, r)
return
}
// If tool not found in scope map, allow the request
if toolScopeInfo == nil {
next.ServeHTTP(w, r)
return
}
// Get OAuth scopes from GitHub API
activeScopes, err := scopeFetcher.FetchTokenScopes(ctx, tokenInfo.Token)
if err != nil {
next.ServeHTTP(w, r)
return
}
// Store active scopes in context for downstream use
ghcontext.SetTokenScopes(ctx, activeScopes)
// Check if user has the required scopes
if toolScopeInfo.HasAcceptedScope(activeScopes...) {
next.ServeHTTP(w, r)
return
}
// User lacks required scopes - get the scopes they need
requiredScopes := toolScopeInfo.GetRequiredScopesSlice()
// Build the resource metadata URL using the shared utility
// GetEffectiveResourcePath returns the original path (e.g., /mcp or /mcp/x/all)
// which is used to construct the well-known OAuth protected resource URL
resourcePath := oauth.ResolveResourcePath(r, oauthCfg)
resourceMetadataURL := oauth.BuildResourceMetadataURL(r, oauthCfg, resourcePath)
// Build recommended scopes: existing scopes + required scopes
recommendedScopes := make([]string, 0, len(activeScopes)+len(requiredScopes))
recommendedScopes = append(recommendedScopes, activeScopes...)
recommendedScopes = append(recommendedScopes, requiredScopes...)
// Build the WWW-Authenticate header value
wwwAuthenticateHeader := fmt.Sprintf(`Bearer error="insufficient_scope", scope=%q, resource_metadata=%q, error_description=%q`,
strings.Join(recommendedScopes, " "),
resourceMetadataURL,
"Additional scopes required: "+strings.Join(requiredScopes, ", "),
)
// Send scope challenge response with the superset of existing and required scopes
w.Header().Set("WWW-Authenticate", wwwAuthenticateHeader)
http.Error(w, "Forbidden: insufficient scopes", http.StatusForbidden)
}
return http.HandlerFunc(fn)
}
}
pkg/http/server.go
Outdated
| severOptions := []HandlerOption{} | ||
| if cfg.ScopeChallenge { | ||
| scopeFetcher := scopes.NewFetcher(scopes.FetcherOptions{ | ||
| APIHost: apiHost, | ||
| }) | ||
| severOptions = append(severOptions, WithScopeFetcher(scopeFetcher)) | ||
| } | ||
|
|
||
| r := chi.NewRouter() | ||
| handler := NewHTTPMcpHandler(ctx, &cfg, deps, t, logger, apiHost, append(severOptions, WithFeatureChecker(featureChecker), WithOAuthConfig(oauthCfg))...) |
Copilot
AI
Jan 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo in variable name: "severOptions" should be "serverOptions". This inconsistent naming makes the code harder to read and maintain.
This issue also appears in the following locations of the same file:
- line 143
- line 139
| severOptions := []HandlerOption{} | |
| if cfg.ScopeChallenge { | |
| scopeFetcher := scopes.NewFetcher(scopes.FetcherOptions{ | |
| APIHost: apiHost, | |
| }) | |
| severOptions = append(severOptions, WithScopeFetcher(scopeFetcher)) | |
| } | |
| r := chi.NewRouter() | |
| handler := NewHTTPMcpHandler(ctx, &cfg, deps, t, logger, apiHost, append(severOptions, WithFeatureChecker(featureChecker), WithOAuthConfig(oauthCfg))...) | |
| serverOptions := []HandlerOption{} | |
| if cfg.ScopeChallenge { | |
| scopeFetcher := scopes.NewFetcher(scopes.FetcherOptions{ | |
| APIHost: apiHost, | |
| }) | |
| serverOptions = append(serverOptions, WithScopeFetcher(scopeFetcher)) | |
| } | |
| r := chi.NewRouter() | |
| handler := NewHTTPMcpHandler(ctx, &cfg, deps, t, logger, apiHost, append(serverOptions, WithFeatureChecker(featureChecker), WithOAuthConfig(oauthCfg))...) |
| @@ -101,6 +122,10 @@ func (h *Handler) RegisterMiddleware(r chi.Router) { | |||
| middleware.ExtractUserToken(h.oauthCfg), | |||
| middleware.WithRequestConfig, | |||
Copilot
AI
Jan 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The WithMCPParse middleware is defined but never registered in the middleware chain. The scope challenge middleware at line 85 mentions that it tries to use pre-parsed MCP method info "if WithMCPParse middleware ran earlier", but this middleware is not being registered anywhere. This means the optimization path will never be taken and the request body will always be parsed twice (once in WithScopeChallenge and again in downstream handlers). Consider adding middleware.WithMCPParse() to the middleware chain before WithScopeChallenge at line 122.
| middleware.WithRequestConfig, | |
| middleware.WithRequestConfig, | |
| middleware.WithMCPParse(), |
| // ScopeChallenge indicates if we should return OAuth scope challenges, and if we should perform | ||
| // tool filtering based on token scopes. |
Copilot
AI
Jan 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new ScopeChallenge configuration field lacks documentation. Add a comment explaining what this field does, when it should be enabled, and its implications for the server behavior. This is especially important given this is a public field in a configuration struct.
| // ScopeChallenge indicates if we should return OAuth scope challenges, and if we should perform | |
| // tool filtering based on token scopes. | |
| // ScopeChallenge controls whether the server returns OAuth scope challenges and filters | |
| // available tools based on the scopes present on the incoming access token. | |
| // | |
| // When enabled: | |
| // - Requests are evaluated against the token's scopes. | |
| // - Tools that require scopes the token does not have may be hidden or rejected. | |
| // - Responses can include information about missing scopes so callers may choose | |
| // to re-authorize with a broader set of permissions. | |
| // | |
| // Enable this when the client or hosting environment can react to scope challenges | |
| // (for example, by prompting the user to grant additional scopes). | |
| // When disabled, the server does not perform scope-based tool filtering and does not | |
| // surface scope upgrade hints; authorization failures are returned as normal errors. |
pkg/utils/api.go
Outdated
| func NewDefaultAPIHostResolver() APIHostResolver { | ||
| a, err := newDotcomHost() | ||
| if err != nil { | ||
| // This should never happen | ||
| panic(fmt.Sprintf("failed to create default API host resolver: %v", err)) | ||
| } | ||
| return a | ||
| } |
Copilot
AI
Jan 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using panic in NewDefaultAPIHostResolver is inappropriate for a library function. The comment says "This should never happen" but if it does happen, it will crash the entire application. Consider returning an error instead or logging and using a fallback value. This is especially important since this function could be called from external code that depends on this repository as a library.
| package middleware | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "encoding/json" | ||
| "io" | ||
| "net/http" | ||
|
|
||
| ghcontext "github.com/github/github-mcp-server/pkg/context" | ||
| ) | ||
|
|
||
| // mcpJSONRPCRequest represents the structure of an MCP JSON-RPC request. | ||
| // We only parse the fields needed for routing and optimization. | ||
| type mcpJSONRPCRequest struct { | ||
| JSONRPC string `json:"jsonrpc"` | ||
| Method string `json:"method"` | ||
| Params struct { | ||
| // For tools/call | ||
| Name string `json:"name,omitempty"` | ||
| Arguments json.RawMessage `json:"arguments,omitempty"` | ||
| // For prompts/get | ||
| // Name is shared with tools/call | ||
| // For resources/read | ||
| URI string `json:"uri,omitempty"` | ||
| } `json:"params"` | ||
| } | ||
|
|
||
| // WithMCPParse creates a middleware that parses MCP JSON-RPC requests early in the | ||
| // request lifecycle and stores the parsed information in the request context. | ||
| // This enables: | ||
| // - Registry filtering via ForMCPRequest (only register needed tools/resources/prompts) | ||
| // - Avoiding duplicate JSON parsing in downstream middlewares | ||
| // - Access to owner/repo for secret-scanning middleware | ||
| // | ||
| // The middleware reads the request body, parses it, restores the body for downstream | ||
| // handlers, and stores the parsed MCPMethodInfo in the request context. | ||
| func WithMCPParse() func(http.Handler) http.Handler { | ||
| return func(next http.Handler) http.Handler { | ||
| fn := func(w http.ResponseWriter, r *http.Request) { | ||
| ctx := r.Context() | ||
|
|
||
| // Skip health check endpoints | ||
| if r.URL.Path == "/_ping" { | ||
| next.ServeHTTP(w, r) | ||
| return | ||
| } | ||
|
|
||
| // Only parse POST requests (MCP uses JSON-RPC over POST) | ||
| if r.Method != http.MethodPost { | ||
| next.ServeHTTP(w, r) | ||
| return | ||
| } | ||
|
|
||
| // Read the request body | ||
| body, err := io.ReadAll(r.Body) | ||
| if err != nil { | ||
| // Log but continue - don't block requests on parse errors | ||
| next.ServeHTTP(w, r) | ||
| return | ||
| } | ||
|
|
||
| // Restore the body for downstream handlers | ||
| r.Body = io.NopCloser(bytes.NewReader(body)) | ||
|
|
||
| // Skip empty bodies | ||
| if len(body) == 0 { | ||
| next.ServeHTTP(w, r) | ||
| return | ||
| } | ||
|
|
||
| // Parse the JSON-RPC request | ||
| var mcpReq mcpJSONRPCRequest | ||
| err = json.Unmarshal(body, &mcpReq) | ||
| if err != nil { | ||
| // Log but continue - could be a non-MCP request or malformed JSON | ||
| next.ServeHTTP(w, r) | ||
| return | ||
| } | ||
|
|
||
| // Skip if not a valid JSON-RPC 2.0 request | ||
| if mcpReq.JSONRPC != "2.0" || mcpReq.Method == "" { | ||
| next.ServeHTTP(w, r) | ||
| return | ||
| } | ||
|
|
||
| // Build the MCPMethodInfo | ||
| methodInfo := &ghcontext.MCPMethodInfo{ | ||
| Method: mcpReq.Method, | ||
| } | ||
|
|
||
| // Extract item name based on method type | ||
|
|
||
| switch mcpReq.Method { | ||
| case "tools/call": | ||
| methodInfo.ItemName = mcpReq.Params.Name | ||
| // Parse arguments if present | ||
| if len(mcpReq.Params.Arguments) > 0 { | ||
| var args map[string]any | ||
| err := json.Unmarshal(mcpReq.Params.Arguments, &args) | ||
| if err == nil { | ||
| methodInfo.Arguments = args | ||
| // Extract owner and repo if present | ||
| if owner, ok := args["owner"].(string); ok { | ||
| methodInfo.Owner = owner | ||
| } | ||
| if repo, ok := args["repo"].(string); ok { | ||
| methodInfo.Repo = repo | ||
| } | ||
| } | ||
| } | ||
| case "prompts/get": | ||
| methodInfo.ItemName = mcpReq.Params.Name | ||
| case "resources/read": | ||
| methodInfo.ItemName = mcpReq.Params.URI | ||
| default: | ||
| // Whatever | ||
| } | ||
|
|
||
| // Store the parsed info in context | ||
| ctx = ghcontext.WithMCPMethodInfo(ctx, methodInfo) | ||
|
|
||
| next.ServeHTTP(w, r.WithContext(ctx)) | ||
| } | ||
| return http.HandlerFunc(fn) | ||
| } | ||
| } |
Copilot
AI
Jan 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new mcp_parse.go middleware lacks test coverage. This middleware parses MCP JSON-RPC requests and extracts method info, owner/repo arguments. Tests should verify:
- Correct parsing of different MCP methods (tools/call, prompts/get, resources/read)
- Handling of malformed JSON
- Proper restoration of request body for downstream handlers
- Extraction of owner/repo from tool arguments
- Behavior when JSON-RPC version is not 2.0 or method is empty
| func WithScopeChallenge(oauthCfg *oauth.Config, scopeFetcher scopes.FetcherInterface) func(http.Handler) http.Handler { | ||
| return func(next http.Handler) http.Handler { | ||
| fn := func(w http.ResponseWriter, r *http.Request) { | ||
| ctx := r.Context() | ||
|
|
||
| // Skip health check endpoints | ||
| if r.URL.Path == "/_ping" { | ||
| next.ServeHTTP(w, r) | ||
| return | ||
| } | ||
|
|
||
| // Get user from context | ||
| tokenInfo, ok := ghcontext.GetTokenInfo(ctx) | ||
| if !ok { | ||
| next.ServeHTTP(w, r) | ||
| return | ||
| } | ||
|
|
||
| // Only check OAuth tokens - scope challenge allows OAuth apps to request additional scopes | ||
| if tokenInfo.TokenType != utils.TokenTypeOAuthAccessToken { | ||
| next.ServeHTTP(w, r) | ||
| return | ||
| } | ||
|
|
||
| // Try to use pre-parsed MCP method info first (performance optimization) | ||
| // This avoids re-parsing the JSON body if WithMCPParse middleware ran earlier | ||
| var toolName string | ||
| if methodInfo, ok := ghcontext.MCPMethod(ctx); ok && methodInfo != nil { | ||
| // Only check tools/call requests | ||
| if methodInfo.Method != "tools/call" { | ||
| next.ServeHTTP(w, r) | ||
| return | ||
| } | ||
| toolName = methodInfo.ItemName | ||
| } else { | ||
| // Fallback: parse the request body directly | ||
| body, err := io.ReadAll(r.Body) | ||
| if err != nil { |
Copilot
AI
Jan 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The exported function FetchScopesFromGitHubAPI is defined but never used anywhere in the codebase. It appears to be redundant with the scopes.FetcherInterface and scopes.Fetcher which are actually used in the middleware. Consider removing this unused function or documenting its intended use case if it's meant to be a public API.
| package utils //nolint:revive //TODO: figure out a better name for this package | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "net/http" | ||
| "regexp" | ||
| "strings" | ||
|
|
||
| httpheaders "github.com/github/github-mcp-server/pkg/http/headers" | ||
| "github.com/github/github-mcp-server/pkg/http/mark" | ||
| ) | ||
|
|
||
| type TokenType int | ||
|
|
||
| const ( | ||
| TokenTypeUnknown TokenType = iota | ||
| TokenTypePersonalAccessToken | ||
| TokenTypeFineGrainedPersonalAccessToken | ||
| TokenTypeOAuthAccessToken | ||
| TokenTypeUserToServerGitHubAppToken | ||
| TokenTypeServerToServerGitHubAppToken | ||
| TokenTypeIDEToken | ||
| ) | ||
|
|
||
| var supportedThirdPartyTokenPrefixes = map[string]TokenType{ | ||
| "ghp_": TokenTypePersonalAccessToken, // Personal access token (classic) | ||
| "github_pat_": TokenTypeFineGrainedPersonalAccessToken, // Fine-grained personal access token | ||
| "gho_": TokenTypeOAuthAccessToken, // OAuth access token | ||
| "ghu_": TokenTypeUserToServerGitHubAppToken, // User access token for a GitHub App | ||
| "ghs_": TokenTypeServerToServerGitHubAppToken, // Installation access token for a GitHub App (a.k.a. server-to-server token) | ||
| } | ||
|
|
||
| var ( | ||
| ErrMissingAuthorizationHeader = fmt.Errorf("%w: missing required Authorization header", mark.ErrBadRequest) | ||
| ErrBadAuthorizationHeader = fmt.Errorf("%w: Authorization header is badly formatted", mark.ErrBadRequest) | ||
| ErrUnsupportedAuthorizationHeader = fmt.Errorf("%w: unsupported Authorization header", mark.ErrBadRequest) | ||
| ) | ||
|
|
||
| // oldPatternRegexp is the regular expression for the old pattern of the token. | ||
| // Until 2021, GitHub API tokens did not have an identifiable prefix. They | ||
| // were 40 characters long and only contained the characters a-f and 0-9. | ||
| var oldPatternRegexp = regexp.MustCompile(`\A[a-f0-9]{40}\z`) | ||
|
|
||
| // ParseAuthorizationHeader parses the Authorization header from the HTTP request | ||
| func ParseAuthorizationHeader(req *http.Request) (tokenType TokenType, token string, _ error) { | ||
| authHeader := req.Header.Get(httpheaders.AuthorizationHeader) | ||
| if authHeader == "" { | ||
| return 0, "", ErrMissingAuthorizationHeader | ||
| } | ||
|
|
||
| switch { | ||
| // decrypt dotcom token and set it as token | ||
| case strings.HasPrefix(authHeader, "GitHub-Bearer "): | ||
| return 0, "", ErrUnsupportedAuthorizationHeader | ||
| default: | ||
| // support both "Bearer" and "bearer" to conform to api.github.com | ||
| if len(authHeader) > 7 && strings.EqualFold(authHeader[:7], "Bearer ") { | ||
| token = authHeader[7:] | ||
| } else { | ||
| token = authHeader | ||
| } | ||
| } | ||
|
|
||
| // Do a naïve check for a colon in the token - currently, only the IDE token has a colon in it. | ||
| // ex: tid=1;exp=25145314523;chat=1:<hmac> | ||
| if strings.Contains(token, ":") { | ||
| return TokenTypeIDEToken, token, nil | ||
| } | ||
|
|
||
| for prefix, tokenType := range supportedThirdPartyTokenPrefixes { | ||
| if strings.HasPrefix(token, prefix) { | ||
| return tokenType, token, nil | ||
| } | ||
| } | ||
|
|
||
| matchesOldTokenPattern := oldPatternRegexp.MatchString(token) | ||
| if matchesOldTokenPattern { | ||
| return TokenTypePersonalAccessToken, token, nil | ||
| } | ||
|
|
||
| return 0, "", ErrBadAuthorizationHeader | ||
| } |
Copilot
AI
Jan 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new token.go file with ParseAuthorizationHeader function and related token type detection logic lacks test coverage. Given that this is critical security-related functionality that parses authentication headers and determines token types, comprehensive tests should be added to verify:
- Correct parsing of different token formats
- Proper error handling for malformed headers
- Token type detection for all supported prefixes
- Edge cases like empty headers, missing Bearer prefix, etc.
Summary
Adds Scope Challenge middleware for OAuth tokens, and Scope tool filtering for PATs
Why
Fixes #
What changed
MCP impact
Prompts tested (tool changes only)
Security / limits
Tool renaming
deprecated_tool_aliases.goNote: if you're renaming tools, you must add the tool aliases. For more information on how to do so, please refer to the official docs.
Lint & tests
./script/lint./script/testDocs