Skip to content

Commit 5554921

Browse files
committed
Adding MCP decorators
1 parent 4a19d87 commit 5554921

File tree

6 files changed

+369
-0
lines changed

6 files changed

+369
-0
lines changed

azure/functions/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@
3939
from . import sql # NoQA
4040
from . import warmup # NoQA
4141
from . import mysql # NoQA
42+
from . import mcp # NoQA
43+
from .decorators.mcp import MCPToolTrigger, MCPToolInput, MCPToolOutput # NoQA
44+
from .mcp import MCPToolRequest, MCPToolTriggerConverter # NoQA
45+
from .mcp import MCPToolRequest, MCPToolTriggerConverter, MCPToolInputConverter, MCPToolOutputConverter # NoQA
4246

4347

4448
__all__ = (
@@ -71,6 +75,7 @@
7175
'WarmUpContext',
7276
'MySqlRow',
7377
'MySqlRowList',
78+
'MCPToolRequest',
7479

7580
# Middlewares
7681
'WsgiMiddleware',

azure/functions/decorators/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,6 @@
4545
SEMANTIC_SEARCH = "semanticSearch"
4646
MYSQL = "mysql"
4747
MYSQL_TRIGGER = "mysqlTrigger"
48+
MCP_TOOL_TRIGGER = "mcpToolTrigger"
49+
MCP_TOOL_INPUT = "mcpToolInput"
50+
MCP_TOOL_OUTPUT = "mcpToolOutput"

azure/functions/decorators/function_app.py

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
AssistantQueryInput, AssistantPostInput, InputType, EmbeddingsInput, \
4343
semantic_search_system_prompt, \
4444
SemanticSearchInput, EmbeddingsStoreOutput
45+
from .mcp import MCPToolTrigger, MCPToolInput, MCPToolOutput
4546
from .retry_policy import RetryPolicy
4647
from .function_name import FunctionName
4748
from .warmup import WarmUpTrigger
@@ -1511,6 +1512,57 @@ def decorator():
15111512

15121513
return wrap
15131514

1515+
def mcp_tool_trigger(self,
1516+
arg_name: str,
1517+
tool_name: str,
1518+
description: Optional[str] = None,
1519+
tool_properties: Optional[str] = None,
1520+
data_type: Optional[Union[DataType, str]] = None,
1521+
**kwargs) -> Callable[..., Any]:
1522+
"""
1523+
The `mcp_tool_trigger` decorator adds :class:`MCPToolTrigger` to the
1524+
:class:`FunctionBuilder` object for building a :class:`Function` object
1525+
used in the worker function indexing model.
1526+
1527+
This is equivalent to defining `MCPToolTrigger` in the `function.json`,
1528+
which enables the function to be triggered when MCP tool requests are
1529+
received by the host.
1530+
1531+
All optional fields will be given default values by the function host when
1532+
they are parsed.
1533+
1534+
Ref: https://aka.ms/azure-function-binding-custom
1535+
1536+
:param arg_name: The name of the trigger parameter in the function code.
1537+
:param tool_name: The logical tool name exposed to the host.
1538+
:param description: Optional human-readable description of the tool.
1539+
:param tool_properties: JSON-serialized tool properties/parameters list.
1540+
:param data_type: Defines how the Functions runtime should treat the
1541+
parameter value.
1542+
:param kwargs: Keyword arguments for specifying additional binding
1543+
fields to include in the binding JSON.
1544+
1545+
:return: Decorator function.
1546+
"""
1547+
1548+
@self._configure_function_builder
1549+
def wrap(fb):
1550+
def decorator():
1551+
fb.add_trigger(
1552+
trigger=MCPToolTrigger(
1553+
name=arg_name,
1554+
tool_name=tool_name,
1555+
description=description,
1556+
tool_properties=tool_properties,
1557+
data_type=parse_singular_param_to_enum(data_type,
1558+
DataType),
1559+
**kwargs))
1560+
return fb
1561+
1562+
return decorator()
1563+
1564+
return wrap
1565+
15141566
def dapr_service_invocation_trigger(self,
15151567
arg_name: str,
15161568
method_name: str,
@@ -3720,6 +3772,57 @@ def decorator():
37203772

37213773
return wrap
37223774

3775+
def mcp_tool_input(self,
3776+
arg_name: str,
3777+
tool_name: str,
3778+
description: Optional[str] = None,
3779+
tool_properties: Optional[str] = None,
3780+
data_type: Optional[Union[DataType, str]] = None,
3781+
**kwargs) -> Callable[..., Any]:
3782+
"""
3783+
The `mcp_tool_input` decorator adds :class:`MCPToolInput` to the
3784+
:class:`FunctionBuilder` object for building a :class:`Function` object
3785+
used in the worker function indexing model.
3786+
3787+
This is equivalent to defining `MCPToolInput` in the `function.json`,
3788+
which enables the function to read data from MCP tool sources.
3789+
3790+
All optional fields will be assigned default values by the function host
3791+
when they are parsed.
3792+
3793+
Ref: https://aka.ms/azure-function-binding-custom
3794+
3795+
:param arg_name: The name of the variable that represents the MCP tool input
3796+
object in the function code.
3797+
:param tool_name: The logical tool name for the MCP binding.
3798+
:param description: Optional human-readable description of the tool.
3799+
:param tool_properties: JSON-serialized tool properties/parameters list.
3800+
:param data_type: Defines how the Functions runtime should treat the
3801+
parameter value.
3802+
:param kwargs: Keyword arguments for specifying additional binding
3803+
fields to include in the binding JSON.
3804+
3805+
:return: Decorator function.
3806+
"""
3807+
3808+
@self._configure_function_builder
3809+
def wrap(fb):
3810+
def decorator():
3811+
fb.add_binding(
3812+
binding=MCPToolInput(
3813+
name=arg_name,
3814+
tool_name=tool_name,
3815+
description=description,
3816+
tool_properties=tool_properties,
3817+
data_type=parse_singular_param_to_enum(data_type,
3818+
DataType),
3819+
**kwargs))
3820+
return fb
3821+
3822+
return decorator()
3823+
3824+
return wrap
3825+
37233826
def mysql_output(self,
37243827
arg_name: str,
37253828
command_text: str,
@@ -3771,6 +3874,57 @@ def decorator():
37713874

37723875
return wrap
37733876

3877+
def mcp_tool_output(self,
3878+
arg_name: str,
3879+
tool_name: str,
3880+
description: Optional[str] = None,
3881+
tool_properties: Optional[str] = None,
3882+
data_type: Optional[Union[DataType, str]] = None,
3883+
**kwargs) -> Callable[..., Any]:
3884+
"""
3885+
The `mcp_tool_output` decorator adds :class:`MCPToolOutput` to the
3886+
:class:`FunctionBuilder` object for building a :class:`Function` object
3887+
used in the worker function indexing model.
3888+
3889+
This is equivalent to defining `MCPToolOutput` in the `function.json`,
3890+
which enables the function to write data to MCP tool destinations.
3891+
3892+
All optional fields will be assigned default values by the function host
3893+
when they are parsed.
3894+
3895+
Ref: https://aka.ms/azure-function-binding-custom
3896+
3897+
:param arg_name: The name of the variable that represents the MCP tool output
3898+
object in the function code.
3899+
:param tool_name: The logical tool name for the MCP binding.
3900+
:param description: Optional human-readable description of the tool.
3901+
:param tool_properties: JSON-serialized tool properties/parameters list.
3902+
:param data_type: Defines how the Functions runtime should treat the
3903+
parameter value.
3904+
:param kwargs: Keyword arguments for specifying additional binding
3905+
fields to include in the binding JSON.
3906+
3907+
:return: Decorator function.
3908+
"""
3909+
3910+
@self._configure_function_builder
3911+
def wrap(fb):
3912+
def decorator():
3913+
fb.add_binding(
3914+
binding=MCPToolOutput(
3915+
name=arg_name,
3916+
tool_name=tool_name,
3917+
description=description,
3918+
tool_properties=tool_properties,
3919+
data_type=parse_singular_param_to_enum(data_type,
3920+
DataType),
3921+
**kwargs))
3922+
return fb
3923+
3924+
return decorator()
3925+
3926+
return wrap
3927+
37743928

37753929
class SettingsApi(DecoratorApi, ABC):
37763930
"""Interface to extend for using an existing settings decorator in functions."""

azure/functions/decorators/mcp.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
from typing import Optional
2+
3+
from azure.functions.decorators.constants import (
4+
MCP_TOOL_TRIGGER, MCP_TOOL_INPUT, MCP_TOOL_OUTPUT
5+
)
6+
from azure.functions.decorators.core import Trigger, DataType, InputBinding, \
7+
OutputBinding
8+
9+
10+
class MCPToolTrigger(Trigger):
11+
12+
@staticmethod
13+
def get_binding_name() -> str:
14+
return MCP_TOOL_TRIGGER
15+
16+
def __init__(self,
17+
name: str,
18+
tool_name: str,
19+
description: Optional[str] = None,
20+
tool_properties: Optional[str] = None,
21+
data_type: Optional[DataType] = None,
22+
**kwargs):
23+
self.tool_name = tool_name
24+
self.description = description
25+
self.tool_properties = tool_properties
26+
super().__init__(name=name, data_type=data_type)
27+
28+
29+
class MCPToolInput(InputBinding):
30+
31+
@staticmethod
32+
def get_binding_name() -> str:
33+
return MCP_TOOL_INPUT
34+
35+
def __init__(self,
36+
name: str,
37+
tool_name: str,
38+
description: Optional[str] = None,
39+
tool_properties: Optional[str] = None,
40+
data_type: Optional[DataType] = None,
41+
**kwargs):
42+
self.tool_name = tool_name
43+
self.description = description
44+
self.tool_properties = tool_properties
45+
super().__init__(name=name, data_type=data_type)
46+
47+
48+
class MCPToolOutput(OutputBinding):
49+
50+
@staticmethod
51+
def get_binding_name() -> str:
52+
return MCP_TOOL_OUTPUT
53+
54+
def __init__(self,
55+
name: str,
56+
tool_name: str,
57+
description: Optional[str] = None,
58+
tool_properties: Optional[str] = None,
59+
data_type: Optional[DataType] = None,
60+
**kwargs):
61+
self.tool_name = tool_name
62+
self.description = description
63+
self.tool_properties = tool_properties
64+
super().__init__(name=name, data_type=data_type)

azure/functions/mcp.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import typing
2+
3+
from . import meta
4+
5+
6+
class MCPToolRequest:
7+
"""Wrapper for MCP tool trigger payload providing raw & parsed access."""
8+
9+
def __init__(self, raw: typing.Any):
10+
self.raw = raw
11+
self.json = None
12+
if isinstance(raw, str):
13+
try:
14+
from ._jsonutils import json as _json
15+
self.json = _json.loads(raw)
16+
except Exception:
17+
self.json = None
18+
elif isinstance(raw, dict):
19+
# If raw is already a dict, use it as the parsed JSON
20+
self.json = raw
21+
22+
23+
class MCPToolTriggerConverter(meta.InConverter, binding='mcpToolTrigger',
24+
trigger=True):
25+
26+
@classmethod
27+
def check_input_type_annotation(cls, pytype: type) -> bool:
28+
return issubclass(pytype, (MCPToolRequest, str))
29+
30+
@classmethod
31+
def decode(cls, data: meta.Datum, *, trigger_metadata):
32+
# Handle different data types appropriately
33+
if data.type == 'json':
34+
# If it's already parsed JSON, use the value directly
35+
val = data.value
36+
elif data.type == 'string':
37+
# If it's a string, use it as-is
38+
val = data.value
39+
else:
40+
# Fallback to python_value for other types
41+
val = data.python_value if hasattr(data, 'python_value') else data.value
42+
return MCPToolRequest(val)
43+
44+
45+
class MCPToolInputConverter(meta.InConverter, binding='mcpToolInput'):
46+
47+
@classmethod
48+
def check_input_type_annotation(cls, pytype: type) -> bool:
49+
return issubclass(pytype, (str, MCPToolRequest))
50+
51+
@classmethod
52+
def decode(cls, data: meta.Datum, *, trigger_metadata):
53+
val = data.python_value if hasattr(data, 'python_value') else data.value
54+
return val
55+
56+
57+
class MCPToolOutputConverter(meta.OutConverter, binding='mcpToolOutput'):
58+
59+
@classmethod
60+
def check_output_type_annotation(cls, pytype: type) -> bool:
61+
return issubclass(pytype, (str,))
62+
63+
@classmethod
64+
def encode(cls, obj: typing.Any, *, expected_type: typing.Optional[type]):
65+
if isinstance(obj, str):
66+
return meta.Datum(type='string', value=obj)
67+
raise NotImplementedError

0 commit comments

Comments
 (0)