mcp: JWT scope based authorization#1482
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1482 +/- ##
==========================================
- Coverage 83.46% 83.17% -0.29%
==========================================
Files 138 139 +1
Lines 12540 12711 +171
==========================================
+ Hits 10466 10573 +107
- Misses 1440 1494 +54
- Partials 634 644 +10 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
d1ea251 to
877d598
Compare
4d2e8d5 to
0ccf737
Compare
29db138 to
2dd862c
Compare
2167726 to
a95087c
Compare
b77c937 to
f8a07cc
Compare
api/v1alpha1/mcp_route.go
Outdated
| // +kubebuilder:validation:Optional | ||
| // +kubebuilder:validation:MaxLength=4096 | ||
| // +optional | ||
| Arguments *string `json:"arguments,omitempty"` |
There was a problem hiding this comment.
After discussing this with @nacx offline, we chose CEL for argument evaluation.
CEL is easier to write and understand than regex evaluation when the arguments are complex object types.
For example:
To check against this argument:
{
"key-level1": {
"key-level2": {
"key-level3": "value"
}
}
}The regex match:
/"key-level1"\s*:\s*\{\s*"key-level2"\s*:\s*\{\s*"key-level3"\s*:\s*"value"\s*\}\s*\}/gm
The CEL match:
args["key-level1"]["key-level2"]["key-level3"] == "value"
There was a problem hiding this comment.
WDYT about renaming this to condition? If we do this, we could also easily support meta in addition to args to have policies based on the contents of the metadata.
There was a problem hiding this comment.
Should meta be inside the ToolCall, or MCPAuthorizationTarget?
There was a problem hiding this comment.
Oh meta is part of the CallToolParams - then condition is a good.
There was a problem hiding this comment.
Thinking about it again, condition feels a bit vague and also used in the status field, some alternatives:
- When
- Predicate
- Matcher
When reads naturally in the context of authorization rules:
tools:
- backend: backend1
tool: listFiles
when: args.folder == "restricted"There was a problem hiding this comment.
I like the when better than the other two options. It is also used for additional conditions in Istio AuthorizationPolicies, so it's not a completely foreign name.
Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>
internal/mcpproxy/authorization.go
Dismissed
| } else { | ||
| claims := jwt.MapClaims{} | ||
| // JWT verification is performed by Envoy before reaching here. So we only need to parse the token without verification. | ||
| if _, _, err := jwt.NewParser().ParseUnverified(token, claims); err != nil { |
Check failure
Code scanning / CodeQL
Missing JWT signature check High
There was a problem hiding this comment.
Explained in the comment why verification is not needed here
Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>
https://github.com/envoyproxy/ai-gateway/pull/1482/files#r2587966887 Thinking about it again, condition feels a bit vague and also used in the status field, some alternatives:
When reads naturally in the context of authorization rules: tools:
- backend: backend1
tool: listFiles
when: args.folder == "restricted" |
Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>
b908be0 to
9271f0b
Compare
Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>
nacx
left a comment
There was a problem hiding this comment.
LGTM! Just one final comment about validations and API semantics, but this looks good!
Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>
4d456ac to
fc15467
Compare
Signed-off-by: Huabing(Robin) Zhao <zhaohuabing@gmail.com>
395cf5a to
fefa1e6
Compare
Signed-off-by: Huabing(Robin) Zhao <zhaohuabing@gmail.com>
|
The CI failure is not related to this PR: We'll need to create a PAT to access the Github MCP server. |
|
Yeah, looks like there are changes on the GH MCP: github/github-mcp-server#1610 |
|
/retest |
…oxy#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 envoyproxy#1578 The addition of the header on 403 requests is implemented in envoyproxy#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 <nacx@apache.org> Signed-off-by: Erica Hughberg <erica.sundberg.90@gmail.com>
**Description** This PR introduces MCP spec compatible scope based authorization for MCPRoutes. According to the 2025-11-25 version of the MCP spec, the MCP Gateway should enforce scope-based authorization on behalf of the backend MCP server, and include the required scopes in the `WWW-Authenticate` header of the 403 response if authoriation fails. https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#runtime-insufficient-scope-errors > When a client makes a request with an access token with insufficient scope during runtime operations, the server SHOULD respond with: HTTP 403 Forbidden status code WWW-Authenticate header with the Bearer scheme and additional parameters: error="insufficient_scope" - indicating the specific type of authorization failure scope="required_scope1 required_scope2" - specifying the minimum scopes needed for the operation resource_metadata - the URI of the Protected Resource Metadata document (for consistency with 401 responses) error_description (optional) - human-readable description of the error Example: ```yaml spec: ... securityPolicy: oauth: ... authorization: rules: - source: jwt: scopes: - echo target: tools: - backend: mcp-backend-authorization toolName: echo when: args.text.matches("^Hello, .*!$") - source: jwt: scopes: - sum target: tools: - backend: mcp-backend-authorization toolName: sum ``` Reference: https://modelcontextprotocol.io/specification/draft/basic/authorization#scope-challenge-handling Implements envoyproxy#1459 --------- Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com> Signed-off-by: Huabing(Robin) Zhao <zhaohuabing@gmail.com> Co-authored-by: Ignasi Barrera <ignasi@tetrate.io> Signed-off-by: Erica Hughberg <erica.sundberg.90@gmail.com>
Description
This PR introduces MCP spec compatible scope based authorization for MCPRoutes.
According to the 2025-11-25 version of the MCP spec, the MCP Gateway should enforce scope-based authorization on behalf of the backend MCP server, and include the required scopes in the
WWW-Authenticateheader of the 403 response if authoriation fails.https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#runtime-insufficient-scope-errors
Example:
Reference: https://modelcontextprotocol.io/specification/draft/basic/authorization#scope-challenge-handling
Implements #1459