Skip to content

feat(mcp): add list and get tools for action log and tasks - #40344

Merged
aminghadersohi merged 20 commits into
masterfrom
amin/mcp-action-log-tasks
May 30, 2026
Merged

feat(mcp): add list and get tools for action log and tasks#40344
aminghadersohi merged 20 commits into
masterfrom
amin/mcp-action-log-tasks

Conversation

@aminghadersohi

@aminghadersohi aminghadersohi commented May 22, 2026

Copy link
Copy Markdown
Contributor

SUMMARY

Adds 4 new MCP tools across two new domains:

Action Log domain (superset/mcp_service/action_log/):

  • list_action_logs: Lists audit log entries with filtering, pagination, and a default 7-day dttm window to prevent oversized result sets on large instances. Admin-only.
  • get_action_log_info: Retrieves a single log entry by integer ID. Admin-only.

Task domain (superset/mcp_service/task/):

  • list_tasks: Lists async tasks. Non-admin users are automatically scoped to tasks they are subscribed to via TaskDAO.base_filter = TaskFilter. Admins see all tasks. Supports search (matched against task_type, task_key, task_name, status, scope) and column-level filters (mutually exclusive).
  • get_task_info: Retrieves a single task by integer ID or UUID string.

Both domains follow the established ModelListCore / ModelGetInfoCore patterns from the database domain. Response field filtering via select_columns uses @model_serializer context propagation, matching the DatabaseInfo pattern.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

N/A — backend-only MCP tools.

TESTING INSTRUCTIONS

Run the new unit tests:

pytest tests/unit_tests/mcp_service/action_log/
pytest tests/unit_tests/mcp_service/task/

Key behaviors verified by tests:

  • list_action_logs injects a dttm >= now - 7 days ISO-string filter when no dttm filter is provided
  • list_action_logs skips the default filter when the caller provides a dttm filter
  • list_action_logs defaults to order_column=dttm, order_direction=desc
  • list_action_logs filters_applied echoes the injected filter as an ISO string (not datetime)
  • list_tasks delegates to TaskDAO.list() so TaskFilter scoping applies automatically
  • get_task_info resolves by both integer ID and UUID string
  • Not-found cases return structured error_type: not_found responses
  • select_columns filtering drops non-requested fields from the response (not null)

ADDITIONAL INFORMATION

  • Has associated issue:
  • Required feature flags:
  • Changes UI
  • Includes DB Migration
  • Introduces new feature or API
  • Removes existing feature or API

Live Test Results

Tested on staging Preset workspace (build ID: 36a56752, MCP server: claude.ai preset stg).

list_action_logs

Request:

{}

Response:

{
  "action_logs": [],
  "count": 0,
  "total_count": 0,
  "page": 1,
  "page_size": 10,
  "total_pages": 0,
  "has_previous": false,
  "has_next": false,
  "columns_requested": ["id", "action", "user_id", "dttm"],
  "columns_loaded": ["id", "action", "user_id", "dttm"],
  "columns_available": ["id", "action", "user_id", "dttm", "dashboard_id", "slice_id", "json"],
  "sortable_columns": ["id", "dttm"],
  "filters_applied": [{"col": "dttm", "opr": "gte", "value": "2026-05-22T17:13:21.220832Z"}],
  "pagination": {"page": 1, "page_size": 10, "total_count": 0, "total_pages": 0, "has_next": false, "has_previous": false},
  "timestamp": "2026-05-29T17:13:21.225768Z"
}

✅ Empty list (expected — action logs not populated in Preset environments), default 7-day dttm filter injected automatically, columns_available present, no permission error.


get_action_log_info

Request:

{"identifier": 1}

Response:

{
  "error": "ActionLogInfo with identifier '1' not found",
  "error_type": "not_found",
  "timestamp": "2026-05-29T17:13:22.416252Z"
}

✅ Structured not_found error, no crash, no permission error.


list_tasks

Request:

{}

Response:

