Skip to content

OIDCProxy forwards MCP resource indicator to upstream IdP, causing invalid_target errors #3939

@aminabedi-bentley

Description

@aminabedi-bentley

Summary

When an MCP client (e.g. VS Code Copilot, Copilot CLI) sends an RFC 8707 resource parameter in the OAuth authorization request, OIDCProxy forwards it verbatim to the upstream Identity Provider. Upstream IdPs that don't recognise the MCP server URL as a valid resource reject the request with invalid_target.

Steps to reproduce

  1. Configure OIDCProxy as a third-party auth proxy in front of an IdP (e.g. Bentley IMS / PingFederate)
  2. Connect from an MCP client that sends a resource parameter (recent VS Code Copilot, Copilot CLI)
  3. The upstream IdP returns:
    400 - invalid_target: The specified resource(s) did not match any
    access token manager for the selected client and authentication context
    

Root cause

In consent.py, _build_upstream_authorize_url forwards the resource from the transaction to the upstream authorize URL:

# Forward resource indicator if present in transaction
if resource := transaction.get("resource"):
    query_params["resource"] = resource

The resource value is the MCP server's own URL (e.g. http://localhost:5000/mcp), set by the MCP client per the MCP OAuth spec. This makes sense for the proxy's own local validation (lines 727-745 of proxy.py), but should not be forwarded upstream — the upstream IdP has its own resource model and doesn't know about the downstream MCP server URL.

Expected behaviour

OIDCProxy should:

  • ✅ Validate the client's resource parameter locally (already done)
  • ✅ Use it as the audience for the downstream JWT it issues (already done)
  • Not forward it to the upstream IdP's authorize or token endpoints

Workaround

Subclass OIDCProxy and strip resource from the upstream authorize URL:

class _FixedOIDCProxy(OIDCProxy):
    def _build_upstream_authorize_url(self, txn_id, transaction):
        url = super()._build_upstream_authorize_url(txn_id, transaction)
        parsed = urlparse(url)
        params = parse_qs(parsed.query, keep_blank_values=True)
        if "resource" in params:
            del params["resource"]
            return urlunparse(parsed._replace(query=urlencode(params, doseq=True)))
        return url

Environment

  • fastmcp 3.1.1 (also present in 3.0.2)
  • Upstream IdP: Bentley IMS (PingFederate-based)
  • MCP clients: VS Code Copilot, GitHub Copilot CLI

Metadata

Metadata

Assignees

No one assigned

    Labels

    authRelated to authentication (Bearer, JWT, OAuth, WorkOS) for client or server.bugSomething isn't working. Reports of errors, unexpected behavior, or broken functionality.potential-duplicateBot-suggested duplicate awaiting human review. Auto-closes after 3 days if unchallenged.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions