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
- Configure
OIDCProxy as a third-party auth proxy in front of an IdP (e.g. Bentley IMS / PingFederate)
- Connect from an MCP client that sends a
resource parameter (recent VS Code Copilot, Copilot CLI)
- 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
Summary
When an MCP client (e.g. VS Code Copilot, Copilot CLI) sends an RFC 8707
resourceparameter in the OAuth authorization request,OIDCProxyforwards it verbatim to the upstream Identity Provider. Upstream IdPs that don't recognise the MCP server URL as a valid resource reject the request withinvalid_target.Steps to reproduce
OIDCProxyas a third-party auth proxy in front of an IdP (e.g. Bentley IMS / PingFederate)resourceparameter (recent VS Code Copilot, Copilot CLI)Root cause
In
consent.py,_build_upstream_authorize_urlforwards theresourcefrom the transaction to the upstream authorize URL:The
resourcevalue 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 ofproxy.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
OIDCProxyshould:resourceparameter locally (already done)Workaround
Subclass
OIDCProxyand stripresourcefrom the upstream authorize URL:Environment