Skip to content

feat(mcp): support JWT claim forwarding to backend MCP servers - #1815

Merged
nacx merged 6 commits into
envoyproxy:mainfrom
blackfuel-ai:mcp-claim-headers
Mar 5, 2026
Merged

feat(mcp): support JWT claim forwarding to backend MCP servers#1815
nacx merged 6 commits into
envoyproxy:mainfrom
blackfuel-ai:mcp-claim-headers

Conversation

@mtparet

@mtparet mtparet commented Jan 26, 2026

Copy link
Copy Markdown
Contributor

Description

This commit adds JWT claim forwarding support to MCP (Model Context Protocol) routes, allowing gateway operators to extract claims from validated JWTs and forward them as HTTP headers to backend MCP servers.

Key features:

  • New claimToHeaders field in MCPRouteOAuth API to define claim-to-header mappings
  • Leverages Envoy Gateway's native ClaimToHeaders in SecurityPolicy.JWTProvider
  • Support for nested claims using dot notation (e.g., realm_access.roles)
  • Security: client-provided headers matching configured names are stripped to prevent forgery

This enables backends to access user identity for authorization, audit logging, or personalization without parsing JWTs themselves.

Example configuration:

spec:
  securityPolicy:
    oauth:
      claimToHeaders:
        - claim: sub
          header: X-User-Id
        - claim: email
          header: X-User-Email
        - claim: realm_access.roles
          header: X-User-Roles

Related Issues/PRs (if applicable)

Fixes #1725

Special notes for reviewers (if applicable)

  • Implementation leverages Envoy's JWT filter for claim extraction and validation - claims are read from headers set by Envoy, not re-extracted from the JWT
  • Maximum of 16 claim-to-header mappings are allowed per route to prevent abuse
    • Claim headers stored in session and forwarded on all backend requests (initialize + subsequent calls)

@mtparet
mtparet requested a review from a team as a code owner January 26, 2026 13:26
@dosubot dosubot Bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label Jan 26, 2026
@fbalicchia

Copy link
Copy Markdown
Contributor

Hi @mtparet, thanks for your work. Does this PR also forward the JWT Authorization header? If not, do you plan to add support for it?

@mtparet

mtparet commented Jan 27, 2026

Copy link
Copy Markdown
Contributor Author

@fbalicchia this PR does not enable forwarding the original JWT token, it could be added as another feature.

Comment thread internal/mcpproxy/handlers.go Outdated
Comment thread internal/mcpproxy/handlers.go Outdated
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:XL This PR changes 500-999 lines, ignoring generated files. labels Jan 29, 2026
@mtparet
mtparet force-pushed the mcp-claim-headers branch 3 times, most recently from 004d1ed to 12335c0 Compare January 29, 2026 09:36
Comment thread api/v1alpha1/mcp_route.go Outdated

@nacx nacx left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good. Thanks!

Could you take a look at the e2e tests and the internal testMCP server and see if you can add an end to end test that validates, on the server side, that the headers extracted from the claims are received?

You may be able to update the MCP OAuth e2e test to configure header extraction and update the test server to verify extracted headers are present?

@codecov-commenter

codecov-commenter commented Feb 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.00000% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.27%. Comparing base (58adc58) to head (1ec056f).

Files with missing lines Patch % Lines
internal/mcpproxy/mcpproxy.go 78.26% 3 Missing and 2 partials ⚠️
internal/controller/gateway.go 0.00% 2 Missing and 1 partial ⚠️
internal/mcpproxy/session.go 0.00% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1815      +/-   ##
==========================================
- Coverage   84.29%   84.27%   -0.02%     
==========================================
  Files         128      128              
  Lines       17549    17585      +36     
==========================================
+ Hits        14793    14820      +27     
- Misses       1829     1835       +6     
- Partials      927      930       +3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@mtparet
mtparet force-pushed the mcp-claim-headers branch 5 times, most recently from 50ad13b to a9536e4 Compare February 13, 2026 08:39
@mtparet

mtparet commented Feb 13, 2026

Copy link
Copy Markdown
Contributor Author

Could you take a look at the e2e tests and the internal testMCP server and see if you can add an end to end test that validates, on the server side, that the headers extracted from the claims are received?

You may be able to update the MCP OAuth e2e test to configure header extraction and update the test server to verify extracted headers are present?