{
  "tasks": [],
  "count": 0,
  "total_count": 0,
  "page": 1,
  "page_size": 10,
  "total_pages": 0,
  "has_previous": false,
  "has_next": false,
  "columns_requested": ["id", "uuid", "task_type", "status", "changed_on"],
  "columns_loaded": ["id", "uuid", "task_type", "status", "changed_on"],
  "columns_available": ["id", "uuid", "task_type", "task_key", "task_name", "status", "scope", "changed_on", "created_on"],
  "sortable_columns": ["id", "changed_on", "created_on", "status"],
  "filters_applied": [],
  "pagination": {"page": 1, "page_size": 10, "total_count": 0, "total_pages": 0, "has_next": false, "has_previous": false},
  "timestamp": "2026-05-29T17:13:23.542322Z"
}

✅ Empty list (no active tasks in this workspace), no error, columns_available present.


get_task_info

Request:

{"identifier": 9999}

Response:

{
  "error": "TaskInfo with identifier '9999' not found",
  "error_type": "not_found",
  "timestamp": "2026-05-29T17:13:24.900648Z"
}

✅ Structured not_found error, no crash.

@aminghadersohi
aminghadersohi marked this pull request as ready for review May 22, 2026 04:03
@bito-code-review

bito-code-review Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #b8d6d6

Actionable Suggestions - 0
Additional Suggestions - 2
  • superset/mcp_service/action_log/tool/get_action_log_info.py - 1
    • Docstring claims admin-only without enforcement · Line 56-56
      The docstring claims 'ADMIN-ONLY: This tool requires admin privileges' but no admin check is implemented. The `class_permission_name='Log'` only enforces FAB's Log permission model, which is not inherently admin-restricted. Either implement actual admin verification or correct the documentation to match the actual permission behavior.
  • superset/mcp_service/action_log/schemas.py - 1
    • DTTM timezone logic duplicated · Line 91-95
      The `dttm` timezone normalization logic (lines 91–95) is semantically duplicated in `serialize_action_log_object` (lines 231–234). Both convert naive datetimes to UTC. Maintaining identical logic in two places increases divergence risk if one is updated without the other.
Filtered by Review Rules

Bito filtered these suggestions based on rules created automatically for your feedback. Manage rules.

  • superset/mcp_service/task/tool/list_tasks.py - 1
  • superset/mcp_service/action_log/tool/list_action_logs.py - 1
    • Naive datetime ValidationError in _serialize · Line 102-103
Review Details
  • Files reviewed - 17 · Commit Range: 9a6c927..0f9b0ee
    • superset/mcp_service/action_log/__init__.py
    • superset/mcp_service/action_log/schemas.py
    • superset/mcp_service/action_log/tool/__init__.py
    • superset/mcp_service/action_log/tool/get_action_log_info.py
    • superset/mcp_service/action_log/tool/list_action_logs.py
    • superset/mcp_service/app.py
    • superset/mcp_service/task/__init__.py
    • superset/mcp_service/task/schemas.py
    • superset/mcp_service/task/tool/__init__.py
    • superset/mcp_service/task/tool/get_task_info.py
    • superset/mcp_service/task/tool/list_tasks.py
    • tests/unit_tests/mcp_service/action_log/__init__.py
    • tests/unit_tests/mcp_service/action_log/tool/__init__.py
    • tests/unit_tests/mcp_service/action_log/tool/test_action_log_tools.py
    • tests/unit_tests/mcp_service/task/__init__.py
    • tests/unit_tests/mcp_service/task/tool/__init__.py
    • tests/unit_tests/mcp_service/task/tool/test_task_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

AI Code Review powered by Bito Logo

@aminghadersohi
aminghadersohi force-pushed the amin/mcp-action-log-tasks branch from 0f9b0ee to d5b95da Compare May 26, 2026 17:27
Comment thread superset/mcp_service/task/schemas.py Outdated
@bito-code-review

Copy link
Copy Markdown
Contributor

The PR comments file is empty (only contains the header row). I cannot analyze any review comments or suggestions. Please ensure the file contains actual comment data.

Comment thread superset/mcp_service/task/tool/get_task_info.py
Comment thread superset/mcp_service/action_log/tool/list_action_logs.py Outdated
Comment thread tests/unit_tests/mcp_service/task/tool/test_task_tools.py
Comment thread tests/unit_tests/mcp_service/task/tool/test_task_tools.py
Comment thread tests/unit_tests/mcp_service/task/tool/test_task_tools.py
Comment thread tests/unit_tests/mcp_service/task/tool/test_task_tools.py
@codecov

codecov Bot commented May 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 56.94444% with 155 lines in your changes missing coverage. Please review.
✅ Project coverage is 64.00%. Comparing base (8d8eeb3) to head (3ce791b).

