Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 28 additions & 10 deletions superset/mcp_service/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,26 @@ def _get_app_context_manager() -> AbstractContextManager[None]:

is_async = inspect.iscoroutinefunction(tool_func)

# Detect if the original function expects a ctx: Context parameter.
# If so, we inject it via get_context() at call time so tool functions
# don't need @parse_request to handle Context injection.
from fastmcp import Context as FMContext

_tool_sig = inspect.signature(tool_func)
_needs_ctx = any(
p.annotation is FMContext
or (hasattr(p.annotation, "__name__") and p.annotation.__name__ == "Context")
for p in _tool_sig.parameters.values()
)

def _inject_ctx(kwargs: dict[str, Any]) -> dict[str, Any]:
"""Inject FastMCP Context into kwargs if the tool function expects it."""
if _needs_ctx and "ctx" not in kwargs:
from fastmcp.server.dependencies import get_context

kwargs["ctx"] = get_context()
return kwargs

if is_async:

@functools.wraps(tool_func)
Expand All @@ -452,7 +472,7 @@ async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
"MCP internal call without Flask context: tool=%s",
tool_func.__name__,
)
return await tool_func(*args, **kwargs)
return await tool_func(*args, **_inject_ctx(kwargs))

# RBAC permission check
if not check_tool_permission(tool_func):
Expand All @@ -470,7 +490,7 @@ async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
user.username,
tool_func.__name__,
)
result = await tool_func(*args, **kwargs)
result = await tool_func(*args, **_inject_ctx(kwargs))
return result
except Exception:
_cleanup_session_on_error()
Expand All @@ -492,7 +512,7 @@ def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
"MCP internal call without Flask context: tool=%s",
tool_func.__name__,
)
return tool_func(*args, **kwargs)
return tool_func(*args, **_inject_ctx(kwargs))

# RBAC permission check
if not check_tool_permission(tool_func):
Expand All @@ -510,7 +530,7 @@ def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
user.username,
tool_func.__name__,
)
result = tool_func(*args, **kwargs)
result = tool_func(*args, **_inject_ctx(kwargs))
return result
except Exception:
_cleanup_session_on_error()
Expand Down Expand Up @@ -549,25 +569,23 @@ def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
# Set __signature__ from the original function, but:
# 1. Remove ctx parameter - FastMCP tools don't expose it to clients
# 2. Skip if original has *args (parse_request output has its own handling)
from fastmcp import Context as FMContext

tool_sig = inspect.signature(tool_func)
has_var_positional = any(
p.kind == inspect.Parameter.VAR_POSITIONAL for p in tool_sig.parameters.values()
p.kind == inspect.Parameter.VAR_POSITIONAL
for p in _tool_sig.parameters.values()
)

if not has_var_positional:
# For functions without *args, preserve signature but remove ctx
new_params = []
for _name, param in tool_sig.parameters.items():
for _name, param in _tool_sig.parameters.items():
# Skip ctx parameter - FastMCP tools don't expose it to clients
if param.annotation is FMContext or (
hasattr(param.annotation, "__name__")
and param.annotation.__name__ == "Context"
):
continue
new_params.append(param)
new_wrapper.__signature__ = tool_sig.replace( # type: ignore[attr-defined]
new_wrapper.__signature__ = _tool_sig.replace( # type: ignore[attr-defined]
parameters=new_params
)

Expand Down
2 changes: 0 additions & 2 deletions superset/mcp_service/chart/tool/generate_chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
GenerateChartResponse,
PerformanceMetadata,
)
from superset.mcp_service.utils.schema_utils import parse_request
from superset.mcp_service.utils.url_utils import get_superset_base_url
from superset.utils import json

