Skip to content

Commit beef953

Browse files
authored
Merge pull request #5 from pamelafox/pamelatweaks
Pamelas formatting and renaming changes
2 parents 23f9e8a + 182ce3c commit beef953

File tree

3 files changed

+47
-39
lines changed

3 files changed

+47
-39
lines changed

README.md

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ urlFragment: remote-mcp-functions-python
1616

1717
# Getting Started with Remote MCP Servers using Azure Functions (Python)
1818

19-
This is a quickstart template to easily build and deploy a custom remote MCP server to the cloud using Azure Functions with Python. You can clone/restore/run on your local machine with debugging, and `azd up` to have it in the cloud in a couple minutes. The MCP server is secured by design using keys and HTTPS, and allows more options for OAuth using EasyAuth and/or API Management as well as network isolation using VNET.
19+
This is a quickstart template to easily build and deploy a custom remote MCP server to the cloud using Azure Functions with Python. You can clone/restore/run on your local machine with debugging, and `azd up` to have it in the cloud in a couple minutes. The MCP server is secured by design using keys and HTTPS, and allows more options for OAuth using built-in auth and/or API Management as well as network isolation using VNET.
2020

2121
If you're looking for this sample in more languages check out the [.NET/C#](https://github.com/Azure-Samples/remote-mcp-functions-dotnet) and [Node.js/TypeScript](https://github.com/Azure-Samples/remote-mcp-functions-typescript) versions.
2222

@@ -50,17 +50,20 @@ An Azure Storage Emulator is needed for this particular sample because we will s
5050

5151
## Run your MCP Server locally from the terminal
5252

53-
1. Change to the src folder in a new terminal window
53+
1. Change to the src folder in a new terminal window:
54+
5455
```shell
5556
cd src
5657
```
5758

58-
1. Install Python dependencies
59+
1. Install Python dependencies:
60+
5961
```shell
6062
pip install -r requirements.txt
6163
```
6264

6365
1. Start the Functions host locally:
66+
6467
```shell
6568
func start
6669
```
@@ -72,9 +75,11 @@ An Azure Storage Emulator is needed for this particular sample because we will s
7275
### VS Code - Copilot Edits
7376

