feat(mcp): Add tool annotations for MCP directory compliance (#38641)

(cherry picked from commit 1c8224f4c6)
This commit is contained in:
Amin Ghadersohi
2026-03-17 03:09:25 +01:00
committed by Joe Li
parent f17351c5e2
commit 3cc27a8b06
20 changed files with 181 additions and 21 deletions

View File

@@ -25,6 +25,7 @@ from typing import Any, Dict, List
from urllib.parse import parse_qs, urlparse
from fastmcp import Context
from mcp.types import ToolAnnotations
from superset.commands.exceptions import CommandException
from superset.extensions import event_logger
@@ -119,7 +120,14 @@ def _compile_chart(
return CompileResult(success=False, error=str(exc))
@mcp.tool(tags=["mutate"])
@mcp.tool(
tags=["mutate"],
annotations=ToolAnnotations(
title="Generate chart",
readOnlyHint=False,
destructiveHint=False,
),
)
@mcp_auth_hook(class_permission_name="Chart", method_permission_name="write")
@parse_request(GenerateChartRequest)
async def generate_chart( # noqa: C901

View File

@@ -25,6 +25,7 @@ from typing import Any, Dict, List, TYPE_CHECKING
from fastmcp import Context
from flask import current_app
from mcp.types import ToolAnnotations
from superset.extensions import event_logger
from superset.mcp_service.app import mcp
@@ -76,7 +77,14 @@ def _get_cached_form_data(form_data_key: str) -> str | None:
return None
@mcp.tool(tags=["data"])
@mcp.tool(
tags=["data"],
annotations=ToolAnnotations(
title="Get chart data",
readOnlyHint=True,
destructiveHint=False,
),
)
@mcp_auth_hook(class_permission_name="Chart")
@parse_request(GetChartDataRequest)
async def get_chart_data( # noqa: C901

View File

@@ -22,6 +22,7 @@ MCP tool: get_chart_info
import logging
from fastmcp import Context
from mcp.types import ToolAnnotations
from sqlalchemy.orm import subqueryload
from superset.commands.exceptions import CommandException
@@ -57,7 +58,14 @@ def _get_cached_form_data(form_data_key: str) -> str | None:
return None
@mcp.tool(tags=["discovery"])
@mcp.tool(
tags=["discovery"],
annotations=ToolAnnotations(
title="Get chart info",
readOnlyHint=True,
destructiveHint=False,
),
)
@mcp_auth_hook(class_permission_name="Chart")
@parse_request(GetChartInfoRequest)
async def get_chart_info(

View File

@@ -23,6 +23,7 @@ import logging
from typing import Any, Dict, List, Protocol
from fastmcp import Context
from mcp.types import ToolAnnotations
from superset.commands.exceptions import CommandException
from superset.exceptions import SupersetException
@@ -2072,7 +2073,14 @@ async def _get_chart_preview_internal( # noqa: C901
)
@mcp.tool(tags=["data"])
@mcp.tool(
tags=["data"],
annotations=ToolAnnotations(
title="Get chart preview",
readOnlyHint=True,
destructiveHint=False,
),
)
@mcp_auth_hook(class_permission_name="Chart")
@parse_request(GetChartPreviewRequest)
async def get_chart_preview(

View File

@@ -23,6 +23,7 @@ import logging
from typing import cast, TYPE_CHECKING
from fastmcp import Context
from mcp.types import ToolAnnotations
if TYPE_CHECKING:
from superset.models.slice import Slice
@@ -63,7 +64,14 @@ SORTABLE_CHART_COLUMNS = [
]
@mcp.tool(tags=["core"])
@mcp.tool(
tags=["core"],
annotations=ToolAnnotations(
title="List charts",
readOnlyHint=True,
destructiveHint=False,
),
)
@mcp_auth_hook(class_permission_name="Chart")
@parse_request(ListChartsRequest)
async def list_charts(request: ListChartsRequest, ctx: Context) -> ChartList:

View File

@@ -23,6 +23,7 @@ import logging
import time
from fastmcp import Context
from mcp.types import ToolAnnotations
from superset.extensions import event_logger
from superset.mcp_service.app import mcp
@@ -46,7 +47,14 @@ from superset.utils import json
logger = logging.getLogger(__name__)
@mcp.tool(tags=["mutate"])
@mcp.tool(
tags=["mutate"],
annotations=ToolAnnotations(
title="Update chart",
readOnlyHint=False,
destructiveHint=False,
),
)
@mcp_auth_hook(class_permission_name="Chart", method_permission_name="write")
@parse_request(UpdateChartRequest)
async def update_chart(

View File

@@ -24,6 +24,7 @@ import time
from typing import Any, Dict
from fastmcp import Context
from mcp.types import ToolAnnotations
from superset.extensions import event_logger
from superset.mcp_service.app import mcp
@@ -45,7 +46,14 @@ from superset.mcp_service.utils.schema_utils import parse_request
logger = logging.getLogger(__name__)
@mcp.tool(tags=["mutate"])
@mcp.tool(
tags=["mutate"],
annotations=ToolAnnotations(
title="Update chart preview",
readOnlyHint=False,
destructiveHint=False,
),
)
@mcp_auth_hook(class_permission_name="Chart", method_permission_name="write")
@parse_request(UpdateChartPreviewRequest)
def update_chart_preview(

View File

@@ -26,6 +26,7 @@ import re
from typing import Any, Dict
from fastmcp import Context
from mcp.types import ToolAnnotations
from superset.extensions import event_logger
from superset.mcp_service.app import mcp
@@ -307,7 +308,14 @@ def _ensure_layout_structure(
layout["DASHBOARD_VERSION_KEY"] = "v2"
@mcp.tool(tags=["mutate"])
@mcp.tool(
tags=["mutate"],
annotations=ToolAnnotations(
title="Add chart to existing dashboard",
readOnlyHint=False,
destructiveHint=False,
),
)
@mcp_auth_hook(class_permission_name="Dashboard", method_permission_name="write")
@parse_request(AddChartToDashboardRequest)
def add_chart_to_existing_dashboard(

View File

@@ -25,6 +25,7 @@ import logging
from typing import Any, Dict, List
from fastmcp import Context
from mcp.types import ToolAnnotations
from superset.extensions import event_logger
from superset.mcp_service.app import mcp
@@ -179,7 +180,14 @@ def _generate_title_from_charts(chart_objects: List[Any]) -> str:
return title
@mcp.tool(tags=["mutate"])
@mcp.tool(
tags=["mutate"],
annotations=ToolAnnotations(
title="Generate dashboard",
readOnlyHint=False,
destructiveHint=False,
),
)
@mcp_auth_hook(class_permission_name="Dashboard", method_permission_name="write")
@parse_request(GenerateDashboardRequest)
def generate_dashboard(

View File

@@ -26,6 +26,7 @@ import logging
from datetime import datetime, timezone
from fastmcp import Context
from mcp.types import ToolAnnotations
from sqlalchemy.orm import subqueryload
from superset.dashboards.permalink.exceptions import DashboardPermalinkGetFailedError
@@ -60,7 +61,14 @@ def _get_permalink_state(permalink_key: str) -> DashboardPermalinkValue | None:
return None
@mcp.tool(tags=["discovery"])
@mcp.tool(
tags=["discovery"],
annotations=ToolAnnotations(
title="Get dashboard info",
readOnlyHint=True,
destructiveHint=False,
),
)
@mcp_auth_hook(class_permission_name="Dashboard")
@parse_request(GetDashboardInfoRequest)
async def get_dashboard_info(

View File

@@ -26,6 +26,7 @@ import logging
from typing import TYPE_CHECKING
from fastmcp import Context
from mcp.types import ToolAnnotations
from superset.extensions import event_logger
from superset.mcp_service.app import mcp
@@ -64,7 +65,14 @@ SORTABLE_DASHBOARD_COLUMNS = [
]
@mcp.tool(tags=["core"])
@mcp.tool(
tags=["core"],
annotations=ToolAnnotations(
title="List dashboards",
readOnlyHint=True,
destructiveHint=False,
),
)
@mcp_auth_hook(class_permission_name="Dashboard")
@parse_request(ListDashboardsRequest)
async def list_dashboards(

View File

@@ -26,6 +26,7 @@ import logging
from datetime import datetime, timezone
from fastmcp import Context
from mcp.types import ToolAnnotations
from sqlalchemy.orm import joinedload, subqueryload
from superset.extensions import event_logger
@@ -43,7 +44,14 @@ from superset.mcp_service.utils.schema_utils import parse_request
logger = logging.getLogger(__name__)
@mcp.tool(tags=["discovery"])
@mcp.tool(
tags=["discovery"],
annotations=ToolAnnotations(
title="Get dataset info",
readOnlyHint=True,
destructiveHint=False,
),
)
@mcp_auth_hook(class_permission_name="Dataset")
@parse_request(GetDatasetInfoRequest)
async def get_dataset_info(

View File

@@ -26,6 +26,7 @@ import logging
from typing import TYPE_CHECKING
from fastmcp import Context
from mcp.types import ToolAnnotations
from superset.extensions import event_logger
from superset.mcp_service.app import mcp
@@ -62,7 +63,14 @@ SORTABLE_DATASET_COLUMNS = [
]
@mcp.tool(tags=["core"])
@mcp.tool(
tags=["core"],
annotations=ToolAnnotations(
title="List datasets",
readOnlyHint=True,
destructiveHint=False,
),
)
@mcp_auth_hook(class_permission_name="Dataset")
@parse_request(ListDatasetsRequest)
async def list_datasets(request: ListDatasetsRequest, ctx: Context) -> DatasetList:

View File

@@ -26,6 +26,7 @@ from typing import Any, Dict
from urllib.parse import parse_qs, urlparse
from fastmcp import Context
from mcp.types import ToolAnnotations
from superset.extensions import event_logger
from superset.mcp_service.app import mcp
@@ -40,7 +41,14 @@ from superset.mcp_service.chart.schemas import (
from superset.mcp_service.utils.schema_utils import parse_request
@mcp.tool(tags=["explore"])
@mcp.tool(
tags=["explore"],
annotations=ToolAnnotations(
title="Generate explore link",
readOnlyHint=True,
destructiveHint=False,
),
)
@mcp_auth_hook(class_permission_name="Explore")
@parse_request(GenerateExploreLinkRequest)
async def generate_explore_link(

View File

@@ -25,6 +25,7 @@ and timeout protection.
import logging
from fastmcp import Context
from mcp.types import ToolAnnotations
from superset.extensions import event_logger
from superset.mcp_service.app import mcp
@@ -39,7 +40,14 @@ from superset.mcp_service.utils.schema_utils import parse_request
logger = logging.getLogger(__name__)
@mcp.tool(tags=["mutate"])
@mcp.tool(
tags=["mutate"],
annotations=ToolAnnotations(
title="Execute SQL query",
readOnlyHint=False,
destructiveHint=True,
),
)
@mcp_auth_hook(
class_permission_name="SQLLab", method_permission_name="execute_sql_query"
)

View File

@@ -25,6 +25,7 @@ import logging
from urllib.parse import urlencode
from fastmcp import Context
from mcp.types import ToolAnnotations
from superset.extensions import event_logger
from superset.mcp_service.app import mcp
@@ -38,7 +39,14 @@ from superset.mcp_service.utils.schema_utils import parse_request
logger = logging.getLogger(__name__)
@mcp.tool(tags=["explore"])
@mcp.tool(
tags=["explore"],
annotations=ToolAnnotations(
title="Open SQL Lab with context",
readOnlyHint=True,
destructiveHint=False,
),
)
@mcp_auth_hook(class_permission_name="SQLLab")
@parse_request(OpenSqlLabRequest)
def open_sql_lab_with_context(

View File

@@ -28,6 +28,7 @@ from __future__ import annotations
import logging
from fastmcp import Context
from mcp.types import ToolAnnotations
from sqlalchemy.exc import SQLAlchemyError
from superset.errors import ErrorLevel, SupersetError, SupersetErrorType
@@ -44,7 +45,14 @@ from superset.mcp_service.utils.schema_utils import parse_request
logger = logging.getLogger(__name__)
@mcp.tool(tags=["mutate"])
@mcp.tool(
tags=["mutate"],
annotations=ToolAnnotations(
title="Save SQL query",
readOnlyHint=False,
destructiveHint=False,
),
)
@mcp_auth_hook(class_permission_name="SavedQuery", method_permission_name="write")
@parse_request(SaveSqlQueryRequest)
async def save_sql_query(
@@ -131,7 +139,7 @@ async def save_sql_query(
except SQLAlchemyError as e:
from superset import db
db.session.rollback()
db.session.rollback() # pylint: disable=consider-using-transaction
await ctx.error(
"Failed to save SQL query: error=%s, database_id=%s"
% (str(e), request.database_id)

View File

@@ -23,6 +23,7 @@ InstanceInfoCore for flexible, extensible metrics calculation.
import logging
from fastmcp import Context
from mcp.types import ToolAnnotations
from superset.extensions import event_logger
from superset.mcp_service.app import mcp
@@ -74,7 +75,14 @@ _instance_info_core = InstanceInfoCore(
)
@mcp.tool(tags=["core"])
@mcp.tool(
tags=["core"],
annotations=ToolAnnotations(
title="Get instance info",
readOnlyHint=True,
destructiveHint=False,
),
)
@mcp_auth_hook
@parse_request(GetSupersetInstanceInfoRequest)
def get_instance_info(

View File

@@ -27,6 +27,7 @@ import logging
from typing import Callable, Literal
from fastmcp import Context
from mcp.types import ToolAnnotations
from superset.extensions import event_logger
from superset.mcp_service.app import mcp
@@ -121,7 +122,14 @@ _SCHEMA_CORE_FACTORIES: dict[
}
@mcp.tool(tags=["discovery"])
@mcp.tool(
tags=["discovery"],
annotations=ToolAnnotations(
title="Get schema",
readOnlyHint=True,
destructiveHint=False,
),
)
@parse_request(GetSchemaRequest)
async def get_schema(request: GetSchemaRequest, ctx: Context) -> GetSchemaResponse:
"""

View File

@@ -23,6 +23,7 @@ import platform
import time
from flask import current_app
from mcp.types import ToolAnnotations
from superset.extensions import event_logger
from superset.mcp_service.app import mcp
@@ -35,7 +36,14 @@ logger = logging.getLogger(__name__)
_start_time = time.monotonic()
@mcp.tool(tags=["core"])
@mcp.tool(
tags=["core"],
annotations=ToolAnnotations(
title="Health check",
readOnlyHint=True,
destructiveHint=False,
),
)
@mcp_auth_hook
async def health_check() -> HealthCheckResponse:
"""