Expand Down Expand Up @@ -133,7 +132,6 @@ def _compile_chart(
destructiveHint=False,
),
)
@parse_request(GenerateChartRequest)
async def generate_chart( # noqa: C901
request: GenerateChartRequest, ctx: Context
) -> GenerateChartResponse:
Expand Down
2 changes: 0 additions & 2 deletions superset/mcp_service/chart/tool/get_chart_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
PerformanceMetadata,
)
from superset.mcp_service.utils.cache_utils import get_cache_status_from_result
from superset.mcp_service.utils.schema_utils import parse_request
from superset.utils.core import merge_extra_filters

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -83,7 +82,6 @@ def _get_cached_form_data(form_data_key: str) -> str | None:
destructiveHint=False,
),
)
@parse_request(GetChartDataRequest)
async def get_chart_data( # noqa: C901
request: GetChartDataRequest, ctx: Context
) -> ChartData | ChartError:
Expand Down
2 changes: 0 additions & 2 deletions superset/mcp_service/chart/tool/get_chart_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
serialize_chart_object,
)
from superset.mcp_service.mcp_core import ModelGetInfoCore
from superset.mcp_service.utils.schema_utils import parse_request

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -124,7 +123,6 @@ def _apply_unsaved_state_override(result: ChartInfo, form_data_key: str) -> None
destructiveHint=False,
),
)
@parse_request(GetChartInfoRequest)
async def get_chart_info(
request: GetChartInfoRequest, ctx: Context
) -> ChartInfo | ChartError:
Expand Down
2 changes: 0 additions & 2 deletions superset/mcp_service/chart/tool/get_chart_preview.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
URLPreview,
VegaLitePreview,
)
from superset.mcp_service.utils.schema_utils import parse_request
from superset.mcp_service.utils.url_utils import get_superset_base_url

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -2186,7 +2185,6 @@ def __init__(self, form_data: Dict[str, Any]):
destructiveHint=False,
),
)
@parse_request(GetChartPreviewRequest)
async def get_chart_preview(
request: GetChartPreviewRequest, ctx: Context
) -> ChartPreview | ChartError:
Expand Down
2 changes: 0 additions & 2 deletions superset/mcp_service/chart/tool/list_charts.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
serialize_chart_object,
)
from superset.mcp_service.mcp_core import ModelListCore
from superset.mcp_service.utils.schema_utils import parse_request

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -71,7 +70,6 @@
destructiveHint=False,
),
)
@parse_request(ListChartsRequest)
async def list_charts(request: ListChartsRequest, ctx: Context) -> ChartList:
"""List charts with filtering and search.

Expand Down
2 changes: 0 additions & 2 deletions superset/mcp_service/chart/tool/update_chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
PerformanceMetadata,
UpdateChartRequest,
)
from superset.mcp_service.utils.schema_utils import parse_request
from superset.mcp_service.utils.url_utils import get_superset_base_url
from superset.utils import json

Expand All @@ -55,7 +54,6 @@
destructiveHint=True,
),
)
@parse_request(UpdateChartRequest)
async def update_chart(
request: UpdateChartRequest, ctx: Context
) -> GenerateChartResponse:
Expand Down
2 changes: 0 additions & 2 deletions superset/mcp_service/chart/tool/update_chart_preview.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
PerformanceMetadata,
UpdateChartPreviewRequest,
)
from superset.mcp_service.utils.schema_utils import parse_request

logger = logging.getLogger(__name__)

Expand All @@ -54,7 +53,6 @@
destructiveHint=True,
),
)
@parse_request(UpdateChartPreviewRequest)
def update_chart_preview(
request: UpdateChartPreviewRequest, ctx: Context
) -> Dict[str, Any]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
AddChartToDashboardResponse,
DashboardInfo,
)
from superset.mcp_service.utils.schema_utils import parse_request
from superset.mcp_service.utils.url_utils import get_superset_base_url
from superset.utils import json

Expand Down Expand Up @@ -314,7 +313,6 @@ def _ensure_layout_structure(
destructiveHint=False,
),
)
@parse_request(AddChartToDashboardRequest)
def add_chart_to_existing_dashboard(
request: AddChartToDashboardRequest, ctx: Context
) -> AddChartToDashboardResponse:
Expand Down
2 changes: 0 additions & 2 deletions superset/mcp_service/dashboard/tool/generate_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
GenerateDashboardRequest,
GenerateDashboardResponse,
)
from superset.mcp_service.utils.schema_utils import parse_request
from superset.mcp_service.utils.url_utils import get_superset_base_url
from superset.utils import json

Expand Down Expand Up @@ -188,7 +187,6 @@ def _generate_title_from_charts(chart_objects: List[Any]) -> str:
destructiveHint=False,
),
)
@parse_request(GenerateDashboardRequest)
def generate_dashboard(
request: GenerateDashboardRequest, ctx: Context
) -> GenerateDashboardResponse:
Expand Down
2 changes: 0 additions & 2 deletions superset/mcp_service/dashboard/tool/get_dashboard_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
GetDashboardInfoRequest,
)
from superset.mcp_service.mcp_core import ModelGetInfoCore
from superset.mcp_service.utils.schema_utils import parse_request

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -68,7 +67,6 @@ def _get_permalink_state(permalink_key: str) -> DashboardPermalinkValue | None:
destructiveHint=False,
),
)
@parse_request(GetDashboardInfoRequest)
async def get_dashboard_info(
request: GetDashboardInfoRequest, ctx: Context
) -> DashboardInfo | DashboardError:
Expand Down
2 changes: 0 additions & 2 deletions superset/mcp_service/dashboard/tool/list_dashboards.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
serialize_dashboard_object,
)
from superset.mcp_service.mcp_core import ModelListCore
from superset.mcp_service.utils.schema_utils import parse_request

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -72,7 +71,6 @@
destructiveHint=False,
),
)
@parse_request(ListDashboardsRequest)
async def list_dashboards(
request: ListDashboardsRequest, ctx: Context
) -> DashboardList:
Expand Down
2 changes: 0 additions & 2 deletions superset/mcp_service/dataset/tool/get_dataset_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
serialize_dataset_object,
)
from superset.mcp_service.mcp_core import ModelGetInfoCore
from superset.mcp_service.utils.schema_utils import parse_request

logger = logging.getLogger(__name__)

Expand All @@ -51,7 +50,6 @@
destructiveHint=False,
),
)
@parse_request(GetDatasetInfoRequest)
async def get_dataset_info(
request: GetDatasetInfoRequest, ctx: Context
) -> DatasetInfo | DatasetError:
Expand Down
2 changes: 0 additions & 2 deletions superset/mcp_service/dataset/tool/list_datasets.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
serialize_dataset_object,
)
from superset.mcp_service.mcp_core import ModelListCore
from superset.mcp_service.utils.schema_utils import parse_request

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -70,7 +69,6 @@
destructiveHint=False,
),
)
@parse_request(ListDatasetsRequest)
async def list_datasets(request: ListDatasetsRequest, ctx: Context) -> DatasetList:
"""List datasets with filtering and search.

