Serve the sample authorization server over loopback HTTP - #1802
Open
anneheartrecord wants to merge 1 commit into
Open
Serve the sample authorization server over loopback HTTP#1802anneheartrecord wants to merge 1 commit into
anneheartrecord wants to merge 1 commit into
Conversation
The ProtectedMcpServer sample pairs with TestOAuthServer, which hosts the authorization server on the ASP.NET Core developer certificate. Clients that keep their own CA list rather than using the OS trust store cannot fetch https://localhost:7029/.well-known/oauth-authorization-server from it. VS Code is one: the fetch fails, it treats that as a server without metadata, and falls back to the pre-2025-06-18 defaults derived from the MCP server URL. That drops the registration endpoint, so it asks for a client id, and then sends the browser to http://localhost:7071/authorize, which 404s. Host the standalone server over plain HTTP on loopback so its metadata is reachable without trusting anything first, and keep the developer certificate available behind --https. Tests construct Program directly and are unaffected.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #689.
Root cause
The sample never gets as far as the authorization server. Reproduced against current
main, with the failure isolated to one request:POST http://localhost:7071/→401,WWW-Authenticate: Bearer resource_metadata="http://localhost:7071/.well-known/oauth-protected-resource/"GETthat document →{"resource":"http://localhost:7071","authorization_servers":["https://localhost:7029"], ...}GET https://localhost:7029/.well-known/oauth-authorization-server→ failsStep 3 fails because
TestOAuthServerhosts the authorization server on the ASP.NET Core developer certificate, and VS Code's HTTP stack carries its own CA list instead of using the operating system trust store. Node reports it asTypeError: fetch failed, causeDEPTH_ZERO_SELF_SIGNED_CERT— which is exactly theError populating auth metadata: TypeError: fetch failedline in the trace on microsoft/vscode#261120, where this was chased from the VS Code side and left as "something to do with your cert on localhost".From there VS Code treats it as a server without metadata and falls back to
getDefaultMetadataForUrl(mcpServerUrl), the compatibility path for the 2025-03-26 spec. That fallback has no registration endpoint, so it prompts for a client id (which reads as "DCR not supported"), and its authorization endpoint ishttp://localhost:7071/authorize— the 404 in the original report.So the answer to "sample, TestOAuthServer, or SDK auth handling" is TestOAuthServer, and neither of the two symptoms is the bug — both are downstream of one unreachable metadata document. The
.NETclient sample works becauseHttpClientdoes use the OS trust store, wheredotnet dev-certs https --trustput the certificate. The repo's own OAuth tests work becauseOAuthTestBaseturns certificate validation off.What changed
TestOAuthServernow listens over plain HTTP on loopback when it's run standalone, so its metadata is reachable without trusting anything first.--https(and thehttpslaunch profile) keeps the developer certificate available. The constructor still defaults to HTTPS, so the tests that constructProgramdirectly are untouched.ProtectedMcpServerpoints athttp://localhost:7029and setsRequireHttpsMetadata = false, scoped with a comment and a README note saying not to do that against anything but a loopback authority you control.Verifying
I don't have VS Code on this machine, so I drove the flow it performs with Node's
fetch— same HTTP stack, same certificate behaviour. Before: step 3 fails withDEPTH_ZERO_SELF_SIGNED_CERTandhttp://localhost:7071/authorizereturns 404. After: metadata → dynamic client registration → authorization code → token → an authenticatedinitializereturning 200. The token exchange also proves the sample'sJwtBearerbackchannel fetches its signing keys over the HTTP authority.dotnet buildis clean andtests/ModelContextProtocol.AspNetCore.Testspasses (582 tests,net10.0). The new test covers the two things this change can regress: the scheme the discovery document advertises has to match the origin it's hosted on, and the standalone default has to stay HTTP.One thing to call out
RFC 8414 wants an
httpsissuer identifier, and in the default mode this fixture now publishes"issuer": "http://localhost:7029". That felt like the right trade for a loopback test server whose whole job is to be easy to point a client at — but it does change what plaindotnet rundoes, so if you'd rather keep HTTPS as the default and make it--httpto opt out, say the word and I'll flip it. The samples would then need the README to tell people to pass the flag, which is the part I expect to keep tripping people up.