Skip to content

fix(mcp): fall back to title match when dashboard slug lookup misses#39567

Open
EnxDev wants to merge 1 commit intomasterfrom
enxdev/fix/mcp-dashboard-empty-slug-lookup
Open

fix(mcp): fall back to title match when dashboard slug lookup misses#39567
EnxDev wants to merge 1 commit intomasterfrom
enxdev/fix/mcp-dashboard-empty-slug-lookup

Conversation

@EnxDev
Copy link
Copy Markdown
Contributor

@EnxDev EnxDev commented Apr 22, 2026

SUMMARY

get_dashboard_info silently returned error_type: not_found when an agent passed a slug-like identifier (e.g. "world-banks-data") for a dashboard whose slug field was empty. Many imported / example dashboards ship with empty slugs, so agents that guess the slug from the dashboard title (the natural thing to do) would hit a dead end.

After the normal id / UUID / slug lookups miss, ModelGetInfoCore now scans dashboard titles, normalizes each title and the identifier via a shared _slugify helper (lowercases, drops apostrophes, collapses non-alphanumerics to hyphens), and returns the first match.
If multiple titles slugify to the same value, it logs a warning and returns the first — collisions in real dashboards are rare and the
caller can always disambiguate by id or UUID.

The fallback column is opt-in per entity: DashboardDAO declares title_column = "dashboard_title", which ModelGetInfoCore picks up via getattr(dao_class, "title_column", None).
Other MCP tools can enable the same behavior by setting one attribute on their DAO — no core or tool changes required.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

N/A — behavior change is in the MCP service's JSON responses.

Before (master):

{
  "error": "DashboardInfo with identifier 'world-banks-data' not found",
  "error_type": "not_found"
}

After:

{
  "id": 1,
  "dashboard_title": "World Bank's Data",
  "slug": "",
   ...
}

TESTING INSTRUCTIONS

Find (or import) a dashboard whose slug field is empty but whose title slugifies to something predictable — e.g. the stock world_bank_data.py example ("World Bank's Data").

Call the MCP tool:

get_dashboard_info(request={"identifier": "world-banks-data"})

Expected: returns the dashboard (not error_type: not_found).

Sanity checks that should behave the same as before:
{"identifier": } — resolves by id.
{"identifier": ""} — resolves by UUID.
{"identifier": ""} — resolves by slug when the slug is non-empty.
{"identifier": "definitely-not-a-dashboard"} — still returns error_type: not_found (no over-matching).

Ambiguous case: create two dashboards whose titles slugify to the same value. Call get_dashboard_info with that slug. Expected: returns the first match and the server logs a warning naming all candidate ids.
Unit tests:

pytest tests/unit_tests/mcp_service/test_mcp_core.py \
tests/unit_tests/mcp_service/dashboard/
All 107 pass.

ADDITIONAL INFORMATION

  • Has associated issue:
  • Required feature flags:
  • Changes UI
  • Includes DB Migration (follow approval process in SIP-59)
    • Migration is atomic, supports rollback & is backwards-compatible
    • Confirm DB migration upgrade and downgrade tested
    • Runtime estimates and downtime expectations provided
  • Introduces new feature or API
  • Removes existing feature or API

Many imported/example dashboards have empty slug fields, so
get_dashboard_info with an agent-guessed slug (e.g. "world-banks-data")
silently returned not_found.

ModelGetInfoCore now tries an exact case-insensitive title match,
then a slugified-title match, before giving up. When multiple titles
slugify to the same value, returns ambiguous_identifier listing the
candidate ids. The column name is sourced from DAO.title_column
(set on DashboardDAO) so other tools can opt in without touching the
core.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bito-code-review
Copy link
Copy Markdown
Contributor

bito-code-review Bot commented Apr 22, 2026

Code Review Agent Run #3ecdb0

Actionable Suggestions - 0
Review Details
  • Files reviewed - 3 · Commit Range: db8d954..db8d954
    • superset/daos/dashboard.py
    • superset/mcp_service/mcp_core.py
    • tests/unit_tests/mcp_service/test_mcp_core.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 the dashboard Namespace | Anything related to the Dashboard label Apr 22, 2026
