Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 179 additions & 0 deletions src/fastmcp/server/auth/providers/ociprovider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
"""OCI OIDC provider for FastMCP.

This module provides OIDC Implementation to integrate MCP servers with OCI.
You only need OCI Identity Domain's discovery URL, client ID, client secret, and base URL.

Example:
```python
from fastmcp import FastMCP
from fastmcp.server.auth.providers.ociprovider import OCIProvider

# Simple OCI OIDC protection
auth = OCIProvider(
config_url="https://{IDCS_GUID}.identity.oraclecloud.com/.well-known/openid-configuration",
client_id="oci-iamdomain-app-client-id",
client_secret="oci-iamdomain-app-client-secret",
base_url="http://localhost:8000",
required_scopes=["openid", "profile", "email"],
redirect_path="/auth/callback",
)

mcp = FastMCP("My Protected Server", auth=auth)
```
"""

from key_value.aio.protocols import AsyncKeyValue
from pydantic import AnyHttpUrl, SecretStr, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict

from fastmcp.server.auth.oidc_proxy import OIDCProxy
from fastmcp.settings import ENV_FILE
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.types import NotSet, NotSetT

logger = get_logger(__name__)


class OCIProviderSettings(BaseSettings):
"""Settings for OCI IAM domain OIDC provider."""

model_config = SettingsConfigDict(
env_prefix="FASTMCP_SERVER_AUTH_OCI_",
env_file=ENV_FILE,
extra="ignore",
)

config_url: AnyHttpUrl | None = None
client_id: str | None = None
client_secret: SecretStr | None = None
audience: str | None = None
base_url: AnyHttpUrl | None = None
issuer_url: AnyHttpUrl | None = None
redirect_path: str | None = None
required_scopes: list[str] | None = None
allowed_client_redirect_uris: list[str] | None = None
jwt_signing_key: str | bytes | None = None

@field_validator("required_scopes", mode="before")
@classmethod
def _parse_scopes(cls, v):
return parse_scopes(v)
Comment on lines +58 to +61
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Add return type annotation to the validator.

For library code, full type annotations improve type safety and code clarity. The validator should explicitly annotate its return type.

Apply this diff:

     @field_validator("required_scopes", mode="before")
     @classmethod
-    def _parse_scopes(cls, v):
+    def _parse_scopes(cls, v) -> list[str] | None:
         return parse_scopes(v)

As per coding guidelines

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@field_validator("required_scopes", mode="before")
@classmethod
def _parse_scopes(cls, v):
return parse_scopes(v)
@field_validator("required_scopes", mode="before")
@classmethod
def _parse_scopes(cls, v) -> list[str] | None:
return parse_scopes(v)
🤖 Prompt for AI Agents
In src/fastmcp/server/auth/providers/ociprovider.py around lines 58 to 61, the
Pydantic field validator _parse_scopes lacks an explicit return type; update its
signature to include the correct return type that matches parse_scopes (for
example -> list[str] or whatever parse_scopes returns), and add any necessary
typing imports (or use from __future__ import annotations) so the module remains
fully typed.



class OCIProvider(OIDCProxy):
"""An OCI IAM Domain provider implementation for FastMCP.

This provider is a complete OCI integration that's ready to use with
just the configuration URL, client ID, client secret, and base URL.

Example:
```python
from fastmcp import FastMCP
from fastmcp.server.auth.providers.ociprovider import OCIProvider

# Simple OCI OIDC protection
auth = OCIProvider(
config_url="https://{IDCS_GUID}.identity.oraclecloud.com/.well-known/openid-configuration",
client_id="oci-iamdomain-app-client-id",
client_secret="oci-iamdomain-app-client-secret",
base_url="http://localhost:8000",
required_scopes=["openid", "profile", "email"],
redirect_path="/auth/callback",
)

mcp = FastMCP("My Protected Server", auth=auth)
```
"""

def __init__(
self,
*,
config_url: AnyHttpUrl | str | NotSetT = NotSet,
client_id: str | NotSetT = NotSet,
client_secret: str | NotSetT = NotSet,
audience: str | NotSetT = NotSet,
base_url: AnyHttpUrl | str | NotSetT = NotSet,
issuer_url: AnyHttpUrl | str | NotSetT = NotSet,
required_scopes: list[str] | NotSetT = NotSet,
redirect_path: str | NotSetT = NotSet,
allowed_client_redirect_uris: list[str] | NotSetT = NotSet,
client_storage: AsyncKeyValue | None = None,
jwt_signing_key: str | bytes | NotSetT = NotSet,
require_authorization_consent: bool = True,
) -> None:
"""Initialize OCI OIDC provider.

Args:
config_url: OCI OIDC Discovery URL
client_id: OCI IAM Domain Integrated Application client id
client_secret: OCI Integrated Application client secret
audience: OCI API audience (optional)
base_url: Public URL where OIDC endpoints will be accessible (includes any mount path)
issuer_url: Issuer URL for OCI IAM Domain metadata. This will override issuer URL from the discovery URL.
required_scopes: Required OCI scopes (defaults to ["openid"])
redirect_path: Redirect path configured in OCI IAM Domain Integrated Application.
The default is "/auth/callback".
allowed_client_redirect_uris: List of allowed redirect URI patterns for MCP clients.
"""
Comment on lines +105 to +118
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Complete the docstring with missing parameter documentation.

The docstring is missing documentation for three parameters: client_storage, jwt_signing_key, and require_authorization_consent. These are important configuration options (especially require_authorization_consent which has security implications) and should be documented for library users.

Consider adding documentation similar to the parent OIDCProxy class:

             redirect_path: Redirect path configured in OCI IAM Domain Integrated Application.
                     The default is "/auth/callback".
             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.
+            client_storage: Storage backend for OAuth state (client registrations, encrypted tokens).
+                If None, a DiskStore will be created in the data directory.
+            jwt_signing_key: Secret for signing FastMCP JWT tokens (any string or bytes).
+                If not provided, the client secret will be used to derive a key.
+            require_authorization_consent: Whether to require user consent before authorizing clients (default True).
+                When False, authorization proceeds without user confirmation.
+                SECURITY WARNING: Only disable for local development or testing environments.
         """
🤖 Prompt for AI Agents
In src/fastmcp/server/auth/providers/ociprovider.py around lines 105 to 118, the
docstring omits documentation for the parameters client_storage,
jwt_signing_key, and require_authorization_consent; update the docstring to add
brief descriptions for each: document client_storage (what storage
implementation is expected and its role), jwt_signing_key (purpose for signing
JWTs and expected format), and require_authorization_consent (boolean behavior
and security implication when true/false), mirroring style and phrasing used in
the parent OIDCProxy class so callers understand defaults and security impact.

settings = OCIProviderSettings.model_validate(
{
k: v
for k, v in {
"config_url": config_url,
"client_id": client_id,
"client_secret": client_secret,
"audience": audience,
"base_url": base_url,
"issuer_url": issuer_url,
"required_scopes": required_scopes,
"redirect_path": redirect_path,
"allowed_client_redirect_uris": allowed_client_redirect_uris,
"jwt_signing_key": jwt_signing_key,
}.items()
if v is not NotSet
}
)

if not settings.config_url:
raise ValueError(
"config_url is required - set via parameter or FASTMCP_SERVER_AUTH_OCI_CONFIG_URL"
)

if not settings.client_id:
raise ValueError(
"client_id is required - set via parameter or FASTMCP_SERVER_AUTH_OCI_CLIENT_ID"
)

if not settings.client_secret:
raise ValueError(
"client_secret is required - set via parameter or FASTMCP_SERVER_AUTH_OCI_CLIENT_SECRET"
)

if not settings.base_url:
raise ValueError(
"base_url is required - set via parameter or FASTMCP_SERVER_AUTH_OCI_BASE_URL"
)

oci_required_scopes = settings.required_scopes or ["openid"]

super().__init__(
config_url=settings.config_url,
client_id=settings.client_id,
client_secret=settings.client_secret.get_secret_value(),
audience=settings.audience,
base_url=settings.base_url,
issuer_url=settings.issuer_url,
redirect_path=settings.redirect_path,
required_scopes=oci_required_scopes,
allowed_client_redirect_uris=settings.allowed_client_redirect_uris,
client_storage=client_storage,
jwt_signing_key=settings.jwt_signing_key,
require_authorization_consent=require_authorization_consent,
)

logger.debug(
"Initialized OCI OAuth provider for client %s with scopes: %s",
settings.client_id,
oci_required_scopes,
)