Skip to content

Commit 63f80c3

Browse files
committed
Final changes, tests
1 parent fd4e269 commit 63f80c3

File tree

6 files changed

+326
-51
lines changed

6 files changed

+326
-51
lines changed

azure/functions/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
DecoratorApi, DataType, AuthLevel,
1111
Cardinality, AccessRights, HttpMethod,
1212
AsgiFunctionApp, WsgiFunctionApp,
13-
ExternalHttpFunctionApp, BlobSource)
13+
ExternalHttpFunctionApp, BlobSource, McpPropertyType)
1414
from ._durable_functions import OrchestrationContext, EntityContext
1515
from .decorators.function_app import (FunctionRegister, TriggerApi,
1616
BindingApi, SettingsApi)
@@ -102,7 +102,8 @@
102102
'AccessRights',
103103
'HttpMethod',
104104
'BlobSource',
105-
'MCPToolContext'
105+
'MCPToolContext',
106+
'McpPropertyType'
106107
)
107108

108109
__version__ = '1.25.0b1'

azure/functions/decorators/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from .function_app import FunctionApp, Function, DecoratorApi, DataType, \
55
AuthLevel, Blueprint, ExternalHttpFunctionApp, AsgiFunctionApp, \
66
WsgiFunctionApp, FunctionRegister, TriggerApi, BindingApi, \
7-
SettingsApi, BlobSource
7+
SettingsApi, BlobSource, McpPropertyType
88
from .http import HttpMethod
99

1010
__all__ = [
@@ -24,5 +24,6 @@
2424
'Cardinality',
2525
'AccessRights',
2626
'HttpMethod',
27-
'BlobSource'
27+
'BlobSource',
28+
'McpPropertyType'
2829
]

azure/functions/decorators/core.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,22 @@ class BlobSource(StringifyEnum):
7373
"""Standard polling mechanism to detect changes in the container."""
7474

7575

76+
class McpPropertyType(StringifyEnum):
77+
"""MCP property types."""
78+
INTEGER = "integer"
79+
"""Integer type."""
80+
FLOAT = "float"
81+
"""Float type."""
82+
STRING = "string"
83+
"""String type."""
84+
BOOLEAN = "boolean"
85+
"""Boolean type."""
86+
OBJECT = "object"
87+
"""Object type."""
88+
DATETIME = "string"
89+
"""Datetime type represented as string."""
90+
91+
7692
class Binding(ABC):
7793
"""Abstract binding class which captures common attributes and
7894
functions. :meth:`get_dict_repr` can auto generate the function.json for

azure/functions/decorators/function_app.py

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313

1414
from azure.functions.decorators.blob import BlobTrigger, BlobInput, BlobOutput
1515
from azure.functions.decorators.core import Binding, Trigger, DataType, \
16-
AuthLevel, SCRIPT_FILE_NAME, Cardinality, AccessRights, Setting, BlobSource
16+
AuthLevel, SCRIPT_FILE_NAME, Cardinality, AccessRights, Setting, BlobSource, \
17+
McpPropertyType
1718
from azure.functions.decorators.cosmosdb import CosmosDBTrigger, \
1819
CosmosDBOutput, CosmosDBInput, CosmosDBTriggerV3, CosmosDBInputV3, \
1920
CosmosDBOutputV3
@@ -44,7 +45,7 @@
4445
AssistantQueryInput, AssistantPostInput, InputType, EmbeddingsInput, \
4546
semantic_search_system_prompt, \
4647
SemanticSearchInput, EmbeddingsStoreOutput
47-
from .mcp import MCPToolTrigger, _TYPE_MAPPING, check_property_type, check_is_array, check_is_required
48+
from .mcp import MCPToolTrigger, check_property_type, check_is_array, check_is_required
4849
from .retry_policy import RetryPolicy
4950
from .function_name import FunctionName
5051
from .warmup import WarmUpTrigger
@@ -55,8 +56,6 @@
5556
MySqlTrigger
5657

5758

58-
logger = logging.getLogger('azure.functions.WsgiMiddleware')
59-
6059
class Function(object):
6160
"""
6261
The function object represents a function in Function App. It
@@ -1593,7 +1592,6 @@ def decorator(fb: FunctionBuilder) -> FunctionBuilder:
15931592

15941593
# Pull any explicitly declared MCP tool properties
15951594
explicit_properties = getattr(target_func, "__mcp_tool_properties__", {})
1596-
logger.info(f"Explicit MCP tool properties: {explicit_properties}")
15971595

15981596
# Parse tool name and description from function signature
15991597
tool_name = target_func.__name__
@@ -1613,26 +1611,27 @@ def decorator(fb: FunctionBuilder) -> FunctionBuilder:
16131611
if param_type_hint is MCPToolContext:
16141612
continue
16151613