@nacx I tried to setup some of this, let me know :)

@nacx nacx left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good. Thank you! (and apologies for the late review).

This mostly looks good, I just left a couple comments about making the overall code a bit cleaner and more generic on the MCP side.

Comment thread internal/mcpproxy/config.go Outdated
Comment thread internal/mcpproxy/handlers.go Outdated
Comment thread internal/mcpproxy/session.go Outdated
@dosubot dosubot Bot added size:XL This PR changes 500-999 lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Feb 20, 2026
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:XL This PR changes 500-999 lines, ignoring generated files. labels Feb 21, 2026
@nacx

nacx commented Feb 25, 2026

Copy link
Copy Markdown
Member

There is great progress in this PR. Any chance you could take a look at the final round of comments?

@mtparet

mtparet commented Feb 25, 2026

Copy link
Copy Markdown
Contributor Author

Yes sorry I was pretty busy these latest days, will adress latest comments soon.

@mtparet
mtparet force-pushed the mcp-claim-headers branch 3 times, most recently from 806e091 to 5b02445 Compare March 2, 2026 16:20
@mtparet

mtparet commented Mar 2, 2026

Copy link
Copy Markdown
Contributor Author

@nacx I addressed your last comments, let me know :)

@nacx nacx left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks very clean now. Thanks!

@nacx
nacx enabled auto-merge (squash) March 4, 2026 05:17
@nacx
nacx disabled auto-merge March 4, 2026 05:21
@mtparet
mtparet force-pushed the mcp-claim-headers branch from b58867f to cf60d52 Compare March 4, 2026 09:33
Add ClaimToHeaders configuration to MCPRoute OAuth settings that allows
extracting JWT claims and forwarding them as HTTP headers to backend
MCP servers. This enables backends to access user identity for
authorization, auditing, or personalization.

Implementation leverages Envoy Gateway's native ClaimToHeaders feature
in the SecurityPolicy's JWTProvider. Envoy's JWT filter extracts the
configured claims and sets them as request headers, which the MCP proxy
then reads and forwards to backends.

Key changes:
- Add ClaimToHeaders field to MCPRouteOAuth API with validation
- Configure JWTProvider.ClaimToHeaders in SecurityPolicy controller
- MCP proxy reads claim headers from request and forwards to backends
- Security: client-provided headers matching claim headers are stripped

refactor(mcp): use Envoy Gateway's ClaimToHeader type

Replace the custom ClaimToHeader type definition with egv1a1.ClaimToHeader
to reduce code duplication between AI Gateway and Envoy Gateway.

fix codegen to append license header

refactor(mcp): rename claimHeaders to extraHeaders and generalize forward headers

Replace the JWT-specific ClaimToHeader config with a generic ForwardHeaders
string list, and rename the session field from claimHeaders to extraHeaders
to reflect that these are arbitrary request headers forwarded to backends,
not necessarily JWT claims.

Signed-off-by: mtparet <mp@blackfuel.ai>
Signed-off-by: Matthieu Paret <mp@blackfuel.ai>
@mtparet
mtparet force-pushed the mcp-claim-headers branch from cf60d52 to 77cc14f Compare March 4, 2026 14:11

@zhaohuabing zhaohuabing left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks!

@mtparet

mtparet commented Mar 4, 2026

Copy link
Copy Markdown
Contributor Author

(I pushed a fix for e2e test)

Comment thread Makefile
mtparet and others added 2 commits March 5, 2026 10:25
@nacx
nacx enabled auto-merge (squash) March 5, 2026 16:05
Signed-off-by: Matthieu Paret <mp@blackfuel.ai>
auto-merge was automatically disabled March 5, 2026 19:59

Head branch was pushed to by a user without write access

@nacx
nacx enabled auto-merge (squash) March 5, 2026 20:36
@nacx
nacx merged commit 5ffad94 into envoyproxy:main Mar 5, 2026
34 checks passed
@missBerg missBerg mentioned this pull request May 1, 2026
4 tasks
nacx pushed a commit that referenced this pull request May 5, 2026
**Description**

Draft release notes for the v0.6.x series, mirroring the existing v0.5
layout. Filed as a draft so reviewers can correct and refine content
while v0.6.0 is still in RC.

**Three commits:**

1. `build: exclude .git, .claude, .cursor from license-eye` — small
drive-by tooling fix. license-eye walks the entire repo and, on
checkouts that contain extra metadata under `.git/worktrees/`,
`.claude/`, or `.cursor/`, it descends into them, hits unsupported file
types, and exits non-zero — failing `make precommit` for changes that
don't touch any source code. None of these directories ever contain
license-bearing project sources, so they belong in `paths-ignore`. Happy
to split this into its own PR if preferred.
2. `site: v0.6 release notes` — the rendered release notes (JSON data,
MDX page, navigation, index card).
3. `release-notes: add plain-markdown copy of v0.6.0 notes` — adds a new
top-level `release-notes/` directory with `v0.6.0.md`, a self-contained
plain-markdown rendering of the same content for direct copy-paste into
the GitHub release body. Sits alongside `RELEASES.md` (the release
process doc).

### Notable content

**Breaking change** — AIGatewayRoute `filterConfig` field has been
removed (#1900). Migration guidance to `GatewayConfig` is in the
**Upgrade Guidance** section of `v0.6.mdx` (and mirrored in
`release-notes/v0.6.0.md`).

**Highlights:**

- Core CRDs + MCPRoute promoted to `v1beta1` (#1900, #2090)
- AWS Bedrock InvokeModel for Claude (#1648), OpenAI ↔ Bedrock
embeddings (#1969)
- Anthropic endpoint on OpenAI backends (#1878), structured output for
Claude (#1846)
- Gemini embeddings (#1625), prefix-style context caching (#1792)
- MCP per-backend ForwardHeaders (#2047), JWT claim forwarding (#1815),
tool-selector excludes (#1963)
- GKE Workload Identity via ADC (#1979), webhook host network (#1954)
- Request/response body redaction (#1758), OTLP access logging in `aigw`
(#1832)
- Responses API phase 2 (#1791), Open Responses API compat (#1847),
batch inference (#1779)
- Dependencies: Go 1.26.2, Envoy Gateway v1.7.0, Envoy v1.37, Gateway
API v1.4.1, MCP Go SDK 1.4.1

### Test plan

- [x] `cd site && npm install && npm run build` — completes successfully
(only warnings are pre-existing broken anchors in old docs versions,
none on the new pages)
- [x] `npm run start` — confirmed `/release-notes/` features v0.6.x with
the "Latest" badge and demotes v0.5.x; `/release-notes/v0.6` renders all
sections (Features, API Updates, Breaking Changes, Bug Fixes, Upgrade
Guidance, Dependencies, Patch Releases placeholder, Acknowledgements,
What's Next) with no console errors
- [x] `release-notes/v0.6.0.md` reviewed for self-containment (no MDX
residue, no `<code>` tags, links to rendered version included)
- [x] `make precommit` passes on this branch (after the license-eye fix)

**Related Issues/PRs (if applicable)**

None directly. Cross-references to the individual feature/fix PRs are
inline in the release-notes content above.

**Special notes for reviewers (if applicable)**

- Release date is currently a placeholder (`May 15, 2026`). Update both
the JSON (`site/src/data/releases/v0.6.json`) and the
`release-notes/v0.6.0.md` heading area before merge to the actual
`v0.6.0` final tag date.
- Any commits landing between this draft and the tag should be folded in
— the JSON, MDX, and the plain-markdown copy each need updating; happy
to script this into the existing release-notes workflow if maintainers
want.
- Each item was cross-checked against `api/v1alpha1/`,
`internal/translator/`, `internal/extproc/`, `internal/controller/`, and
`internal/backendauth/` per the existing release-notes workflow, but a
fresh pair of eyes on accuracy is very welcome — call out anything
that's mischaracterized, missing, or shouldn't be there.
- Drafted with AI assistance (Claude Code) following the existing v0.5
release-notes pattern; every item was cross-checked against the source
PRs before inclusion.

---------

Signed-off-by: Erica Hughberg <erica.sundberg.90@gmail.com>
anurags25 pushed a commit to anurags25/ai-gateway that referenced this pull request May 12, 2026
**Description**

Draft release notes for the v0.6.x series, mirroring the existing v0.5
layout. Filed as a draft so reviewers can correct and refine content
while v0.6.0 is still in RC.

**Three commits:**

1. `build: exclude .git, .claude, .cursor from license-eye` — small
drive-by tooling fix. license-eye walks the entire repo and, on
checkouts that contain extra metadata under `.git/worktrees/`,
`.claude/`, or `.cursor/`, it descends into them, hits unsupported file
types, and exits non-zero — failing `make precommit` for changes that
don't touch any source code. None of these directories ever contain
license-bearing project sources, so they belong in `paths-ignore`. Happy
to split this into its own PR if preferred.
2. `site: v0.6 release notes` — the rendered release notes (JSON data,
MDX page, navigation, index card).
3. `release-notes: add plain-markdown copy of v0.6.0 notes` — adds a new
top-level `release-notes/` directory with `v0.6.0.md`, a self-contained
plain-markdown rendering of the same content for direct copy-paste into
the GitHub release body. Sits alongside `RELEASES.md` (the release
process doc).

### Notable content

**Breaking change** — AIGatewayRoute `filterConfig` field has been
removed (envoyproxy#1900). Migration guidance to `GatewayConfig` is in the
**Upgrade Guidance** section of `v0.6.mdx` (and mirrored in
`release-notes/v0.6.0.md`).

**Highlights:**

- Core CRDs + MCPRoute promoted to `v1beta1` (envoyproxy#1900, envoyproxy#2090)
- AWS Bedrock InvokeModel for Claude (envoyproxy#1648), OpenAI ↔ Bedrock
embeddings (envoyproxy#1969)
- Anthropic endpoint on OpenAI backends (envoyproxy#1878), structured output for
Claude (envoyproxy#1846)
- Gemini embeddings (envoyproxy#1625), prefix-style context caching (envoyproxy#1792)
- MCP per-backend ForwardHeaders (envoyproxy#2047), JWT claim forwarding (envoyproxy#1815),
tool-selector excludes (envoyproxy#1963)
- GKE Workload Identity via ADC (envoyproxy#1979), webhook host network (envoyproxy#1954)
- Request/response body redaction (envoyproxy#1758), OTLP access logging in `aigw`
(envoyproxy#1832)
- Responses API phase 2 (envoyproxy#1791), Open Responses API compat (envoyproxy#1847),
batch inference (envoyproxy#1779)
- Dependencies: Go 1.26.2, Envoy Gateway v1.7.0, Envoy v1.37, Gateway
API v1.4.1, MCP Go SDK 1.4.1

### Test plan

- [x] `cd site && npm install && npm run build` — completes successfully
(only warnings are pre-existing broken anchors in old docs versions,
none on the new pages)
- [x] `npm run start` — confirmed `/release-notes/` features v0.6.x with
the "Latest" badge and demotes v0.5.x; `/release-notes/v0.6` renders all
sections (Features, API Updates, Breaking Changes, Bug Fixes, Upgrade
Guidance, Dependencies, Patch Releases placeholder, Acknowledgements,
What's Next) with no console errors
- [x] `release-notes/v0.6.0.md` reviewed for self-containment (no MDX
residue, no `<code>` tags, links to rendered version included)
- [x] `make precommit` passes on this branch (after the license-eye fix)

**Related Issues/PRs (if applicable)**

None directly. Cross-references to the individual feature/fix PRs are
inline in the release-notes content above.

**Special notes for reviewers (if applicable)**

- Release date is currently a placeholder (`May 15, 2026`). Update both
the JSON (`site/src/data/releases/v0.6.json`) and the
`release-notes/v0.6.0.md` heading area before merge to the actual
`v0.6.0` final tag date.
- Any commits landing between this draft and the tag should be folded in
— the JSON, MDX, and the plain-markdown copy each need updating; happy
to script this into the existing release-notes workflow if maintainers
want.
- Each item was cross-checked against `api/v1alpha1/`,
`internal/translator/`, `internal/extproc/`, `internal/controller/`, and
`internal/backendauth/` per the existing release-notes workflow, but a
fresh pair of eyes on accuracy is very welcome — call out anything
that's mischaracterized, missing, or shouldn't be there.
- Drafted with AI assistance (Claude Code) following the existing v0.5
release-notes pattern; every item was cross-checked against the source
PRs before inclusion.

---------

Signed-off-by: Erica Hughberg <erica.sundberg.90@gmail.com>
Signed-off-by: Anurag Saykar <anuragsaikar100@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support JWT claim forwarding to backend MCP servers

5 participants