From 1c8224f4c633c56288ce18beb9190aef223c5fff Mon Sep 17 00:00:00 2001 From: Amin Ghadersohi Date: Tue, 17 Mar 2026 03:09:25 +0100 Subject: [PATCH] feat(mcp): Add tool annotations for MCP directory compliance (#38641) --- superset-core/src/superset_core/mcp/decorators.py | 11 +++++++++++ superset/core/mcp/core_mcp_injection.py | 8 ++++++++ superset/mcp_service/chart/tool/generate_chart.py | 12 ++++++++++-- superset/mcp_service/chart/tool/get_chart_data.py | 12 ++++++++++-- superset/mcp_service/chart/tool/get_chart_info.py | 12 ++++++++++-- .../mcp_service/chart/tool/get_chart_preview.py | 12 ++++++++++-- superset/mcp_service/chart/tool/list_charts.py | 12 ++++++++++-- superset/mcp_service/chart/tool/update_chart.py | 12 ++++++++++-- .../chart/tool/update_chart_preview.py | 13 +++++++++++-- .../tool/add_chart_to_existing_dashboard.py | 12 ++++++++++-- .../dashboard/tool/generate_dashboard.py | 12 ++++++++++-- .../dashboard/tool/get_dashboard_info.py | 12 ++++++++++-- .../mcp_service/dashboard/tool/list_dashboards.py | 12 ++++++++++-- .../mcp_service/dataset/tool/get_dataset_info.py | 12 ++++++++++-- .../mcp_service/dataset/tool/list_datasets.py | 12 ++++++++++-- .../explore/tool/generate_explore_link.py | 12 ++++++++++-- superset/mcp_service/sql_lab/tool/execute_sql.py | 7 ++++++- .../sql_lab/tool/open_sql_lab_with_context.py | 13 +++++++++++-- .../mcp_service/sql_lab/tool/save_sql_query.py | 15 ++++++++++++--- .../mcp_service/system/tool/get_instance_info.py | 11 +++++++++-- superset/mcp_service/system/tool/get_schema.py | 11 +++++++++-- superset/mcp_service/system/tool/health_check.py | 11 +++++++++-- 22 files changed, 216 insertions(+), 40 deletions(-) diff --git a/superset-core/src/superset_core/mcp/decorators.py b/superset-core/src/superset_core/mcp/decorators.py index 6ad29c9adc55..264d079be30f 100644 --- a/superset-core/src/superset_core/mcp/decorators.py +++ b/superset-core/src/superset_core/mcp/decorators.py @@ -37,6 +37,13 @@ def another_tool(value: int) -> str: from typing import Any, Callable, TypeVar +try: + from mcp.types import ToolAnnotations +except ( + ImportError +): # MCP extras may not be installed in superset-core-only environments + ToolAnnotations = dict + # Type variable for decorated functions F = TypeVar("F", bound=Callable[..., Any]) @@ -50,6 +57,7 @@ def tool( protect: bool = True, class_permission_name: str | None = None, method_permission_name: str | None = None, + annotations: ToolAnnotations | None = None, ) -> Any: # Use Any to avoid mypy issues with dependency injection """ Decorator to register an MCP tool with optional authentication. @@ -77,6 +85,8 @@ def my_tool(): ... permission checking via security_manager.can_access(). method_permission_name: FAB action name (e.g., "read", "write"). Defaults to "write" if tags includes "mutate", else "read". + annotations: MCP tool annotations (title, readOnlyHint, destructiveHint, etc.) + These hints help MCP clients understand tool behavior and safety. Returns: Decorator function that registers and wraps the tool, or the wrapped function @@ -178,4 +188,5 @@ async def public_prompt_handler(ctx: Context) -> str: __all__ = [ "tool", "prompt", + "ToolAnnotations", ] diff --git a/superset/core/mcp/core_mcp_injection.py b/superset/core/mcp/core_mcp_injection.py index fa2ec23738a7..abfbeac236ac 100644 --- a/superset/core/mcp/core_mcp_injection.py +++ b/superset/core/mcp/core_mcp_injection.py @@ -25,6 +25,11 @@ import logging from typing import Any, Callable, Optional, TypeVar +try: + from mcp.types import ToolAnnotations +except ImportError: + ToolAnnotations = dict + from superset.extensions.context import get_current_extension_context # Type variable for decorated functions @@ -62,6 +67,7 @@ def create_tool_decorator( protect: bool = True, class_permission_name: Optional[str] = None, method_permission_name: Optional[str] = None, + annotations: ToolAnnotations | None = None, ) -> Callable[[F], F] | F: """ Create the concrete MCP tool decorator implementation. @@ -82,6 +88,7 @@ def create_tool_decorator( (e.g., "Chart", "Dashboard", "SQLLab"). Enables permission checking. method_permission_name: FAB action name (e.g., "read", "write"). Defaults to "write" if tags has "mutate", else "read". + annotations: MCP tool annotations (title, readOnlyHint, destructiveHint, etc.) Returns: Decorator that registers and wraps the tool with optional authentication, @@ -130,6 +137,7 @@ def decorator(func: F) -> F: name=tool_name, description=tool_description, tags=tool_tags, + annotations=annotations, ) mcp.add_tool(tool) diff --git a/superset/mcp_service/chart/tool/generate_chart.py b/superset/mcp_service/chart/tool/generate_chart.py index 76ce79b5212e..3c9b0192cf8f 100644 --- a/superset/mcp_service/chart/tool/generate_chart.py +++ b/superset/mcp_service/chart/tool/generate_chart.py @@ -25,7 +25,7 @@ from urllib.parse import parse_qs, urlparse from fastmcp import Context -from superset_core.mcp.decorators import tool +from superset_core.mcp.decorators import tool, ToolAnnotations from superset.commands.exceptions import CommandException from superset.extensions import event_logger @@ -119,7 +119,15 @@ def _compile_chart( return CompileResult(success=False, error=str(exc)) -@tool(tags=["mutate"], class_permission_name="Chart") +@tool( + tags=["mutate"], + class_permission_name="Chart", + annotations=ToolAnnotations( + title="Create chart", + readOnlyHint=False, + destructiveHint=False, + ), +) @parse_request(GenerateChartRequest) async def generate_chart( # noqa: C901 request: GenerateChartRequest, ctx: Context diff --git a/superset/mcp_service/chart/tool/get_chart_data.py b/superset/mcp_service/chart/tool/get_chart_data.py index c6f9123c5cc5..8bacd35217fc 100644 --- a/superset/mcp_service/chart/tool/get_chart_data.py +++ b/superset/mcp_service/chart/tool/get_chart_data.py @@ -25,7 +25,7 @@ from fastmcp import Context from flask import current_app -from superset_core.mcp.decorators import tool +from superset_core.mcp.decorators import tool, ToolAnnotations if TYPE_CHECKING: from superset.models.slice import Slice @@ -74,7 +74,15 @@ def _get_cached_form_data(form_data_key: str) -> str | None: return None -@tool(tags=["data"], class_permission_name="Chart") +@tool( + tags=["data"], + class_permission_name="Chart", + annotations=ToolAnnotations( + title="Get chart data", + readOnlyHint=True, + destructiveHint=False, + ), +) @parse_request(GetChartDataRequest) async def get_chart_data( # noqa: C901 request: GetChartDataRequest, ctx: Context diff --git a/superset/mcp_service/chart/tool/get_chart_info.py b/superset/mcp_service/chart/tool/get_chart_info.py index 1061ec821c01..e1cb54420035 100644 --- a/superset/mcp_service/chart/tool/get_chart_info.py +++ b/superset/mcp_service/chart/tool/get_chart_info.py @@ -23,7 +23,7 @@ from fastmcp import Context from sqlalchemy.orm import subqueryload -from superset_core.mcp.decorators import tool +from superset_core.mcp.decorators import tool, ToolAnnotations from superset.commands.exceptions import CommandException from superset.commands.explore.form_data.parameters import CommandParameters @@ -115,7 +115,15 @@ def _apply_unsaved_state_override(result: ChartInfo, form_data_key: str) -> None ) -@tool(tags=["discovery"], class_permission_name="Chart") +@tool( + tags=["discovery"], + class_permission_name="Chart", + annotations=ToolAnnotations( + title="Get chart info", + readOnlyHint=True, + destructiveHint=False, + ), +) @parse_request(GetChartInfoRequest) async def get_chart_info( request: GetChartInfoRequest, ctx: Context diff --git a/superset/mcp_service/chart/tool/get_chart_preview.py b/superset/mcp_service/chart/tool/get_chart_preview.py index d95e95006616..f844fcc630ee 100644 --- a/superset/mcp_service/chart/tool/get_chart_preview.py +++ b/superset/mcp_service/chart/tool/get_chart_preview.py @@ -23,7 +23,7 @@ from typing import Any, Dict, List, Protocol from fastmcp import Context -from superset_core.mcp.decorators import tool +from superset_core.mcp.decorators import tool, ToolAnnotations from superset.commands.exceptions import CommandException from superset.exceptions import SupersetException @@ -2177,7 +2177,15 @@ def __init__(self, form_data: Dict[str, Any]): ) -@tool(tags=["data"], class_permission_name="Chart") +@tool( + tags=["data"], + class_permission_name="Chart", + annotations=ToolAnnotations( + title="Get chart preview", + readOnlyHint=True, + destructiveHint=False, + ), +) @parse_request(GetChartPreviewRequest) async def get_chart_preview( request: GetChartPreviewRequest, ctx: Context diff --git a/superset/mcp_service/chart/tool/list_charts.py b/superset/mcp_service/chart/tool/list_charts.py index 8ef2daf91f08..ed92f1d057be 100644 --- a/superset/mcp_service/chart/tool/list_charts.py +++ b/superset/mcp_service/chart/tool/list_charts.py @@ -23,7 +23,7 @@ from typing import cast, TYPE_CHECKING from fastmcp import Context -from superset_core.mcp.decorators import tool +from superset_core.mcp.decorators import tool, ToolAnnotations if TYPE_CHECKING: from superset.models.slice import Slice @@ -62,7 +62,15 @@ ] -@tool(tags=["core"], class_permission_name="Chart") +@tool( + tags=["core"], + class_permission_name="Chart", + annotations=ToolAnnotations( + title="List charts", + readOnlyHint=True, + destructiveHint=False, + ), +) @parse_request(ListChartsRequest) async def list_charts(request: ListChartsRequest, ctx: Context) -> ChartList: """List charts with filtering and search. diff --git a/superset/mcp_service/chart/tool/update_chart.py b/superset/mcp_service/chart/tool/update_chart.py index d69e09693a91..b10bd9186219 100644 --- a/superset/mcp_service/chart/tool/update_chart.py +++ b/superset/mcp_service/chart/tool/update_chart.py @@ -23,7 +23,7 @@ import time from fastmcp import Context -from superset_core.mcp.decorators import tool +from superset_core.mcp.decorators import tool, ToolAnnotations from superset.extensions import event_logger from superset.mcp_service.chart.chart_utils import ( @@ -45,7 +45,15 @@ logger = logging.getLogger(__name__) -@tool(tags=["mutate"], class_permission_name="Chart") +@tool( + tags=["mutate"], + class_permission_name="Chart", + annotations=ToolAnnotations( + title="Update chart", + readOnlyHint=False, + destructiveHint=True, + ), +) @parse_request(UpdateChartRequest) async def update_chart( request: UpdateChartRequest, ctx: Context diff --git a/superset/mcp_service/chart/tool/update_chart_preview.py b/superset/mcp_service/chart/tool/update_chart_preview.py index f3242b140911..007be88cc1ec 100644 --- a/superset/mcp_service/chart/tool/update_chart_preview.py +++ b/superset/mcp_service/chart/tool/update_chart_preview.py @@ -24,7 +24,7 @@ from typing import Any, Dict from fastmcp import Context -from superset_core.mcp.decorators import tool +from superset_core.mcp.decorators import tool, ToolAnnotations from superset.extensions import event_logger from superset.mcp_service.chart.chart_utils import ( @@ -44,7 +44,16 @@ logger = logging.getLogger(__name__) -@tool(tags=["mutate"], class_permission_name="Chart", method_permission_name="write") +@tool( + tags=["mutate"], + class_permission_name="Chart", + method_permission_name="write", + annotations=ToolAnnotations( + title="Update chart preview", + readOnlyHint=False, + destructiveHint=True, + ), +) @parse_request(UpdateChartPreviewRequest) def update_chart_preview( request: UpdateChartPreviewRequest, ctx: Context diff --git a/superset/mcp_service/dashboard/tool/add_chart_to_existing_dashboard.py b/superset/mcp_service/dashboard/tool/add_chart_to_existing_dashboard.py index 2629aded0dc4..9765cace49c8 100644 --- a/superset/mcp_service/dashboard/tool/add_chart_to_existing_dashboard.py +++ b/superset/mcp_service/dashboard/tool/add_chart_to_existing_dashboard.py @@ -26,7 +26,7 @@ from typing import Any, Dict from fastmcp import Context -from superset_core.mcp.decorators import tool +from superset_core.mcp.decorators import tool, ToolAnnotations from superset.extensions import event_logger from superset.mcp_service.chart.schemas import serialize_chart_object @@ -305,7 +305,15 @@ def _ensure_layout_structure( layout["DASHBOARD_VERSION_KEY"] = "v2" -@tool(tags=["mutate"], class_permission_name="Dashboard") +@tool( + tags=["mutate"], + class_permission_name="Dashboard", + annotations=ToolAnnotations( + title="Add chart to dashboard", + readOnlyHint=False, + destructiveHint=False, + ), +) @parse_request(AddChartToDashboardRequest) def add_chart_to_existing_dashboard( request: AddChartToDashboardRequest, ctx: Context diff --git a/superset/mcp_service/dashboard/tool/generate_dashboard.py b/superset/mcp_service/dashboard/tool/generate_dashboard.py index 4bbc4450502b..52bdb8b501ac 100644 --- a/superset/mcp_service/dashboard/tool/generate_dashboard.py +++ b/superset/mcp_service/dashboard/tool/generate_dashboard.py @@ -25,7 +25,7 @@ from typing import Any, Dict, List from fastmcp import Context -from superset_core.mcp.decorators import tool +from superset_core.mcp.decorators import tool, ToolAnnotations from superset.extensions import event_logger from superset.mcp_service.chart.schemas import serialize_chart_object @@ -178,7 +178,15 @@ def _generate_title_from_charts(chart_objects: List[Any]) -> str: return title -@tool(tags=["mutate"], class_permission_name="Dashboard") +@tool( + tags=["mutate"], + class_permission_name="Dashboard", + annotations=ToolAnnotations( + title="Create dashboard", + readOnlyHint=False, + destructiveHint=False, + ), +) @parse_request(GenerateDashboardRequest) def generate_dashboard( request: GenerateDashboardRequest, ctx: Context diff --git a/superset/mcp_service/dashboard/tool/get_dashboard_info.py b/superset/mcp_service/dashboard/tool/get_dashboard_info.py index fa0df2bee143..7ad42e97955d 100644 --- a/superset/mcp_service/dashboard/tool/get_dashboard_info.py +++ b/superset/mcp_service/dashboard/tool/get_dashboard_info.py @@ -27,7 +27,7 @@ from fastmcp import Context from sqlalchemy.orm import subqueryload -from superset_core.mcp.decorators import tool +from superset_core.mcp.decorators import tool, ToolAnnotations from superset.dashboards.permalink.exceptions import DashboardPermalinkGetFailedError from superset.dashboards.permalink.types import DashboardPermalinkValue @@ -59,7 +59,15 @@ def _get_permalink_state(permalink_key: str) -> DashboardPermalinkValue | None: return None -@tool(tags=["discovery"], class_permission_name="Dashboard") +@tool( + tags=["discovery"], + class_permission_name="Dashboard", + annotations=ToolAnnotations( + title="Get dashboard info", + readOnlyHint=True, + destructiveHint=False, + ), +) @parse_request(GetDashboardInfoRequest) async def get_dashboard_info( request: GetDashboardInfoRequest, ctx: Context diff --git a/superset/mcp_service/dashboard/tool/list_dashboards.py b/superset/mcp_service/dashboard/tool/list_dashboards.py index 823533a9086c..1feee0d2b6e3 100644 --- a/superset/mcp_service/dashboard/tool/list_dashboards.py +++ b/superset/mcp_service/dashboard/tool/list_dashboards.py @@ -26,7 +26,7 @@ from typing import TYPE_CHECKING from fastmcp import Context -from superset_core.mcp.decorators import tool +from superset_core.mcp.decorators import tool, ToolAnnotations if TYPE_CHECKING: from superset.models.dashboard import Dashboard @@ -63,7 +63,15 @@ ] -@tool(tags=["core"], class_permission_name="Dashboard") +@tool( + tags=["core"], + class_permission_name="Dashboard", + annotations=ToolAnnotations( + title="List dashboards", + readOnlyHint=True, + destructiveHint=False, + ), +) @parse_request(ListDashboardsRequest) async def list_dashboards( request: ListDashboardsRequest, ctx: Context diff --git a/superset/mcp_service/dataset/tool/get_dataset_info.py b/superset/mcp_service/dataset/tool/get_dataset_info.py index 59c3603a1851..888e1452d7e3 100644 --- a/superset/mcp_service/dataset/tool/get_dataset_info.py +++ b/superset/mcp_service/dataset/tool/get_dataset_info.py @@ -27,7 +27,7 @@ from fastmcp import Context from sqlalchemy.orm import joinedload, subqueryload -from superset_core.mcp.decorators import tool +from superset_core.mcp.decorators import tool, ToolAnnotations from superset.extensions import event_logger from superset.mcp_service.dataset.schemas import ( @@ -42,7 +42,15 @@ logger = logging.getLogger(__name__) -@tool(tags=["discovery"], class_permission_name="Dataset") +@tool( + tags=["discovery"], + class_permission_name="Dataset", + annotations=ToolAnnotations( + title="Get dataset info", + readOnlyHint=True, + destructiveHint=False, + ), +) @parse_request(GetDatasetInfoRequest) async def get_dataset_info( request: GetDatasetInfoRequest, ctx: Context diff --git a/superset/mcp_service/dataset/tool/list_datasets.py b/superset/mcp_service/dataset/tool/list_datasets.py index 4810350df1af..97a8b0597a3c 100644 --- a/superset/mcp_service/dataset/tool/list_datasets.py +++ b/superset/mcp_service/dataset/tool/list_datasets.py @@ -26,7 +26,7 @@ from typing import TYPE_CHECKING from fastmcp import Context -from superset_core.mcp.decorators import tool +from superset_core.mcp.decorators import tool, ToolAnnotations if TYPE_CHECKING: from superset.connectors.sqla.models import SqlaTable @@ -61,7 +61,15 @@ ] -@tool(tags=["core"], class_permission_name="Dataset") +@tool( + tags=["core"], + class_permission_name="Dataset", + annotations=ToolAnnotations( + title="List datasets", + readOnlyHint=True, + destructiveHint=False, + ), +) @parse_request(ListDatasetsRequest) async def list_datasets(request: ListDatasetsRequest, ctx: Context) -> DatasetList: """List datasets with filtering and search. diff --git a/superset/mcp_service/explore/tool/generate_explore_link.py b/superset/mcp_service/explore/tool/generate_explore_link.py index d60c0fd4d422..0c5f11824eae 100644 --- a/superset/mcp_service/explore/tool/generate_explore_link.py +++ b/superset/mcp_service/explore/tool/generate_explore_link.py @@ -26,7 +26,7 @@ from urllib.parse import parse_qs, urlparse from fastmcp import Context -from superset_core.mcp.decorators import tool +from superset_core.mcp.decorators import tool, ToolAnnotations from superset.extensions import event_logger from superset.mcp_service.chart.chart_utils import ( @@ -39,7 +39,15 @@ from superset.mcp_service.utils.schema_utils import parse_request -@tool(tags=["explore"], class_permission_name="Explore") +@tool( + tags=["explore"], + class_permission_name="Explore", + annotations=ToolAnnotations( + title="Generate explore link", + readOnlyHint=False, + destructiveHint=False, + ), +) @parse_request(GenerateExploreLinkRequest) async def generate_explore_link( request: GenerateExploreLinkRequest, ctx: Context diff --git a/superset/mcp_service/sql_lab/tool/execute_sql.py b/superset/mcp_service/sql_lab/tool/execute_sql.py index 7f3164d88522..5490dc1b5d75 100644 --- a/superset/mcp_service/sql_lab/tool/execute_sql.py +++ b/superset/mcp_service/sql_lab/tool/execute_sql.py @@ -28,7 +28,7 @@ from typing import Any from fastmcp import Context -from superset_core.mcp.decorators import tool +from superset_core.mcp.decorators import tool, ToolAnnotations from superset_core.queries.types import ( CacheOptions, QueryOptions, @@ -55,6 +55,11 @@ tags=["mutate"], class_permission_name="SQLLab", method_permission_name="execute_sql_query", + annotations=ToolAnnotations( + title="Execute SQL query", + readOnlyHint=False, + destructiveHint=True, + ), ) @parse_request(ExecuteSqlRequest) async def execute_sql(request: ExecuteSqlRequest, ctx: Context) -> ExecuteSqlResponse: diff --git a/superset/mcp_service/sql_lab/tool/open_sql_lab_with_context.py b/superset/mcp_service/sql_lab/tool/open_sql_lab_with_context.py index d326cf26244b..6e46b483eedd 100644 --- a/superset/mcp_service/sql_lab/tool/open_sql_lab_with_context.py +++ b/superset/mcp_service/sql_lab/tool/open_sql_lab_with_context.py @@ -25,7 +25,7 @@ from urllib.parse import urlencode from fastmcp import Context -from superset_core.mcp.decorators import tool +from superset_core.mcp.decorators import tool, ToolAnnotations from superset.extensions import event_logger from superset.mcp_service.sql_lab.schemas import ( @@ -38,7 +38,16 @@ logger = logging.getLogger(__name__) -@tool(tags=["explore"], class_permission_name="SQLLab", method_permission_name="read") +@tool( + tags=["explore"], + class_permission_name="SQLLab", + method_permission_name="read", + annotations=ToolAnnotations( + title="Open SQL Lab with context", + readOnlyHint=True, + destructiveHint=False, + ), +) @parse_request(OpenSqlLabRequest) def open_sql_lab_with_context( request: OpenSqlLabRequest, ctx: Context diff --git a/superset/mcp_service/sql_lab/tool/save_sql_query.py b/superset/mcp_service/sql_lab/tool/save_sql_query.py index f97777930854..f13a1b65bd6e 100644 --- a/superset/mcp_service/sql_lab/tool/save_sql_query.py +++ b/superset/mcp_service/sql_lab/tool/save_sql_query.py @@ -29,7 +29,7 @@ from fastmcp import Context from sqlalchemy.exc import SQLAlchemyError -from superset_core.mcp.decorators import tool +from superset_core.mcp.decorators import tool, ToolAnnotations from superset.errors import ErrorLevel, SupersetError, SupersetErrorType from superset.exceptions import SupersetErrorException, SupersetSecurityException @@ -43,7 +43,16 @@ logger = logging.getLogger(__name__) -@tool(tags=["mutate"]) +@tool( + tags=["mutate"], + class_permission_name="SQLLab", + method_permission_name="write", + annotations=ToolAnnotations( + title="Save SQL query", + readOnlyHint=False, + destructiveHint=False, + ), +) @parse_request(SaveSqlQueryRequest) async def save_sql_query( request: SaveSqlQueryRequest, ctx: Context @@ -129,7 +138,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) diff --git a/superset/mcp_service/system/tool/get_instance_info.py b/superset/mcp_service/system/tool/get_instance_info.py index 3f13ba874e45..f6f162570e15 100644 --- a/superset/mcp_service/system/tool/get_instance_info.py +++ b/superset/mcp_service/system/tool/get_instance_info.py @@ -23,7 +23,7 @@ import logging from fastmcp import Context -from superset_core.mcp.decorators import tool +from superset_core.mcp.decorators import tool, ToolAnnotations from superset.extensions import event_logger from superset.mcp_service.mcp_core import InstanceInfoCore @@ -73,7 +73,14 @@ ) -@tool(tags=["core"]) +@tool( + tags=["core"], + annotations=ToolAnnotations( + title="Get instance info", + readOnlyHint=True, + destructiveHint=False, + ), +) @parse_request(GetSupersetInstanceInfoRequest) def get_instance_info( request: GetSupersetInstanceInfoRequest, ctx: Context diff --git a/superset/mcp_service/system/tool/get_schema.py b/superset/mcp_service/system/tool/get_schema.py index e76ca73d6681..0757b028e05b 100644 --- a/superset/mcp_service/system/tool/get_schema.py +++ b/superset/mcp_service/system/tool/get_schema.py @@ -27,7 +27,7 @@ from typing import Callable, Literal from fastmcp import Context -from superset_core.mcp.decorators import tool +from superset_core.mcp.decorators import tool, ToolAnnotations from superset.extensions import event_logger from superset.mcp_service.common.schema_discovery import ( @@ -121,7 +121,14 @@ def _get_dashboard_schema_core() -> ModelGetSchemaCore[ModelSchemaInfo]: } -@tool(tags=["discovery"]) +@tool( + tags=["discovery"], + annotations=ToolAnnotations( + title="Get schema", + readOnlyHint=True, + destructiveHint=False, + ), +) @parse_request(GetSchemaRequest) async def get_schema(request: GetSchemaRequest, ctx: Context) -> GetSchemaResponse: """ diff --git a/superset/mcp_service/system/tool/health_check.py b/superset/mcp_service/system/tool/health_check.py index 7faf3f18ee7e..e41f673ac8b9 100644 --- a/superset/mcp_service/system/tool/health_check.py +++ b/superset/mcp_service/system/tool/health_check.py @@ -23,7 +23,7 @@ import time from flask import current_app -from superset_core.mcp.decorators import tool +from superset_core.mcp.decorators import tool, ToolAnnotations from superset.extensions import event_logger from superset.mcp_service.system.schemas import HealthCheckResponse @@ -34,7 +34,14 @@ _start_time = time.monotonic() -@tool(tags=["core"]) +@tool( + tags=["core"], + annotations=ToolAnnotations( + title="Health check", + readOnlyHint=True, + destructiveHint=False, + ), +) async def health_check() -> HealthCheckResponse: """ Simple health check tool for testing the MCP service.