Skip to content

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

Closed
aminghadersohi wants to merge 6 commits into
masterfrom
mcp-action-tasks-99978
Closed

feat(mcp): add list and get tools for action log and tasks#40304
aminghadersohi wants to merge 6 commits into
masterfrom
mcp-action-tasks-99978

Conversation

@aminghadersohi

Copy link
Copy Markdown
Contributor

SUMMARY

Adds 4 new MCP tools across two new domains as part of story #99978:

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.
  • 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.

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 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_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

ADDITIONAL INFORMATION

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

@codecov

codecov Bot commented May 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 63.19218% with 113 lines in your changes missing coverage. Please review.
✅ Project coverage is 63.83%. Comparing base (73f66e4) to head (0f9b0ee).
⚠️ Report is 11 commits behind head on master.

Files with missing lines Patch % Lines
...et/mcp_service/action_log/tool/list_action_logs.py 30.76% 27 Missing ⚠️
superset/mcp_service/action_log/schemas.py 75.53% 22 Missing and 1 partial ⚠️
superset/mcp_service/task/tool/list_tasks.py 33.33% 20 Missing ⚠️
superset/mcp_service/task/schemas.py 80.89% 16 Missing and 1 partial ⚠️
...mcp_service/action_log/tool/get_action_log_info.py 43.47% 13 Missing ⚠️
superset/mcp_service/task/tool/get_task_info.py 43.47% 13 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #40304      +/-   ##
==========================================
- Coverage   64.14%   63.83%   -0.32%     
==========================================
  Files        2592     2600       +8     
  Lines      138846   140236    +1390     
  Branches    32201    32497     +296     
==========================================
+ Hits        89069    89516     +447     
- Misses      48245    49155     +910     
- Partials     1532     1565      +33     
Flag Coverage Δ
hive 39.16% <63.19%> (-0.16%) ⬇️
mysql 58.28% <63.19%> (-0.59%) ⬇️
postgres 58.35% <63.19%> (-0.59%) ⬇️
presto 40.80% <63.19%> (-0.20%) ⬇️
python 59.88% <63.19%> (-0.62%) ⬇️
sqlite 58.00% <63.19%> (-0.58%) ⬇️
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.

@aminghadersohi
aminghadersohi force-pushed the mcp-action-tasks-99978 branch 3 times, most recently from 5dbc511 to 66a4ef4 Compare May 21, 2026 18:04
aminghadersohi and others added 6 commits May 21, 2026 19:24
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.
@aminghadersohi
aminghadersohi force-pushed the mcp-action-tasks-99978 branch from 66a4ef4 to 0f9b0ee Compare May 21, 2026 19:24
@aminghadersohi
aminghadersohi marked this pull request as ready for review May 22, 2026 03:02
@aminghadersohi

Copy link
Copy Markdown
Contributor Author

Closing in favor of ##40344

@bito-code-review bito-code-review Bot 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.

Code Review Agent Run #8033ab

Actionable Suggestions - 2
  • superset/mcp_service/action_log/schemas.py - 1
  • superset/mcp_service/task/tool/get_task_info.py - 1
Additional Suggestions - 3
  • superset/mcp_service/action_log/schemas.py - 1
    • Inconsistent timezone in error create · Line 207-215
      ActionLogError.create() uses timezone-aware datetime.now(timezone.utc) while DashboardError.create() uses naive datetime.now(). For consistency with other error schemas, use naive datetime.
      Code suggestion
      --- superset/mcp_service/action_log/schemas.py (lines 207-215) ---
       207:     @classmethod
       208:     def create(cls, error: str, error_type: str) -> "ActionLogError":
       209:         from datetime import timezone
       210: 
       211:         return cls(
       212:             error=error,
       213:             error_type=error_type,
       214: -            timestamp=datetime.now(timezone.utc),
       215: +            timestamp=datetime.now(),
       216:         )
  • superset/mcp_service/action_log/tool/list_action_logs.py - 1
    • Unused function parameter · Line 102-102
      The `_serialize` function at line 102 accepts a `cols` parameter that is never used in the function body. Either remove it or prefix with underscore to indicate intentional unused parameter.
      Code suggestion
      --- superset/mcp_service/action_log/tool/list_action_logs.py
      +++ superset/mcp_service/action_log/tool/list_action_logs.py
       @@ -99,7 +99,7 @@ async def list_action_logs(
                    filters = [default_filter] + filters
                    await ctx.debug("Applied default 7-day dttm filter: cutoff=%s" % (cutoff,))
       
      -        def _serialize(obj: object, cols: list[str] | None) -> ActionLogInfo | None:
      +        def _serialize(obj: object, _cols: list[str] | None) -> ActionLogInfo | None:
                    return serialize_action_log_object(obj)
  • superset/mcp_service/task/tool/list_tasks.py - 1
    • Unused parameter in callback · Line 87-87
      The `_serialize` function accepts a `cols` parameter but never uses it. The field filtering is handled by `TaskInfo._filter_fields_by_context` (model_serializer), so the `cols` parameter is unused here. Consider removing it for clarity, or add a comment like list_charts.py line 167 does: 'field filtering handled by model_serializer'.
Filtered by Review Rules

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

  • superset/mcp_service/task/schemas.py - 1
  • superset/mcp_service/action_log/schemas.py - 1
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

Comment on lines +201 to +215
class ActionLogError(BaseModel):
error: str = Field(..., description="Error message")
error_type: str = Field(..., description="Error type")
timestamp: str | datetime | None = Field(None, description="Error timestamp")
model_config = ConfigDict(ser_json_timedelta="iso8601")

@classmethod
def create(cls, error: str, error_type: str) -> "ActionLogError":
from datetime import timezone

return cls(
error=error,
error_type=error_type,
timestamp=datetime.now(timezone.utc),
)

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.

Missing error sanitization validator

ActionLogError is missing the sanitize_for_llm_context validator present in all other error schemas. Error messages could contain unsanitized content when exposed to LLM context. Add the validator decorator to match DashboardError (line 123-127), ChartError (line 188-191), and DatasetError (line 301-304).

Code suggestion
Check the AI-generated fix before applying
 --- superset/mcp_service/action_log/schemas.py (lines 201-215) ---
 201: class ActionLogError(BaseModel):
 202:     error: str = Field(..., description="Error message")
 203:     error_type: str = Field(..., description="Error type")
 204:     timestamp: str | datetime | None = Field(None, description="Error timestamp")
 205:     model_config = ConfigDict(ser_json_timedelta="iso8601")
 206: 
 207: +    @field_validator("error")
 208: +    @classmethod
 209: +    def sanitize_error_for_llm_context(cls, value: str) -> str:
 210: +        """Wrap error text before it is exposed to LLM context."""
 211: +        from superset.mcp_service.utils import sanitize_for_llm_context
 212: +
 213: +        return sanitize_for_llm_context(value, field_path=("error",))
 214: +
 215:     @classmethod
 216:     def create(cls, error: str, error_type: str) -> "ActionLogError":

Code Review Run #8033ab


Should Bito avoid suggestions like this for future reviews? (Manage Rules)

  • Yes, avoid them


return result

except Exception as e:

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.

Broad exception catch

Broad except Exception at line 99 masks unexpected errors that may indicate real bugs. Consider catching specific exceptions (e.g., DAOFindFailedError, SQLAlchemyError) or re-raising unexpected ones to avoid silent failure on unexpected error types.

Code Review Run #8033ab


Should Bito avoid suggestions like this for future reviews? (Manage Rules)

  • Yes, avoid them

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.

1 participant