feat(mcp): add list and get tools for users and roles#40305
feat(mcp): add list and get tools for users and roles#40305aminghadersohi wants to merge 3 commits into
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #40305 +/- ##
==========================================
- Coverage 64.15% 63.82% -0.33%
==========================================
Files 2592 2601 +9
Lines 138861 140228 +1367
Branches 32208 32498 +290
==========================================
+ Hits 89085 89505 +420
- Misses 48244 49160 +916
- Partials 1532 1563 +31
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
4a2dc4e to
3ceb6c8
Compare
Implements 4 new MCP tools across two domains: - list_users, get_user_info: admin-only user directory tools with privacy controls (email/roles redacted unless caller has data model metadata access) - list_roles, get_role_info: admin-only role listing tools backed by a minimal RoleDAO wrapping the FAB Role model Registers all tools in app.py and adds them to the server instructions. Unit tests cover basic listing, privacy enforcement, not-found paths, and filter/search mutual-exclusion validation.
- Add model_serializer to UserInfo and RoleInfo so select_columns
context filtering is honored in list_users/list_roles output
(matches pattern used by list_databases/list_dashboards)
- Apply model_dump(context={"select_columns": ...}) in list_users and
list_roles, propagating column selection to nested Info serializers
- Move RoleDAO from mcp_service/role/dao.py to superset/daos/role.py
to be consistent with all other DAO locations in the repo
- Update list_users privacy tests: email requires explicit select_columns
request (not in default column set); roles unavailable in list context
because USER_DIRECTORY_FIELDS filters it from DAO column selection
- Add select_columns output-filtering tests for both list_users and
list_roles; fix role tool test patches to use new DAO module path
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ool layer Pydantic model_validators in ListRolesRequest and ListUsersRequest fired during fastmcp parameter deserialization, producing a raw ToolError with "1 validation error for call[list_roles]" text that the StructuredContent- StripperMiddleware wrapped as non-JSON content. Tests that called json.loads() on the result then raised JSONDecodeError instead of seeing the expected error dict. Removes the model_validators from both schemas and moves the mutual exclusion check into the tool functions' try blocks, returning a proper RoleError/UserError JSON object so tests can assert "error" in data. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3ceb6c8 to
01fe935
Compare
|
Closing in favor of a correctly named branch (amin/mcp-users-roles). Will open new draft PR. |
There was a problem hiding this comment.
Code Review Agent Run #51f8a4
Actionable Suggestions - 1
-
superset/mcp_service/role/schemas.py - 1
- Semantic duplication in column constants · Line 42-46
Additional Suggestions - 3
-
superset/mcp_service/user/tool/get_user_info.py - 1
-
Use UserError.create() classmethod · Line 108-111The exception handler manually constructs UserError with timestamp=datetime.now(timezone.utc), duplicating logic that UserError.create() already handles at schemas.py:224-229. Use UserError.create() instead for consistency and reduced maintenance risk.
Code suggestion
--- a/superset/mcp_service/user/tool/get_user_info.py +++ b/superset/mcp_service/user/tool/get_user_info.py @@ -105,9 +105,6 @@ async def get_user_info( "User information retrieval failed: identifier=%s, error=%s, error_type=%s" % (request.identifier, str(e), type(e).__name__) ) - return UserError( - error=f"Failed to get user info: {str(e)}", - error_type="InternalError", - timestamp=datetime.now(timezone.utc), - ) + return UserError.create( + error=f"Failed to get user info: {str(e)}", + error_type="InternalError", + )
-
-
superset/mcp_service/user/tool/list_users.py - 2
-
Incorrect return type annotation · Line 58-58The function signature declares return type `UserList | UserError` but the implementation always returns `dict` (from `model_dump(mode='json')` on lines 90 and 145). This type annotation mismatch is misleading for static type checkers and API consumers. Update the return type to `dict[str, Any]` to match the actual behavior.
Code suggestion
--- superset/mcp_service/user/tool/list_users.py +++ superset/mcp_service/user/tool/list_users.py @@ -55,7 +55,7 @@ from superset.mcp_service.user.schemas import ( async def list_users( request: ListUsersRequest | None = None, ctx: Context | None = None, -) -> UserList | UserError: +) -> dict[str, Any]: """List users with filtering and search. Admin only.
-
Dead parameter in function signature · Line 101-101The `_serialize_user` function accepts a `cols: list[str] | None` parameter but never uses it. The function only references `can_view_sensitive`. This dead parameter creates misleading API expectations for callers and adds unnecessary cognitive load. Either remove it or implement column-based filtering using this parameter.
Code suggestion
--- superset/mcp_service/user/tool/list_users.py +++ superset/mcp_service/user/tool/list_users.py @@ -98,7 +98,7 @@ async def list_users( ) - def _serialize_user(obj: Any, cols: list[str] | None) -> UserInfo | None: + def _serialize_user(obj: Any) -> UserInfo | None: return serialize_user_object(obj, include_sensitive=can_view_sensitive)
-
Filtered by Review Rules
Bito filtered these suggestions based on rules created automatically for your feedback. Manage rules.
-
superset/mcp_service/role/tool/get_role_info.py - 1
- Broad exception catch · Line 90-90
Review Details
-
Files reviewed - 18 · Commit Range:
3a8ea04..01fe935- superset/daos/role.py
- superset/mcp_service/app.py
- superset/mcp_service/role/__init__.py
- superset/mcp_service/role/schemas.py
- superset/mcp_service/role/tool/__init__.py
- superset/mcp_service/role/tool/get_role_info.py
- superset/mcp_service/role/tool/list_roles.py
- superset/mcp_service/user/__init__.py
- superset/mcp_service/user/schemas.py
- superset/mcp_service/user/tool/__init__.py
- superset/mcp_service/user/tool/get_user_info.py
- superset/mcp_service/user/tool/list_users.py
- tests/unit_tests/mcp_service/role/__init__.py
- tests/unit_tests/mcp_service/role/tool/__init__.py
- tests/unit_tests/mcp_service/role/tool/test_role_tools.py
- tests/unit_tests/mcp_service/user/__init__.py
- tests/unit_tests/mcp_service/user/tool/__init__.py
- tests/unit_tests/mcp_service/user/tool/test_user_tools.py
-
Files skipped - 0
-
Tools
- Whispers (Secret Scanner) - ✔︎ Successful
- Detect-secrets (Secret Scanner) - ✔︎ Successful
- MyPy (Static Code Analysis) - ✔︎ Successful
- Astral Ruff (Static Code Analysis) - ✔︎ Successful
Bito Usage Guide
Commands
Type the following command in the pull request comment and save the comment.
-
/review- Manually triggers a full AI review. -
/pause- Pauses automatic reviews on this pull request. -
/resume- Resumes automatic reviews. -
/resolve- Marks all Bito-posted review comments as resolved. -
/abort- Cancels all in-progress reviews.
Refer to the documentation for additional commands.
Configuration
This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.
Documentation & Help
| DEFAULT_ROLE_COLUMNS = ["id", "name"] | ||
|
|
||
| ROLE_ALL_COLUMNS = ["id", "name"] | ||
|
|
||
| ROLE_SORTABLE_COLUMNS = ["id", "name"] |
There was a problem hiding this comment.
The three column constants DEFAULT_ROLE_COLUMNS, ROLE_ALL_COLUMNS, and ROLE_SORTABLE_COLUMNS are semantically duplicated — all contain ["id", "name"]. This creates maintenance risk where a future column addition requires updating three locations instead of one. Consider consolidating to a single constant.
Code suggestion
Check the AI-generated fix before applying
--- superset/mcp_service/role/schemas.py (lines 42-46) ---
42: +DEFAULT_ROLE_COLUMNS = ["id", "name"]
43: +
44: +ROLE_ALL_COLUMNS = ["id", "name"]
45: +
46: +ROLE_SORTABLE_COLUMNS = ["id", "name"]
---
Should become:
42: +# All role columns (used for defaults, all-columns listing, and sorting)
43: +ROLE_COLUMNS = ["id", "name"]
44: +
45: +DEFAULT_ROLE_COLUMNS = ROLE_COLUMNS
46: +ROLE_ALL_COLUMNS = ROLE_COLUMNS
47: +ROLE_SORTABLE_COLUMNS = ROLE_COLUMNS
Code Review Run #51f8a4
Should Bito avoid suggestions like this for future reviews? (Manage Rules)
- Yes, avoid them
SUMMARY
Adds 4 new MCP tools implementing the user and role management domain for the Superset MCP service:
list_users/get_user_info— admin-only user directory tools backed byUserDAOlist_roles/get_role_info— admin-only role listing tools backed by a new minimalRoleDAOwrapping the FABRolemodelPrivacy controls:
emailandrolesfields onUserInfoare redacted by default. They are only returned when the caller has data model metadata access (user_can_view_data_model_metadata()). Non-sensitive fields (id,username,first_name,last_name,active) are always returned.Both domains follow the established MCP tool pattern:
ModelListCore/ModelGetInfoCorefrommcp_core.py@tooldecorator withclass_permission_nameRBAC enforcementevent_loggerinstrumentationBEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
N/A — backend-only MCP tool additions.
TESTING INSTRUCTIONS
Run the new unit tests:
Call tools via MCP client (requires admin credentials):
Verify privacy: connect as a user without
can_get_drill_info/can_writeon Dataset and confirmemail/rolesarenullin list_users and get_user_info responses.ADDITIONAL INFORMATION