Files with missing lines Patch % Lines
superset/mcp_service/action_log/schemas.py 62.80% 44 Missing and 1 partial ⚠️
...et/mcp_service/action_log/tool/list_action_logs.py 33.33% 26 Missing ⚠️
superset/mcp_service/task/schemas.py 76.84% 21 Missing and 1 partial ⚠️
superset/mcp_service/app.py 17.39% 19 Missing ⚠️
superset/mcp_service/task/tool/list_tasks.py 36.66% 19 Missing ⚠️
...mcp_service/action_log/tool/get_action_log_info.py 47.82% 12 Missing ⚠️
superset/mcp_service/task/tool/get_task_info.py 47.82% 12 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #40344      +/-   ##
==========================================
- Coverage   64.02%   64.00%   -0.02%     
==========================================
  Files        2612     2620       +8     
  Lines      140615   140974     +359     
  Branches    32501    32527      +26     
==========================================
+ Hits        90032    90237     +205     
- Misses      49034    49186     +152     
- Partials     1549     1551       +2     
Flag Coverage Δ
hive 39.47% <56.94%> (+0.09%) ⬆️
mysql 58.48% <56.94%> (-0.01%) ⬇️
postgres 58.56% <56.94%> (-0.01%) ⬇️
presto 41.10% <56.94%> (+0.08%) ⬆️
python 60.07% <56.94%> (-0.02%) ⬇️
sqlite 58.21% <56.94%> (-0.01%) ⬇️
unit 100.00% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@richardfogaca richardfogaca left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Posting on Richard's behalf -- this is his PR reviewer agent. Forward any pushback to him and he'll loop me back in.

Left a few notes below -- the main things I would check before merge are the feature/config guards around these new MCP surfaces and the dttm filter value type. All line numbers verified against HEAD 28461c7.

Functional -- worth checking before merge

  • superset/mcp_service/action_log/tool/list_action_logs.py:87

    The action-log MCP tools query LogDAO directly whenever the caller has the Log read permission, but the existing REST/model views also honor FAB_ADD_SECURITY_VIEWS and SUPERSET_LOG_VIEW through LogRestApi.is_enabled(). If an operator disables the action-log surface with those config flags, the REST API returns 404 while these MCP tools still register and can read logs.

    WDYT -- could we mirror the existing LogRestApi.is_enabled() guard for both list_action_logs and get_action_log_info, so MCP follows the same disabled-state behavior as the rest of Superset?

  • superset/mcp_service/app.py:677

    The task tools are imported and registered unconditionally, but the existing task REST API and TaskManager initialization are both gated by GLOBAL_TASK_FRAMEWORK. Since that flag defaults off, this can advertise and execute task metadata tools in an instance where /api/v1/task and task-manager behavior were intentionally disabled.

    WDYT -- should the MCP task tools be hidden or return a disabled-feature error unless feature_flag_manager.is_feature_enabled("GLOBAL_TASK_FRAMEWORK") is true?

  • superset/mcp_service/action_log/tool/list_action_logs.py:93

    The injected default dttm filter is an ISO string, and the schema also limits filter values to string/number/bool/list. BaseDAO.apply_column_operators() then compares Log.dttm >= value directly; with this string value SQLAlchemy infers the bind as VARCHAR, so on Postgres this looks likely to compile as a timestamp-to-varchar comparison rather than a timestamp bind.

    Could we keep the DAO filter value as a datetime and let response serialization turn filters_applied into an ISO string, or otherwise parse dttm filter values before they reach ColumnOperatorEnum.gte?

Praise

  • superset/mcp_service/task/tool/list_tasks.py:90

    Nice reuse of TaskDAO.base_filter for task visibility. Keeping subscription scoping in the DAO path avoids duplicating a second, possibly divergent access filter in the MCP layer.

@aminghadersohi

Copy link
Copy Markdown
Contributor Author

Thanks for the review @richardfogaca! All three issues addressed in 946bb37:

Issue 1 — Config guard for action-log tools
Added _apply_config_guards() helper in app.py that mirrors LogRestApi.is_enabled(): if FAB_ADD_SECURITY_VIEWS or SUPERSET_LOG_VIEW is False, list_action_logs and get_action_log_info are removed from the MCP server before it starts advertising tools. This prevents the MCP endpoint from serving log data when the REST API would return 404.

