Skip to content

fix(mcp): fix dashboard slug null and execute_sql encoding error#38710

Merged
Antonio-RiveroMartnez merged 1 commit intoapache:masterfrom
aminghadersohi:amin/fix-mcp-dashboard-slug-and-sql-encoding
Mar 20, 2026
Merged

fix(mcp): fix dashboard slug null and execute_sql encoding error#38710
Antonio-RiveroMartnez merged 1 commit intoapache:masterfrom
aminghadersohi:amin/fix-mcp-dashboard-slug-and-sql-encoding

Conversation

@aminghadersohi
Copy link
Copy Markdown
Contributor

@aminghadersohi aminghadersohi commented Mar 18, 2026

User description

Summary

  • Dashboard slug null: list_dashboards returned slug: null for dashboards without a slug, while get_dashboard_info returned "". Fixed serialize_dashboard_object() to use slug or "" for consistency.
  • execute_sql encoding error: execute_sql failed with encoding without a string argument when query results contained bytes, memoryview, Decimal, or other non-JSON-serializable types. Added _sanitize_row_values() to handle these types gracefully.

BEFORE/AFTER

Before: list_dashboards returns "slug": null for dashboards without slugs; execute_sql crashes on non-serializable column types.
After: Slugs return as "" consistently; all column types are safely serialized to JSON-compatible values.

TESTING INSTRUCTIONS

  1. Run list_dashboards via MCP - verify no null slugs
  2. Run execute_sql with a query that returns binary/Decimal columns - verify no encoding error

ADDITIONAL INFORMATION

  • 5 new dashboard schema tests
  • 10 new execute_sql sanitization tests (8 unit + 2 integration)
  • All 32 new tests pass

CodeAnt-AI Description

Fix dashboard slug null and prevent execute_sql JSON encoding errors

What Changed

  • Dashboards without a slug now return slug="" instead of null and their public URL falls back to the dashboard id (so list_dashboards and get_dashboard_info are consistent).
  • SQL execution results no longer fail when result rows contain bytes, memoryview, Decimal, or other non-JSON types: binary values decode to UTF-8 when possible or become hex, Decimal values become floats, and other non-serializable values are converted to strings.
  • Added unit and integration tests covering slug handling and sanitization of bytes, memoryview, Decimal, and custom types in execute_sql responses.

Impact

✅ Consistent dashboard slugs in API responses (no nulls)
✅ No execute_sql crashes on binary or Decimal columns
✅ Stable JSON serialization for query results

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

- serialize_dashboard_object now returns slug="" instead of null when
  dashboard has no slug, matching dashboard_serializer behavior
- Add _sanitize_row_values to handle Decimal, bytes, memoryview, and
  other non-JSON-serializable types in execute_sql query results
- Add unit tests for both fixes
@bito-code-review
Copy link
Copy Markdown
Contributor

bito-code-review Bot commented Mar 18, 2026

Code Review Agent Run #9f7417

Actionable Suggestions - 0
Additional Suggestions - 1
  • superset/mcp_service/sql_lab/tool/execute_sql.py - 1
    • Incomplete JSON sanitization for nested structures · Line 163-176
      The _sanitize_row_values function only processes top-level values in row dictionaries, but if a value is a list or dict containing non-serializable types like Decimal or bytes, those nested items won't be sanitized. This could cause JSON serialization failures in the MCP response if SQL results include nested structures with such types. Consider making the sanitization recursive to handle nested data safely.
Review Details
  • Files reviewed - 4 · Commit Range: 9eff6f3..9eff6f3
    • superset/mcp_service/dashboard/schemas.py
    • superset/mcp_service/sql_lab/tool/execute_sql.py
    • tests/unit_tests/mcp_service/dashboard/test_dashboard_schemas.py
    • tests/unit_tests/mcp_service/sql_lab/tool/test_execute_sql.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

@dosubot dosubot Bot added api Related to the REST API change:backend Requires changing the backend labels Mar 18, 2026
@codeant-ai-for-open-source codeant-ai-for-open-source Bot added the size:L This PR changes 100-499 lines, ignoring generated files label Mar 18, 2026
@codeant-ai-for-open-source
Copy link
Copy Markdown
Contributor

Sequence Diagram

This PR standardizes MCP output formats in two core paths: dashboard serialization and SQL execution responses. It ensures missing dashboard slugs are returned as empty strings and query row values are sanitized into JSON-safe types before returning to clients.

sequenceDiagram
    participant Client
    participant MCPService
    participant DashboardSerializer
    participant SQLEngine

    Client->>MCPService: Request dashboard list
    MCPService->>DashboardSerializer: Serialize dashboard objects
    DashboardSerializer->>DashboardSerializer: Replace missing slug with empty string
    DashboardSerializer-->>MCPService: Return normalized dashboard data
    MCPService-->>Client: Dashboard list with consistent slug values

    Client->>MCPService: Request execute sql
    MCPService->>SQLEngine: Execute query
    SQLEngine-->>MCPService: Return statement rows
    MCPService->>MCPService: Sanitize non serializable row values
    MCPService-->>Client: SQL result with JSON safe rows
Loading

Generated by CodeAnt AI

@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 18, 2026

