feat(mcp): add list_reports and get_report_info tools#40348
feat(mcp): add list_reports and get_report_info tools#40348aminghadersohi wants to merge 6 commits into
Conversation
Adds list_reports and get_report_info MCP tools under a new superset/mcp_service/report/ domain, following the canonical database domain pattern. Includes unit tests and app.py registration.
- Register report model type in get_schema (Fix #1): add _get_report_schema_core factory + "report" entry in _SCHEMA_CORE_FACTORIES; ModelType now includes "report" - Add OwnedByMeMixin/CreatedByMeMixin to ListReportsRequest (Fix #2) - DRY up list_reports.py column constants (Fix #3): import REPORT_* constants and get_report_columns from schema_discovery; pass created_by_me/owned_by_me to run_tool - Extend test coverage (Fix #6): humanized timestamp fields, invalid order_column guard, owned_by_me/created_by_me DAO filter injection Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tools
- Add ReportListCore subclass in list_reports.py that overrides filter
injection to use owners.id (instead of generic owner) and calls the
DAO with filters= kwarg (instead of column_operators=) so tests can
assert on the kwarg by name
- Extract _call_dao_list hook in ModelListCore so subclasses can change
the DAO kwarg name without duplicating run_tool
- Add owners.id to SELF_REFERENCING_FILTER_COLUMNS so it is excluded
from filters_applied in responses
Fixes: test_list_reports_owned_by_me_passed_to_dao,
test_list_reports_created_by_me_passed_to_dao
created_by_fk was removed from SELF_REFERENCING_FILTER_COLUMNS so it now appears in filters_applied, but ReportFilter.col Literal didn't include it, causing pydantic validation error in list_reports responses.
…e ColumnOperator for filters_applied - ReportFilter.col Literal no longer includes created_by_fk; callers must use the created_by_me flag instead (internal-only filter) - ReportList.filters_applied typed as List[ColumnOperator] so that internally-injected ColumnOperator instances (e.g. created_by_fk from created_by_me=True) pass pydantic validation without coercion errors
There was a problem hiding this comment.
Code Review Agent Run #07067a
Actionable Suggestions - 2
-
superset/mcp_service/report/schemas.py - 1
- datetime.now() called without timezone · Line 266-268
-
superset/mcp_service/report/tool/get_report_info.py - 1
- Blind exception catch too broad · Line 105-105
Additional Suggestions - 2
-
superset/mcp_service/privacy.py - 1
-
Test gap for owners.id exclusion · Line 61-63The addition of `"owners.id"` to `SELF_REFERENCING_FILTER_COLUMNS` is correct and the comment on line 57 accurately documents its purpose. However, the existing schema-exclusion tests at test_get_schema.py lines 400, 430, 460 only assert that `"owner"` and `"created_by_fk_or_owner"` are absent from `filter_columns` — they do not verify `"owners.id"` is also excluded when the DAO returns it. The docstring on those tests states they exist to ensure synthetic server-generated columns are not advertised to LLM callers, which applies equally to `"owners.id"`.
-
-
superset/mcp_service/report/tool/list_reports.py - 1
-
Unused Serializer Parameter · Line 185-188The nested `_serialize_report` function ignores its `cols` parameter and unconditionally delegates to `serialize_report_object`. This is dead code in the diff's serializer path and mirrors `list_charts.py`'s identical pattern (which also ignores `cols`). Either use `serialize_report_object` directly or properly use the `cols` argument for field-level filtering.
Code suggestion
--- a/superset/mcp_service/report/tool/list_reports.py +++ b/superset/mcp_service/report/tool/list_reports.py @@ -182,15 +182,11 @@ async def list_reports( try: from superset.daos.report import ReportScheduleDAO - def _serialize_report( - obj: "ReportSchedule | None", cols: list[str] | None - ) -> ReportInfo | None: - return serialize_report_object(obj) - list_tool = ReportListCore( dao_class=ReportScheduleDAO, output_schema=ReportInfo, - item_serializer=_serialize_report, + item_serializer=serialize_report_object, filter_type=ReportFilter, default_columns=REPORT_DEFAULT_COLUMNS, search_columns=REPORT_SEARCH_COLUMNS, @@ -199,6 +195,10 @@ async def list_reports( all_columns=get_all_column_names(get_report_columns()), sortable_columns=REPORT_SORTABLE_COLUMNS, logger=logger, + # Type: ignore needed because ModelListCore.item_serializer expects + # Callable[[Any, list[str] | None], Any] but serialize_report_object + # is Callable[[Any], ReportInfo | None]. The cols argument is handled + # by model_dump context at the list level, not by individual serializers. )
-
Filtered by Review Rules
Bito filtered these suggestions based on rules created automatically for your feedback. Manage rules.
-
superset/mcp_service/report/tool/list_reports.py - 1
- Wrong DAO parameter name · Line 122-131
Review Details
-
Files reviewed - 14 · Commit Range:
2e87c2c..2305683- superset/mcp_service/app.py
- superset/mcp_service/common/schema_discovery.py
- superset/mcp_service/constants.py
- superset/mcp_service/mcp_core.py
- superset/mcp_service/privacy.py
- superset/mcp_service/report/__init__.py
- superset/mcp_service/report/schemas.py
- superset/mcp_service/report/tool/__init__.py
- superset/mcp_service/report/tool/get_report_info.py
- superset/mcp_service/report/tool/list_reports.py
- superset/mcp_service/system/tool/get_schema.py
- tests/unit_tests/mcp_service/report/__init__.py
- tests/unit_tests/mcp_service/report/tool/__init__.py
- tests/unit_tests/mcp_service/report/tool/test_report_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
| if dt is None: | ||
| return None | ||
| now = datetime.now(dt.tzinfo) if dt.tzinfo else datetime.now() |
There was a problem hiding this comment.
Add timezone argument to datetime.now() call on line 268. Use datetime.now(timezone.utc) or datetime.now(dt.tzinfo) to ensure timezone-aware datetime.
Code suggestion
Check the AI-generated fix before applying
| if dt is None: | |
| return None | |
| now = datetime.now(dt.tzinfo) if dt.tzinfo else datetime.now() | |
| if dt is None: | |
| return None | |
| now = datetime.now(dt.tzinfo) if dt.tzinfo else datetime.now(timezone.utc) |
Code Review Run #07067a
Should Bito avoid suggestions like this for future reviews? (Manage Rules)
- Yes, avoid them
|
|
||
| return result | ||
|
|
||
| except Exception as e: |
There was a problem hiding this comment.
Replace bare Exception catch with specific exception types (e.g., ValueError, KeyError, or custom exceptions) to follow BLE001 rule and improve error handling.
Code suggestion
Check the AI-generated fix before applying
| except Exception as e: | |
| except (ValueError, KeyError, AttributeError) as e: |
Code Review Run #07067a
Should Bito avoid suggestions like this for future reviews? (Manage Rules)
- Yes, avoid them
SUMMARY
Adds two new MCP tools for the Alerts & Reports domain:
list_reports: List alert and report schedules with filtering (by name, type, active, dashboard_id, chart_id), text search, sorting, select_columns, and 1-based pagination. Supportsowned_by_meandcreated_by_meflags to filter by ownership/creator.get_report_info: Fetch a single report/alert schedule by numeric ID, returning schedule configuration including type (Alert/Report), active status, cron expression, and associated dashboard or chart.Schema discovery is wired in via
get_schema(model_type="report").Key design decisions:
ReportScheduleDAO.base_filter = ReportScheduleFilterownersfield stripped byUSER_DIRECTORY_FIELDSprivacy controlsget_report_columns()live inschema_discovery.pyfor DRY access across list/schema toolsBEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
N/A — backend-only MCP tools.
TESTING INSTRUCTIONS
list_reports— verify it returns reports accessible to the current userlist_reportswith{"owned_by_me": true}— verify only owned reports are returnedget_report_infowith a valid report ID — verify schedule config is returnedget_report_infowith an invalid ID — verify a not_found error is returnedget_schema(model_type="report")— verify column/filter metadata is returnedUnit tests:
ADDITIONAL INFORMATION