Issue 2 — GLOBAL_TASK_FRAMEWORK gate for task tools
The same _apply_config_guards() helper also checks FEATURE_FLAGS["GLOBAL_TASK_FRAMEWORK"]. If the flag is False (the default), list_tasks and get_task_info are removed from the MCP server — mirroring the conditional appbuilder.add_api(TaskRestApi) in initialization/__init__.py.

Issue 3 — dttm filter as datetime object
Changed ActionLogFilter.value to include datetime in its union type (str | int | float | bool | datetime | list[...]). The default 7-day cutoff in list_action_logs now passes a datetime object directly instead of an ISO string, so SQLAlchemy binds the parameter as TIMESTAMP rather than VARCHAR. Pydantic's model_dump(mode="json") serialises it to ISO string in the response, so the filters_applied output is unchanged.

Tests added for all three guards in test_mcp_tool_registration.py.

@bito-code-review

bito-code-review Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #ea15ec

Actionable Suggestions - 0
Additional Suggestions - 2
  • superset/mcp_service/action_log/schemas.py - 1
    • Filter schema type inconsistency · Line 69-69
      ActionLogFilter now accepts `datetime` in its `value` type, but the four other filter schemas (ChartFilter, DashboardFilter, DatasetFilter, TaskFilter, DatabaseFilter) retain the narrower `str | int | float | bool | List[...]` union. If datetime support is intentional here (required for dttm column filtering), the same pattern should be applied consistently across all ColumnOperator subclasses to avoid divergent behavior.
  • superset/mcp_service/task/schemas.py - 1
    • Duplicate inline import · Line 229-229
      Please move `from datetime import timezone` to the module level (alongside the existing `datetime` import on line 22) and remove both inline imports at lines 208 and 229 to avoid redundant per-call imports.
Filtered by Review Rules

Bito filtered these suggestions based on rules created automatically for your feedback. Manage rules.

  • tests/unit_tests/mcp_service/test_mcp_tool_registration.py - 2
Review Details
  • Files reviewed - 8 · Commit Range: 0f9b0ee..e3ba2d1
    • superset/mcp_service/task/schemas.py
    • tests/unit_tests/mcp_service/task/tool/test_task_tools.py
    • superset/mcp_service/action_log/schemas.py
    • superset/mcp_service/action_log/tool/list_action_logs.py
    • superset/mcp_service/app.py
    • tests/unit_tests/mcp_service/action_log/tool/test_action_log_tools.py
    • tests/unit_tests/mcp_service/test_mcp_tool_registration.py
    • superset/mcp_service/action_log/tool/get_action_log_info.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

AI Code Review powered by Bito Logo

@aminghadersohi

aminghadersohi commented May 27, 2026

Copy link
Copy Markdown
Contributor Author

Integration test results

list_tasks ✅ and get_task_info ✅ both work correctly.

list_action_logs ❌ and get_action_log_info ❌ fail with:

Permission denied: can_read on Log for user <workspace-admin>

In a multi-tenant deployment where workspace admins are mapped to a custom FAB role, can_read on Log may not be included. This permission exists on the standard Superset Admin role but may be excluded from a custom admin role mapping. Action log access likely needs to be added to the custom admin role in the deployment's configuration.

@aminghadersohi

aminghadersohi commented May 27, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed staging report.

After reviewing the tool files and comparing against LogRestApi (the existing REST API for action logs in superset/views/log/api.py):

  • LogRestApi uses class_permission_name = "Log" + can_read for its GET endpoints (get and get_list)
  • The MCP tools (list_action_logs and get_action_log_info) use the same class_permission_name="Log", which is consistent and correct for OSS Superset

Action log access is intentionally admin-only in OSS — can_read on Log is the right permission guard. No OSS code change is needed here.

The fix is in the deployment's role config: add can_read on Log to the custom admin role mapping so it matches what the standard Superset Admin role includes. The list_tasks / get_task_info tools work because those use a different permission class that is already present in the custom admin role.

@richardfogaca richardfogaca left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Posting on Richard's behalf — this is his PR reviewer agent. Forward any pushback to him and he'll loop me back in.

Left one functional note below. The line number is verified against HEAD e3ba2d170e9c068d76b9e27a37e5a65a5127a28d.

