Skip to content

Commit 3aced0a

Browse files
Separate MCP app and Weather app (#40)
* separate mcp tool and weather app * update local settings * update readmes * update host.json for FunctionsMcpTool * update readmes; yaml file --------- Co-authored-by: lilyjma <jm4303@columbia.edu>
1 parent 3e94838 commit 3aced0a

27 files changed

+505
-2895
lines changed

.vscode/mcp.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
},
77
"local-mcp-function": {
88
"type": "http",
9-
"url": "http://0.0.0.0:7071/runtime/webhooks/mcp"
9+
"url": "http://localhost:7071/runtime/webhooks/mcp"
1010
}
1111
},
1212
"inputs": [

README.md

Lines changed: 63 additions & 359 deletions
Large diffs are not rendered by default.

azure.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ metadata:
55
template: remote-mcp-functions-python@1.0.1
66
services:
77
api:
8-
project: ./src/
8+
project: ./src/FunctionsMcpTool
99
language: python
10-
host: function
10+
host: function
215 KB
Loading

media/weather-ui.png

400 KB
Loading

src/FunctionsMcpTool/README.md

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# FunctionsMcpTool - MCP Server Sample
2+
3+
This Azure Functions app implements an MCP server that demonstrates various tool patterns. It includes sample tools for connectivity testing, code snippet management with Azure Blob Storage, and more. Additional tools will be added over time to showcase different MCP capabilities.
4+
5+
## Features
6+
7+
This MCP server currently provides the following tools:
8+
9+
- **hello_mcp**: A simple hello world tool for testing connectivity
10+
- **get_snippet**: Retrieve a saved code snippet by name from Azure Blob Storage
11+
- **save_snippet**: Save a code snippet with a name to Azure Blob Storage
12+
13+
More tools will be added to demonstrate additional MCP patterns and Azure Functions bindings.
14+
15+
## Prerequisites
16+
17+
- [Python](https://www.python.org/downloads/) version 3.13 or higher
18+
- [Azure Functions Core Tools](https://learn.microsoft.com/azure/azure-functions/functions-run-local?pivots=programming-language-python#install-the-azure-functions-core-tools) >= `4.8.0`
19+
- Azure Storage Emulator (Azurite) for local development
20+
21+
## Local Development
22+
23+
### 1. Start Azurite
24+
25+
An Azure Storage Emulator is needed to store snippets locally:
26+
27+
```shell
28+
docker run -p 10000:10000 -p 10001:10001 -p 10002:10002 \
29+
mcr.microsoft.com/azure-storage/azurite
30+
```
31+
32+
> **Note**: If using the Azurite VS Code extension, run `Azurite: Start` from the command palette.
33+
34+
### 2. Install Dependencies
35+
36+
From the `src/FunctionsMcpTool` directory, create and activate a virtual environment, then install dependencies:
37+
38+
```shell
39+
python3 -m venv .venv
40+
41+
# macOS/Linux
42+
source .venv/bin/activate
43+
44+
# Windows
45+
.venv\Scripts\activate
46+
47+
pip install -r requirements.txt
48+
```
49+
50+
### 3. Run the Function App
51+
52+
```shell
53+
func start
54+
```
55+
56+
## Using the MCP Server
57+
58+
### Connect from VS Code - GitHub Copilot
59+
60+
1. Open [.vscode/mcp.json](../../.vscode/mcp.json)
61+
2. Find the server called `local-mcp-function` and click **Start**. The server uses the endpoint: `http://localhost:7071/runtime/webhooks/mcp`
62+
3. In Copilot chat agent mode, try these prompts:
63+
- "Say Hello"
64+
- "Save this snippet as snippet1" (with code selected)
65+
- "Retrieve snippet1 and apply to newFile.py"
66+
67+
### Connect from MCP Inspector
68+
69+
1. Install and run MCP Inspector:
70+
```shell
71+
npx @modelcontextprotocol/inspector
72+
```
73+
2. Open the URL displayed (e.g., http://0.0.0.0:5173/#resources)
74+
3. Set transport type to `Streamable HTTP`
75+
4. Set URL to `http://0.0.0.0:7071/runtime/webhooks/mcp` and **Connect**
76+
5. **List Tools**, select a tool, and **Run Tool**
77+
78+
## Verify Local Storage
79+
80+
After saving snippets, verify they're stored in Azurite:
81+
82+
### Using Azure Storage Explorer
83+
84+
1. Open Azure Storage Explorer
85+
2. Navigate to **Emulator & Attached****Storage Accounts****(Emulator - Default Ports) (Key)**
86+
3. Go to **Blob Containers****snippets**
87+
4. View your saved snippet blobs
88+
89+
### Using Azure CLI
90+
91+
```shell
92+
# List blobs in the snippets container
93+
az storage blob list --container-name snippets --connection-string "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;"
94+
```
95+
96+
## How It Works
97+
98+
The function uses Azure Functions' first-class MCP decorators to expose tools:
99+
100+
```python
101+
@app.mcp_tool()
102+
def hello_mcp() -> str:
103+
"""Hello world."""
104+
return "Hello I am MCPTool!"
105+
106+
@app.mcp_tool()
107+
@app.mcp_tool_property(arg_name="snippetname", description="The name of the snippet.")
108+
@app.blob_input(arg_name="file", connection="AzureWebJobsStorage", path=_BLOB_PATH)
109+
def get_snippet(file: func.InputStream, snippetname: str) -> str:
110+
"""Retrieve a snippet by name from Azure Blob Storage."""
111+
# ... implementation
112+
113+
@app.mcp_tool()
114+
@app.mcp_tool_property(arg_name="snippetname", description="The name of the snippet.")
115+
@app.mcp_tool_property(arg_name="snippet", description="The content of the snippet.")
116+
@app.blob_output(arg_name="file", connection="AzureWebJobsStorage", path=_BLOB_PATH)
117+
def save_snippet(file: func.Out[str], snippetname: str, snippet: str) -> str:
118+
"""Save a snippet with a name to Azure Blob Storage."""
119+
# ... implementation
120+
```
121+
122+
The MCP decorators automatically:
123+
- Infer tool properties from function signatures and type hints
124+
- Handle JSON serialization
125+
- Expose the functions as MCP tools without manual configuration
126+
127+
## Deployment to Azure
128+
129+
See [Deploy to Azure for Remote MCP](../../README.md#deploy-to-azure-for-remote-mcp) for deployment instructions.
130+
131+
## Troubleshooting
132+
133+
## Troubleshooting
134+
135+
| Error | Solution |
136+
|---|---|
137+
| `AttributeError: 'FunctionApp' object has no attribute 'mcp_resource_trigger'` | Python 3.13 is required. Verify with `python3 --version`. Install via `brew install python@3.13` (macOS) or from [python.org](https://www.python.org/downloads/). Recreate your virtual environment with Python 3.13 after installing. |
138+
| Connection refused | Ensure Azurite is running (`docker run -p 10000:10000 -p 10001:10001 -p 10002:10002 mcr.microsoft.com/azure-storage/azurite`) |
139+
| API version not supported by Azurite | Pull the latest Azurite image (`docker pull mcr.microsoft.com/azure-storage/azurite`) then restart Azurite and the app |
140+
| Blob not found | Verify the snippet was saved successfully and the name matches exactly |
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import logging
2+
3+
import azure.functions as func
4+
5+
app = func.FunctionApp(http_auth_level=func.AuthLevel.FUNCTION)
6+
7+
# Constants for the Azure Blob Storage container, file, and blob path
8+
_SNIPPET_NAME_PROPERTY_NAME = "snippetname"
9+
_BLOB_PATH = "snippets/{mcptoolargs." + _SNIPPET_NAME_PROPERTY_NAME + "}.json"
10+
11+
12+
@app.mcp_tool()
13+
def hello_mcp() -> str:
14+
"""Hello world."""
15+
return "Hello I am MCPTool!"
16+
17+
18+
@app.mcp_tool()
19+
@app.mcp_tool_property(arg_name="snippetname", description="The name of the snippet.")
20+
@app.blob_input(arg_name="file", connection="AzureWebJobsStorage", path=_BLOB_PATH)
21+
def get_snippet(file: func.InputStream, snippetname: str) -> str:
22+
"""Retrieve a snippet by name from Azure Blob Storage."""
23+
snippet_content = file.read().decode("utf-8")
24+
logging.info(f"Retrieved snippet: {snippet_content}")
25+
return snippet_content
26+
27+
28+
@app.mcp_tool()
29+
@app.mcp_tool_property(arg_name="snippetname", description="The name of the snippet.")
30+
@app.mcp_tool_property(arg_name="snippet", description="The content of the snippet.")
31+
@app.blob_output(arg_name="file", connection="AzureWebJobsStorage", path=_BLOB_PATH)
32+
def save_snippet(file: func.Out[str], snippetname: str, snippet: str) -> str:
33+
"""Save a snippet with a name to Azure Blob Storage."""
34+
if not snippetname:
35+
return "No snippet name provided"
36+
37+
if not snippet:
38+
return "No snippet content provided"
39+
40+
file.set(snippet)
41+
logging.info(f"Saved snippet: {snippet}")
42+
return f"Snippet '{snippet}' saved successfully"

0 commit comments

Comments
 (0)