Comment on lines +35 to +37
class _FakeOutput(BaseModel):
id: int
title: str
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: Add a class docstring describing the purpose of this test schema. [custom_rule]

Severity Level: Minor ⚠️

Why it matters? 🤔

The class is newly introduced and has no docstring. If the custom rule requires
documentation for new class definitions, this is a real violation in the
existing code.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** tests/unit_tests/mcp_service/test_mcp_core.py
**Line:** 35:37
**Comment:**
	*Custom Rule: Add a class docstring describing the purpose of this test schema.

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.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +40 to +43
class _FakeError(BaseModel):
error: str
error_type: str
timestamp: datetime
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: Add a class docstring explaining the role of this error schema in the tests. [custom_rule]

Severity Level: Minor ⚠️

Why it matters? 🤔

The class has no docstring in the final file. This matches the suggestion and
constitutes a real documentation omission if the rule requires docstrings on
new class definitions.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** tests/unit_tests/mcp_service/test_mcp_core.py
**Line:** 40:43
**Comment:**
	*Custom Rule: Add a class docstring explaining the role of this error schema in the tests.

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.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎



@pytest.fixture(autouse=True)
def _patch_id_or_slug_filter():
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: Add an explicit return type annotation for this fixture function (for example, an iterator/generator type) to satisfy typing requirements for new functions. [custom_rule]

Severity Level: Minor ⚠️

Why it matters? 🤔

The fixture function is defined without a return type annotation. If the rule
requires explicit typing on new functions, this is a genuine violation in the
current code.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** tests/unit_tests/mcp_service/test_mcp_core.py
**Line:** 51:51
**Comment:**
	*Custom Rule: Add an explicit return type annotation for this fixture function (for example, an iterator/generator type) to satisfy typing requirements for new functions.

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.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

yield


def _make_dashboard(id_: int, title: str, slug: str = "") -> MagicMock:
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: Add a short docstring describing what object this helper constructs and what inputs it expects. [custom_rule]

Severity Level: Minor ⚠️

Why it matters? 🤔

The helper function has no docstring in the final file. That is a real
documentation gap if the custom rule mandates docstrings for added helpers.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** tests/unit_tests/mcp_service/test_mcp_core.py
**Line:** 59:59
**Comment:**
	*Custom Rule: Add a short docstring describing what object this helper constructs and what inputs it expects.

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.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

("leading--and--trailing--", "leading-and-trailing"),
],
)
def test_slugify_handles_edge_cases(identifier: str, expected_slug: str) -> None:
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: Add a test docstring summarizing the edge-case behavior validated by this parametrized test. [custom_rule]

Severity Level: Minor ⚠️

Why it matters? 🤔

The parametrized test function has no docstring in the final file. If the
custom rule requires docstrings for new test functions, this is a real
violation.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** tests/unit_tests/mcp_service/test_mcp_core.py
**Line:** 212:212
**Comment:**
	*Custom Rule: Add a test docstring summarizing the edge-case behavior validated by this parametrized test.

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.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 22, 2026

Codecov Report

❌ Patch coverage is 13.79310% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 64.56%. Comparing base (73c4240) to head (db8d954).
⚠️ Report is 6 commits behind head on master.

Files with missing lines Patch % Lines
superset/mcp_service/mcp_core.py 10.71% 25 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #39567      +/-   ##
==========================================
- Coverage   64.57%   64.56%   -0.01%     
==========================================
  Files        2562     2563       +1     
  Lines      133535   133569      +34     
  Branches    31030    31036       +6     
==========================================
+ Hits        86228    86238      +10     
- Misses      45815    45839      +24     
  Partials     1492     1492              
Flag Coverage Δ
hive 39.86% <13.79%> (-0.01%) ⬇️
mysql 60.42% <13.79%> (-0.02%) ⬇️
postgres 60.50% <13.79%> (-0.02%) ⬇️
presto 41.64% <13.79%> (-0.01%) ⬇️
python 62.07% <13.79%> (-0.02%) ⬇️
sqlite 60.13% <13.79%> (-0.02%) ⬇️
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.

