-
Notifications
You must be signed in to change notification settings - Fork 64
Description
Summary
The sample currently uses @app.generic_trigger(type="mcpToolTrigger", ...) and @app.generic_input_binding / @app.generic_output_binding decorators with a manual ToolProperty helper class and JSON-serialized toolProperties. As of azure-functions==1.25.0b2, first-class MCP decorators are available that dramatically simplify this code. The sample should be updated to use them.
Motivation
- The current code requires a boilerplate
ToolPropertyclass, manual JSON serialization of tool properties, and parsingcontextJSON to extract arguments — all of which the new decorators eliminate. - The new decorators infer tool properties (name, type, description) directly from the Python function signature and docstrings, aligning with standard Python patterns.
- This sample is a quickstart template; it should showcase the latest recommended API surface.
Prerequisites / Environment Changes
src/requirements.txt
Pin the new beta version:
azure-functions==1.25.0b2
src/local.settings.json
Add the required worker isolation setting:
{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "python",
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"PYTHON_ISOLATE_WORKER_DEPENDENCIES": "1"
}
}Deployed App Settings (infra / bicep / azd)
Ensure PYTHON_ISOLATE_WORKER_DEPENDENCIES: 1 is also set in the deployed Function App configuration (bicep / app settings).
Required Code Changes in src/function_app.py
1. Remove the ToolProperty helper class and all related boilerplate
Delete the entire ToolProperty class definition, the tool_properties_save_snippets_object / tool_properties_get_snippets_object lists, and the tool_properties_save_snippets_json / tool_properties_get_snippets_json JSON serialization lines. These are no longer needed — the new decorator infers tool properties from the function signature.
2. Migrate hello_mcp
Current:
@app.generic_trigger(
arg_name="context",
type="mcpToolTrigger",
toolName="hello_mcp",
description="Hello world.",
toolProperties="[]",
)
def hello_mcp(context) -> None:
return "Hello I am MCPTool!"New:
@app.mcp_tool()
def hello_mcp() -> str:
"""Hello world."""
return "Hello I am MCPTool!"Key changes:
@app.generic_trigger(...)→@app.mcp_tool()- The tool
descriptionis now inferred from the function docstring. - The
toolNameis inferred from the function name. - No
contextarg is needed when the tool has no properties. - Return type should be
str, notNone.
3. Migrate get_snippet
Current:
@app.generic_trigger(
arg_name="context",
type="mcpToolTrigger",
toolName="get_snippet",
description="Retrieve a snippet by name.",
toolProperties=tool_properties_get_snippets_json,
)
@app.generic_input_binding(arg_name="file", type="blob", connection="AzureWebJobsStorage", path=_BLOB_PATH)
def get_snippet(file: func.InputStream, context) -> str:
snippet_content = file.read().decode("utf-8")
logging.info(f"Retrieved snippet: {snippet_content}")
return snippet_contentNew:
@app.mcp_tool()
@app.mcp_tool_property(arg_name="snippetname", description="The name of the snippet.")
@app.blob_input(arg_name="file", connection="AzureWebJobsStorage", path=_BLOB_PATH)
def get_snippet(file: func.InputStream, snippetname: str) -> str:
"""Retrieve a snippet by name from Azure Blob Storage."""
snippet_content = file.read().decode("utf-8")
logging.info(f"Retrieved snippet: {snippet_content}")
return snippet_contentKey changes:
@app.generic_trigger(...)→@app.mcp_tool()@app.generic_input_binding(...)→@app.blob_input(...); thetype="blob"parameter is no longer needed (it is implicit).toolPropertiesJSON is replaced by@app.mcp_tool_property(...)input binding and/or function parameter type hints. Args in the function signature that don't map to another binding are automatically treated as MCP tool properties.contextparameter is removed; MCP arguments are received as named function parameters (snippetname).
4. Migrate save_snippet
Current:
@app.generic_trigger(
arg_name="context",
type="mcpToolTrigger",
toolName="save_snippet",
description="Save a snippet with a name.",
toolProperties=tool_properties_save_snippets_json,
)
@app.generic_output_binding(arg_name="file", type="blob", connection="AzureWebJobsStorage", path=_BLOB_PATH)
def save_snippet(file: func.Out[str], context) -> str:
content = json.loads(context)
snippet_name_from_args = content["arguments"][_SNIPPET_NAME_PROPERTY_NAME]
snippet_content_from_args = content["arguments"][_SNIPPET_PROPERTY_NAME]
if not snippet_name_from_args:
return "No snippet name provided"
if not snippet_content_from_args:
return "No snippet content provided"
file.set(snippet_content_from_args)
logging.info(f"Saved snippet: {snippet_content_from_args}")
return f"Snippet '{ snippet_content_from_args}' saved successfully"New:
@app.mcp_tool()
@app.mcp_tool_property(arg_name="snippetname", description="The name of the snippet.")
@app.mcp_tool_property(arg_name="snippet", description="The content of the snippet.")
@app.blob_output(arg_name="file", connection="AzureWebJobsStorage", path=_BLOB_PATH)
def save_snippet(file: func.Out[str], snippetname: str, snippet: str) -> str:
"""Save a snippet to Azure Blob Storage."""
if not snippetname:
return "No snippet name provided"
if not snippet:
return "No snippet content provided"
file.set(snippet)
logging.info(f"Saved snippet: {snippetname}")
return f"Snippet '{ snippetname}' saved successfully with content: {snippet}"Key changes:
@app.generic_trigger(...)→@app.mcp_tool()@app.generic_output_binding(...)→@app.blob_output(...);type="blob"is implicit.- Manual
json.loads(context)andcontent["arguments"][...]extraction is eliminated — arguments arrive as named function parameters. @app.mcp_tool_property(...)decorators explicitly set descriptions for each tool property. These are optional if the defaults are acceptable; properties are inferred from the signature.
5. Clean up imports
The json import can likely be removed since context is no longer manually parsed. Keep logging and azure.functions as func.
How new decorator property inference works (for reference)
| Scenario | Behavior |
|---|---|
Arg has a type hint but no mcp_tool_property |
Type and name inferred from signature; description left empty |
Arg has mcp_tool_property |
Values from the decorator take precedence over inferred values |
Both type hint and property_type in decorator |
Decorator value wins |
Arg matches another binding's arg_name (e.g. file in blob_output) |
Excluded from MCP tool properties automatically |
Arg has Optional[T] type hint |
Treated as an optional tool property |
Documentation Updates
The README.md Source Code section includes inline code snippets of the old API. These must be updated to reflect the new decorators. The prerequisites section should also note the minimum azure-functions version.
Checklist
- Update
src/requirements.txtto pinazure-functions==1.25.0b2 - Add
PYTHON_ISOLATE_WORKER_DEPENDENCIES: 1tosrc/local.settings.json - Add
PYTHON_ISOLATE_WORKER_DEPENDENCIES: 1to deployed app settings (infra/bicep) - Remove
ToolPropertyclass and alltool_properties_*boilerplate fromfunction_app.py - Migrate
hello_mcpto@app.mcp_tool() - Migrate
get_snippetto@app.mcp_tool()+@app.blob_input()+@app.mcp_tool_property() - Migrate
save_snippetto@app.mcp_tool()+@app.blob_output()+@app.mcp_tool_property() - Remove
jsonimport (no longer needed) - Update
README.mdSource Code section with new decorator examples - Test locally with Azurite + MCP Inspector
- Test deployed with
azd up