Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ Here are some specific requirements and pieces of context:
- This must build a working Azure Function project complete with code, Readme changes, and AZD bicep. The original repo is already in this state, so your job is to preserve it.
- If I ever ask you to run a tool (e.g. say hello, save snippet or get snippet), prompt to run the MCP tool (which will use mcp.json) and never try to get me to rerun this project or a process to run the tool first.
- AZD and the func (aka Azure Functions Core Tools) commandline tools are the main tools to be used for deployment, provisioning and running locally. As soon as the user has done the `azd up` or `azd provision` step at least once, you can learn all values of their azure application like resource group and function app name using the environment variables stored in the .azure folder. Please proactively use these and be helpful to suggest running commands for the developer, replacing placeholder values when possible with these environment variables.
- This particular project is dotnet-isolated (.NET 8) Azure Function in C#
- This particular project is dotnet-isolated (.NET 10) Azure Function in C#
- We prefer using Azure Functions bindings if they can work versus the Azure SDKs, but Azure SDKs are ok if there is no substitute.
11 changes: 1 addition & 10 deletions .vscode/mcp.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
{
"inputs": [
{
"type": "promptString",
"id": "functions-mcp-extension-system-key",
"description": "Azure Functions MCP Extension System Key",
"password": true
},
{
"type": "promptString",
"id": "functionapp-name",
Expand All @@ -15,10 +9,7 @@
"servers": {
"remote-mcp-function": {
"type": "http",
"url": "https://${input:functionapp-name}.azurewebsites.net/runtime/webhooks/mcp",
"headers": {
"x-functions-key": "${input:functions-mcp-extension-system-key}"
}
"url": "https://${input:functionapp-name}.azurewebsites.net/runtime/webhooks/mcp"
},
"local-mcp-function": {
"type": "http",
Expand Down
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"azureFunctions.deploySubpath": "src/bin/Release/net8.0/publish",
"azureFunctions.deploySubpath": "src/bin/Release/net10.0/publish",
"azureFunctions.projectLanguage": "C#",
"azureFunctions.projectRuntime": "~4",
"debug.internalConsoleOptions": "neverOpen",
Expand Down
2 changes: 1 addition & 1 deletion .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
"type": "func",
"dependsOn": "build (functions)",
"options": {
"cwd": "${workspaceFolder}/src/bin/Debug/net8.0"
"cwd": "${workspaceFolder}/src/bin/Debug/net10.0"
},
"command": "host start",
"isBackground": true,
Expand Down
110 changes: 57 additions & 53 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ urlFragment: remote-mcp-functions-dotnet

# Getting Started with Remote MCP Servers using Azure Functions (.NET/C#)

This is a quickstart template to easily build and deploy a custom remote MCP server 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. 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.
This is a quickstart template to easily build and deploy a custom remote MCP server 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.

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.

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.

**Watch the video overview**

Expand All @@ -31,7 +35,7 @@ If you're looking for this sample in more languages check out the [Node.js/TypeS
## Prerequisites

### Required for all development approaches:
+ [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0)
+ [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)
+ [Azure Functions Core Tools](https://learn.microsoft.com/azure/azure-functions/functions-run-local?pivots=programming-language-csharp#install-the-azure-functions-core-tools) >= `4.0.7030`
+ [Azure Developer CLI](https://aka.ms/azd) (for deployment)

Expand All @@ -51,14 +55,12 @@ Below is the architecture diagram for the Remote MCP Server using Azure Function

## Prepare your local environment

An Azure Storage Emulator is needed for this particular sample because we will save and get snippets from blob storage.

1. Start Azurite
An Azure Storage Emulator is needed for this particular sample because we will save and get snippets from blob storage. Start Azurite emulator:

```shell
docker run -p 10000:10000 -p 10001:10001 -p 10002:10002 \
mcr.microsoft.com/azure-storage/azurite
```
```shell
docker run -p 10000:10000 -p 10001:10001 -p 10002:10002 \
mcr.microsoft.com/azure-storage/azurite
```

>**Note** if you use Azurite coming from VS Code extension you need to run `Azurite: Start` now or you will see errors.

Expand All @@ -76,22 +78,19 @@ Choose your preferred development environment:
func start
```

Note by default this will use the webhooks route: `/runtime/webhooks/mcp`. Later we will use this in Azure to set the key on client/host calls: `/runtime/webhooks/mcp?code=<system_key>`

## Connect to your *local* MCP server from MCP client tools

Once your Azure Functions MCP server is running locally (via either Visual Studio Code or Visual Studio), you can connect to it from various MCP client tools:

### Using VS Code with GitHub Copilot

1. **Add MCP Server** from command palette and add URL to your running Function app's MCP endpoint:
1. Open **.vscode/mcp.json**. Find the server called _local-mcp-function_ and click **Start** above the name. The server is already set up with the running Function app's MCP endpoint:
```shell
http://localhost:7071/runtime/webhooks/mcp
```
> **Note**: If you're running from Visual Studio (not VS Code), use `http://localhost:7071/runtime/webhooks/mcp/sse` instead.

1. **List MCP Servers** from command palette and start the server
1. In Copilot chat agent mode enter a prompt to trigger the tool, e.g., select some code and enter this prompt
1. In Copilot chat **agent** mode enter a prompt to trigger the tool, e.g., select some code and enter this prompt

```plaintext
Say Hello
Expand All @@ -104,16 +103,17 @@ Once your Azure Functions MCP server is running locally (via either Visual Studi
```plaintext
Retrieve snippet1 and apply to NewFile.cs
```
1. When prompted to run the tool, consent by clicking **Continue**

1. When prompted to run the tool, consent by clicking **Continue**

1. When you're done, press Ctrl+C in the terminal window to stop the `func.exe` host process (or stop debugging in your IDE).

### Using MCP Inspector

1. In a **new terminal window**, install and run MCP Inspector

```shell
npx @modelcontextprotocol/inspector node build/index.js
npx @modelcontextprotocol/inspector
```

1. 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)
Expand All @@ -132,11 +132,14 @@ Once your Azure Functions MCP server is running locally (via either Visual Studi
- **Solution**: Ensure Azurite is running (`docker run -p 10000:10000 -p 10001:10001 -p 10002:10002 mcr.microsoft.com/azure-storage/azurite`)

**Problem**: Wrong URL (0.0.0.0 vs localhost)
- **Solution**: Use `http://0.0.0.0:7071/runtime/webhooks/mcp/sse` for VS Code, `http://localhost:7071/runtime/webhooks/mcp/sse` for Visual Studio
- **Solution**: Use `http://0.0.0.0:7071/runtime/webhooks/mcp` for VS Code, `http://localhost:7071/runtime/webhooks/mcp/sse` for Visual Studio

**Problem**: Visual Studio F5 doesn't work
- **Solution**: Ensure Azure development workload is installed and `FunctionsMcpTool` is set as startup project

**Problem**: The API version 2025-07-05 is not supported by Azurite
- **Solution**: Pull the latest Azurite image (`docker pull mcr.microsoft.com/azure-storage/azurite`) then restart Azurite and the app.

## Verify local blob storage in Azurite

After testing the snippet save functionality locally, you can verify that blobs are being stored correctly in your local Azurite storage emulator.
Expand Down Expand Up @@ -166,6 +169,28 @@ az storage blob download --container-name snippets --name <blob-name> --file <lo
This verification step ensures your MCP server is correctly interacting with the local storage emulator and that the blob storage functionality is working as expected before deploying to Azure.

## Deploy to Azure for Remote MCP
Stop the local server with `Ctrl+C`.

Switch back to the root directory `remote-mcp-functions-dotnet`.

Sign in to Azure and initialize [azd](https://aka.ms/azd):

```shell
az login
azd auth login
```

Create a new azd project environment:

```shell
azd env new <environment-name>
```

We'll test the remote server with Visual Studio Code, so configure it as an allowed client application to request access tokens from Microsoft Entra:

```shell
azd env set PRE_AUTHORIZED_CLIENT_IDS aebc6443-996d-45c2-90f0-388ff96faa56
```

Run this [azd](https://aka.ms/azd) command to provision the function app, with any required Azure resources, and deploy your code:

Expand All @@ -179,30 +204,16 @@ You can opt-in to a VNet being used in the sample. To do so, do this before `azd
azd env set VNET_ENABLED true
```

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.
### Connect to remote MCP server in Visual Studio Code - GitHub Copilot

## Connect to your *remote() MCP server function app from a client
Connect to the remote MCP server after deployment finishes. For GitHub Copilot within VS Code, use `https://<funcappname>.azurewebsites.net/runtime/webhooks/mcp` for the URL. Note a [mcp.json](.vscode/mcp.json) is already included in this repo and will be picked up by VS Code, so just click **Start** above _remote-mcp-function_ to be prompted for `functionapp-name` (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.

Your client will need a key in order to invoke the new hosted MCP endpoint, which will be of the form `https://<funcappname>.azurewebsites.net/runtime/webhooks/mcp`. 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`.

### Connect to remote MCP server in MCP Inspector
For MCP Inspector, you can include the key in the URL:
```plaintext
https://<funcappname>.azurewebsites.net/runtime/webhooks/mcp?code=<your-mcp-extension-system-key>
```

### Connect to remote MCP server in VS Code - GitHub Copilot
For GitHub Copilot within VS Code, you should instead set the key as the `x-functions-key` header in `mcp.json`, and you would just use `https://<funcappname>.azurewebsites.net/runtime/webhooks/mcp` for the URL. The following example uses an input and will prompt you to provide the key when you start the server from VS Code. Note [mcp.json]() has already been included in this repo and will be picked up by VS Code. Click Start on the server to be prompted for values including `functionapp-name` (in your /.azure/*/.env file) and `functions-mcp-extension-system-key` which can be obtained from CLI command above or API Keys in the portal for the Function App.
>[!TIP]
>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.

```json
{
"inputs": [
{
"type": "promptString",
"id": "functions-mcp-extension-system-key",
"description": "Azure Functions MCP Extension System Key",
"password": true
},
{
"type": "promptString",
"id": "functionapp-name",
Expand All @@ -213,20 +224,15 @@ For GitHub Copilot within VS Code, you should instead set the key as the `x-func
"remote-mcp-function": {
"type": "http",
"url": "https://${input:functionapp-name}.azurewebsites.net/runtime/webhooks/mcp",
"headers": {
"x-functions-key": "${input:functions-mcp-extension-system-key}"
}
},
"local-mcp-function": {
"type": "http",
"url": "http://0.0.0.0:7071/runtime/webhooks/mcp"
"url": "http://localhost:7071/runtime/webhooks/mcp"
}
}
}
```

Click Start on the server to be prompted for values including `functionapp-name` (in your /.azure/*/.env file) and `functions-mcp-extension-system-key` which can be obtained from CLI command above or API Keys in the portal for the Function App.

## Redeploy your code

You can run the `azd up` command as many times as you need to both provision your Azure resources and deploy code updates to your function app.
Expand All @@ -242,23 +248,13 @@ When you're done working with your function app and related resources, you can u
azd down
```


## Source Code

The function code for the `GetSnippet` and `SaveSnippet` endpoints are defined in [`SnippetsTool.cs`](./src/SnippetsTool.cs). The `McpToolsTrigger` attribute applied to the async `Run` method exposes the code function as an MCP Server.

This shows the code for a few MCP server examples (get string, get object, save object):
The following shows the code for a few MCP server examples (get string, get object, save object):

```csharp
[Function(nameof(SayHello))]
public string SayHello(
[McpToolTrigger(HelloToolName, HelloToolDescription)] ToolInvocationContext context
)
{
logger.LogInformation("Saying hello");
return "Hello I am MCP Tool!";
}

[Function(nameof(GetSnippet))]
public object GetSnippet(
[McpToolTrigger(GetSnippetToolName, GetSnippetToolDescription)] ToolInvocationContext context,
Expand All @@ -276,11 +272,19 @@ public string SaveSnippet(
{
return snippet;
}

[Function(nameof(SayHello))]
public string SayHello(
[McpToolTrigger(HelloToolName, HelloToolDescription)] ToolInvocationContext context
)
{
logger.LogInformation("C# MCP tool trigger function processed a request.");
return "Hello I am MCP Tool!";
}
```

## Next Steps

- Add [API Management](https://github.com/Azure-Samples/remote-mcp-apim-functions-python) to your MCP server
- Add [EasyAuth](https://learn.microsoft.com/en-us/azure/app-service/overview-authentication-authorization) to your MCP server
- Enable VNET using VNET_ENABLED=true flag
- Learn more about [related MCP efforts from Microsoft](https://github.com/microsoft/mcp/tree/main/Resources)
2 changes: 1 addition & 1 deletion architecture-diagram.drawio
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<mxPoint as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="11" value="Azure Function App (.NET 8 Isolated)" style="swimlane;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;startSize=30;fontSize=14;fontStyle=1;swimlaneFillColor=#f5f5f5;" parent="1" vertex="1">
<mxCell id="11" value="Azure Function App (.NET 10 Isolated)" style="swimlane;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;startSize=30;fontSize=14;fontStyle=1;swimlaneFillColor=#f5f5f5;" parent="1" vertex="1">
<mxGeometry x="360" y="110" width="470" height="340" as="geometry"/>
</mxCell>
<mxCell id="12" value="MCP Tools" style="swimlane;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;startSize=30;" parent="11" vertex="1">
Expand Down
Loading