- Notifications
You must be signed in to change notification settings - Fork 615
Allow WWW-Authenticate header to be accessed by client #402
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Allow WWW-Authenticate header to be accessed by client #402
Conversation
In case it's useful at all, I've got a demo MCP server that returns 401/www-authenticate here! I have tested this with a client that handles auth properly according to spec and it's working. Would be great if the inspector worked this way 👀 |
Only problem is I don't have a Clerk application. If you don't mind, though, keep an eye on this PR and when it gets merged, raise a flag here if it doesn't work with your server. |
Hey Folks, Just wanted to know what is currently blocking this MR and if needed how I can help. This would be awesome to get merged in considering the latest spec. |
Waiting for SDK support. @pcarleton can probably elucidate the gap. |
Hmm do you mean modelcontextprotocol/typescript-sdk#503 (this is server side) and this is client side modelcontextprotocol/typescript-sdk#416 unless im missing something. |
@pcarleton mentioned this one when I asked in Discord. But I'm not certain that keeps 401 from reaching the client, it's just related to scopes. So I'll look into this one again today. ALSO: A big problem Is I don't have an in-project server that returns a 401 with this header for testing. |
I am in the process of building one.. if you can get the MR up I can try testing! |
Thanks @MOmarMiraj but I don't want to merge this without testing, there might be more to do. However, in order to test this PR, you could
|
I see in the request that it is exposed and when I try to start the server.. it immediately runs the OAuth flow and tries to fetch the When I change the status code to 200 but send the |
@MOmarMiraj This report is sort of confusing. You never see the 401 at the client? |
@MOmarMiraj Can you show the output both when you do and when you don't have these changes in place for comparison? Open the network tab of devtools and select any request to view its response headers directly, without relying on error console output. this is what we really want to verify. E.g, ... ![]() |
Ok, right. But if you notice, the actual header you're looking at is What I need to see now, is the headers of a request that returned a 401 response. In that case, we want to see |
Ok this is super useful. We can see that the server is actually returning a WWW-Authenticate header with a 401, but that isn't making back to the client via the proxy. There are two transports involved; Client <-> Proxy and Proxy <-> Server. This report tells me we need to do more to bridge the headers across the transports in the proxy. |
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
adding a guard to ensure error is an object before attempting to attach the authHeader property. Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Narrow rather than cast for error handling Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
…ader-to-client Forward WWW-Authenticate to client
@MOmarMiraj I've had a crack at a fix. Can you test it again? |
@cliffhall Just tried it and still similar issue, don't see the 401 on the network side of things. |
Also what I noticed, the MCP inspector doesn't extract the URL from the WWW-Authenticate header. No matter the value of the |
It would be great to see the response header list there. The screenshot says there are 10 headers, but the header list is closed |
First we have to get the header. We're working on that part. |
The second paragraph speaking about the server metadata URL. If we look inside the RFC pointed out in the MCP spec, you can see it talks about the full URL. |
Response headers. Can you show them? You posted the request headers.
|
Ok, thanks @MOmarMiraj for your help. I'm going to have to figure out a good way to test this locally. |
I believe the issue lies in the SDK not accepting a custom fetch function. I believe you are correctly overriding it but inside the Heres an issue about it as well modelcontextprotocol/typescript-sdk#476 |
Thanks for this insight @MOmarMiraj! |
Waiting on modelcontextprotocol/typescript-sdk#721 |
@MOmarMiraj could you try this again? You'll need to run |
I updated to the latest SDK and pulled ur branch and still not getting that WWW-Authenticate header across.. I still think the fetch isn't working correctly. I try to debug from inside that function and I do not receive my debug statement... From the proxy, we get a 401 unauthorized but for some reason the actual |
I realized the way we create the
as the constructor for
which currently we are doing opts?.requestInit?.fetch. When I changed it, I can see that the auth header gets set correctly but not showing up in console still. |
Good catch, @MOmarMiraj. Updated the location of the fetch in transport options. But are you actually throwing an error from your server or just setting the header? |
I believe the issue is that when setting up the proxy, everything goes smoothly which is why I get a 200 OK on the request and then when it tries to actually ping the server it gets the 401. |
Summary of Changes
This pull request enhances the server's proxy functionality by enabling the correct forwarding of
WWW-Authenticate
headers from the backend to the client. This ensures that clients receive appropriate authentication challenges. The changes involve capturing the header during transport creation, exposing it through the transport object, and applying it to the proxy's outgoing responses. A significant refactoring was also undertaken to improve the robustness and maintainability of thefetch
interception mechanism and error handling, directly addressing critical feedback regarding potential race conditions and type safety.Highlights
WWW-Authenticate
header from backend (MCP server) responses to the client, ensuring proper authentication challenges are propagated.createTransport
function to use a localizedinterceptingFetch
mechanism, which is passed directly to theSSEClientTransport
andStreamableHTTPClientTransport
constructors. This change addresses critical race condition concerns by avoiding global modification ofglobalThis.fetch
.createTransport
function now returns an object containing both theTransport
instance and any capturedWWW-Authenticate
header, allowing for its propagation to the client.maybeSetAuthHeader
andsetAuthHeaderFromError
, to centralize and reuse the logic for setting theWWW-Authenticate
header on proxy responses, particularly in error scenarios, improving code maintainability and reducing duplication.Changelog
maybeSetAuthHeader
helper function to conditionally set theWWW-Authenticate
header on an Express response.setAuthHeaderFromError
helper function to safely extract and set theWWW-Authenticate
header from an error object, including type checks for robustness.createTransport
function signature to return an object containing both theTransport
instance and an optionalauthHeader
.interceptingFetch
withincreateTransport
to capture theWWW-Authenticate
header from 401 responses, passing this custom fetch toSSEClientTransport
andStreamableHTTPClientTransport
to avoid global side effects.app.post
route for StreamableHTTP connections to utilize the newcreateTransport
return value and themaybeSetAuthHeader
andsetAuthHeaderFromError
helpers.app.get
route for STDIO connections to utilize the newcreateTransport
return value and themaybeSetAuthHeader
andsetAuthHeaderFromError
helpers.app.get
route for SSE connections to utilize the newcreateTransport
return value and themaybeSetAuthHeader
andsetAuthHeaderFromError
helpers.Motivation and Context
When a
401 Unauthorized
error is returned from a server, theWWW-Authenticate
header should be included in the response, advertising the HTTP authentication methods (or challenges) that might be used to gain access.By adding this header name to the Access-Control-Expose-Headers header in the server response, it should tell the browser to expose that header to scripts, so that the client can extract the authentication method or challenge and react appropriately.
However, even with the above change, we can see that with a server that is actually returning a WWW-Authenticate header with a 401, the header isn't making back to the client via the proxy.
There are two transports involved; Client <-> Proxy and Proxy <-> Server. We also need to bridge the headers across the transports in the proxy, not just the error message.
How Has This Been Tested?
User @MOmarMiraj has been helping with a server he has that returns WWW-Authenticate header on 401.
Breaking Changes
Nope.
Types of changes
Checklist
Additional context