1616-
# Check if explicit metadata exists for this param
1617-
if param_name in explicit_properties:
1618-
logger.info(f"Using explicit MCP tool property for param: {param_name}") # noqa
1619-
prop = explicit_properties[param_name].copy()
1620-
prop["propertyName"] = param_name
1621-
tool_properties.append(prop)
1622-
continue
1623-
1624-
# Otherwise infer it
1614+
# Inferred defaults
16251615
is_required = check_is_required(param, param_type_hint)
16261616
is_array = check_is_array(param_type_hint)
16271617
property_type = check_property_type(param_type_hint, is_array)
16281618

1629-
tool_properties.append({
1619+
property_data = {
16301620
"propertyName": param_name,
16311621
"propertyType": property_type,
16321622
"description": "",
16331623
"isArray": is_array,
16341624
"isRequired": is_required
1635-
})
1625+
}
1626+
1627+
# Merge in any explicit overrides
1628+
if param_name in explicit_properties:
1629+
overrides = explicit_properties[param_name]
1630+
for key, value in overrides.items():
1631+
if value is not None:
1632+
property_data[key] = value
1633+
1634+
tool_properties.append(property_data)
16361635

16371636
tool_properties_json = json.dumps(tool_properties)
16381637

@@ -1682,39 +1681,46 @@ async def wrapper(context: str, *args, **kwargs):
16821681
return decorator
16831682

16841683
def mcp_tool_property(self, arg_name: str,
1685-
description: Optional[str] = "",
1686-
property_type: Optional[str] = None,
1687-
is_required: Optional[bool] = True,
1688-
is_array: Optional[bool] = False):
1684+
description: Optional[str] = None,
1685+
property_type: Optional[McpPropertyType] = None,
1686+
is_required: Optional[bool] = True,
1687+
is_array: Optional[bool] = False):
16891688
"""
16901689
Decorator for defining explicit MCP tool property metadata for a specific argument.
16911690
1691+
:param arg_name: The name of the argument.
1692+
:param description: The description of the argument.
1693+
:param property_type: The type of the argument.
1694+
:param is_required: If the argument is required or not.
1695+
:param is_array: If the argument is array or not.
1696+
1697+
:return: Decorator function.
1698+
16921699
Example:
16931700
@app.mcp_tool_property(
16941701
arg_name="snippetname",
16951702
description="The name of the snippet.",
1696-
property_type="string",
1703+
property_type=func.McpPropertyType.STRING,
16971704
is_required=True,
16981705
is_array=False
16991706
)
17001707
"""
17011708
def decorator(func):
1702-
# If this function is already wrapped by FunctionBuilder or similar, unwrap it
1709+
# If this function is already wrapped by FunctionBuilder or similar, unwrap it
17031710
target_func = getattr(func, "_function", func)
17041711
target_func = getattr(target_func, "_func", target_func)
17051712

17061713
existing = getattr(target_func, "__mcp_tool_properties__", {})
17071714
existing[arg_name] = {
1708-
"description": description or "",
1709-
"propertyType": property_type or "string",
1715+
"description": description,
1716+
"propertyType": property_type.value if property_type else None, # Get enum value
17101717
"isRequired": is_required,
17111718
"isArray": is_array,
17121719
}
17131720
setattr(target_func, "__mcp_tool_properties__", existing)
17141721
return func
17151722
return decorator
17161723

1717-
17181724
def dapr_service_invocation_trigger(self,
17191725
arg_name: str,
17201726
method_name: str,

azure/functions/decorators/mcp.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from azure.functions.decorators.constants import (
99
MCP_TOOL_TRIGGER
1010
)
11-
from azure.functions.decorators.core import Trigger, DataType
11+
from azure.functions.decorators.core import Trigger, DataType, McpPropertyType
1212

1313
# Mapping Python types to MCP property types
1414
_TYPE_MAPPING = {
@@ -39,6 +39,7 @@ def __init__(self,
3939
self.tool_properties = tool_properties
4040
super().__init__(name=name, data_type=data_type)
4141

42+
4243
def unwrap_optional(pytype: type):
4344
"""If Optional[T], return T; else return pytype unchanged."""
4445
origin = get_origin(pytype)
@@ -58,13 +59,16 @@ def check_is_array(param_type_hint: type) -> bool:
5859

5960
def check_property_type(pytype: type, is_array: bool) -> str:
6061
"""Map Python type hints to MCP property types."""
62+
if isinstance(pytype, McpPropertyType):
63+
return pytype.value
6164
base_type = unwrap_optional(pytype)
6265
if is_array:
6366
args = get_args(base_type)
6467
inner_type = unwrap_optional(args[0]) if args else str
6568
return _TYPE_MAPPING.get(inner_type, "string")
6669
return _TYPE_MAPPING.get(base_type, "string")
6770

71+
6872
def check_is_required(param: type, param_type_hint: type) -> bool:
6973
"""
7074
Return True when param is required, False when optional.

0 commit comments

Comments
 (0)