Functional — worth checking before merge

  • superset/mcp_service/app.py:727

    This guard reads only flask_app.config["FEATURE_FLAGS"], but Superset registers TaskRestApi through feature_flag_manager.is_feature_enabled("GLOBAL_TASK_FRAMEWORK"). That manager also respects supported enablement paths like DEFAULT_FEATURE_FLAGS, SUPERSET_FEATURE_*, GET_FEATURE_FLAGS_FUNC, and IS_FEATURE_ENABLED_FUNC.

    In those configurations, GTF can be enabled for the normal task API while MCP still removes list_tasks and get_task_info.

    WDYT — could we use feature_flag_manager.is_feature_enabled("GLOBAL_TASK_FRAMEWORK") here too, so this mirrors initialization/__init__.py?

@aminghadersohi

Copy link
Copy Markdown
Contributor Author

Good catch @richardfogaca — fixed in 8a84b91.

_apply_config_guards now imports feature_flag_manager from superset.extensions and calls feature_flag_manager.is_feature_enabled("GLOBAL_TASK_FRAMEWORK"), matching initialization/__init__.py exactly. This respects DEFAULT_FEATURE_FLAGS, GET_FEATURE_FLAGS_FUNC, IS_FEATURE_ENABLED_FUNC, and other Superset enablement paths.

Test update: swapped the raw FEATURE_FLAGS config dict approach for a pytest fixture (gtf_ffm) that patches superset.extensions.feature_flag_manager — the one disabled-behavior test overrides is_feature_enabled.return_value = False directly on that mock.

@bito-code-review

bito-code-review Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #466863

Actionable Suggestions - 0
Review Details
  • Files reviewed - 2 · Commit Range: e3ba2d1..8a84b91
    • superset/mcp_service/app.py
    • tests/unit_tests/mcp_service/test_mcp_tool_registration.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

AI Code Review powered by Bito Logo

@richardfogaca richardfogaca left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Posting on Richard's behalf -- this is his PR reviewer agent. Forward any pushback to him and he'll loop me back in.

Left two notes below. Line numbers verified against HEAD 8a84b91f89e021d8d0248542c9075eb152f25a25.

Functional -- worth checking before merge

  • superset/mcp_service/action_log/schemas.py:69

    Explicit dttm filters from MCP clients can still reach the DAO as strings. JSON callers will send values like "2020-01-01T00:00:00", and since ActionLogFilter.value includes str before datetime, Pydantic keeps that value as a string. The test at test_action_log_tools.py:154 currently asserts that exact behavior.

    That means caller-provided dttm filters still hit BaseDAO.apply_column_operators() as timestamp-vs-string comparisons, even though the injected default cutoff was fixed to use a datetime.

    WDYT -- could we normalize ActionLogFilter.value to a datetime whenever col == "dttm" before passing filters into ModelListCore?

  • superset/mcp_service/app.py:125

    The default MCP instructions' “Available tools” section does not mention the new action-log or task tools. Tool discovery may still expose them, but these instructions are the curated guide agents see for how to use the service, so the new tools are effectively undocumented in the primary prompt surface.

    WDYT -- should we add a short Action Logs / Tasks section here, and make sure config-guarded tools are omitted from the generated instructions when removed?

@aminghadersohi

Copy link
Copy Markdown
Contributor Author

Thanks for the continued review, @richardfogaca!

Round 3 fixes — eeb4f0f:

Issue A — dttm string normalization in ActionLogFilter:
Added a model_validator(mode="after") to ActionLogFilter that parses ISO string dttm values into timezone-aware datetime objects before they reach the DAO. Pydantic's left-to-right union matching keeps strings as str when str appears before datetime in the union, so caller-supplied strings like "2020-01-01T00:00:00" would have reached the DAO as-is and caused a VARCHAR bind type mismatch on Postgres TIMESTAMP columns. Naive datetimes are treated as UTC (consistent with the existing 7-day default cutoff logic). Updated the corresponding test assertion to verify the normalized datetime value.

