diff --git a/docs/servers/auth/oauth-proxy.mdx b/docs/servers/auth/oauth-proxy.mdx
index 5f3a1c1ad..fadf9b03c 100644
--- a/docs/servers/auth/oauth-proxy.mdx
+++ b/docs/servers/auth/oauth-proxy.mdx
@@ -159,7 +159,7 @@ The `OAuthProxy` class provides the complete proxy implementation:
- Resource server URL (defaults to base_url)
+ Path of the FastMCP server (defaults to base_url). **Important**: This should point to your MCP endpoint path. For example, if your MCP server is accessible at `{base_url}/mcp`, specify `https://your-server.com/mcp` here for proper RFC 8707 compliance.
@@ -228,7 +228,10 @@ auth = OAuthProxy(
base_url="https://your-server.com",
# Optional: customize callback path (defaults to "/auth/callback")
- redirect_path="/auth/callback"
+ redirect_path="/auth/callback",
+
+ # Optional: specify MCP endpoint path if different from base_url
+ # resource_server_url="https://your-server.com/mcp"
)
mcp = FastMCP(name="My Server", auth=auth)
diff --git a/docs/servers/auth/remote-oauth.mdx b/docs/servers/auth/remote-oauth.mdx
index 5f6a485cd..6ffe876e5 100644
--- a/docs/servers/auth/remote-oauth.mdx
+++ b/docs/servers/auth/remote-oauth.mdx
@@ -111,7 +111,7 @@ token_verifier = JWTVerifier(
auth = RemoteAuthProvider(
token_verifier=token_verifier,
authorization_servers=[AnyHttpUrl("https://auth.yourcompany.com")],
- resource_server_url="https://api.yourcompany.com",
+ resource_server_url="https://api.yourcompany.com/mcp", # Point to your MCP endpoint
# Optional: customize allowed client redirect URIs (defaults to localhost only)
allowed_client_redirect_uris=["http://localhost:*", "http://127.0.0.1:*"]
)
@@ -121,7 +121,7 @@ mcp = FastMCP(name="Company API", auth=auth)
This configuration creates a server that accepts tokens issued by `auth.yourcompany.com` and provides the OAuth discovery metadata that MCP clients need. The `JWTVerifier` handles token validation using your identity provider's public keys, while the `RemoteAuthProvider` generates the required OAuth endpoints.
-The `authorization_servers` list tells MCP clients which identity providers you trust. The `resource_server_url` identifies your server in OAuth metadata, enabling proper token audience validation.
+The `authorization_servers` list tells MCP clients which identity providers you trust. The `resource_server_url` identifies your server in OAuth metadata, enabling proper token audience validation. **Important**: The `resource_server_url` should point to your actual MCP endpoint - for example, if your MCP server is accessible at `https://api.yourcompany.com/mcp`, use that full path rather than just the base URL.
### Custom Endpoints
@@ -143,7 +143,7 @@ class CompanyAuthProvider(RemoteAuthProvider):
super().__init__(
token_verifier=token_verifier,
authorization_servers=[AnyHttpUrl("https://auth.yourcompany.com")],
- resource_server_url="https://api.yourcompany.com"
+ resource_server_url="https://api.yourcompany.com/mcp" # Your MCP endpoint path
)
def get_routes(self) -> list[Route]:
diff --git a/examples/auth/azure_oauth/server.py b/examples/auth/azure_oauth/server.py
index 2d5062612..81687cfd4 100644
--- a/examples/auth/azure_oauth/server.py
+++ b/examples/auth/azure_oauth/server.py
@@ -23,6 +23,7 @@ auth = AzureProvider(
tenant_id=os.getenv("AZURE_TENANT_ID")
or "", # Required for single-tenant apps - get from Azure Portal
base_url="http://localhost:8000",
+ resource_server_url="http://localhost:8000/mcp",
# redirect_path="/auth/callback", # Default path - change if using a different callback URL
)
diff --git a/examples/auth/github_oauth/server.py b/examples/auth/github_oauth/server.py
index 1f88c6977..a0fdc0504 100644
--- a/examples/auth/github_oauth/server.py
+++ b/examples/auth/github_oauth/server.py
@@ -19,6 +19,7 @@ auth = GitHubProvider(
client_id=os.getenv("FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID") or "",
client_secret=os.getenv("FASTMCP_SERVER_AUTH_GITHUB_CLIENT_SECRET") or "",
base_url="http://localhost:8000",
+ resource_server_url="http://localhost:8000/mcp",
# redirect_path="/auth/callback", # Default path - change if using a different callback URL
)
diff --git a/examples/auth/google_oauth/server.py b/examples/auth/google_oauth/server.py
index 2a5b1c7df..feb1fe1d4 100644
--- a/examples/auth/google_oauth/server.py
+++ b/examples/auth/google_oauth/server.py
@@ -19,6 +19,7 @@ auth = GoogleProvider(
client_id=os.getenv("FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_ID") or "",
client_secret=os.getenv("FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_SECRET") or "",
base_url="http://localhost:8000",
+ resource_server_url="http://localhost:8000/mcp",
# redirect_path="/auth/callback", # Default path - change if using a different callback URL
# Optional: specify required scopes
# required_scopes=["openid", "https://www.googleapis.com/auth/userinfo.email"],
diff --git a/examples/auth/workos_oauth/server.py b/examples/auth/workos_oauth/server.py
index 08c1db62b..8db24f13b 100644
--- a/examples/auth/workos_oauth/server.py
+++ b/examples/auth/workos_oauth/server.py
@@ -21,6 +21,7 @@ auth = WorkOSProvider(
client_secret=os.getenv("WORKOS_CLIENT_SECRET") or "",
authkit_domain=os.getenv("WORKOS_AUTHKIT_DOMAIN") or "https://your-app.authkit.app",
base_url="http://localhost:8000",
+ resource_server_url="http://localhost:8000/mcp",
# redirect_path="/auth/callback", # Default path - change if using a different callback URL
)
diff --git a/src/fastmcp/server/auth/oauth_proxy.py b/src/fastmcp/server/auth/oauth_proxy.py
index 8f7418a83..14a6e9fd0 100644
--- a/src/fastmcp/server/auth/oauth_proxy.py
+++ b/src/fastmcp/server/auth/oauth_proxy.py
@@ -254,45 +254,20 @@ class OAuthProxy(OAuthProvider):
upstream_client_secret: Client secret for upstream server
upstream_revocation_endpoint: Optional upstream revocation endpoint
token_verifier: Token verifier for validating access tokens
- base_url: Public URL of this FastMCP server
+ base_url: Public URL of the server that exposes this FastMCP server; redirect path is
+ relative to this URL
redirect_path: Redirect path configured in upstream OAuth app (defaults to "/auth/callback")
issuer_url: Issuer URL for OAuth metadata (defaults to base_url)
service_documentation_url: Optional service documentation URL
- resource_server_url: Resource server URL (defaults to base_url)
+ resource_server_url: Path of the FastMCP server. If None, FastMCP will
+ attempt to overwrite this with the correct path to the server
+ e.g. {base_url}/mcp
allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
Patterns support wildcards (e.g., "http://localhost:*", "https://*.example.com/*").
If None (default), only localhost redirect URIs are allowed.
If empty list, all redirect URIs are allowed (not recommended for production).
These are for MCP clients performing loopback redirects, NOT for the upstream OAuth app.
"""
- # Convert string URLs to AnyHttpUrl for parent class
- base_url_parsed = (
- AnyHttpUrl(base_url) if isinstance(base_url, str) else base_url
- )
- issuer_url_parsed = (
- (AnyHttpUrl(issuer_url) if isinstance(issuer_url, str) else issuer_url)
- if issuer_url
- else None
- )
- service_documentation_url_parsed = (
- (
- AnyHttpUrl(service_documentation_url)
- if isinstance(service_documentation_url, str)
- else service_documentation_url
- )
- if service_documentation_url
- else None
- )
- resource_server_url_parsed = (
- (
- AnyHttpUrl(resource_server_url)
- if isinstance(resource_server_url, str)
- else resource_server_url
- )
- if resource_server_url
- else None
- )
-
# Always enable DCR since we implement it locally for MCP clients
client_registration_options = ClientRegistrationOptions(enabled=True)
@@ -302,13 +277,13 @@ class OAuthProxy(OAuthProvider):
)
super().__init__(
- base_url=base_url_parsed,
- issuer_url=issuer_url_parsed,
- service_documentation_url=service_documentation_url_parsed,
+ base_url=base_url,
+ issuer_url=issuer_url,
+ service_documentation_url=service_documentation_url,
client_registration_options=client_registration_options,
revocation_options=revocation_options,
required_scopes=token_verifier.required_scopes,
- resource_server_url=resource_server_url_parsed,
+ resource_server_url=resource_server_url,
)
# Store upstream configuration
diff --git a/src/fastmcp/server/auth/providers/azure.py b/src/fastmcp/server/auth/providers/azure.py
index 362f35f58..806dddf18 100644
--- a/src/fastmcp/server/auth/providers/azure.py
+++ b/src/fastmcp/server/auth/providers/azure.py
@@ -36,6 +36,8 @@ class AzureProviderSettings(BaseSettings):
redirect_path: str | None = None
required_scopes: list[str] | None = None
timeout_seconds: int | None = None
+ resource_server_url: str | None = None
+ allowed_client_redirect_uris: list[str] | None = None
@field_validator("required_scopes", mode="before")
@classmethod
@@ -160,7 +162,8 @@ class AzureProvider(OAuthProxy):
redirect_path: str | NotSetT = NotSet,
required_scopes: list[str] | None | NotSetT = NotSet,
timeout_seconds: int | NotSetT = NotSet,
- allowed_client_redirect_uris: list[str] | None = None,
+ resource_server_url: str | NotSetT = NotSet,
+ allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
):
"""Initialize Azure OAuth provider.
@@ -172,6 +175,8 @@ class AzureProvider(OAuthProxy):
redirect_path: Redirect path configured in Azure (defaults to "/auth/callback")
required_scopes: Required scopes (defaults to ["User.Read", "email", "openid", "profile"])
timeout_seconds: HTTP request timeout for Azure API calls
+ resource_server_url: Path of the FastMCP server (defaults to base_url). If your MCP endpoint is at
+ a different path like {base_url}/mcp, specify it here for RFC 8707 compliance.
allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
If None (default), all URIs are allowed. If empty list, no URIs are allowed.
"""
@@ -186,6 +191,8 @@ class AzureProvider(OAuthProxy):
"redirect_path": redirect_path,
"required_scopes": required_scopes,
"timeout_seconds": timeout_seconds,
+ "resource_server_url": resource_server_url,
+ "allowed_client_redirect_uris": allowed_client_redirect_uris,
}.items()
if v is not NotSet
}
@@ -220,6 +227,8 @@ class AzureProvider(OAuthProxy):
"openid",
"profile",
]
+ resource_server_url_final = settings.resource_server_url or base_url_final
+ allowed_client_redirect_uris_final = settings.allowed_client_redirect_uris
# Extract secret string from SecretStr
client_secret_str = (
@@ -250,7 +259,8 @@ class AzureProvider(OAuthProxy):
base_url=base_url_final,
redirect_path=redirect_path_final,
issuer_url=base_url_final,
- allowed_client_redirect_uris=allowed_client_redirect_uris,
+ allowed_client_redirect_uris=allowed_client_redirect_uris_final,
+ resource_server_url=resource_server_url_final,
)
logger.info(
diff --git a/src/fastmcp/server/auth/providers/github.py b/src/fastmcp/server/auth/providers/github.py
index 20dc23616..49613897a 100644
--- a/src/fastmcp/server/auth/providers/github.py
+++ b/src/fastmcp/server/auth/providers/github.py
@@ -51,6 +51,8 @@ class GitHubProviderSettings(BaseSettings):
redirect_path: str | None = None
required_scopes: list[str] | None = None
timeout_seconds: int | None = None
+ resource_server_url: AnyHttpUrl | str | None = None
+ allowed_client_redirect_uris: list[str] | None = None
@field_validator("required_scopes", mode="before")
@classmethod
@@ -199,9 +201,10 @@ class GitHubProvider(OAuthProxy):
client_secret: str | NotSetT = NotSet,
base_url: AnyHttpUrl | str | NotSetT = NotSet,
redirect_path: str | NotSetT = NotSet,
- required_scopes: list[str] | None | NotSetT = NotSet,
+ required_scopes: list[str] | NotSetT = NotSet,
timeout_seconds: int | NotSetT = NotSet,
- allowed_client_redirect_uris: list[str] | None = None,
+ resource_server_url: AnyHttpUrl | str | NotSetT = NotSet,
+ allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
):
"""Initialize GitHub OAuth provider.
@@ -212,6 +215,8 @@ class GitHubProvider(OAuthProxy):
redirect_path: Redirect path configured in GitHub OAuth app (defaults to "/auth/callback")
required_scopes: Required GitHub scopes (defaults to ["user"])
timeout_seconds: HTTP request timeout for GitHub API calls
+ resource_server_url: Path of the FastMCP server (defaults to base_url). If your MCP endpoint is at
+ a different path like {base_url}/mcp, specify it here for RFC 8707 compliance.
allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
If None (default), all URIs are allowed. If empty list, no URIs are allowed.
"""
@@ -225,6 +230,8 @@ class GitHubProvider(OAuthProxy):
"redirect_path": redirect_path,
"required_scopes": required_scopes,
"timeout_seconds": timeout_seconds,
+ "resource_server_url": resource_server_url,
+ "allowed_client_redirect_uris": allowed_client_redirect_uris,
}.items()
if v is not NotSet
}
@@ -245,6 +252,8 @@ class GitHubProvider(OAuthProxy):
redirect_path_final = settings.redirect_path or "/auth/callback"
timeout_seconds_final = settings.timeout_seconds or 10
required_scopes_final = settings.required_scopes or ["user"]
+ resource_server_url_final = settings.resource_server_url or base_url_final
+ allowed_client_redirect_uris_final = settings.allowed_client_redirect_uris
# Create GitHub token verifier
token_verifier = GitHubTokenVerifier(
@@ -267,7 +276,8 @@ class GitHubProvider(OAuthProxy):
base_url=base_url_final,
redirect_path=redirect_path_final,
issuer_url=base_url_final, # We act as the issuer for client registration
- allowed_client_redirect_uris=allowed_client_redirect_uris,
+ allowed_client_redirect_uris=allowed_client_redirect_uris_final,
+ resource_server_url=resource_server_url_final,
)
logger.info(
diff --git a/src/fastmcp/server/auth/providers/google.py b/src/fastmcp/server/auth/providers/google.py
index f025fe67b..337bf6157 100644
--- a/src/fastmcp/server/auth/providers/google.py
+++ b/src/fastmcp/server/auth/providers/google.py
@@ -53,6 +53,8 @@ class GoogleProviderSettings(BaseSettings):
redirect_path: str | None = None
required_scopes: list[str] | None = None
timeout_seconds: int | None = None
+ resource_server_url: AnyHttpUrl | str | None = None
+ allowed_client_redirect_uris: list[str] | None = None
@field_validator("required_scopes", mode="before")
@classmethod
@@ -215,9 +217,10 @@ class GoogleProvider(OAuthProxy):
client_secret: str | NotSetT = NotSet,
base_url: AnyHttpUrl | str | NotSetT = NotSet,
redirect_path: str | NotSetT = NotSet,
- required_scopes: list[str] | None | NotSetT = NotSet,
+ required_scopes: list[str] | NotSetT = NotSet,
timeout_seconds: int | NotSetT = NotSet,
- allowed_client_redirect_uris: list[str] | None = None,
+ resource_server_url: AnyHttpUrl | str | NotSetT = NotSet,
+ allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
):
"""Initialize Google OAuth provider.
@@ -244,6 +247,8 @@ class GoogleProvider(OAuthProxy):
"redirect_path": redirect_path,
"required_scopes": required_scopes,
"timeout_seconds": timeout_seconds,
+ "resource_server_url": resource_server_url,
+ "allowed_client_redirect_uris": allowed_client_redirect_uris,
}.items()
if v is not NotSet
}
@@ -265,6 +270,8 @@ class GoogleProvider(OAuthProxy):
timeout_seconds_final = settings.timeout_seconds or 10
# Google requires at least one scope - openid is the minimal OIDC scope
required_scopes_final = settings.required_scopes or ["openid"]
+ resource_server_url_final = settings.resource_server_url or base_url_final
+ allowed_client_redirect_uris_final = settings.allowed_client_redirect_uris
# Create Google token verifier
token_verifier = GoogleTokenVerifier(
@@ -287,7 +294,8 @@ class GoogleProvider(OAuthProxy):
base_url=base_url_final,
redirect_path=redirect_path_final,
issuer_url=base_url_final, # We act as the issuer for client registration
- allowed_client_redirect_uris=allowed_client_redirect_uris,
+ allowed_client_redirect_uris=allowed_client_redirect_uris_final,
+ resource_server_url=resource_server_url_final,
)
logger.info(
diff --git a/src/fastmcp/server/auth/providers/workos.py b/src/fastmcp/server/auth/providers/workos.py
index a0f939fb6..2c443eb64 100644
--- a/src/fastmcp/server/auth/providers/workos.py
+++ b/src/fastmcp/server/auth/providers/workos.py
@@ -43,6 +43,8 @@ class WorkOSProviderSettings(BaseSettings):
redirect_path: str | None = None
required_scopes: list[str] | None = None
timeout_seconds: int | None = None
+ resource_server_url: AnyHttpUrl | str | None = None
+ allowed_client_redirect_uris: list[str] | None = None
@field_validator("required_scopes", mode="before")
@classmethod
@@ -167,7 +169,8 @@ class WorkOSProvider(OAuthProxy):
redirect_path: str | NotSetT = NotSet,
required_scopes: list[str] | None | NotSetT = NotSet,
timeout_seconds: int | NotSetT = NotSet,
- allowed_client_redirect_uris: list[str] | None = None,
+ resource_server_url: AnyHttpUrl | str | NotSetT = NotSet,
+ allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
):
"""Initialize WorkOS OAuth provider.
@@ -179,6 +182,8 @@ class WorkOSProvider(OAuthProxy):
redirect_path: Redirect path configured in WorkOS (defaults to "/auth/callback")
required_scopes: Required OAuth scopes (no default)
timeout_seconds: HTTP request timeout for WorkOS API calls
+ resource_server_url: Path of the FastMCP server (defaults to base_url). If your MCP endpoint is at
+ a different path like {base_url}/mcp, specify it here for RFC 8707 compliance.
allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
If None (default), all URIs are allowed. If empty list, no URIs are allowed.
"""
@@ -193,6 +198,8 @@ class WorkOSProvider(OAuthProxy):
"redirect_path": redirect_path,
"required_scopes": required_scopes,
"timeout_seconds": timeout_seconds,
+ "resource_server_url": resource_server_url,
+ "allowed_client_redirect_uris": allowed_client_redirect_uris,
}.items()
if v is not NotSet
}
@@ -221,6 +228,8 @@ class WorkOSProvider(OAuthProxy):
redirect_path_final = settings.redirect_path or "/auth/callback"
timeout_seconds_final = settings.timeout_seconds or 10
scopes_final = settings.required_scopes or []
+ resource_server_url_final = settings.resource_server_url or base_url_final
+ allowed_client_redirect_uris_final = settings.allowed_client_redirect_uris
# Extract secret string from SecretStr
client_secret_str = (
@@ -244,7 +253,8 @@ class WorkOSProvider(OAuthProxy):
base_url=base_url_final,
redirect_path=redirect_path_final,
issuer_url=base_url_final,
- allowed_client_redirect_uris=allowed_client_redirect_uris,
+ allowed_client_redirect_uris=allowed_client_redirect_uris_final,
+ resource_server_url=resource_server_url_final,
)
logger.info(