11package http
22
33import (
4+ "bytes"
45 "context"
6+ "encoding/json"
57 "fmt"
8+ "io"
69 "net/http"
10+ "slices"
711 "strings"
812
9- "github.com/coreos/go-oidc/v3/oidc"
10- "github.com/go-jose/go-jose/v4"
11- "github.com/go-jose/go-jose/v4/jwt"
1213 "k8s.io/klog/v2"
1314
1415 "github.com/containers/kubernetes-mcp-server/pkg/mcp"
16+ "github.com/coreos/go-oidc/v3/oidc"
17+ "github.com/go-jose/go-jose/v4"
18+ "github.com/go-jose/go-jose/v4/jwt"
1519)
1620
1721const (
1822 Audience = "kubernetes-mcp-server"
1923)
2024
25+ // MCPRequest represents the structure of an MCP request
26+ type MCPRequest struct {
27+ Method string `json:"method"`
28+ Params struct {
29+ Name string `json:"name"`
30+ Arguments map [string ]interface {} `json:"arguments"`
31+ } `json:"params"`
32+ }
33+
2134// AuthorizationMiddleware validates the OAuth flow using Kubernetes TokenReview API
2235func AuthorizationMiddleware (requireOAuth bool , serverURL string , oidcProvider * oidc.Provider , mcpServer * mcp.Server ) func (http.Handler ) http.Handler {
2336 return func (next http.Handler ) http.Handler {
@@ -88,6 +101,18 @@ func AuthorizationMiddleware(requireOAuth bool, serverURL string, oidcProvider *
88101 // Scopes are likely to be used for authorization.
89102 scopes := claims .GetScopes ()
90103 klog .V (2 ).Infof ("JWT token validated - Scopes: %v" , scopes )
104+ if len (scopes ) == 0 {
105+ http .Error (w , "InsufficientScopeError" , http .StatusForbidden )
106+ return
107+ }
108+
109+ if (r .URL .Path == mcpEndpoint || r .URL .Path == sseMessageEndpoint ) && r .Method == "POST" {
110+ if err := validateMCPToolCallScopes (r , scopes , mcpServer .GetEnabledTools ()); err != nil {
111+ klog .V (1 ).Infof ("Authorization failed for MCP tool call: %s %s from %s, error: %v" , r .Method , r .URL .Path , r .RemoteAddr , err )
112+ http .Error (w , "InsufficientScopeError" , http .StatusForbidden )
113+ return
114+ }
115+ }
91116
92117 // Now, there are a couple of options:
93118 // 1. If there is no authorization url configured for this MCP Server,
@@ -101,7 +126,7 @@ func AuthorizationMiddleware(requireOAuth bool, serverURL string, oidcProvider *
101126 // 2. b. If this is not the only token in the headers, the token in here is used
102127 // only for authentication and authorization. Therefore, we need to send TokenReview request
103128 // with the other token in the headers (TODO: still need to validate aud and exp of this token separately).
104- _ , _ , err = mcpServer .VerifyTokenAPIServer (r .Context (), token , audience )
129+ /* _, _, err = mcpServer.VerifyTokenAPIServer(r.Context(), token, audience)
105130 if err != nil {
106131 klog.V(1).Infof("Authentication failed - token validation error: %s %s from %s, error: %v", r.Method, r.URL.Path, r.RemoteAddr, err)
107132
@@ -112,13 +137,46 @@ func AuthorizationMiddleware(requireOAuth bool, serverURL string, oidcProvider *
112137 }
113138 http.Error(w, "Unauthorized: Invalid token", http.StatusUnauthorized)
114139 return
115- }
140+ }*/
116141
117142 next .ServeHTTP (w , r )
118143 })
119144 }
120145}
121146
147+ func validateMCPToolCallScopes (r * http.Request , tokenScopes , enabledScopes []string ) error {
148+ body , err := io .ReadAll (r .Body )
149+ if err != nil {
150+ return fmt .Errorf ("failed to read request body: %w" , err )
151+ }
152+
153+ r .Body = io .NopCloser (bytes .NewReader (body ))
154+
155+ var mcpReq MCPRequest
156+ if err := json .Unmarshal (body , & mcpReq ); err != nil {
157+ return fmt .Errorf ("failed to parse MCP request: %w" , err )
158+ }
159+
160+ if mcpReq .Method != "tools/call" {
161+ return nil
162+ }
163+
164+ toolName := mcpReq .Params .Name
165+ if toolName == "" {
166+ return fmt .Errorf ("tool name is empty" )
167+ }
168+
169+ if slices .Contains (enabledScopes , toolName ) {
170+ scopedToolName := "mcp:" + toolName
171+ if ! slices .Contains (tokenScopes , scopedToolName ) {
172+ return fmt .Errorf ("tool '%s' not allowed in scopes %v" , scopedToolName , tokenScopes )
173+ }
174+ klog .V (2 ).Infof ("MCP resource tool call authorized - tool '%s' found in scopes" , scopedToolName )
175+ }
176+
177+ return nil
178+ }
179+
122180var allSignatureAlgorithms = []jose.SignatureAlgorithm {
123181 jose .EdDSA ,
124182 jose .HS256 ,
0 commit comments