@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 22, 2026

Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit db8d954
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/69e8f363d94ff90008d7fec8
😎 Deploy Preview https://deploy-preview-39567--superset-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Comment on lines +317 to +323
query = db.session.query(model_class)
if self.query_options:
query = query.options(*self.query_options)
matches = [
obj
for obj in query.all()
if _slugify(getattr(obj, self.title_column_name, "") or "") == target
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.

🔴 Architect Review — CRITICAL

The slugified-title fallback in ModelGetInfoCore queries Dashboard rows directly via db.session.query without applying the DAO's base_filter (DashboardAccessFilter), so get_dashboard_info can return dashboards the user is not allowed to access when resolving slug-like identifiers.

Suggestion: Build the fallback query through the DAO (or call BaseDAO._apply_base_filter/DashboardAccessFilter on the query) before scanning titles so that only rows allowed by the base_filter are considered for slugified-title matches.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is an **Architect / Logical Review** comment left during a code review. These reviews are first-class, important findings — not optional suggestions. Do NOT dismiss this as a 'big architectural change' just because the title says architect review; most of these can be resolved with a small, localized fix once the intent is understood.

**Path:** superset/mcp_service/mcp_core.py
**Line:** 317:323
**Comment:**
	*CRITICAL: The slugified-title fallback in ModelGetInfoCore queries Dashboard rows directly via db.session.query without applying the DAO's base_filter (DashboardAccessFilter), so get_dashboard_info can return dashboards the user is not allowed to access when resolving slug-like identifiers.

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.
If a suggested approach is provided above, use it as the authoritative instruction. If no explicit code suggestion is given, you MUST still draft and apply your own minimal, localized fix — do not punt back with 'no suggestion provided, review manually'. Keep the change as small as possible: add a guard clause, gate on a loading state, reorder an await, wrap in a conditional, etc. Do not refactor surrounding code or expand scope beyond the finding.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix

Comment on lines 18 to 23
from __future__ import annotations

import logging
import re
from abc import ABC, abstractmethod
from datetime import datetime, timezone
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: This fallback performs query.all() with full query_options, which in dashboard lookups includes heavy eager loads; on large instances this can load every dashboard plus related slices/owners/tags into memory just to find one match. Restrict the scan to lightweight columns (e.g., id/title only, without eager options), then fetch the single matched row with query_options. [performance]

Severity Level: Critical 🚨
- ❌ get_dashboard_info slug-fallback loads every dashboard row eagerly.
- ⚠️ Large installations risk timeouts on slug-like lookups.
- ⚠️ Increased memory pressure from unnecessary relationship loading.
Steps of Reproduction ✅
1. In `superset/mcp_service/dashboard/tool/get_dashboard_info.py:111–120`, see that
`eager_options` includes multiple `subqueryload` calls for `Dashboard.slices`,
`Slice.owners`, `Slice.tags`, `Dashboard.owners`, `Dashboard.tags`, and `Dashboard.roles`,
and these are passed to `ModelGetInfoCore` as `query_options=eager_options` at lines
122–131.

2. For a Superset instance with a large number of dashboards and charts (hundreds or
thousands), ensure there exists at least one dashboard whose `slug` is empty but whose
`dashboard_title` slugifies to a predictable value such as `"world-banks-data"`.

3. From any MCP client, call the `get_dashboard_info` tool with `request.identifier` set
to that slug-like value so that `ModelGetInfoCore._find_object` in
`superset/mcp_service/mcp_core.py:47–81` exhausts ID/UUID/slug lookups and falls through
to `_find_by_slugified_title`.

4. In `_find_by_slugified_title` (`superset/mcp_service/mcp_core.py`, lines shown in this
PR from `if self.query_options:` through the `matches = [...]` list comprehension), the
code applies the heavy `query.options(*self.query_options)` and then executes
`query.all()`, loading every dashboard plus all eagerly-loaded related
slices/owners/tags/roles into memory just to filter in Python, causing high memory usage
and slow responses for this fallback path.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** superset/mcp_service/mcp_core.py
**Line:** 18:23
**Comment:**
	*Performance: This fallback performs `query.all()` with full `query_options`, which in dashboard lookups includes heavy eager loads; on large instances this can load every dashboard plus related slices/owners/tags into memory just to find one match. Restrict the scan to lightweight columns (e.g., id/title only, without eager options), then fetch the single matched row with `query_options`.

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.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

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

Labels

dashboard Namespace | Anything related to the Dashboard size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant