Skip to content

Commit 9275970

Browse files
authored
Merge pull request #10 from ahmedmuhsin/feature/mcp-resource-and-metadata
Add McpWeatherApp sample, restructure repo with samples/ directory
2 parents 4cc9b91 + d8ffc07 commit 9275970

25 files changed

Lines changed: 3701 additions & 179 deletions

.gitignore

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ yarn-error.log*
8787
!.vscode/tasks.json
8888
!.vscode/launch.json
8989
!.vscode/extensions.json
90+
!.vscode/mcp.json
9091
*.code-workspace
9192

9293
# Local History for Visual Studio Code
@@ -118,8 +119,7 @@ buildNumber.properties
118119
*.tar.gz
119120
*.rar
120121
hs_err_pid*
122+
replay_pid*
121123

122-
# Azure Functions
123-
local.settings.json
124-
__azurite_db*
125-
__blobstorage__/
124+
# Vite build output
125+
**/app/dist/

.vscode/mcp.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"inputs": [
3+
{
4+
"type": "promptString",
5+
"id": "functionapp-name",
6+
"description": "Azure Functions App Name"
7+
}
8+
],
9+
"servers": {
10+
"remote-mcp-function": {
11+
"type": "http",
12+
"url": "https://${input:functionapp-name}.azurewebsites.net/runtime/webhooks/mcp"
13+
},
14+
"local-mcp-function": {
15+
"type": "http",
16+
"url": "http://localhost:7071/runtime/webhooks/mcp"
17+
}
18+
}
19+
}

README.md

Lines changed: 76 additions & 174 deletions
Original file line numberDiff line numberDiff line change
@@ -16,239 +16,141 @@ urlFragment: remote-mcp-functions-java
1616

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

19-
This quick-start shows how to build and deploy a **remote MCP server** with Azure Functions (Java).
20-
You can run it locally for debugging, then ship it to the cloud with `azd up` in minutes.
21-
The server is secured by design (system keys + HTTPS), supports OAuth via EasyAuth or API Management, and can be isolated inside a VNet.
19+
This is a quickstart template to easily build and deploy custom remote MCP servers to the cloud using Azure Functions. You can clone/restore/run on your local machine with debugging, and `azd up` to have it in the cloud in a couple minutes.
2220

23-
[Watch the overview video.](https://www.youtube.com/watch?v=U9DsLcP5vEk)
24-
---
21+
The MCP server is configured with [built-in authentication](https://learn.microsoft.com/en-us/azure/app-service/overview-authentication-authorization) using Microsoft Entra as the identity provider.
2522

26-
## Prerequisites
23+
You can also use [API Management](https://learn.microsoft.com/azure/api-management/secure-mcp-servers) to secure the server, as well as network isolation using VNET.
2724

28-
| Purpose | Tool | Notes |
29-
|---------|------|-------|
30-
| **Java build + run** | JDK 17 (or newer) | Java 8 runtime still works but JDK 17 is recommended. |
31-
| **Local Functions runtime** | [Azure Functions Core Tools v4](https://learn.microsoft.com/azure/azure-functions/functions-run-local)`4.0.7030` | Used by `mvn azure-functions:run`. |
32-
| **Provision & deploy** | [Azure Developer CLI (azd)](https://aka.ms/azd) | Simplifies end-to-end deployment. |
33-
| **IDE (optional)** | Visual Studio Code + [Azure Functions extension](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions) | One-click debug & log streaming. |
34-
| **Blob Storage emulator** | [Docker](https://www.docker.com/) to run **Azurite** | Needed only when running locally with `UseDevelopmentStorage=true`. |
25+
If you're looking for this sample in more languages check out the [.NET/C#](https://github.com/Azure-Samples/remote-mcp-functions-dotnet), [Node.js/TypeScript](https://github.com/Azure-Samples/remote-mcp-functions-typescript) and [Python](https://github.com/Azure-Samples/remote-mcp-functions-python) samples.
3526

36-
---
27+
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/Azure-Samples/remote-mcp-functions-java)
3728

38-
## Prepare your local environment
29+
## Samples
3930

40-
> **Why Azurite?**
41-
> The `SaveSnippet` / `GetSnippet` tools persist snippets in Azure Blob Storage.
42-
> Azurite emulates that storage account on your dev machine.
31+
| Sample | Description |
32+
|--------|-------------|
33+
| [**FunctionsMcpTool**](samples/FunctionsMcpTool/) | MCP tools with Azure Blob Storage - say hello, save and retrieve code snippets. |
34+
| [**McpWeatherApp**](samples/McpWeatherApp/) | MCP Apps - a weather tool with an interactive UI (MCP resource + metadata). |
4335

44-
```bash
45-
docker run -p 10000:10000 -p 10001:10001 -p 10002:10002 \
46-
mcr.microsoft.com/azure-storage/azurite
47-
````
36+
## Prerequisites
4837

49-
If you prefer the Azurite **VS Code extension**, run **“Azurite: Start”** from the command palette instead.
38+
+ [JDK 17](https://learn.microsoft.com/java/openjdk/download) (or newer)
39+
+ [Apache Maven](https://maven.apache.org/download.cgi)
40+
+ [Azure Functions Core Tools v4](https://learn.microsoft.com/azure/azure-functions/functions-run-local) >= `4.0.7030`
41+
+ [Azure Developer CLI](https://aka.ms/azd) (for deployment)
42+
+ [Visual Studio Code](https://code.visualstudio.com/) + [Azure Functions extension](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions) (recommended)
5043

51-
---
44+
## Run and test locally
5245

53-
## Run the MCP server locally
46+
### FunctionsMcpTool
5447

55-
```bash
56-
# 1 – Build the project
57-
mvn clean package
48+
> **Requires [Azurite](https://learn.microsoft.com/azure/storage/common/storage-use-azurite)** for local blob storage. Start it with `docker run -d -p 10000:10000 -p 10001:10001 -p 10002:10002 mcr.microsoft.com/azure-storage/azurite` or use the Azurite VS Code extension.
5849
59-
# 2 – Start the Functions host (via Maven wrapper)
50+
```
51+
cd samples/FunctionsMcpTool
52+
mvn clean package
6053
mvn azure-functions:run
6154
```
6255

63-
The SSE endpoint will be available at:
56+
Open **.vscode/mcp.json**, click **Start** above *local-mcp-function*, then try prompts in Copilot agent mode:
6457

6558
```
66-
http://127.0.0.1:7071/runtime/webhooks/mcp/sse
59+
Say Hello
60+
Save this snippet as snippet1
61+
Retrieve snippet1
6762
```
6863

69-
---
70-
71-
## Try the *local* MCP server
72-
73-
### A. GitHub Copilot in VS Code
74-
75-
1. **Add MCP Server** → choose **HTTP (Server-Sent Events)**.
76-
77-
2. URL:
78-
79-
```text
80-
http://127.0.0.1:7071/runtime/webhooks/mcp/sse
81-
```
64+
See [samples/FunctionsMcpTool/README.md](samples/FunctionsMcpTool/README.md) for full details including MCP Inspector setup.
8265

83-
3. Give it any server-ID you like and save to *User* or *Workspace* settings.
66+
### McpWeatherApp
8467

85-
4. **List MCP Servers****Start** your new server.
86-
87-
5. In Copilot Chat (agent-mode) try prompts such as:
88-
89-
```text
90-
Say Hello
91-
Save this snippet as snippet1
92-
Retrieve snippet1 and apply to MyFile.java
93-
```
94-
95-
6. When finished, **Stop** the server (command palette) and `Ctrl+C` the terminal running the function host.
96-
97-
### B. MCP Inspector
98-
99-
```bash
100-
# In a second terminal
101-
npx @modelcontextprotocol/inspector node build/index.js
102-
# If the Functions host isn’t running, start it:
103-
# mvn azure-functions:run
68+
```
69+
cd samples/McpWeatherApp/app
70+
npm install && npm run build
71+
cd ..
72+
mvn clean package
73+
mvn azure-functions:run
10474
```
10575

106-
* Open the Inspector UI (URL printed in the terminal).
107-
* Transport: **SSE**.
108-
* URL: `http://127.0.0.1:7071/runtime/webhooks/mcp/sse`**Connect**.
109-
* **List Tools** → pick one → **Run Tool**.
110-
111-
Stop both terminals with `Ctrl+C` when done.
76+
Open **.vscode/mcp.json**, click **Start** above *local-mcp-function*, then ask Copilot: *"What's the weather in Seattle?"*
11277

113-
---
78+
See [samples/McpWeatherApp/README.md](samples/McpWeatherApp/README.md) for details on MCP Apps architecture.
11479

115-
## Deploy to Azure (remote MCP)
80+
## Deploy to Azure
11681

117-
Want the function app inside a VNet *before* provisioning? Just set:
82+
### Step 1: Sign in to Azure
11883

119-
```bash
120-
azd env set VNET_ENABLED true
12184
```
122-
123-
Then provision + deploy in one step:
124-
125-
```bash
126-
azd up
85+
az login
86+
azd auth login
12787
```
12888

129-
---
130-
131-
## Connect clients to the *remote* MCP server
132-
133-
The hosted SSE endpoint will be:
89+
### Step 2: Create an environment and configure
13490

13591
```
136-
https://<FUNC_APP_NAME>.azurewebsites.net/runtime/webhooks/mcp/sse
92+
azd env new <environment-name>
13793
```
13894

139-
> **Key required**
140-
> Grab the **system key** named `mcp_extension` from the Azure Portal
141-
> or via CLI:
142-
> `az functionapp keys list --resource-group <rg> --name <func-app>`
143-
144-
### MCP Inspector
95+
Configure VS Code as an allowed client application to request access tokens from Microsoft Entra (applies to the FunctionsMcpTool service):
14596

14697
```
147-
https://<FUNC_APP_NAME>.azurewebsites.net/runtime/webhooks/mcp/sse?code=<mcp_extension_key>
98+
azd env set PRE_AUTHORIZED_CLIENT_IDS aebc6443-996d-45c2-90f0-388ff96faa56
14899
```
149100

150-
### VS Code – GitHub Copilot
101+
**Optional:** Enable VNet isolation:
151102

152-
Add a header in `mcp.json`:
153-
154-
```jsonc
155-
{
156-
"servers": {
157-
"remote-mcp-function": {
158-
"type": "sse",
159-
"url": "https://<FUNC_APP_NAME>.azurewebsites.net/runtime/webhooks/mcp/sse",
160-
"headers": {
161-
"x-functions-key": "<mcp_extension_key>"
162-
}
163-
},
164-
"local-mcp-function": {
165-
"type": "sse",
166-
"url": "http://127.0.0.1:7071/runtime/webhooks/mcp/sse"
167-
}
168-
}
169-
}
103+
```
104+
azd env set VNET_ENABLED true
170105
```
171106

172-
Start **remote-mcp-function** and chat as usual in Copilot.
173-
174-
---
107+
### Step 3: Deploy
175108

176-
## Redeploy code
109+
Deploy both apps:
177110

178-
```bash
179-
azd up # Safe to run repeatedly—always overwrites the app with the latest build
180111
```
181-
182-
## Clean up
183-
184-
```bash
185-
azd down
112+
azd up
186113
```
187114

188-
---
115+
Or deploy a specific app:
189116

190-
## Source Code Layout
117+
```
118+
# Deploy only the MCP Tool (with Entra auth)
119+
azd deploy api
191120
192-
| Tool | Path | Description |
193-
| ------------------------------ | -------------------------------------------------------------------------------------------- | ------------------------------------------- |
194-
| **HelloWorld** | [`src/main/java/com/function/HelloWorld.java`](./src/main/java/com/function/HelloWorld.java) | Logs an argument then prints “Hello World”. |
195-
| **SaveSnippets / GetSnippets** | [`src/main/java/com/function/Snippets.java`](./src/main/java/com/function/Snippets.java) | Saves / retrieves snippets to Blob Storage. |
121+
# Deploy only the Weather App (no auth required)
122+
azd deploy weather
123+
```
196124

197-
---
125+
### Step 4: Connect to the remote MCP server
198126

199-
## How It Works
127+
After deployment finishes, open **.vscode/mcp.json** and click **Start** above *remote-mcp-function*. You'll be prompted for `functionapp-name` (find it in your azd command output or `/.azure/*/.env` file). You'll also be prompted to authenticate with Microsoft - click **Allow** and login with your Azure subscription email.
200128

201-
<details>
202-
<summary>Click to expand</summary>
129+
> [!TIP]
130+
> Successful connect shows the number of tools the server has. You can see more details on the interactions between VS Code and server by clicking on **More... -> Show Output** above the server name.
203131
204-
### MCP Tool Trigger
132+
## Redeploy and clean up
205133

206-
```java
207-
@McpToolTrigger(
208-
toolName = "saveSnippets",
209-
description = "Saves a text snippet to your snippets collection.",
210-
toolProperties = SAVE_SNIPPET_ARGUMENTS // JSON schema string
211-
)
212-
```
134+
**Redeploy all:** Run `azd up` as many times as needed to deploy code updates.
213135

214-
* `toolProperties` defines the JSON payload expected from the client.
215-
* At runtime the JSON is passed to your function in `toolArguments`.
136+
**Redeploy one service:** Use `azd deploy api` or `azd deploy weather` to redeploy a specific app.
216137

217-
### Storage Bindings
138+
**Clean up:** Delete all Azure resources when done:
218139

219-
```java
220-
@BlobOutput(
221-
name = "snippetOut",
222-
path = "snippets/{mcptoolargs.snippetName}.json",
223-
dataType = "binary")
224140
```
225-
226-
* `SaveSnippets` writes to a blob; `GetSnippets` reads the same path.
227-
* The default storage account is referenced with `@StorageAccount("AzureWebJobsStorage")`.
228-
229-
### Required SDK + Extension Bundle
230-
231-
```xml
232-
<azure.functions.java.library.version>3.1.1-alpha</azure.functions.java.library.version>
233-
```
234-
235-
```jsonc
236-
// host.json
237-
{
238-
"extensionBundle": {
239-
"id": "Microsoft.Azure.Functions.ExtensionBundle.Experimental",
240-
"version": "[4.*, 5.0.0)"
241-
}
242-
}
141+
azd down
243142
```
244143

245-
</details>
144+
## Next Steps
246145

247-
---
146+
+ Add [API Management](https://github.com/Azure-Samples/remote-mcp-apim-functions-python) to your MCP server
147+
+ Enable VNET using `VNET_ENABLED=true` flag
148+
+ Learn more about [related MCP efforts from Microsoft](https://github.com/microsoft/mcp/tree/main/Resources)
248149

249-
## Next Steps
150+
## Troubleshooting
250151

251-
* Front your MCP server with **API Management** for fine-grained policies.
252-
* Add **EasyAuth** to use your favourite OAuth provider (including Entra ID).
253-
* Toggle VNet integration via `VNET_ENABLED=true` for network isolation.
254-
* Explore the [Model Context Protocol](https://github.com/modelcontextprotocol) ecosystem for more tools.
152+
| Problem | Solution |
153+
|---------|----------|
154+
| Connection refused | Ensure Azurite is running (`docker run -p 10000:10000 -p 10001:10001 -p 10002:10002 mcr.microsoft.com/azure-storage/azurite`) |
155+
| 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 |
156+
| `mvn clean package` fails | Ensure JDK 17+ is on your PATH (`java -version`) |

azure.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ infra:
88
path: ./infra
99
services:
1010
api:
11-
project: ./
11+
project: ./samples/FunctionsMcpTool/
12+
language: java
13+
host: function
14+
weather:
15+
project: ./samples/McpWeatherApp/
1216
language: java
1317
host: function

0 commit comments

Comments
 (0)