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)

Abierto
#24 1 comentario 2 reacciones 2 asignados Reclamado por @vrdmr Ver en GitHub
Lenguaje dominante
Python
Estrellas
125
Forks
83
Métricas de merge de PR
Sin PR fusionados en 30 d

Descripción

## 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`

Guía de contribución

No hay ninguna guía de contribución indexada para este repositorio

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.