This is an automated email from the ASF dual-hosted git repository.
arivero pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new db7665c0bc4 feat(mcp): add user roles to MCP response and
permission-aware instructions (#38367)
db7665c0bc4 is described below
commit db7665c0bc40ad1878714c423927bef60c1ef55d
Author: Amin Ghadersohi <[email protected]>
AuthorDate: Fri Mar 6 02:16:51 2026 -0500
feat(mcp): add user roles to MCP response and permission-aware instructions
(#38367)
---
superset/mcp_service/app.py | 17 +++++++++++++++++
superset/mcp_service/system/schemas.py | 7 +++++++
superset/mcp_service/system/tool/get_instance_info.py | 11 +++++++++++
.../mcp_service/system/tool/test_get_current_user.py | 5 +++++
4 files changed, 40 insertions(+)
diff --git a/superset/mcp_service/app.py b/superset/mcp_service/app.py
index 5f6f867c709..dd48c0a6766 100644
--- a/superset/mcp_service/app.py
+++ b/superset/mcp_service/app.py
@@ -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.
diff --git a/superset/mcp_service/system/schemas.py
b/superset/mcp_service/system/schemas.py
index f59243f4e65..f1667471693 100644
--- a/superset/mcp_service/system/schemas.py
+++ b/superset/mcp_service/system/schemas.py
@@ -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):
diff --git a/superset/mcp_service/system/tool/get_instance_info.py
b/superset/mcp_service/system/tool/get_instance_info.py
index 668b5e44845..4ffa7b8bfc7 100644
--- a/superset/mcp_service/system/tool/get_instance_info.py
+++ b/superset/mcp_service/system/tool/get_instance_info.py
@@ -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
diff --git a/tests/unit_tests/mcp_service/system/tool/test_get_current_user.py
b/tests/unit_tests/mcp_service/system/tool/test_get_current_user.py
index 74000ae8945..0a0dddfa5dc 100644
--- a/tests/unit_tests/mcp_service/system/tool/test_get_current_user.py
+++ b/tests/unit_tests/mcp_service/system/tool/test_get_current_user.py
@@ -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 = "[email protected]"
+ 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"] == "[email protected]"
+ 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"] == []
# ---------------------------------------------------------------------------