Skip to content

Commit fb86c58

Browse files
authored
mcp: include scopes supported in the www-authenticate header (#1608)
**Description** Add the configured scopes to the `WWW-Authenticate` headers. At initialization time, which is when the first authentication will occur, we don't have enough information to provide a fine-grained list of scopes, so the best we can do is to default to the ones defined in the protected resource metadata. **Related Issues/PRs (if applicable)** Fixes #1578 The addition of the header on 403 requests is implemented in #1482, but this issue can be closed as soon as this PR is merged, because we'll be compatible with the latest spec. **Special notes for reviewers (if applicable)** cc @zhaohuabing can you take a look? Signed-off-by: Ignasi Barrera <[email protected]>
1 parent 6d7059c commit fb86c58

File tree

3 files changed

+41
-2
lines changed

3 files changed

+41
-2
lines changed

internal/controller/mcp_route_security_policy.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,11 @@ func buildWWWAuthenticateHeaderValue(metadata *aigv1a1.ProtectedResourceMetadata
323323
// Add resource_metadata as per RFC 9728 Section 5.1.
324324
headerValue = fmt.Sprintf(`%s, resource_metadata="%s"`, headerValue, resourceMetadataURL)
325325

326+
if len(metadata.ScopesSupported) > 0 {
327+
// Add scope as per RFC 6750 Section 3.
328+
headerValue = fmt.Sprintf(`%s, scope="%s"`, headerValue, strings.Join(metadata.ScopesSupported, " "))
329+
}
330+
326331
return headerValue
327332
}
328333

internal/controller/mcp_route_security_policy_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,30 @@ func Test_buildWWWAuthenticateHeaderValue(t *testing.T) {
654654
},
655655
expected: `Bearer error="invalid_request", error_description="No access token was provided in this request", resource_metadata="https://api.example.com/.well-known/oauth-protected-resource/v1/mcp/endpoint"`,
656656
},
657+
{
658+
name: "with empty scopes supported",
659+
metadata: &aigv1a1.ProtectedResourceMetadata{
660+
Resource: "https://api.example.com/mcp",
661+
ScopesSupported: []string{},
662+
},
663+
expected: `Bearer error="invalid_request", error_description="No access token was provided in this request", resource_metadata="https://api.example.com/.well-known/oauth-protected-resource/mcp"`,
664+
},
665+
{
666+
name: "with single scope supported",
667+
metadata: &aigv1a1.ProtectedResourceMetadata{
668+
Resource: "https://api.example.com/mcp",
669+
ScopesSupported: []string{"read"},
670+
},
671+
expected: `Bearer error="invalid_request", error_description="No access token was provided in this request", resource_metadata="https://api.example.com/.well-known/oauth-protected-resource/mcp", scope="read"`,
672+
},
673+
{
674+
name: "with multiple scopes supported",
675+
metadata: &aigv1a1.ProtectedResourceMetadata{
676+
Resource: "https://api.example.com/mcp",
677+
ScopesSupported: []string{"read", "write"},
678+
},
679+
expected: `Bearer error="invalid_request", error_description="No access token was provided in this request", resource_metadata="https://api.example.com/.well-known/oauth-protected-resource/mcp", scope="read write"`,
680+
},
657681
}
658682

659683
for _, tt := range tests {

tests/e2e/mcp_route_oauth_test.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,12 @@ func TestMCPRouteOAuth(t *testing.T) {
150150
require.Contains(t, wwwAuthHeader, "Bearer", "WWW-Authenticate header should contain Bearer scheme")
151151

152152
// Validate WWW-Authenticate header contains resource_metadata parameter.
153-
require.Contains(t, wwwAuthHeader, "resource_metadata", "WWW-Authenticate header should contain resource_metadata parameter")
153+
require.Contains(t, wwwAuthHeader, `resource_metadata="https://foo.bar.com/.well-known/oauth-protected-resource/mcp"`,
154+
"WWW-Authenticate header should contain resource_metadata parameter")
155+
t.Logf("WWW-Authenticate header: %s", wwwAuthHeader)
156+
157+
// Validate WWW-Authenticate header contains scope parameter.
158+
require.Contains(t, wwwAuthHeader, `scope="echo sum countdown"`, "WWW-Authenticate header should contain resource_metadata parameter")
154159
t.Logf("WWW-Authenticate header: %s", wwwAuthHeader)
155160
})
156161

@@ -320,7 +325,12 @@ func TestMCPRouteOAuth(t *testing.T) {
320325
require.Contains(t, wwwAuthHeader, "Bearer", "WWW-Authenticate header should contain Bearer scheme")
321326

322327
// Validate WWW-Authenticate header contains resource_metadata parameter.
323-
require.Contains(t, wwwAuthHeader, "resource_metadata", "WWW-Authenticate header should contain resource_metadata parameter")
328+
require.Contains(t, wwwAuthHeader, `resource_metadata="https://foo.bar.com/.well-known/oauth-protected-resource/mcp"`,
329+
"WWW-Authenticate header should contain resource_metadata parameter")
330+
t.Logf("WWW-Authenticate header: %s", wwwAuthHeader)
331+
332+
// Validate WWW-Authenticate header contains scope parameter.
333+
require.Contains(t, wwwAuthHeader, `scope="echo sum countdown"`, "WWW-Authenticate header should contain resource_metadata parameter")
324334
t.Logf("WWW-Authenticate header: %s", wwwAuthHeader)
325335
})
326336
}

0 commit comments

Comments
 (0)