| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
# Bug Report: Incorrect `.well-known/oauth-protected-resource` endpoint path when `resource_server_url` ends with `/sse`
## Summary
When configuring FastMCP with OAuth2 authentication and setting the `resource_server_url` to end with `/sse` (as required by VSCode MCP clients), FastMCP incorrectly serves the `.well-known/oauth-protected-resource` endpoint at `/sse/.well-known/oauth-protected-resource` instead of the expected root path `/.well-known/oauth-protected-resource`.
## Environment
- **FastMCP version**: Part of `mcp` Python library (check with `pip show mcp`)
- **Python version**: 3.13
- **Operating System**: macOS
- **MCP Client**: VSCode with MCP extension
- **Affected Files**:
- `mcp/server/fastmcp/server.py` (lines ~790-797)
- `mcp/server/auth/routes.py` (lines ~215-224)
## Expected Behavior
1. The `.well-known/oauth-protected-resource` endpoint should always be served at the root path (`/.well-known/oauth-protected-resource`) regardless of the `resource_server_url` configuration
2. The `resource` field in the `.well-known` response should point to the actual protected resource (e.g., `/sse`)
3. OAuth2 discovery should work correctly with MCP clients like VSCode
## Actual Behavior
When `resource_server_url` is set to `http://localhost:8099/sse`, FastMCP:
1. Serves `.well-known/oauth-protected-resource` at `/sse/.well-known/oauth-protected-resource`
2. The SSE endpoint `/sse` returns a `www-authenticate` header with `resource_metadata="http://localhost:8099/sse/.well-known/oauth-protected-resource"`
3. OAuth2 discovery fails because clients expect the `.well-known` endpoint at the root
## Steps to Reproduce
1. Create a FastMCP server with OAuth2 configuration:
```python
from mcp.server.fastmcp import FastMCP
from mcp.server.auth.settings import AuthSettings
from pydantic import AnyHttpUrl
# Configure auth settings with /sse endpoint
auth_settings = AuthSettings(
issuer_url=AnyHttpUrl("https://login.microsoftonline.com/tenant-id/v2.0"),
resource_server_url=AnyHttpUrl("http://localhost:8099/sse"), # Note: ends with /sse
required_scopes=["https://example.com/scope"]
)
mcp = FastMCP(
"Test Server",
token_verifier=your_token_verifier,
auth=auth_settings,
)
app = mcp.sse_app()
```
2. Start the server: `uvicorn server:app --port 8099`
3. Test the endpoints:
```bash
# This should work but returns 404
curl http://localhost:8099/.well-known/oauth-protected-resource
# This works but shouldn't be the location
curl http://localhost:8099/sse/.well-known/oauth-protected-resource
# SSE endpoint references wrong .well-known location
curl -I http://localhost:8099/sse
# Returns: resource_metadata="http://localhost:8099/sse/.well-known/oauth-protected-resource"
```
## Root Cause Analysis
**Exact Location of Bug**:
- **File**: `mcp/server/fastmcp/server.py`
- **Lines**: ~790-797 in the `sse_app()` method
- **Function**: `FastMCP.sse_app()`
**The Issue**: When setting up OAuth2 authentication, FastMCP constructs the `resource_metadata_url` incorrectly:
```python
# BUGGY CODE - Line ~790-797 in sse_app() method
resource_metadata_url = AnyHttpUrl(
str(self.settings.auth.resource_server_url).rstrip("/") + "/.well-known/oauth-protected-resource"
)
```
When `resource_server_url` is `http://localhost:8099/sse`, this creates `http://localhost:8099/sse/.well-known/oauth-protected-resource`.
However, the actual `.well-known` endpoint is created by `create_protected_resource_routes()` (in `mcp/server/auth/routes.py` lines ~215-224), which always creates it at the root path:
```python
# CORRECT CODE - This always creates /.well-known/oauth-protected-resource at root
return [
Route(
"/.well-known/oauth-protected-resource",
endpoint=cors_middleware(handler.handle, ["GET", "OPTIONS"]),
methods=["GET", "OPTIONS"],
)
]
```
**The Fix**: The `resource_metadata_url` should be constructed from the base URL, not the `resource_server_url`:
```python
# PROPOSED FIX
if self.settings.auth and self.settings.auth.resource_server_url:
from pydantic import AnyHttpUrl
from urllib.parse import urlparse
# Extract base URL from resource_server_url
parsed = urlparse(str(self.settings.auth.resource_server_url))
base_url = f"{parsed.scheme}://{parsed.netloc}"
resource_metadata_url = AnyHttpUrl(
base_url + "/.well-known/oauth-protected-resource"
)
```
## Impact
- **High**: Breaks OAuth2 discovery for MCP clients like VSCode
- MCP servers cannot be properly authenticated when using the recommended `/sse` resource URL pattern
- Workarounds require custom endpoint overrides, defeating the purpose of built-in auth support
## Proposed Solution
The `.well-known/oauth-protected-resource` endpoint should always be served at the root path (`/.well-known/oauth-protected-resource`), regardless of the `resource_server_url` configuration. The `resource_server_url` should only affect:
1. The `resource` field value in the `.well-known` response
2. The `resource_metadata` reference in `www-authenticate` headers
## Current Workaround
Override the built-in `.well-known` endpoint with a custom implementation:
```python
async def custom_well_known_endpoint(request):
return JSONResponse({
"resource": f"{config.EXTERNAL_ADDRESS}/sse",
"authorization_servers": ["https://login.microsoftonline.com/tenant/v2.0"],
"scopes_supported": ["https://example.com/scope"],
"bearer_methods_supported": ["header"]
})
# Override the built-in endpoint
app.router.routes.insert(0, Route("/.well-known/oauth-protected-resource", custom_well_known_endpoint, methods=["GET"]))
```
## Additional Context
- This issue specifically affects integration with VSCode MCP clients, which require the resource URL to end with `/sse`
- The OAuth2 specification (RFC 8414) defines `.well-known` endpoints should be at predictable root paths
- Other OAuth2 implementations (e.g., Auth0, Okta) serve `.well-known` endpoints at root regardless of resource configuration
## Related Documentation
- [RFC 8414 - OAuth 2.0 Authorization Server Metadata](https://tools.ietf.org/html/rfc8414)
- [MCP OAuth2 Authentication Documentation](https://modelcontextprotocol.io/docs/concepts/authentication)
|
Can we speed up reviewing current PR. I ran into the same issue with vscode and solved by using your branch. Besides, the sample code will also need to be modified. ( ) # Create FastMCP server as a Resource Server
app = FastMCP(
name="MCP Resource Server",
instructions="Resource Server that validates tokens via Authorization Server introspection",
host=settings.host,
port=settings.port,
debug=True,
# Auth configuration for RS mode
token_verifier=token_verifier,
auth=AuthSettings(
issuer_url=settings.auth_server_url,
required_scopes=[settings.mcp_scope],
resource_server_url=AnyHttpUrl(f"{settings.server_url}mcp"), # <------ add "mcp"
),
)
|
Sorry, something went wrong.
|
Question... The listed Expected Behavior in this pull request states: "The .well-known/oauth-protected-resource endpoint should always be served at the root path (/.well-known/oauth-protected-resource) regardless of the resource_server_url configuration". That seems the exact opposite of what would be needed in use cases where the MCP server must run at a custom path. When attached to a GKE gateway + HttpRoute (or api management, such as Apigee or APIM), serving the well-known endpoints from the root path is always going to return 404 responses. |
Sorry, something went wrong.
|
Where did you get The .well-known/oauth-protected-resource endpoint should always be served at the root path (/.well-known/oauth-protected-resource) regardless of the resource_server_url configuration? I looked at https://www.ietf.org/rfc/rfc9728.pdf and there's no such statement. I find thou Different applications utilizing OAuth protected resources in application-specific ways define and register different well-known URI path suffixes for publishing protected resource metadata used by those applications Actually I have opposite problem: |
Sorry, something went wrong.
|
I agree that if someone is hosting an mcp endpoint somewhere deep into a path structure that this PR will not work. I suggest being able to explicitly configure the protected resource path. Currently this library is not compatible with VSCode's protected resource resolution. You must supply an "/sse" endpoint in the mcp configuration and then this library will host the well known endpoint at "/sse/.well-known" which is not what VSCode requests. If you feel this is a bug in VSCode then we can close the PR and go on our way and people cause use the workaround I provided. Is there anything in the standard that clarifies these paths for auth? Is VSCode supposed to require you to include the "/sse" portion of the URL? |
Sorry, something went wrong.
|
This change moves things closer to the RFC, so generally supportive. If you could add a test, then we can merge this. There's a draft PR to the spec to clarify the preference order here that should get merged today: With ^ the way to handle a deeply nested metadata document is to indicate it in the WWW-Authenticate header as the metadata path. |
Sorry, something went wrong.
There was a problem hiding this comment.
(see above, please add a test)
Sorry, something went wrong.
There was a problem hiding this comment.
Requesting changes to implement a test + there seem to be some CI failures still.
Sorry, something went wrong.
|
Hi @pcarleton, @felixweinberger - After reviewing the PR and the linked issue, I don’t think the proposed fix fully resolves the problem. Root cause: https://example.com/mcp/.well-known/oauth-protected-resource But, the server route is registered at following URL all the tme: https://example.com/.well-known/oauth-protected-resource These don’t align. Besides, the RFC 9728 spec expects the .well-known suffix inserted between the host and the path, not appended after the full URL. I have created a pull request to implement such fixes. Please see: |
Sorry, something went wrong.
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
Bug Report: Incorrect .well-known/oauth-protected-resource endpoint path when resource_server_url ends with /sse
This is reported in 1264
Summary
When configuring FastMCP with OAuth2 authentication and setting the resource_server_url to end with /sse (as required by VSCode MCP clients), FastMCP incorrectly serves the .well-known/oauth-protected-resource endpoint at /sse/.well-known/oauth-protected-resource instead of the expected root path /.well-known/oauth-protected-resource.
Environment
Expected Behavior
Actual Behavior
When resource_server_url is set to http://localhost:8099/sse, FastMCP:
Steps to Reproduce
Start the server: uvicorn server:app --port 8099
Test the endpoints:
Root Cause Analysis
Exact Location of Bug:
The Issue: When setting up OAuth2 authentication, FastMCP constructs the resource_metadata_url incorrectly:
When resource_server_url is http://localhost:8099/sse, this creates http://localhost:8099/sse/.well-known/oauth-protected-resource.
However, the actual .well-known endpoint is created by create_protected_resource_routes() (in mcp/server/auth/routes.py lines ~215-224), which always creates it at the root path:
The Fix: The resource_metadata_url should be constructed from the base URL, not the resource_server_url:
Impact
Proposed Solution
The .well-known/oauth-protected-resource endpoint should always be served at the root path (/.well-known/oauth-protected-resource), regardless of the resource_server_url configuration. The resource_server_url should only affect:
Current Workaround
Override the built-in .well-known endpoint with a custom implementation:
Additional Context
Related Documentation