Azure-Samples / Azure-Samples/remote-mcp-functions-python
Migrate function_app.py from generic_trigger/generic_*_binding to new @app.mcp_tool() decorators (azure-functions 1.25.0b2)
- Lingua principale
- Python
- Stelle
- 125
- Fork
- 83
- Metriche di merge delle PR
- Nessuna PR unita negli ultimi 30g
Descrizione
## 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 `ToolProperty` class, manual JSON serialization of tool properties, and parsing `context` JSON 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:
```json
{
"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:**
```python
@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:**
```python
@app.mcp_tool()
def hello_mcp() -> str:
"""Hello world."""
return "Hello I am MCPTool!"
```
Key changes:
- `@app.generic_trigger(...)` → `@app.mcp_tool()`
- The tool `description` is now inferred from the function docstring.
- The `toolName` is inferred from the function name.
- No `context` arg is needed when the tool has no properties.
- Return type should be `str`, not `None`.
### 3. Migrate `get_snippet`
**Current:**
```python
@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_content
```
**New:**
```python
@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_content
```
Key changes:
- `@app.generic_trigger(...)` → `@app.mcp_tool()`
- `@app.generic_input_binding(...)` → `@app.blob_input(...)`; the `type="blob"` parameter is no longer needed (it is implicit).
- `toolProperties` JSON 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.
- `context` parameter is removed; MCP arguments are received as named function parameters (`snippetname`).
### 4. Migrate `save_snippet`
**Current:**
```python
@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:**
```python
@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)` and `content["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.txt` to pin `azure-functions==1.25.0b2`
- [ ] Add `PYTHON_ISOLATE_WORKER_DEPENDENCIES: 1` to `src/local.settings.json`
- [ ] Add `PYTHON_ISOLATE_WORKER_DEPENDENCIES: 1` to deployed app settings (infra/bicep)
- [ ] Remove `ToolProperty` class and all `tool_properties_*` boilerplate from `function_app.py`
- [ ] Migrate `hello_mcp` to `@app.mcp_tool()`
- [ ] Migrate `get_snippet` to `@app.mcp_tool()` + `@app.blob_input()` + `@app.mcp_tool_property()`
- [ ] Migrate `save_snippet` to `@app.mcp_tool()` + `@app.blob_output()` + `@app.mcp_tool_property()`
- [ ] Remove `json` import (no longer needed)
- [ ] Update `README.md` Source Code section with new decorator examples
- [ ] Test locally with Azurite + MCP Inspector
- [ ] Test deployed with `azd up`
Guida per i contributori
Nessuna guida per i contributori indicizzata per questo repository
Valutazione
Questa issue non è ancora stata valutata.