mirror of
https://github.com/apache/superset.git
synced 2026-08-03 20:42:30 +00:00
feat(mcp): Add tool annotations for MCP directory compliance (#38641)
(cherry picked from commit 1c8224f4c6)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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:
|
||||
"""
|
||||
|
||||
@@ -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:
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user