7477
1. **Add MCP Server** from command palette and add URL to your running Function app's SSE endpoint:
78+
7579
```shell
7680
http://0.0.0.0:7071/runtime/webhooks/mcp/sse
7781
```
82+
7883
1. **List MCP Servers** from command palette and start the server
7984
1. In Copilot chat agent mode enter a prompt to trigger the tool, e.g., select some code and enter this prompt
8085
@@ -89,6 +94,7 @@ An Azure Storage Emulator is needed for this particular sample because we will s
8994
```plaintext
9095
Retrieve snippet1 and apply to newFile.py
9196
```
97+
9298
1. When prompted to run the tool, consent by clicking **Continue**
9399
94100
1. When you're done, press Ctrl+C in the terminal window to stop the Functions host process.
@@ -102,11 +108,13 @@ An Azure Storage Emulator is needed for this particular sample because we will s
102108
```
103109

104110
2. CTRL click to load the MCP Inspector web app from the URL displayed by the app (e.g. http://0.0.0.0:5173/#resources)
105-
3. Set the transport type to `SSE`
111+
3. Set the transport type to `SSE`
106112
4. Set the URL to your running Function app's SSE endpoint and **Connect**:
113+
107114
```shell
108115
http://0.0.0.0:7071/runtime/webhooks/mcp/sse
109116
```
117+
110118
5. **List Tools**. Click on a tool and **Run Tool**.
111119
112120
## Deploy to Azure for Remote MCP
@@ -123,11 +131,11 @@ You can opt-in to a VNet being used in the sample. To do so, do this before `azd
123131
azd env set VNET_ENABLED true
124132
```
125133
126-
Additionally, [API Management]() can be used for improved security and policies over your MCP Server, and [App Service built-in authentication](https://learn.microsoft.com/en-us/azure/app-service/overview-authentication-authorization) can be used to set up your favorite OAuth provider including Entra.
134+
Additionally, [API Management]() can be used for improved security and policies over your MCP Server, and [App Service built-in authentication](https://learn.microsoft.com/azure/app-service/overview-authentication-authorization) can be used to set up your favorite OAuth provider including Entra.
127135
128136
### Connect to your function app from a client
129137
130-
Your client will need a key in order to invoke the new hosted SSE endpoint, which will be of the form `https://<funcappname>.azurewebsites.net/runtime/webhooks/mcp/sse`. The hosted function requires a system key by default which can be obtained from the [portal](https://learn.microsoft.com/en-us/azure/azure-functions/function-keys-how-to?tabs=azure-portal) or the CLI (`az functionapp keys list --resource-group <resource_group> --name <function_app_name>`). Obtain the system key named `mcp_extension`.
138+
Your client will need a key in order to invoke the new hosted SSE endpoint, which will be of the form `https://<funcappname>.azurewebsites.net/runtime/webhooks/mcp/sse`. The hosted function requires a system key by default which can be obtained from the [portal](https://learn.microsoft.com/azure/azure-functions/function-keys-how-to?tabs=azure-portal) or the CLI (`az functionapp keys list --resource-group <resource_group> --name <function_app_name>`). Obtain the system key named `mcp_extension`.
131139
132140
For MCP Inspector, you can include the key in the URL: `https://<funcappname>.azurewebsites.net/runtime/webhooks/mcp/sse?code=<your-mcp-extension-system-key>`.
133141
@@ -284,6 +292,6 @@ Note that the `host.json` file also includes a reference to the experimental bun
284292
## Next Steps
285293
286294
- Add [API Management]() to your MCP server
287-
- Add [EasyAuth]() to your MCP server
295+
- Add [built-in auth]() to your MCP server
288296
- Enable VNET using VNET_ENABLED=true flag
289297
- Learn more about [related MCP efforts from Microsoft]()

pyproject.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[tool.ruff]
2+
line-length = 120
3+
target-version = "py311"
4+
lint.select = ["E", "F", "I", "UP", "A"]
5+
lint.ignore = ["D203"]

src/function_app.py

Lines changed: 27 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
import azure.functions as func
2-
import logging
31
import json
4-
2+
import logging
3+
4+
import azure.functions as func
5+
56
app = func.FunctionApp(http_auth_level=func.AuthLevel.FUNCTION)
6-
7+
78
# Constants for the Azure Blob Storage container, file, and blob path
89
_SNIPPET_NAME_PROPERTY_NAME = "snippetname"
910
_SNIPPET_PROPERTY_NAME = "snippet"
1011
_BLOB_PATH = "snippets/{mcptoolargs." + _SNIPPET_NAME_PROPERTY_NAME + "}.json"
1112

13+
1214
class ToolProperty:
1315
def __init__(self, property_name: str, property_type: str, description: str):
1416
self.propertyName = property_name
@@ -19,27 +21,30 @@ def to_dict(self):
1921
return {
2022
"propertyName": self.propertyName,
2123
"propertyType": self.propertyType,
22-
"description": self.description
24+
"description": self.description,
2325
}
2426

27+
2528
# Define the tool properties using the ToolProperty class
2629
tool_properties_save_snippets_object = [
2730
ToolProperty(_SNIPPET_NAME_PROPERTY_NAME, "string", "The name of the snippet."),
28-
ToolProperty(_SNIPPET_PROPERTY_NAME, "string", "The content of the snippet.")
31+
ToolProperty(_SNIPPET_PROPERTY_NAME, "string", "The content of the snippet."),
2932
]
3033

31-
tool_properties_get_snippets_object = [
32-
ToolProperty(_SNIPPET_NAME_PROPERTY_NAME, "string", "The name of the snippet.")
33-
]
34+
tool_properties_get_snippets_object = [ToolProperty(_SNIPPET_NAME_PROPERTY_NAME, "string", "The name of the snippet.")]
3435

3536
# Convert the tool properties to JSON
3637
tool_properties_save_snippets_json = json.dumps([prop.to_dict() for prop in tool_properties_save_snippets_object])
3738
tool_properties_get_snippets_json = json.dumps([prop.to_dict() for prop in tool_properties_get_snippets_object])
3839

3940

40-
@app.generic_trigger(arg_name="context", type="mcpToolTrigger", toolName="hello",
41-
description="Hello world.",
42-
toolProperties="[]")
41+
@app.generic_trigger(
42+
arg_name="context",
43+
type="mcpToolTrigger",
44+
toolName="hello_mcp",
45+
description="Hello world.",
46+
toolProperties="[]",
47+
)
4348
def hello_mcp(context) -> None:
4449
"""
4550
A simple function that returns a greeting message.
@@ -56,24 +61,19 @@ def hello_mcp(context) -> None:
5661
@app.generic_trigger(
5762
arg_name="context",
5863
type="mcpToolTrigger",
59-
toolName="getsnippet",
64+
toolName="get_snippet",
6065
description="Retrieve a snippet by name.",
61-
toolProperties=tool_properties_get_snippets_json
62-
)
63-
@app.generic_input_binding(
64-
arg_name="file",
65-
type="blob",
66-
connection="AzureWebJobsStorage",
67-
path=_BLOB_PATH
66+
toolProperties=tool_properties_get_snippets_json,
6867
)
68+
@app.generic_input_binding(arg_name="file", type="blob", connection="AzureWebJobsStorage", path=_BLOB_PATH)
6969
def get_snippet(file: func.InputStream, context) -> str:
7070
"""
7171
Retrieves a snippet by name from Azure Blob Storage.
72-
72+
7373
Args:
7474
file (func.InputStream): The input binding to read the snippet from Azure Blob Storage.
7575
context: The trigger context containing the input arguments.
76-
76+
7777
Returns:
7878
str: The content of the snippet or an error message.
7979
"""
@@ -85,16 +85,11 @@ def get_snippet(file: func.InputStream, context) -> str:
8585
@app.generic_trigger(
8686
arg_name="context",
8787
type="mcpToolTrigger",
88-
toolName="savesnippet",
88+
toolName="save_snippet",
8989
description="Save a snippet with a name.",
90-
toolProperties=tool_properties_save_snippets_json
91-
)
92-
@app.generic_output_binding(
93-
arg_name="file",
94-
type="blob",
95-
connection="AzureWebJobsStorage",
96-
path=_BLOB_PATH
90+
toolProperties=tool_properties_save_snippets_json,
9791
)
92+
@app.generic_output_binding(arg_name="file", type="blob", connection="AzureWebJobsStorage", path=_BLOB_PATH)
9893
def save_snippet(file: func.Out[str], context) -> str:
9994
content = json.loads(context)
10095
snippet_name_from_args = content["arguments"][_SNIPPET_NAME_PROPERTY_NAME]
@@ -105,7 +100,7 @@ def save_snippet(file: func.Out[str], context) -> str:
105100

106101
if not snippet_content_from_args:
107102
return "No snippet content provided"
108-
103+
109104
file.set(snippet_content_from_args)
110105
logging.info(f"Saved snippet: {snippet_content_from_args}")
111-
return f"Snippet '{snippet_content_from_args}' saved successfully"
106+
return f"Snippet '{snippet_content_from_args}' saved successfully"

0 commit comments

Comments
 (0)