Issue B — action-log/task tools in generated instructions:

  • Added Action Logs and Task Management sections to get_default_instructions() with bullet entries for all four tools. The existing per-line filter in get_default_instructions already strips bullet lines for any tool in the disabled_tools set, so those entries are automatically excluded when the tools are removed.
  • Changed _apply_config_guards to return the set of tool names it removed. init_fastmcp_server now merges that set with MCP_DISABLED_TOOLS before calling get_default_instructions, so config-guard removals also suppress the corresponding instruction lines.
  • Added test coverage: test_no_disabled_tools_returns_full_instructions now asserts all four new tool bullets are present in the full instructions; test_config_guard_tools_excluded_from_instructions verifies that action-log tools are passed to get_default_instructions in the disabled_tools set when SUPERSET_LOG_VIEW=False.

@aminghadersohi

Copy link
Copy Markdown
Contributor Author

A few schema issues worth addressing before merge:

TaskInfo missing started_at, ended_at, user_id
The Task model has started_at and ended_at (DateTime) and user_id (Integer). Without these, list_tasks / get_task_info can't answer the most basic monitoring questions: when did this task run, and who triggered it. Recommend adding all three to TaskInfo, ALL_TASK_COLUMNS, and serialize_task_object.

TaskColumnFilter should allow started_at, ended_at, user_id
No time-range filtering on list_tasks makes it much less useful for operational monitoring. Recommend adding these to col: Literal[...].

