Skip to content

Accept names under localhost for the URLs Protected Resource Metadata publishes - #683

Open
koic wants to merge 1 commit into
modelcontextprotocol:mainfrom
koic:accept_names_under_localhost_for_the_urls_protected_resource_metadata_publishes
Open

koic wants to merge 1 commit into
modelcontextprotocol:mainfrom
koic:accept_names_under_localhost_for_the_urls_protected_resource_metadata_publishes

Conversation

@koic

@koic koic commented Nov 13, 2026

Copy link
Copy Markdown
Member

Motivation and Context

ProtectedResourceMetadata refused a plain-http resource or authorization_servers entry unless its host was localhost or a loopback address, so a server that gives each tenant a subdomain could not be run in development as http://acme.localhost:3000/mcp: neither the metadata document nor the 401 challenge could be built.

Every name under localhost is reserved for loopback and cannot be registered (RFC 6761 Section 6.3). These two members are values the server publishes and compares with a token's audience and issuer; it never connects to them. They now accept plain http on such a name, through a predicate of their own in ProtectedResourceMetadata.

Discovery.secure_url? is unchanged, and with it every URL that is connected to: jwks_uri: and introspection_endpoint: on the server, and everything in the client. A resolver is not bound to answer a name under localhost with the loopback address, so plain http to one stays refused there. A server reached under such a name lists it in the allowed_hosts: of the transport, and MCP::Client::HTTP with oauth: does not connect to it over plain http.

Resolves #677.

How Has This Been Tested?

test/mcp/server/oauth/protected_resource_metadata_test.rb, jwt_verifier_test.rb and introspection_verifier_test.rb; the test that builds the metadata for http://acme.localhost:3000/mcp fails on the previous library.

Breaking Changes

None.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

…ta publishes

## Motivation and Context

`ProtectedResourceMetadata` refused a plain-http `resource` or `authorization_servers` entry unless its host was `localhost`
or a loopback address, so a server that gives each tenant a subdomain could not be run in development as `http://acme.localhost:3000/mcp`:
neither the metadata document nor the `401` challenge could be built.

Every name under `localhost` is reserved for loopback and cannot be registered (RFC 6761 Section 6.3). These two members are values
the server publishes and compares with a token's audience and issuer; it never connects to them. They now accept plain `http` on such a name,
through a predicate of their own in `ProtectedResourceMetadata`.

`Discovery.secure_url?` is unchanged, and with it every URL that is connected to: `jwks_uri:` and `introspection_endpoint:` on the server,
and everything in the client. A resolver is not bound to answer a name under `localhost` with the loopback address,
so plain `http` to one stays refused there. A server reached under such a name lists it in the `allowed_hosts:` of the transport,
and `MCP::Client::HTTP` with `oauth:` does not connect to it over plain `http`.

Resolves modelcontextprotocol#677.

## How Has This Been Tested?

`test/mcp/server/oauth/protected_resource_metadata_test.rb`, `jwt_verifier_test.rb` and `introspection_verifier_test.rb`;
the test that builds the metadata for `http://acme.localhost:3000/mcp` fails on the previous library.

## Breaking Changes

None.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

secure_url? refuses http://<name>.localhost although RFC 6761 makes every *.localhost name loopback

2 participants