-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Added Provider for OCI IAM service. #2317
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
2cb1f41
397bf6e
bab1352
fc3c3bf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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) | ||
|
|
||
|
|
||
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: Consider adding documentation similar to the parent 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 |
||
| 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, | ||
| ) | ||
There was a problem hiding this comment.
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
🤖 Prompt for AI Agents