Codecov Report

❌ Patch coverage is 12.50000% with 14 lines in your changes missing coverage. Please review.
✅ Project coverage is 64.31%. Comparing base (834d2ab) to head (9eff6f3).
⚠️ Report is 20 commits behind head on master.

Files with missing lines Patch % Lines
superset/mcp_service/sql_lab/tool/execute_sql.py 12.50% 14 Missing ⚠️

❌ Your project status has failed because the head coverage (99.92%) is below the target coverage (100.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #38710      +/-   ##
==========================================
- Coverage   65.12%   64.31%   -0.81%     
==========================================
  Files        1819     2532     +713     
  Lines       72690   129745   +57055     
  Branches    23235    29999    +6764     
==========================================
+ Hits        47339    83449   +36110     
- Misses      25351    44844   +19493     
- Partials        0     1452    +1452     
Flag Coverage Δ
hive 40.58% <12.50%> (?)
mysql 61.58% <12.50%> (?)
postgres 61.65% <12.50%> (?)
presto 40.60% <12.50%> (?)
python 63.26% <12.50%> (?)
sqlite 61.28% <12.50%> (?)
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.

Comment on lines +165 to +176
for row in rows:
for key, value in row.items():
if isinstance(value, (bytes, memoryview)):
raw = bytes(value) if isinstance(value, memoryview) else value
try:
row[key] = raw.decode("utf-8")
except (UnicodeDecodeError, AttributeError):
row[key] = raw.hex()
elif isinstance(value, Decimal):
row[key] = float(value)
elif not isinstance(value, (str, int, float, bool, type(None), list, dict)):
row[key] = str(value)
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.

Suggestion: The sanitizer only converts top-level cell values and skips nested values inside list/dict, so rows that contain structures like {"meta": {"amount": Decimal("1.2")}} or lists with bytes can still fail JSON encoding at runtime. Sanitize recursively so nested non-serializable values are converted too. [logic error]

Severity Level: Major ⚠️
- ❌ execute_sql crashes on nested non-JSON row values.
- ⚠️ JSON/array SQL columns may return unusable MCP responses.
- ⚠️ Current tests miss nested sanitization regression coverage.
Suggested change
for row in rows:
for key, value in row.items():
if isinstance(value, (bytes, memoryview)):
raw = bytes(value) if isinstance(value, memoryview) else value
try:
row[key] = raw.decode("utf-8")
except (UnicodeDecodeError, AttributeError):
row[key] = raw.hex()
elif isinstance(value, Decimal):
row[key] = float(value)
elif not isinstance(value, (str, int, float, bool, type(None), list, dict)):
row[key] = str(value)
def _sanitize_value(value: Any) -> Any:
if isinstance(value, (bytes, memoryview)):
raw = bytes(value) if isinstance(value, memoryview) else value
try:
return raw.decode("utf-8")
except UnicodeDecodeError:
return raw.hex()
if isinstance(value, Decimal):
return float(value)
if isinstance(value, list):
return [_sanitize_value(item) for item in value]
if isinstance(value, tuple):
return [_sanitize_value(item) for item in value]
if isinstance(value, dict):
return {k: _sanitize_value(v) for k, v in value.items()}
if isinstance(value, (str, int, float, bool, type(None))):
return value
return str(value)
for row in rows:
for key, value in row.items():
row[key] = _sanitize_value(value)
Steps of Reproduction ✅
1. Call MCP tool `execute_sql` through FastMCP (same entrypoint used in
`tests/unit_tests/mcp_service/sql_lab/tool/test_execute_sql.py:163`, `213`, `305`) after
server registration in `superset/mcp_service/app.py:417-418`.

2. Execution reaches `execute_sql()` at
`superset/mcp_service/sql_lab/tool/execute_sql.py:66`, then `_convert_to_response()` at
line `179` after `database.execute(...)` at line `128`.

3. `_convert_to_response()` converts DataFrame rows (`line 197`) and calls
`_sanitize_row_values(rows_data)` (`line 198`); inside sanitizer, `list`/`dict` values are
explicitly treated as already-safe by the allowlist check at `line 175`, so nested members
are not traversed.

4. If a row contains a container like `{"meta": {"raw": b"\x00\xff"}}` or `{"vals":
[Decimal("1.2")]}`, nested values remain non-JSON-safe and response serialization fails on
the MCP return path; this is the same failure class already documented in existing
regression comments
(`tests/unit_tests/mcp_service/sql_lab/tool/test_execute_sql.py:896-897`, `956-957`) but
still reachable for nested values.
Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** superset/mcp_service/sql_lab/tool/execute_sql.py
**Line:** 165:176
**Comment:**
	*Logic Error: The sanitizer only converts top-level cell values and skips nested values inside `list`/`dict`, so rows that contain structures like `{"meta": {"amount": Decimal("1.2")}}` or lists with `bytes` can still fail JSON encoding at runtime. Sanitize recursively so nested non-serializable values are converted too.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
👍 | 👎

@Antonio-RiveroMartnez Antonio-RiveroMartnez merged commit c2a2191 into apache:master Mar 20, 2026
86 of 89 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api Related to the REST API change:backend Requires changing the backend size/L size:L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants