You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Title: OAuth 2.0 Token Exchange (RFC 8693) β User Delegation for Virtual Servers Goal: Allow ContextForge Virtual Servers to forward MCP tool/resource requests to upstream MCP servers using OAuth 2.0 Token Exchange (on-behalf-of), so that downstream actions carry the identity and scoped permissions of the original authenticated user β not a shared service account. Why now: Multi-user MCP clients like LibreChat authenticate end-users via OAuth against ContextForge Virtual Servers (using the existing RFC 9728 + Authorization Code flow). When ContextForge then fans out to upstream MCP servers that also require OAuth, the current implementation has no way to exchange the user's inbound token for an audience-restricted token scoped to the upstream server. This forces operators to either share a single service credential (losing per-user auditability) or bypass OAuth on upstream servers entirely.
MCP's use of OAuth 2.1, resource indicators, and protected resource metadata
Delegation vs. Impersonation (RFC 8693 Β§1.1):
Delegation (preferred): The exchanged token carries both a sub (original user) and act (ContextForge service) claim, creating a clear audit trail.
Impersonation: The exchanged token appears as if the original user issued it directly. Simpler but weaker auditability.
ContextForge should support delegation as the default and allow impersonation as an opt-in for IDPs that don't support the act claim.
Proposed Design
sequenceDiagram
participant Client as LibreChat / MCP Client
participant VS as ContextForge Virtual Server
participant AS as Authorization Server (IDP)
participant US as Upstream MCP Server
Client->>VS: MCP request (Bearer user_token)
VS->>VS: Validate user_token (existing JWT verification)
VS->>AS: POST /token (grant_type=token-exchange,<br/>subject_token=user_token,<br/>resource=upstream_server_url,<br/>audience=upstream_client_id)
AS-->>VS: exchanged_token (scoped to upstream, act claim)
VS->>US: MCP request (Bearer exchanged_token)
US-->>VS: MCP response
VS-->>Client: MCP response
πββοΈ User Story 1 β Per-User Token Exchange on Tool Invocation
As a: platform administrator I want: upstream MCP servers to receive a token scoped to the calling user's identity So that: per-user audit trails, rate limits, and access policies are enforced end-to-end through the gateway
β Acceptance Criteria
Scenario: Successful token exchange on tool callGiven a Virtual Server with an upstream gateway configured for token-exchange
And the upstream gateway's IDP supports RFC 8693
When an authenticated user calls a tool via the Virtual Server
Then ContextForge exchanges the user's inbound token for an upstream-scoped token
And the upstream request carries the exchanged token with "act" claim identifying ContextForge
And the tool response is returned to the user
Scenario: Token exchange failure falls back to errorGiven a Virtual Server with token-exchange configured
When the IDP rejects the exchange (invalid_grant, scope mismatch)
Then the tool call returns an error to the client with a clear message
And the failure is logged with correlation ID
Scenario: Cached exchanged token is reused within validity windowGiven a previously exchanged token that is still valid
When the same user calls another tool on the same upstream
Then the cached exchanged token is reused without a new exchange request
πββοΈ User Story 2 β Admin Configuration of Token Exchange
As a: platform administrator I want: to configure token exchange parameters per upstream gateway via the Admin UI and API So that: I can enable user delegation without modifying upstream server configurations
β Acceptance Criteria
Scenario: Configure token exchange via Admin UIGiven the gateway edit form in the Admin UI
When the admin selects "Token Exchange" as the grant type
Then the form shows fields for token_endpoint, client_id, client_secret, resource, audience
And the client_secret is encrypted before storage
Scenario: Configure token exchange via APIGiven a PATCH /gateways/{id} request with oauth_config.grant_type = "token-exchange"When the request includes valid token_endpoint and client credentials
Then the gateway configuration is updated
And the oauth_config is validated for required fields
πββοΈ User Story 3 β LibreChat Multi-User Delegation
As a: LibreChat user authenticating via OAuth to a ContextForge Virtual Server I want: my identity to be preserved when ContextForge calls upstream MCP servers on my behalf So that: upstream servers can enforce per-user policies and I see only my authorized tools/resources
β Acceptance Criteria
Scenario: LibreChat user identity preserved through gatewayGiven LibreChat authenticates user via OAuth against a ContextForge Virtual Server
And the Virtual Server fans out to upstream MCP servers with token-exchange
When the user calls a tool
Then the upstream server receives a token with sub=user and act=contextforge
And team-scoped filtering applies based on the user's identity, not a service account
π Implementation Considerations
OAuthManager Extension: Add token-exchange grant type alongside existing authorization_code, client_credentials, and password flows in mcpgateway/services/oauth_manager.py.
Token Caching: Cache exchanged tokens per (user, upstream_gateway) pair in the existing oauth_tokens table with appropriate TTL.
Subject Token Extraction: Extract the inbound Bearer token from the MCP request context and pass it as subject_token to the exchange endpoint.
Fallback Behavior: If token exchange fails and a client_credentials fallback is configured, optionally fall back to M2M token (see companion epic).
Encryption: Reuse existing protect_oauth_config_for_storage() / decrypt_oauth_config_for_runtime() for client secrets.
π MCP Standards Check
Change adheres to current MCP specifications β MCP Authorization spec mandates OAuth 2.1 with resource indicators
No breaking changes to existing MCP-compliant integrations β new grant type is additive
RFC 9728 metadata endpoints remain unchanged
π Alternatives Considered
Alternative
Why not
Pass inbound token directly to upstream
Audience mismatch; upstream server may reject tokens not issued for it
Use client_credentials for all upstream calls
Loses per-user identity; no audit trail of which user triggered which action
Require each user to separately OAuth into each upstream
Poor UX; ContextForge's value is transparent federation
π Additional Context
Companion epic: Machine-to-Machine (Client Credentials) for Virtual Servers β covers the non-delegated case
π§ Type of Feature
π§ Epic
Title: OAuth 2.0 Token Exchange (RFC 8693) β User Delegation for Virtual Servers
Goal: Allow ContextForge Virtual Servers to forward MCP tool/resource requests to upstream MCP servers using OAuth 2.0 Token Exchange (on-behalf-of), so that downstream actions carry the identity and scoped permissions of the original authenticated user β not a shared service account.
Why now: Multi-user MCP clients like LibreChat authenticate end-users via OAuth against ContextForge Virtual Servers (using the existing RFC 9728 + Authorization Code flow). When ContextForge then fans out to upstream MCP servers that also require OAuth, the current implementation has no way to exchange the user's inbound token for an audience-restricted token scoped to the upstream server. This forces operators to either share a single service credential (losing per-user auditability) or bypass OAuth on upstream servers entirely.
Background & Standards
urn:ietf:params:oauth:grant-type:token-exchangegrant type for trading one token for anotherDelegation vs. Impersonation (RFC 8693 Β§1.1):
sub(original user) andact(ContextForge service) claim, creating a clear audit trail.ContextForge should support delegation as the default and allow impersonation as an opt-in for IDPs that don't support the
actclaim.Proposed Design
sequenceDiagram participant Client as LibreChat / MCP Client participant VS as ContextForge Virtual Server participant AS as Authorization Server (IDP) participant US as Upstream MCP Server Client->>VS: MCP request (Bearer user_token) VS->>VS: Validate user_token (existing JWT verification) VS->>AS: POST /token (grant_type=token-exchange,<br/>subject_token=user_token,<br/>resource=upstream_server_url,<br/>audience=upstream_client_id) AS-->>VS: exchanged_token (scoped to upstream, act claim) VS->>US: MCP request (Bearer exchanged_token) US-->>VS: MCP response VS-->>Client: MCP responseGateway
oauth_configExtension{ "grant_type": "token-exchange", "token_endpoint": "https://idp.example.com/oauth/token", "client_id": "contextforge-gateway", "client_secret": "encrypted:...", "resource": "https://upstream.example.com/mcp", "audience": "upstream-mcp-client-id", "requested_token_type": "urn:ietf:params:oauth:token-type:access_token", "subject_token_type": "urn:ietf:params:oauth:token-type:access_token", "scopes": ["tools:read", "tools:execute"] }πββοΈ User Story 1 β Per-User Token Exchange on Tool Invocation
As a: platform administrator
I want: upstream MCP servers to receive a token scoped to the calling user's identity
So that: per-user audit trails, rate limits, and access policies are enforced end-to-end through the gateway
β Acceptance Criteria
πββοΈ User Story 2 β Admin Configuration of Token Exchange
As a: platform administrator
I want: to configure token exchange parameters per upstream gateway via the Admin UI and API
So that: I can enable user delegation without modifying upstream server configurations
β Acceptance Criteria
πββοΈ User Story 3 β LibreChat Multi-User Delegation
As a: LibreChat user authenticating via OAuth to a ContextForge Virtual Server
I want: my identity to be preserved when ContextForge calls upstream MCP servers on my behalf
So that: upstream servers can enforce per-user policies and I see only my authorized tools/resources
β Acceptance Criteria
π Implementation Considerations
token-exchangegrant type alongside existingauthorization_code,client_credentials, andpasswordflows inmcpgateway/services/oauth_manager.py.oauth_tokenstable with appropriate TTL.subject_tokento the exchange endpoint.protect_oauth_config_for_storage()/decrypt_oauth_config_for_runtime()for client secrets.π MCP Standards Check
π Alternatives Considered
π Additional Context
docs/docs/architecture/oauth-design.md