Skip to content

Commit bc365a5

Browse files
committed
removed APIM server
1 parent 09effe1 commit bc365a5

File tree

3 files changed

+256
-0
lines changed

3 files changed

+256
-0
lines changed

.vscode/mcp.json

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

README.md

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
# Host remote MCP servers built with official MCP SDKs on Azure Functions (early preview)
2+
3+
This repo contains instructions and sample for running MCP server built with the C# (.NET) MCP SDK on Azure Functions. The repo uses the weather sample server to demonstrate how this can be done. You can clone to run and test the server locally, follow by easy deploy with `azd up` to have it in the cloud in a few minutes.
4+
5+
**Watch the video overview**
6+
7+
<a href="https://www.youtube.com/watch?v=PAxBlQ9mFv8" target="_blank">
8+
<img src="./media/video-thumbnail.jpg" alt="Watch the video" width="500" />
9+
</a>
10+
11+
## Running MCP server as custom handler on Azure Functions
12+
13+
Recently Azure Functions released the [Functions MCP extension](https://techcommunity.microsoft.com/blog/appsonazureblog/build-ai-agent-tools-using-remote-mcp-with-azure-functions/4401059), allowing developers to build MCP servers using Functions programming model, which is essentially Function's event-driven framework, and host them remotely on the serverless platform.
14+
15+
For those who have already built servers with [Anthropic's MCP SDKs](https://github.com/modelcontextprotocol/servers?tab=readme-ov-file#model-context-protocol-servers), it's also possible to host the servers on Azure Functions by running them as _custom handlers_, which are lightweight web servers that receive events from the Functions host. They allow you to host your already-built MCP servers with no code changes and benefit from Function's bursty scale, serverless pricing model, and security features.
16+
17+
This repo focuses on the second hosting scenario:
18+
19+
<div align="center">
20+
<img src="./media/function_hosting.png" alt="Diagram showing hosting of Function app and custom handler apps." width="500">
21+
</div>
22+
23+
## Prerequisites
24+
25+
Ensure you have the following:
26+
27+
* [Azure subscription](https://azure.microsoft.com/free/dotnet/) (you can create one for free)
28+
* [Azure Developer CLI](https://learn.microsoft.com/azure/developer/azure-developer-cli/install-azd) v1.17.2 or above
29+
* [Azure Functions Core Tools](https://learn.microsoft.com/azure/azure-functions/functions-run-local?tabs=windows%2Cisolated-process%2Cnode-v4%2Cpython-v2%2Chttp-trigger%2Ccontainer-apps&pivots=programming-language-typescript) v4.5.0 or above
30+
* [Visual Studio Code](https://code.visualstudio.com/)
31+
* [Azure Functions extension on Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions)
32+
* [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0)
33+
34+
>[!NOTE]
35+
>This sample requires that you have permission to create a [Microsoft Entra app](https://docs.azure.cn/entra/fundamentals/what-is-entra) in the Azure subscription you use.
36+
37+
## If you already have an existing server...
38+
39+
>[!IMPORTANT]
40+
>Your server must be **stateless** and uses the **streamable-http** transport to be hosted remotely on Azure Functions today.
41+
42+
The following instructions will pull in artifacts required for local server testing and deployment. The most important are: `host.json`, `local.settings.json`, and `infra`. Azure Functions only requires the first two JSON files. The `infra` directory isn't a requirement, but it's handy for provisioning Azure resources.
43+
44+
It's unlikely that your project would have files and directory with the same names, but if it does, you'll need to rename them so they won't be overwritten.
45+
46+
Once you've done the necessary renaming, follow these steps:
47+
1. Inside the MCP server project, run `azd init --template self-hosted-mcp-scaffold-dotnet`.
48+
1. Answer the prompts
49+
- Continue initializing an app in '/your/mcp/project/folder'?: Select Yes.
50+
- Files present both locally and in template: Likely the only one is README, and you can keep the existing.
51+
- Enter a unique environment name: This will become the name of the resource group the server is deployed in.
52+
1. In `host.json`:
53+
- Ensure the executable path and arguments match your .NET project's compiled DLL
54+
- Ensure the `port` value is the same as the one used by the MCP server
55+
1. Follow instructions starting in the [Test the server locally](#test-the-server-locally) section.
56+
57+
You can find out more details about the template (coming soon).
58+
59+
## If you're starting from scratch...
60+
61+
Clone the repo and open the sample in Visual Studio Code
62+
63+
```shell
64+
git clone https://github.com/Azure-Samples/mcp-sdk-functions-hosting-dotnet.git
65+
```
66+
67+
## Test the server locally
68+
>[!NOTE]
69+
>Oct 31 2024 update: Running the server locally requires Azure Functions Core Tools v4.5.0, which is not yet released. Skip to [next section](#register-resource-provider-before-deploying) to prepare for deployment.
70+
71+
1. In the root directory, run `func start` to build the project and start the server locally
72+
1. Open _mcp.json_ (in the _.vscode_ directory)
73+
1. Start the server by selecting the _Start_ button above the **local-mcp-server**
74+
1. Click on the Copilot icon at the top to open chat (or `Ctrl+Command+I / Ctrl+Alt+I`), and then change to _Agent_ mode in the question window.
75+
1. Click the tools icon and make sure **local-mcp-server** is checked for Copilot to use in the chat:
76+
77+
<img src="./media/mcp-tools.png" width="200" alt="MCP tools list screenshot">
78+
1. Once the server displays the number of tools available, ask "What is the weather in NYC?" Copilot should call one of the weather tools to help answer this question.
79+
80+
## Register resource provider before deploying
81+
82+
Before deploying, you need to register the `Microsoft.App` resource provider:
83+
```shell
84+
az provider register --namespace 'Microsoft.App'
85+
```
86+
87+
Wait a few seconds for registration to complete. You can check status by using:
88+
```shell
89+
az provider show -n Microsoft.App
90+
```
91+
92+
## Deployment
93+
94+
1. This sample uses Visual Studio Code as the main client. Configure it as an allowed client application:
95+
```shell
96+
azd env set PRE_AUTHORIZED_CLIENT_IDS aebc6443-996d-45c2-90f0-388ff96faa56
97+
```
98+
99+
1. Specify a service management reference if required by your organization. If you're not a Microsoft employee and don't know that you need to set this, you can skip this step. However, if provisioning fails with an error about a missing service management reference, you may need to revisit this step. Microsoft employees using a Microsoft tenant must provide a service management reference (your Service Tree ID). Without this, you won't be able to create the Entra app registration, and provisioning will fail.
100+
```shell
101+
azd env set SERVICE_MANAGEMENT_REFERENCE <service-management-reference>
102+
```
103+
104+
1. Run `azd up` in the root directory. Then pick an Azure subcription to deploy resources to and select from the available regions.
105+
106+
When the deployment finishes, your terminal will display output similar to the following:
107+
108+
```shell
109+
(✓) Done: Resource group: rg-resource-group-name (12.061s)
110+
(✓) Done: App Service plan: plan-random-guid (6.748s)
111+
(✓) Done: Virtual Network: vnet-random-guid (8.566s)
112+
(✓) Done: Log Analytics workspace: log-random-guid (29.422s)
113+
(✓) Done: Storage account: strandomguid (34.527s)
114+
(✓) Done: Application Insights: appi-random-guid (8.625s)
115+
(✓) Done: Function App: func-mcp-random-guid (36.096s)
116+
(✓) Done: Private Endpoint: blob-private-endpoint (30.67s)
117+
118+
Deploying services (azd deploy)
119+
(✓) Done: Deploying service api
120+
- Endpoint: https://functionapp-name.azurewebsites.net/
121+
```
122+
123+
### Connect to server on Visual Studio Code
124+
125+
1. Open _mcp.json_ in the editor.
126+
1. Stop the local server by selecting the _Stop_ button above the **local-mcp-server**.
127+
1. Start the remote server by selecting the _Start_ button above the **remote-mcp-server**.
128+
1. Visual Studio Code will prompt you for the Function App domain. Copy it from either the terminal output or the Portal.
129+
1. Open Copilot in Agent mode and make sure **remote-mcp-server** is checked in the tool's list.
130+
1. VS Code should prompt you to authenticate to Microsoft. Click _Allow_, and then login into your Microsoft account (the one used to access Azure Portal).
131+
1. Ask Copilot "What is the weather in Seattle?". It should call one of the weather tools to help answer.
132+
133+
>[!TIP]
134+
>In addition to starting an MCP server in _mcp.json_, you can see output of a server by clicking _More..._ -> _Show Output_. The output provides useful information like why a connection might've failed.
135+
>
136+
>You can also click the gear icon to change log levels to "Traces" to get even more details on the interactions between the client (Visual Studio Code) and the server.
137+
>
138+
><img src="./media/log-level.png" width="200" alt="Log level screenshot">
139+
140+
### Redeployment
141+
142+
If you want to redeploy the server after making changes, there are different options:
143+
144+
1. Run `azd deploy`. (See azd command [reference](https://learn.microsoft.com/azure/developer/azure-developer-cli/reference).)
145+
1. Open command palette in Visual Studio Code (`Command+Shift+P/Cntrl+Shift+P`) and search for **Azure Functions: Deploy to Function App**. Then select the name of the function app to deploy to.
146+
147+
## Built-in server authorization with Easy Auth
148+
149+
The server app is configured with [Easy Auth](https://learn.microsoft.com/azure/app-service/overview-authentication-authorization), which implements the requirements of the [MCP authorization specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization#authorization-server-discovery), such as issuing 401 challenge and exposing a Protected Resource Metadata (PRM).
150+
151+
When the server is configured to use Easy Auth for MCP Server authorization, you should see the following sequence of events in the debug output from Visual Studio Code:
152+
153+
1. The editor sends an initialization request to the MCP server.
154+
1. The MCP server responds with an error indicating that authorization is required. The response includes a pointer to the protected resource metadata (PRM) for the application. The Easy Auth feature generates the PRM for an app built with this sample.
155+
1. The editor fetches the PRM and uses it to identify the authorization server.
156+
1. The editor attempts to obtain authorization server metadata (ASM) from a well-known endpoint on the authorization server.
157+
1. Microsoft Entra ID doesn't support ASM on the well-known endpoint, so the editor falls back to using the OpenID Connect metadata endpoint to obtain the ASM. It tries to discover this using by inserting the well-known endpoint before any other path information.
158+
1. The OpenID Connect specifications actually defined the well-known endpoint as being after path information, and that is where Microsoft Entra ID hosts it. So the editor tries again with that format.
159+
1. The editor successfully retrieves the ASM. It then can then use this information in conjunction with its own client ID to perform a login. At this point, the editor prompts you to sign in and consent to the application.
160+
1. Assuming you successfully sign in and consent, the editor completes the login. It repeats the intialization request to the MCP server, this time including an authorization token in the request. This reattempt isn't visible at the Debug output level, but you can see it in the Trace output level.
161+
1. The MCP server validates the token and responds with a successful response to the initialization request. The standard MCP flow continues from this point, ultimately resulting in discovery of the MCP tool defined in this sample.
162+
163+
### Support for other clients
164+
165+
Other than Visual Studio Code, agents in Azure AI Foundry can also connect to Function-hosted MCP servers that are configured with Easy Auth. Docs coming soon.
166+
167+
## Clean up resources
168+
169+
When you're done working with your server, you can use this command to delete the resources created on Azure and avoid incurring any further costs:
170+
171+
```shell
172+
azd down
173+
```
174+
175+
## Next steps
176+
177+
### Find this sample in other languages
178+
179+
| Language (Stack) | Repo Location |
180+
|------------------|---------------|
181+
| Python | [mcp-sdk-functions-hosting-python](https://github.com/Azure-Samples/mcp-sdk-functions-hosting-python) |
182+
| Node | [mcp-sdk-functions-hosting-node](https://github.com/Azure-Samples/mcp-sdk-functions-hosting-node) |
183+
184+
## Troubleshooting
185+
The following are some common issues that come up.
186+
187+
1. **InternalServerError: There was an unexpected InternalServerError. Please try again later.**
188+
189+
Check if you have registered the `Microsoft.App` resource provider:
190+
191+
```shell
192+
az provider show -n Microsoft.App
193+
```
194+
195+
If it's showing up as unregistered, register it:
196+
```shell
197+
az provider register --namespace 'Microsoft.App'
198+
```
199+
200+
Successful registration should show:
201+
```shell
202+
Namespace RegistrationPolicy RegistrationState
203+
------------- -------------------- -------------------
204+
Microsoft.App RegistrationRequired Registered
205+
```
206+
207+
Then run `azd up` again.
208+
209+
2. **Error: error executing step command 'deploy --all': getting target resource: resource not found: unable to find a resource tagged with 'azd-server-name: api'. Ensure the service resource is corrected tagged in your infrastructure configuration, and rerun provision**
210+
211+
This is a [known transient error](https://github.com/Azure/azure-dev/issues/5580). Try re-running `azd up`.
212+
213+
3. **Need admin approval. Visual Studio Code needs permission to access resources in your organization that only an admin can grant. Please ask an admin to grant permission to this app before you can use it.**
214+
215+
This means your Entra app hasn't authorized VS Code as a trusted client. To fix this issue, go to the Azure Portal and search for your Entra app ("MCP Authorization App") in the global search bar. Inside the Entra app resource, click on **Expose an API** in the left menu. Look for the **+ Add a client application** button. Click to add VS Code's Client ID `aebc6443-996d-45c2-90f0-388ff96faa56`, remembering to check the authorized scopes box and click **Add application**:
216+
217+
<img src="./media/add-vscode-id.png" width="400" alt="Add VS Code client ID screenshot">
218+
219+
4. **Connection state: Error Error sending message to {endpoint}: TypeError: fetch failed**
220+
221+
Ensure the Function app domain is correct when connecting to the server.
222+
223+
5. **Ensure you have the latest version of Azure Functions Core Tools installed.**
224+
225+
You need [version >=4.4.0](https://learn.microsoft.com/azure/azure-functions/functions-run-local?tabs=windows%2Cisolated-process%2Cnode-v4%2Cpython-v2%2Chttp-trigger%2Ccontainer-apps&pivots=programming-language-typescript). Check by running `func --version`.
226+
227+
6. **`.vscode/mcp.json` must be in the root for VS Code to detect MCP server registration**
228+
229+
If you don't see the _Start_ button above server registrations, it's likely because `.vscode/mcp.json` isn't located in the root of your workspace folder.
230+

local.settings.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"IsEncrypted": false,
3+
"Values": {
4+
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
5+
"AzureWebJobsFeatureFlags": "EnableMcpCustomHandlerPreview"
6+
}
7+
}

0 commit comments

Comments
 (0)