Expand Down
2 changes: 0 additions & 2 deletions superset/mcp_service/explore/tool/generate_explore_link.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
from superset.mcp_service.chart.schemas import (
GenerateExploreLinkRequest,
)
from superset.mcp_service.utils.schema_utils import parse_request


@tool(
Expand All @@ -48,7 +47,6 @@
destructiveHint=False,
),
)
@parse_request(GenerateExploreLinkRequest)
async def generate_explore_link(
request: GenerateExploreLinkRequest, ctx: Context
) -> Dict[str, Any]:
Expand Down
4 changes: 0 additions & 4 deletions superset/mcp_service/sql_lab/tool/execute_sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
Database.execute() API with RLS, template rendering, and security validation.
"""

from __future__ import annotations

import logging
from decimal import Decimal
from typing import Any
Expand All @@ -48,7 +46,6 @@
StatementData,
StatementInfo,
)
from superset.mcp_service.utils.schema_utils import parse_request

logger = logging.getLogger(__name__)

Expand All @@ -63,7 +60,6 @@
destructiveHint=True,
),
)
@parse_request(ExecuteSqlRequest)
async def execute_sql(request: ExecuteSqlRequest, ctx: Context) -> ExecuteSqlResponse:
"""Execute SQL query against database using the unified Database.execute() API."""
await ctx.info(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
OpenSqlLabRequest,
SqlLabResponse,
)
from superset.mcp_service.utils.schema_utils import parse_request
from superset.mcp_service.utils.url_utils import get_superset_base_url

logger = logging.getLogger(__name__)
Expand All @@ -48,7 +47,6 @@
destructiveHint=False,
),
)
@parse_request(OpenSqlLabRequest)
def open_sql_lab_with_context(
request: OpenSqlLabRequest, ctx: Context
) -> SqlLabResponse:
Expand Down
4 changes: 0 additions & 4 deletions superset/mcp_service/sql_lab/tool/save_sql_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@
reloaded/shared via URL.
"""

from __future__ import annotations

import logging

from fastmcp import Context
Expand All @@ -38,7 +36,6 @@
SaveSqlQueryRequest,
SaveSqlQueryResponse,
)
from superset.mcp_service.utils.schema_utils import parse_request

logger = logging.getLogger(__name__)

Expand All @@ -53,7 +50,6 @@
destructiveHint=False,
),
)
@parse_request(SaveSqlQueryRequest)
async def save_sql_query(
request: SaveSqlQueryRequest, ctx: Context
) -> SaveSqlQueryResponse:
Expand Down
Loading
Loading