Skip to content

Commit 6ed2ade

Browse files
villebroaminghadersohi
authored andcommitted
feat: add mcp abstractions to core (apache#36151)
1 parent 8751bf2 commit 6ed2ade

37 files changed

+1044
-151
lines changed

docs/developer_portal/extensions/mcp.md

Lines changed: 460 additions & 0 deletions
Large diffs are not rendered by default.

requirements/base.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,9 @@ pyasn1-modules==0.4.2
298298
pycparser==2.22
299299
# via cffi
300300
pydantic==2.11.7
301-
# via apache-superset (pyproject.toml)
301+
# via
302+
# apache-superset (pyproject.toml)
303+
# apache-superset-core
302304
pydantic-core==2.33.2
303305
# via pydantic
304306
pygments==2.19.1

requirements/development.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,7 @@ pydantic==2.11.7
718718
# via
719719
# -c requirements/base-constraint.txt
720720
# apache-superset
721+
# apache-superset-core
721722
# fastmcp
722723
# mcp
723724
# openapi-pydantic

superset-core/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ classifiers = [
4343
]
4444
dependencies = [
4545
"flask-appbuilder>=5.0.2,<6",
46+
"pydantic>=2.8.0",
4647
"sqlalchemy>=1.4.0,<2.0",
4748
"sqlalchemy-utils>=0.38.0",
4849
"sqlglot>=27.15.2, <28",
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
"""
19+
MCP (Model Context Protocol) tool registration for Superset MCP server.
20+
21+
This module provides a decorator interface to register MCP tools with the
22+
host application.
23+
24+
Usage:
25+
from superset_core.mcp import tool
26+
27+
@tool(name="my_tool", description="Custom business logic", tags=["extension"])
28+
def my_extension_tool(param: str) -> dict:
29+
return {"message": f"Hello {param}!"}
30+
31+
# Or use function name and docstring:
32+
@tool
33+
def another_tool(value: int) -> str:
34+
'''Tool description from docstring'''
35+
return str(value * 2)
36+
"""
37+
38+
from typing import Any, Callable, TypeVar
39+
40+
# Type variable for decorated functions
41+
F = TypeVar("F", bound=Callable[..., Any])
42+
43+
44+
def tool(
45+
func_or_name: str | Callable[..., Any] | None = None,
46+
*,
47+
name: str | None = None,
48+
description: str | None = None,
49+
tags: list[str] | None = None,
50+
protect: bool = True,
51+
) -> Any: # Use Any to avoid mypy issues with dependency injection
52+
"""
53+
Decorator to register an MCP tool with optional authentication.
54+
55+
This decorator combines FastMCP tool registration with optional authentication.
56+
57+
Can be used as:
58+
@tool
59+
def my_tool(): ...
60+
61+
Or:
62+
@tool(name="custom_name", protect=False)
63+
def my_tool(): ...
64+
65+
Args:
66+
func_or_name: When used as @tool, this will be the function.
67+
When used as @tool("name"), this will be the name.
68+
name: Tool name (defaults to function name, prefixed with extension ID)
69+
description: Tool description (defaults to function docstring)
70+
tags: List of tags for categorizing the tool (defaults to empty list)
71+
protect: Whether to require Superset authentication (defaults to True)
72+
73+
Returns:
74+
Decorator function that registers and wraps the tool, or the wrapped function
75+
76+
Raises:
77+
NotImplementedError: If called before host implementation is initialized
78+
79+
Example:
80+
@tool(name="my_tool", description="Does something useful", tags=["utility"])
81+
def my_custom_tool(param: str) -> dict:
82+
return {"result": param}
83+
84+
@tool # Uses function name and docstring with auth
85+
def simple_tool(value: int) -> str:
86+
'''Doubles the input value'''
87+
return str(value * 2)
88+
89+
@tool(protect=False) # No authentication required
90+
def public_tool() -> str:
91+
'''Public tool accessible without auth'''
92+
return "Hello world"
93+
"""
94+
raise NotImplementedError(
95+
"MCP tool decorator not initialized. "
96+
"This decorator should be replaced during Superset startup."
97+
)
98+
99+
100+
def prompt(
101+
func_or_name: str | Callable[..., Any] | None = None,
102+
*,
103+
name: str | None = None,
104+
title: str | None = None,
105+
description: str | None = None,
106+
tags: set[str] | None = None,
107+
protect: bool = True,
108+
) -> Any: # Use Any to avoid mypy issues with dependency injection
109+
"""
110+
Decorator to register an MCP prompt with optional authentication.
111+
112+
This decorator combines FastMCP prompt registration with optional authentication.
113+
114+
Can be used as:
115+
@prompt
116+
async def my_prompt_handler(): ...
117+
118+
Or:
119+
@prompt("my_prompt")
120+
async def my_prompt_handler(): ...
121+
122+
Or:
123+
@prompt("my_prompt", protected=False, title="Custom Title")
124+
async def my_prompt_handler(): ...
125+
126+
Args:
127+
func_or_name: When used as @prompt, this will be the function.
128+
When used as @prompt("name"), this will be the name.
129+
name: Prompt name (defaults to function name if not provided)
130+
title: Prompt title (defaults to function name)
131+
description: Prompt description (defaults to function docstring)
132+
tags: Set of tags for categorizing the prompt
133+
protect: Whether to require Superset authentication (defaults to True)
134+
135+
Returns:
136+
Decorator function that registers and wraps the prompt, or the wrapped function
137+
138+
Raises:
139+
NotImplementedError: If called before host implementation is initialized
140+
141+
Example:
142+
@prompt
143+
async def my_prompt_handler(ctx: Context) -> str:
144+
'''Interactive prompt for doing something.'''
145+
return "Prompt instructions here..."
146+
147+
@prompt("custom_prompt", protect=False, title="Custom Title")
148+
async def public_prompt_handler(ctx: Context) -> str:
149+
'''Public prompt accessible without auth'''
150+
return "Public prompt accessible without auth"
151+
"""
152+
raise NotImplementedError(
153+
"MCP prompt decorator not initialized. "
154+
"This decorator should be replaced during Superset startup."
155+
)
156+
157+
158+
__all__ = [
159+
"tool",
160+
"prompt",
161+
]

0 commit comments

Comments
 (0)