Naming inconsistency: TaskColumnFilter and RlsColumnFilter (in #40347)
Every other filter class in the MCP suite follows the pattern <Resource>Filter (QueryFilter, TagFilter, RoleFilter, ReportFilter, etc.). TaskColumnFilter and RlsColumnFilter are the only outliers. Suggest renaming to TaskFilter and RlsFilter respectively for consistency.

ActionLogInfo missing duration_ms and referrer
The Log model has duration_ms = Column(Integer) and referrer = Column(String(1024)). duration_ms is particularly valuable for performance analysis ("show me slow actions"). Recommend adding both to the schema and ALL_LOG_COLUMNS.

@richardfogaca richardfogaca left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Posting on Richard's behalf - this is his PR reviewer agent. Forward any pushback to him and he'll loop me back in.

Left one data-boundary note below. Line number verified against HEAD 1fd7ecf4a67b725e9f3ca06cc7cc7e9a25dae8aa.

Functional - worth checking before merge

  • superset/mcp_service/action_log/schemas.py:266

    The new action-log serializer returns the stored log json payload directly. That payload is workspace/user-controlled data, and MCP results are later placed in an LLM context; other MCP serializers wrap user-controlled strings with the untrusted-content sanitizer so result content cannot masquerade as instructions. Here, a log payload containing prompt-like text would be returned raw by both list_action_logs and get_action_log_info.

    WDYT - could we run this field through the existing LLM-context sanitization helper before returning it? If we expect callers to inspect raw JSON, preserving the JSON shape and sanitizing/wrapping only string leaves would keep it usable while maintaining the MCP data boundary.

aminghadersohi added a commit that referenced this pull request May 28, 2026
Three issues identified in sibling PR reviews (#40344, #40348) that apply
equally to the tag tools:

1. DEFAULT_INSTRUCTIONS omitted list_tags/get_tag_info — adds a Tag
   Management section so agents see the tools in the curated guide.

2. serialize_tag_object returned name/description without wrapping in
   UNTRUSTED-CONTENT delimiters — user-controlled tag text could masquerade
   as instructions in LLM context. Adds _sanitize_tag_info_for_llm_context
   matching the pattern used by chart, dashboard, and dataset serializers.

3. ALL_TAG_COLUMNS omitted changed_on_humanized / created_on_humanized —
   TagInfo exposes these derived fields but columns_available didn't
   advertise them, so clients following schema discovery couldn't request
   them. ModelListCore already maps *_humanized → *_on for DB loading.

Updates tests: value assertions now use `in` to tolerate the UNTRUSTED-CONTENT
wrapper; adds test_get_tag_info_sanitizes_user_controlled_fields to verify
the boundary is enforced.
@bito-code-review

bito-code-review Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #e201e0

Actionable Suggestions - 0
Review Details
  • Files reviewed - 4 · Commit Range: f206b04..ce47d0e
    • superset/mcp_service/app.py
    • superset/mcp_service/task/schemas.py
    • tests/unit_tests/mcp_service/task/tool/test_task_tools.py
    • tests/unit_tests/mcp_service/test_mcp_tool_registration.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

AI Code Review powered by Bito Logo

@richardfogaca richardfogaca left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@bito-code-review

bito-code-review Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #b3e52a

Actionable Suggestions - 0
Review Details
  • Files reviewed - 1 · Commit Range: ce47d0e..bd83017
    • tests/unit_tests/mcp_service/action_log/tool/test_action_log_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

AI Code Review powered by Bito Logo

@aminghadersohi
aminghadersohi force-pushed the amin/mcp-action-log-tasks branch from 89b5644 to 18a7def Compare May 30, 2026 02:17
aminghadersohi and others added 20 commits May 30, 2026 02:19
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The injected 7-day default filter used a datetime object as the value,
but ActionLogFilter.value only allows str|int|float|bool|list. Pydantic
rejects the datetime when building the filters_applied list in
ActionLogList, causing a ValidationError on every call that triggered
the default filter.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add model_serializer to ActionLogInfo and TaskInfo that drops
  non-requested fields from output when select_columns context is set,
  matching the DatabaseInfo pattern
- Switch list_action_logs and list_tasks to return model_dump with
  serialization context so only requested columns appear in responses
- Add search field + search-XOR-filters validator to
  ListActionLogsRequest and ListTasksRequest
- Pass search=request.search through to ModelListCore.run_tool()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rage

- Add task_key and task_name fields to TaskInfo schema and ALL_TASK_COLUMNS;
  these are real Task model columns present in the REST API search_columns
- Expand search_columns in list_tasks to include task_key and task_name
- Strengthen test_list_action_logs_default_7day_filter_applied to also
  assert the injected filter appears in filters_applied with an ISO string value

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pydantic v2 rejects a ColumnOperator instance when validating
list[ActionLogFilter] — it requires an exact instance or dict, not
a parent-class instance. The injected 7-day default dttm filter was
created as a plain ColumnOperator, causing every test_list_action_logs_*
call to fail with '1 validation error for ActionLogList'.

Fix: construct the default filter as ActionLogFilter (which is a
subclass of ColumnOperator), so it passes pydantic validation for
ActionLogList.filters_applied: list[ActionLogFilter] and is still
accepted everywhere ColumnOperator is expected.
…ests

- Normalize changed_on/created_on naive datetimes in serialize_task_object (mirrors
  serialize_action_log_object pattern for dttm)
- Add filter-forwarding assertion to test_list_tasks_with_status_filter
- Add id_column="uuid" assertion to test_get_task_info_by_uuid

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…permissions

The ADMIN-ONLY label was misleading — access is gated by the Log
permission in Superset's RBAC, not a hard admin check.  Updated both
docstrings to describe the actual permission model.
…docs

- Add model_validator to ActionLogFilter to parse ISO string dttm values
  to timezone-aware datetime objects, preventing VARCHAR bind mismatch on
  Postgres TIMESTAMP columns (Pydantic's left-to-right union keeps strings
  as str when str precedes datetime in the union)
- Feed config-guard removed tools (action-log, task) into disabled_tools
  before calling get_default_instructions so removed tools are never
  advertised in LLM instructions
- Add Action Logs and Task Management sections to get_default_instructions
  output; existing per-line filtering strips them when tools are disabled
- Update test assertions and add new coverage for both behaviors
The stored log `json` field is user-controlled data.  Parse it and run
each string leaf through `sanitize_for_llm_context` so the payload
cannot masquerade as instructions when placed in an LLM context.

Preserves the JSON shape (dict/list structure) so callers can still
inspect individual fields; only string leaves are wrapped in
UNTRUSTED-CONTENT delimiters.  Falls back to sanitizing the raw
string when the payload is not valid JSON.

Addresses review feedback from richardfogaca.
- Pass excluded_field_names=frozenset() to sanitize_for_llm_context in
  _sanitize_log_json so that all string leaves in the user-controlled json
  blob are wrapped in UNTRUSTED-CONTENT delimiters. Previously, fields like
  url, schema, and uuid were only escaped (not wrapped) because they appear in
  the default exclusion list intended for structured, trusted output fields.
- Extend normalize_dttm_value to also normalize string elements inside list
  values so dttm IN (...) filters are also converted to datetime objects,
  preventing TIMESTAMP/VARCHAR bind mismatch on Postgres for that operator.
- Update ActionLogFilter.value type annotation to include datetime in the list
  element union, matching the post-validation runtime type.
- Add tests: url/schema wrapping and dttm list filter normalization.
…ion-log

Log JSON payloads are fully user-controlled, including field names.  A
crafted key like {"ignore previous instructions": "..."} would previously
pass through _sanitize_log_json with only delimiter-token escaping on the
key, not the full UNTRUSTED-CONTENT wrapping applied to string values.

Add a wrap_dict_keys parameter to sanitize_for_llm_context so callers
that know the entire blob is untrusted can opt into key wrapping.
_sanitize_log_json now passes wrap_dict_keys=True alongside the existing
excluded_field_names=frozenset() to ensure both keys and values of the
log JSON are wrapped.

Add regression tests:
- test_get_action_log_info_malicious_json_key_wrapped
- test_list_action_logs_malicious_json_key_wrapped
…_schema scope

- Remove wrap_dict_keys param from sanitize_for_llm_context: wrapping keys
  breaks dict shape (callers cannot navigate by original key name) while
  providing no meaningful safety gain — values are already fully wrapped via
  excluded_field_names=frozenset(). Keys are still delimiter-escaped to prevent
  forged UNTRUSTED-CONTENT tokens in key names.
- Align TASK_SORTABLE_COLUMNS with TaskRestApi.order_columns: remove id (not
  sortable in REST), add task_type/scope/started_at/ended_at. Change default
  sort from changed_on to created_on to match REST base_order.
- Clarify in instructions that get_schema only covers chart/dataset/dashboard/
  database; action_log and task tools list their columns in their docstrings.
- Update malicious-key tests to match new behavior: verify value is wrapped
  and key tokens are escaped rather than checking that keys are wrapped.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…inline imports

Closes Richard's open review thread on dict-key injection: _sanitize_log_json
now serializes the entire JSON blob as a canonical JSON string and wraps it in
a single UNTRUSTED-CONTENT block, so keys and values alike are inside the trust
boundary — no key can inject instructions into the LLM context.

Also fixes Scan-1 inline-import BLOCKERs across all changed files:
- DAO imports (LogDAO, TaskDAO) moved to module top level in tool files
- Redundant `from datetime import timezone` removed from schema methods
  (timezone already imported at module top in both schemas.py files)
- `from unittest.mock import Mock` moved to module top level in test fixtures
- Redundant re-imports of `mcp` / `init_fastmcp_server` removed from test
  function bodies in test_mcp_tool_registration.py (already at module top)
- `from superset.utils import json as json_utils` moved to module top in
  action_log/schemas.py (was inline inside _sanitize_log_json with noqa comment)

Updated tests: all json-payload assertions now check isinstance(payload, str)
and verify UNTRUSTED-CONTENT wrapping on the string blob; malicious-key tests
verify that injecting keys are enclosed in the wrapper (not accessible as
dict keys) and that embedded UNTRUSTED-CONTENT tokens are escaped inside it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…STED-CONTENT delimiter

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@aminghadersohi
aminghadersohi force-pushed the amin/mcp-action-log-tasks branch from 18a7def to 3ce791b Compare May 30, 2026 02:20
aminghadersohi added a commit to aminghadersohi/superset that referenced this pull request May 30, 2026
…itization

Sync patterns from sibling PRs apache#40344 and apache#40348:

- Inline-import BLOCKERs: move RLSDAO and DynamicPluginDAO imports from
  inside try blocks to module top level in all four tool files (list/get
  for rls and plugin)
- Remove redundant inline `from datetime import timezone` from
  RlsFilterError.create and PluginError.create (timezone already
  imported at module top after adding it to the top-level import)
- Add @field_validator("error") to RlsFilterError and PluginError to
  wrap error text in UNTRUSTED-CONTENT delimiters before LLM exposure,
  matching the DashboardError/DatasetError/ReportError pattern

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@aminghadersohi
aminghadersohi merged commit 40de44f into master May 30, 2026
58 checks passed
@aminghadersohi
aminghadersohi deleted the amin/mcp-action-log-tasks branch May 30, 2026 03:16
@bito-code-review

Copy link
Copy Markdown
Contributor

Bito Automatic Review Skipped – PR Already Merged

Bito scheduled an automatic review for this pull request, but the review was skipped because this PR was merged before the review could be run.
No action is needed if you didn't intend to review it. To get a review, you can type /review in a comment and save it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants