Skip to content

datarobot_genai.drmcp.core.oauth_scopes

oauth_scopes

Read OAuth scope settings from this server's configuration.

The mechanism lives in :mod:datarobot_genai.drmcpbase.oauth_scopes, which knows nothing about where settings come from. This module is the adapter that reads them off :class:MCPServerConfig and the environment.

Tag requirements are one environment variable per tag::

MCP_OAUTH_TAG_SCOPES_DATABASE=mcp:tools:execute,mcp:tools:database:write
MCP_OAUTH_TAG_SCOPES_READONLY=mcp:tools:read,mcp:resources:read

rather than one packed variable holding every tag. Each tag is then independent: readable in a diff, greppable, overridable on its own per environment, and comma-separated like every other list setting here — no second delimiter to learn and no long line where one misplaced separator silently reshapes the rules. The suffix is the component's tag, matched case-insensitively with - and _ treated alike, so MCP_OAUTH_TAG_SCOPES_READ_ONLY guards a component tagged read-only.

Values may also arrive as DataRobot runtime parameters, which the platform exposes with a MLOPS_RUNTIME_PARAM_ prefix and a JSON envelope around the value, so both spellings are accepted and the envelope is unwrapped.

split_setting

split_setting(value: str | None) -> list[str]

Read a comma-separated setting, treating blank and unset alike.

Source code in datarobot_genai/drmcp/core/oauth_scopes.py
def split_setting(value: str | None) -> list[str]:
    """Read a comma-separated setting, treating blank and unset alike."""
    if not value:
        return []
    return [item.strip() for item in value.split(",") if item.strip()]

read_tag_scopes

read_tag_scopes(environ: dict[str, str] | None = None) -> dict[str, list[str]]

Collect MCP_OAUTH_TAG_SCOPES_<TAG> variables into {tag: scopes}.

A variable with no scopes in it is skipped rather than registered as an empty requirement, so blanking one out turns the guard off instead of guarding with nothing.

Source code in datarobot_genai/drmcp/core/oauth_scopes.py
def read_tag_scopes(environ: dict[str, str] | None = None) -> dict[str, list[str]]:
    """Collect ``MCP_OAUTH_TAG_SCOPES_<TAG>`` variables into ``{tag: scopes}``.

    A variable with no scopes in it is skipped rather than registered as an
    empty requirement, so blanking one out turns the guard off instead of
    guarding with nothing.
    """
    environ = os.environ.copy() if environ is None else environ
    collected: dict[str, list[str]] = {}
    for name, value in environ.items():
        bare = name
        is_runtime_param = bare.startswith(RUNTIME_PARAM_ENV_VAR_NAME_PREFIX)
        if is_runtime_param:
            bare = bare[len(RUNTIME_PARAM_ENV_VAR_NAME_PREFIX) :]
        if not bare.upper().startswith(TAG_SCOPES_ENV_PREFIX):
            continue
        tag = bare[len(TAG_SCOPES_ENV_PREFIX) :]
        if is_runtime_param:
            # The serverless path delivers a JSON envelope, not the bare value.
            scopes = split_setting(_runtime_param_payload(value))
        else:
            scopes = split_setting(value)
        if tag and scopes:
            collected[tag] = scopes
    return collected

build_scope_settings

build_scope_settings(config: MCPServerConfig | None = None) -> ScopeSettings

Assemble the scope settings this server is configured with.

Which scopes each component requires, and which declaration mechanism is read. Enforcement is the ASGI middleware's (see drmcp.core.middleware), which validates a tools/call's token claims against the called tool's declared scopes, and the checks' own subset test against the same token (see drmcpbase.oauth_scopes), which keeps an under-scoped token from seeing the tool in tools/list. Nothing verifies tokens here.

Source code in datarobot_genai/drmcp/core/oauth_scopes.py
def build_scope_settings(config: MCPServerConfig | None = None) -> ScopeSettings:
    """Assemble the scope settings this server is configured with.

    Which scopes each component requires, and which declaration mechanism is
    read. Enforcement is the ASGI middleware's (see ``drmcp.core.middleware``),
    which validates a ``tools/call``'s token claims against the called tool's
    declared scopes, and the checks' own subset test against the same token
    (see ``drmcpbase.oauth_scopes``), which keeps an under-scoped token from
    seeing the tool in ``tools/list``. Nothing verifies tokens here.
    """
    config = config or get_config()
    return ScopeSettings(
        source=ScopeSource.parse(config.mcp_oauth_scope_source),
        tag_scopes=read_tag_scopes(),
    )

wire_scopes async

wire_scopes(mcp: Any, config: MCPServerConfig | None = None) -> None

Install this server's scope settings and apply them to every component.

Also says out loud when requirements are declared but nothing enforces them. The scope-validation middleware — and with it the token and audience checks — is gated by MCP_ENABLE_OAUTH_CLAIM_VALIDATION; with the gate off a declared requirement is still published in scopes_supported while every tools/call passes, which would otherwise look configured and stay silent.

Source code in datarobot_genai/drmcp/core/oauth_scopes.py
async def wire_scopes(mcp: Any, config: MCPServerConfig | None = None) -> None:
    """Install this server's scope settings and apply them to every component.

    Also says out loud when requirements are declared but nothing enforces them.
    The scope-validation middleware — and with it the token and audience checks —
    is gated by ``MCP_ENABLE_OAUTH_CLAIM_VALIDATION``; with the gate off a declared
    requirement is still published in ``scopes_supported`` while every
    ``tools/call`` passes, which would otherwise look configured and stay silent.
    """
    config = config or get_config()
    await _wire_scopes(mcp, build_scope_settings(config))
    if not config.mcp_enable_oauth_claim_validation and (declared := derived_scopes()):
        logger.warning(
            "OAuth scopes %s are declared but MCP_ENABLE_OAUTH_CLAIM_VALIDATION is off: the "
            "scope-validation middleware is not running, so no tools/call is checked against "
            "them (nor is any token or audience checked). They are still published in "
            "scopes_supported. Set MCP_ENABLE_OAUTH_CLAIM_VALIDATION=true to enforce them.",
            declared,
        )