DataRobot entitlements-backed feature flag evaluation.
Lives in drtools so consumers (tools, global-mcp, any drtools-only
package) can evaluate per-user entitlements without depending on the
heavier drmcp/drmcpbase packages or their module-load side effects.
This evaluator is per-user: it requires a request-scoped
:class:~datarobot.rest.RESTClientObject (e.g. from
:func:datarobot_genai.drmcputils.clients.datarobot.request_user_dr_client)
and caches results per (flag, principal). It is the building block for per-user,
live tool gating (e.g. hiding the sandbox tool unless an entitlement is set).
It is intentionally separate from
:mod:datarobot_genai.drmcp.core.feature_flags, which evaluates the
application-static MCP-container account against drmcpbase for dynamic
tool/prompt registration. The two may be consolidated later.
FeatureFlag
dataclass
Source code in datarobot_genai/drmcputils/feature_flags.py
| @dataclass
class FeatureFlag:
name: str
enabled: bool
@classmethod
def create(
cls,
feature_flag_name: str,
*,
client: RESTClientObject,
) -> FeatureFlag:
"""Evaluate a DR entitlement against the principal owning ``client``.
``client`` is required at this layer. If you need the historical
"use the application-static client by default" behavior, use
``datarobot_genai.drmcp.core.feature_flags.FeatureFlag`` instead.
"""
response = client.post(
"entitlements/evaluate/",
json={"entitlements": [{"name": feature_flag_name}]},
)
feature_flag_info = response.json()["entitlements"][0]
return cls(
name=feature_flag_info["name"],
enabled=bool(feature_flag_info["value"]),
)
@classmethod
def is_enabled(
cls,
feature_flag_name: str,
*,
client: RESTClientObject,
ttl_seconds: float = _DEFAULT_TTL_SECONDS,
) -> bool:
"""Return the cached entitlement state, keyed by ``(flag, principal)``.
Pass ``ttl_seconds=0`` to bypass the cache. The principal portion of
the key is a hash of the client's token, so different users do not
share cache entries.
"""
cache_key = (feature_flag_name, _principal_key(client))
now = time.monotonic()
if ttl_seconds > 0:
cached = _eval_cache.get(cache_key)
if cached is not None and (now - cached[0]) < ttl_seconds:
return cached[1]
enabled = cls.create(feature_flag_name, client=client).enabled
if ttl_seconds > 0:
_prune_expired(now, ttl_seconds)
_eval_cache[cache_key] = (now, enabled)
return enabled
@classmethod
def is_mcp_tools_gallery_support_enabled(cls, *, client: RESTClientObject) -> bool:
return cls.is_enabled("ENABLE_MCP_TOOLS_GALLERY_SUPPORT", client=client)
|
create
classmethod
create(feature_flag_name: str, *, client: RESTClientObject) -> FeatureFlag
Evaluate a DR entitlement against the principal owning client.
client is required at this layer. If you need the historical
"use the application-static client by default" behavior, use
datarobot_genai.drmcp.core.feature_flags.FeatureFlag instead.
Source code in datarobot_genai/drmcputils/feature_flags.py
| @classmethod
def create(
cls,
feature_flag_name: str,
*,
client: RESTClientObject,
) -> FeatureFlag:
"""Evaluate a DR entitlement against the principal owning ``client``.
``client`` is required at this layer. If you need the historical
"use the application-static client by default" behavior, use
``datarobot_genai.drmcp.core.feature_flags.FeatureFlag`` instead.
"""
response = client.post(
"entitlements/evaluate/",
json={"entitlements": [{"name": feature_flag_name}]},
)
feature_flag_info = response.json()["entitlements"][0]
return cls(
name=feature_flag_info["name"],
enabled=bool(feature_flag_info["value"]),
)
|
is_enabled
classmethod
is_enabled(feature_flag_name: str, *, client: RESTClientObject, ttl_seconds: float = _DEFAULT_TTL_SECONDS) -> bool
Return the cached entitlement state, keyed by (flag, principal).
Pass ttl_seconds=0 to bypass the cache. The principal portion of
the key is a hash of the client's token, so different users do not
share cache entries.
Source code in datarobot_genai/drmcputils/feature_flags.py
| @classmethod
def is_enabled(
cls,
feature_flag_name: str,
*,
client: RESTClientObject,
ttl_seconds: float = _DEFAULT_TTL_SECONDS,
) -> bool:
"""Return the cached entitlement state, keyed by ``(flag, principal)``.
Pass ``ttl_seconds=0`` to bypass the cache. The principal portion of
the key is a hash of the client's token, so different users do not
share cache entries.
"""
cache_key = (feature_flag_name, _principal_key(client))
now = time.monotonic()
if ttl_seconds > 0:
cached = _eval_cache.get(cache_key)
if cached is not None and (now - cached[0]) < ttl_seconds:
return cached[1]
enabled = cls.create(feature_flag_name, client=client).enabled
if ttl_seconds > 0:
_prune_expired(now, ttl_seconds)
_eval_cache[cache_key] = (now, enabled)
return enabled
|
is_tool_feature_enabled(feature_flag_name: str | None, *, evaluator: Callable[[str], bool]) -> bool
Decide whether a feature-flag-gated tool should be exposed.
Shared gating policy for every MCP tool registry: drmcp's static-account
registry and global-mcp's per-user registry both call this, so the
"no flag → expose; flag set → evaluate; lookup error → fail closed" decision
lives once in the drtools layer instead of being duplicated per server.
evaluator performs the actual entitlement check for one flag name. Only
the calling registry knows which principal/client to evaluate against (the
static container account for drmcp, the requesting user for global-mcp),
so it supplies that via the closure rather than this layer reaching for a
client it cannot choose correctly.
Parameters:
| Name |
Type |
Description |
Default |
feature_flag_name
|
str | None
|
The entitlement gating the tool, or None if ungated.
|
required
|
evaluator
|
Callable[[str], bool]
|
Callable returning whether feature_flag_name is enabled.
|
required
|
``True`` if the tool should be registered. ``False`` if the flag is
disabled or its evaluation raised (fail-closed, so a lookup failure
never accidentally exposes a gated tool).
Source code in datarobot_genai/drmcputils/feature_flags.py
| def is_tool_feature_enabled(
feature_flag_name: str | None,
*,
evaluator: Callable[[str], bool],
) -> bool:
"""Decide whether a feature-flag-gated tool should be exposed.
Shared gating policy for every MCP tool registry: ``drmcp``'s static-account
registry and ``global-mcp``'s per-user registry both call this, so the
"no flag → expose; flag set → evaluate; lookup error → fail closed" decision
lives once in the drtools layer instead of being duplicated per server.
``evaluator`` performs the actual entitlement check for one flag name. Only
the calling registry knows which principal/client to evaluate against (the
static container account for ``drmcp``, the requesting user for global-mcp),
so it supplies that via the closure rather than this layer reaching for a
client it cannot choose correctly.
Args:
feature_flag_name: The entitlement gating the tool, or ``None`` if ungated.
evaluator: Callable returning whether ``feature_flag_name`` is enabled.
Returns
-------
``True`` if the tool should be registered. ``False`` if the flag is
disabled or its evaluation raised (fail-closed, so a lookup failure
never accidentally exposes a gated tool).
"""
if feature_flag_name is None:
return True
try:
return evaluator(feature_flag_name)
except Exception:
logger.debug(
"feature flag %s evaluation failed; gating tool off (fail-closed)",
feature_flag_name,
exc_info=True,
)
return False
|