mlflow/mlflow · error · MlflowException

PERMISSION_DENIED

PERMISSION_DENIED

Error message

Permission denied.

What it means

MLflow raises PERMISSION_DENIED when auto-creation of the parent MCP server registry entry is not permitted during create_mcp_server_version. If the parent did not exist and was created on the fly, but the caller lacks update permission on the (re-checked) existing parent, the operation is denied to prevent privilege escalation via auto-create.

Source

Thrown at mlflow/server/mcp_server_api.py:564


def _ensure_version_create_parent_access(
    store, name: str, username: str | None, request: Request
) -> None:
    if not getattr(request.state, "mcp_server_parent_auto_created", False):
        return

    try:
        store.create_mcp_server(name=name, created_by=username)
    except MlflowException as e:
        if e.error_code != ErrorCode.Name(RESOURCE_ALREADY_EXISTS):
            raise
        request.state.mcp_server_parent_auto_created = False
        can_update_existing = getattr(
            request.state, "mcp_server_can_update_existing_recheck", lambda: False
        )
        if not can_update_existing():
            raise MlflowException("Permission denied.", error_code=PERMISSION_DENIED)


def _update_mcp_access_endpoint_kwargs(
    server_name: str, endpoint_id: str, body: UpdateMCPAccessEndpointRequest
) -> dict[str, Any]:
    kwargs: dict[str, Any] = {"server_name": server_name, "endpoint_id": endpoint_id}
    provided_fields = body.model_fields_set
    for field_name in ("server_version", "server_alias", "url"):
        if field_name in provided_fields:
            kwargs[field_name] = getattr(body, field_name)
    if "transport_type" in provided_fields:
        kwargs["transport_type"] = (
            None if body.transport_type is None else _parse_transport_type(body.transport_type)
        )
    return kwargs


mcp_server_router = APIRouter(tags=["MCP Server Registry"])

View on GitHub (pinned to 6a27f2decc)

Solutions

  1. Have an admin grant the user UPDATE permission on the parent MCP server.
  2. Retry the request - if the parent now exists and you have access, the create may succeed via the normal path.
  3. Create the parent MCP server explicitly with adequate permissions before creating versions.
  4. Check workspace/registry permission configuration to ensure the role includes update on existing servers.
Defensive patterns

Strategy: try-catch

Validate before calling

import mlflow
from mlflow.exceptions import MlflowException

def can_create_version(name: str) -> bool:
    try:
        mlflow.mcp_server.get_mcp_server(name)
        return True
    except MlflowException:
        return False  # will need auto-create/update permission

Type guard

def has_update_permission(server) -> bool:
    return getattr(server, "can_update", False) or getattr(server, "user_can_update", False)

Try / catch

from mlflow.exceptions import MlflowException
try:
    client.create_mcp_server_version(name, body)
except MlflowException as e:
    if e.error_code == "PERMISSION_DENIED":
        request_admin_grant("UPDATE", resource=name)  # escalate via admin, not retry-spam

Prevention

When it happens

Trigger: create_mcp_server_version where the parent server record does not exist (auto-create path runs), then the can_update_existing recheck finds a parent was created concurrently by another request and the current user lacks update permission on it.

Common situations: Race between two clients creating the same server version simultaneously; users with create-but-not-update permissions calling version creation for a server they do not own.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of mlflow/mlflow@6a27f2decc (2026-08-29). Data as JSON: /api/errors/7f8c8e24da729460. Report an issue: GitHub.