feat(mcp): add user roles to MCP response and permission-aware instructions (#38367)

This commit is contained in:
Amin Ghadersohi
2026-03-06 02:16:51 -05:00
committed by GitHub
parent 84a53eab31
commit db7665c0bc
4 changed files with 40 additions and 0 deletions

View File

@@ -159,6 +159,23 @@ Feature Availability:
- Call get_instance_info to discover accessible menus for the current user.
- Do NOT assume features exist; always check get_instance_info first.
Permission Awareness:
- get_instance_info returns current_user.roles (e.g., ["Admin"], ["Alpha"], ["Viewer"]).
- ALWAYS check the user's roles BEFORE suggesting write operations (creating datasets,
charts, dashboards, or running SQL).
- Common roles and their typical capabilities:
- Admin: Full access to all features
- Alpha: Can create and modify charts, dashboards, datasets, and run SQL
- Gamma: Can view charts and dashboards they have been granted access to
- Viewer: Read-only access to shared dashboards and charts
- If a user has a read-only role (Viewer, Gamma) and a listing tool returns 0 results,
do NOT suggest they create resources. Instead:
1. Explain that they may not have access to the requested resources
2. Suggest they ask a workspace admin to grant them access or share content with them
3. Offer to help with what they CAN do (e.g., viewing dashboards they have access to)
- If you are unsure about a user's capabilities, check their accessible_menus in
feature_availability from get_instance_info.
If you are unsure which tool to use, start with get_instance_info
or use the quickstart prompt for an interactive guide.

View File

@@ -161,6 +161,13 @@ class UserInfo(BaseModel):
last_name: str | None = None
email: str | None = None
active: bool | None = None
roles: List[str] = Field(
default_factory=list,
description=(
"Role names assigned to the user (e.g., Admin, Alpha, Gamma, Viewer). "
"Use this to determine what actions the user can perform."
),
)
class TagInfo(BaseModel):

View File

@@ -110,12 +110,23 @@ def get_instance_info(
# Attach the authenticated user's identity to the response
user = getattr(g, "user", None)
if user is not None:
raw_roles = getattr(user, "roles", None)
user_roles = []
if raw_roles is not None:
try:
user_roles = [
role.name for role in raw_roles if hasattr(role, "name")
]
except TypeError:
logger.debug("Could not iterate user.roles: %s", type(raw_roles))
user_roles = []
result.current_user = UserInfo(
id=getattr(user, "id", None),
username=getattr(user, "username", None),
first_name=getattr(user, "first_name", None),
last_name=getattr(user, "last_name", None),
email=getattr(user, "email", None),
roles=user_roles,
)
return result

View File

@@ -213,12 +213,15 @@ class TestGetInstanceInfoCurrentUserViaMCP:
# and breaks mock resolution on Python 3.10.
from superset.mcp_service.mcp_core import InstanceInfoCore
mock_role = Mock()
mock_role.name = "Alpha"
mock_g_user = Mock()
mock_g_user.id = 5
mock_g_user.username = "sophie"
mock_g_user.first_name = "Sophie"
mock_g_user.last_name = "Beaumont"
mock_g_user.email = "sophie@preset.io"
mock_g_user.roles = [mock_role]
with (
patch.object(
@@ -241,6 +244,7 @@ class TestGetInstanceInfoCurrentUserViaMCP:
assert cu["first_name"] == "Sophie"
assert cu["last_name"] == "Beaumont"
assert cu["email"] == "sophie@preset.io"
assert cu["roles"] == ["Alpha"]
@pytest.mark.asyncio
async def test_get_instance_info_no_user_returns_null(self, mcp_server):
@@ -295,6 +299,7 @@ class TestGetInstanceInfoCurrentUserViaMCP:
assert cu["first_name"] is None
assert cu["last_name"] is None
assert cu["email"] is None
assert cu["roles"] == []
# ---------------------------------------------------------------------------