Sunday, June 29, 2025

Build and deploy a remote MCP server with Azure Functions and C#

In this walkthrough, we will build an MCP Server based on Azure Funtions and C#. It will be tested locally, then deployed to Azure using the az tool. To avoid confusion, we will go through this journey step-by-step. Although the methods we implement are very basic, the same concepts can be extended to cover much more sophisticated solutions.

MCP, what is that?

The Model Context Protocol (MCP) is a specification created by Anthropic that makes it easier for AI applications to talk to tooling.

Why Remote MCP Servers?

Installing the same MCP server locally everywhere you need it is unrealistic. Making sure people on your team have the same version installed is a daunting task.

The solution is MCP servers that run remotely. As long as the endpoint supports server-side events (SSE), you are good to go.

Prerequisites

Getting Started

We will create a vanilla C# console application with:

mkdir RemoteMcpFunc
cd RemoteMcpFunc

 Open the console application in VS Code with:

code .

Click on the Azure tool on the left-side, then click on the "Azure Functions" tool and choose "Create New Project...".

On the "Create new project" panel, choose the already highlighted current directory:

Next, choose C#.

Choose the latest version of .NET, which at the time of writing is ".NET 9.0 Isolated".

We will keep it simple by choosing the "HTTP trigger" template.

It the "Create new HTTP trigger" panel, provide a name for the Azure Function class file. In my case I simply named it HttpMCP.

Next you will provide a namespace (like Mcp.Function).

Again, to keep it simple, we will choose Anonymous.

To make sure that our basic Azure Function works as expected, type this command in the root of your project:

func start

This should show you output similar to this:

If you point your browser to the given URL, you will experience a welcome message similar to this:

Hit CTRL C to stop the application.

Add MCP package

At this stage our Azure Functions application has no MCP capability. We will change this by adding the following package:

dotnet add package Microsoft.Azure.Functions.Worker.Extensions.Mcp --prerelease

NOTE: At this time, the above package is experimental and in pre-release mode.

Add MCP smarts

We will add three simple MCP server functions with these abilities:

  1. the classic Hello World
  2. reverse a message
  3. multiply two numbers

Add the following ToolDefinitions.cs class file:

public static class ToolDefinitions {
  public static class HelloWorldTool {
    public const string Name = "HelloWorldTool";
    public const string Description = "A simple tool that says: Hello World!";
  }


  public static class ReverseMessageTool {
    public const string Name = "ReverseMessageTool";
    public const string Description = "Echoes back message in reverse.";

    public static class Param {
      public const string Name = "Message";
      public const string Description = "The Message to reverse";
    }
  }

  public static class MultiplyNumbersTool {
    public const string Name = "MultiplyNumbersTool";
    public const string Description = "A tool that shows paramater usage by asking for two numbers and multiplying them together";

    public static class Param1 {
      public const string Name = "FirstNumber";
      public const string Description = "The first number to multiply";
    }

    public static class Param2 {
      public const string Name = "SecondNumber";
      public const string Description = "The second number to multiply";
    }
  }

  public static class DataTypes {
    public const string Number = "number";
    public const string String = "string";
  }
}

The above file contains tool names, descriptions and data types. There are three MCP tools that we will be creating:

Name Purpose Parameters
HelloWorldTool Simply displays 'Hello World'  
ReverseMessageTool Reverses text string
MultiplyNumbersTool Multiplies two numbers number,number

To keep things clean, we will create a separate class for each tool. 

HelloWorldMcpTool.cs

public class HelloWorldMcpTool {
  [Function("HelloWorldMcpTool")]
  public IActionResult Run(
    [McpToolTrigger(ToolDefinitions.HelloWorldTool.Name, ToolDefinitions.HelloWorldTool.Description)]
    ToolInvocationContext context
  ) {
      return new OkObjectResult($"Hi. I am {ToolDefinitions.HelloWorldTool.Name} and my message is 'HELLO WORLD!'");
  }
}

ReverseMessageMcpTool.cs

public class ReverseMessageMcpTool {
  [Function("ReverseMessageMcpTool")]
  public IActionResult Run(
    [McpToolTrigger(ToolDefinitions.ReverseMessageTool.Name, ToolDefinitions.ReverseMessageTool.Description)]
    ToolInvocationContext context,
    [McpToolProperty(ToolDefinitions.ReverseMessageTool.Param.Name, ToolDefinitions.DataTypes.String, ToolDefinitions.ReverseMessageTool.Param.Description)]
    string message
  ) {
    string reversedMessage = new string(message.ToCharArray().Reverse().ToArray());
    return new OkObjectResult($"Hi. I'm {ToolDefinitions.ReverseMessageTool.Name}!. The reversed message is: {reversedMessage}");
  }
}

MultiplyNumbersMcpTool.cs

public class MultiplyNumbersMcpTool {
  [Function("MultiplyNumbersMcpTool")]
  public IActionResult Run(
    [McpToolTrigger(ToolDefinitions.MultiplyNumbersTool.Name, ToolDefinitions.MultiplyNumbersTool.Description)]
    ToolInvocationContext context,
    [McpToolProperty(ToolDefinitions.MultiplyNumbersTool.Param1.Name, ToolDefinitions.DataTypes.Number, ToolDefinitions.MultiplyNumbersTool.Param1.Description)]
    int firstNumber,
    [McpToolProperty(ToolDefinitions.MultiplyNumbersTool.Param2.Name, ToolDefinitions.DataTypes.Number, ToolDefinitions.MultiplyNumbersTool.Param2.Description)]
    int secondNumber) {
    return new OkObjectResult($"Hi. I am {ToolDefinitions.MultiplyNumbersTool.Name}!. The result of {firstNumber} multiplied by {secondNumber} is: {firstNumber * secondNumber}");
  }
}

At this stage, we have created all the business logic for our three tools. What remains is configuring these tools in our Program.cs file. Add this code to Program.cs before: builder.Build().Run();

builder.EnableMcpToolMetadata();

builder.ConfigureMcpTool(ToolDefinitions.HelloWorldTool.Name);

builder.ConfigureMcpTool(ToolDefinitions.ReverseMessageTool.Name)
  .WithProperty(ToolDefinitions.ReverseMessageTool.Param.Name, ToolDefinitions.DataTypes.String, ToolDefinitions.ReverseMessageTool.Param.Description);

builder.ConfigureMcpTool(ToolDefinitions.MultiplyNumbersTool.Name)
  .WithProperty(ToolDefinitions.MultiplyNumbersTool.Param1.Name, ToolDefinitions.DataTypes.Number, ToolDefinitions.MultiplyNumbersTool.Param1.Description)
  .WithProperty(ToolDefinitions.MultiplyNumbersTool.Param2.Name, ToolDefinitions.DataTypes.Number, ToolDefinitions.MultiplyNumbersTool.Param2.Description);

In local.settings.json, make the following changes:

  1. Set the value of AzureWebJobsStorage to "UseDevelopmentStorage=true" for local development with the Azure emulator.
  2. Inside the "Values" section, add: "AzureWebJobsSecretStorageType": "Files". This is to use local file system for secrets instead of blob storage.

Contents of local.settings.json will look like this:

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
    "AzureWebJobsSecretStorageType": "Files"
  }
}

Testing our MCP server locally

Start your Azure Functions app with:

func start

Note the MCP server SSE endpoint which will be later used to configure the server in VS Code.

Open the VS Code Command Palatte.

Choose 'MCP: Add Server...".

Next, choose "HTTP (HTTP or Server-Sent Events)".

Paste the MCP server SSE endpoint. In my case it is http://localhost:7071/runtime/webhooks/mcp/sse.

Next, you will be asked to give your MCP Server a name. You can accept the defaut value.

Select "Workspace Settings" so that the configuration is saved in your project in the .vscode/mcp.json file.

The .vscode/mcp.json file is open in the editor.

{
  "servers": {
    "my-mcp-server-c5a639d4": {
      "url": "http://localhost:7071/runtime/webhooks/mcp/sse"
    }
  }
}

Click on Start to start the MCP server.

Open the Open the GitHub Copilot Chat panel in the top right of VS Code.

In the chat panel, choose Agent, any Claude model, then click on the tools icon.

Our three tools appear. This means that we can now use them.

Back in the chat window, try this prompt:

Call the HelloWorldTool.

The MCP HelloWorldTool is detected. Click on the Continue button.

This is the respose I received:

I also tried this prompt to test the reverse merssage tool.

Reverse the message: MCP is very cool

And, received this response:

Again, I tried this prompt to test the multiplication tool tool.

Multiply 100 and 89.

And, received this result:

Our Azure Functions MCP Server works well locally. The next challenge is to make it work remotely by deploying it to Azure.

Deploy to Azure

We will use the az Azure CLI Tool. Folow these steps.

# Login to Azure

az login

# Create a resource group named: rg-mcp-func-server

az group create --name rg-mcp-func-server --location eastus

# Create a storage account named mcpstoreacc in resource-group rg-mcp-func-server in the eastus data center

az storage account create --name mcpstoreacc --resource-group rg-mcp-func-server --location eastus --sku Standard_LRS

# Create a function app named mcp-func-app

az functionapp create --resource-group rg-mcp-func-server --consumption-plan-location eastus --runtime dotnet-isolated --functions-version 4 --name mcp-func-app --storage-account mcpstoreacc

# Deploy (or redeploy) function app named mcp-func-app

func azure functionapp publish mcp-func-app

Upon successful deployment, the endpoint will be displayed in your terminal window:

Login into your Azure account and search for the resource group to find the deployed Azure Function.

Click on the functions app. In the example above, the functions app is named mcp-func-app. Copy the Default Domain and add to it https://. In .vscode/mcp.json replace http://localhost:7071 with the 'Default Domain' from Azure. Leave /runtime/webhooks/mcp/sse in the URL. Your .vscode/mcp.json will be similar to this:

{
    "servers": {
        "my-mcp-server-c5a639d4": {
            //"url": "http://localhost:7071/runtime/webhooks/mcp/sse"
            "url": "https://mcp-func-app.azurewebsites.net/runtime/webhooks/mcp/sse"
        }
    }
}

We will need to get an mcp_extension key from Azure. You will find that by clicking on "App keys" on the left navigarion in Azure and copying the mcp_extension key.

In .vscode/mcp.json, add this code above “servers”:

"inputs": [
  {
    "type": "promptString",
    "id": "functions-mcp-extension-system-key",
    "description": "Azure Functions MCP Extension System Key",
    "password": true
  }
],

.vscode/mcp.json looks like this:

The above added JSON will prompt the user to enter the mcp-extension key.

Again, in .vscode/mcp.json, add this “headers” block under “url”:

"headers": {
  "x-functions-key": "${input:functions-mcp-extension-system-key}"
}

.vscode/mcp.json now looks like this:

When you start the server, you will be prompted for the MCP Extension System Key. Enter the mcp_extension key you obtained from Azure. Once the server is running again, you can start prompting it as before. The big difference this time is that it is running remotely, instead of on your local computer.

I entered this prompt:

Multiply 11 and 22 and provide the answer.

And received this result:

Conclusion

We have learned how to create MCP servers using Microsoft's Azure Functions technology with C#. We went through the development, testing, and deployment process. In general, this is a much more compelling solution for large scale implementations of MCP Servers. You can take these basic concepts and build bigger and better MCP Servers.

Tuesday, June 24, 2025

Deploying two .NET images to Azure Containers App service

We will create a .NET solution involving a Blazor WASM project that communicates with a WebAPI project through REST calls. We will then dockerize both projects and have them orchestrated through docker-compose for testing purposes. Finally, we will deploy both images to Azure Container Apps.

Source Code: https://github.com/medhatelmasry/BlazorApiACR

Prerequisites

  • .NET 9.0
  • Azure CLI
  • Visual Studio Code
  • Docker Desktop

Getting Started

We will run terminal window commands that will create the following:

  1. A solution folder named BlazorApiACR
  2. A Minimal API project named WeatherApiBackend
  3. A Blazor WASM project named BlazorFrontend

Run the following commands in a suitable working directory:

mkdir BlazorApiACR
cd BlazorApiACR
dotnet new sln
dotnet new webapi --no-https -o WeatherApiBackend
dotnet sln add WeatherApiBackend/WeatherApiBackend.csproj
dotnet new blazorwasm --no-https -o BlazorFrontend
dotnet sln add BlazorFrontend/BlazorFrontend.csproj

Enable CORS in the WebAPI project

In the WeatherApiBackend/Program.cs file, add this code right before “var app = builder.Build();”:


// Add CORS Service
builder.Services.AddCors(o => o.AddPolicy("CorsPolicy", builder =>
{
    builder
        .AllowAnyOrigin()
        .AllowAnyMethod()
        .AllowAnyHeader();
}));

Also, in WeatherApiBackend/Program.cs, add this code right after “var app = builder.Build();”:

app.UseCors("CorsPolicy");

Link Frontend to Backend

We must change the Weather Forecast endpoint so that it points to the WebAPI endpoint. 

  • View WeatherApiBackend/Properties/launchSettings.json and note the value of applicationUrl. Although I will be using http://localhost:5025, you must adjust the URL to your environment.
  • Edit BlazorFrontend/Program.cs and change this statement:

builder.Services.AddScoped(
    sp => new HttpClient
    {
        BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)
    }
);

TO

builder.Services.AddScoped(
    sp => new HttpClient
    {
        BaseAddress = new Uri("http://localhost:5025/")
    }
);

NOTE: Make sure you use the port number in your environment instead of 5085.

  • Edit BlazorFrontend/Pages/Weather.razor. Change:
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("sample-data/weather.json");

TO

forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");

Test Locally

Start the backend with:

cd WeatherApiBackend
dotnet run

Start the frontend with:

cd BlazorFrontend
dotnet watch

The Blazor app opens in your default browser. Click on the Weather tab.

Testing in an Orchestrated Docker Environment

Stop the apps in both the frontend and backend windows.

Create a text file WeatherApiBackend/Dockerfile and add to it this code for the backend WebAPI project:

# WeatherApiBackend/Dockerfile
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src
COPY ["WeatherApiBackend.csproj", "."]
RUN dotnet restore "WeatherApiBackend.csproj"
COPY . .
RUN dotnet build -c Release -o /app/build
RUN dotnet publish -c Release -o /app/publish /p:UseAppHost=false

FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS final
WORKDIR /app
COPY --from=build /app/publish .
ENV ASPNETCORE_URLS=http://+:80
EXPOSE 80/tcp
EXPOSE 443/tcp
ENTRYPOINT ["dotnet", "WeatherApiBackend.dll"]

# docker build -t weatherapi .
# docker run -p 8080:80 weatherapi

Create a text file BlazorFrontend/Dockerfile and add to it this code for the frontend Blazor WASM project:

# BlazorFrontend/Dockerfile
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src
COPY ["BlazorFrontend.csproj", "./"]
RUN dotnet restore "BlazorFrontend.csproj"
COPY . .
RUN dotnet build "BlazorFrontend.csproj" -c Release -o /app/build
RUN dotnet publish "BlazorFrontend.csproj" -c Release -o /app/publish

FROM nginx:alpine AS final
WORKDIR /var/www/web
COPY --from=build /app/publish/wwwroot .
COPY ./nginx.conf /etc/nginx/nginx.conf
EXPOSE 80

# docker build -t blazorfrontend .
# docker run -p 8080:80 blazorfrontend

The frontend will be hosted in the Docker image by the nginx web server. This requires some basic configuration. Therefore, add a text file BlazorFrontend/nginx.conf with this content:

events { }
   http {
      include mime.types;
      types {
         application/wasm wasm;
       }
     server {
        listen 80;
        index index.html;
        location / {
           root /var/www/web;
           try_files $uri $uri/ /index.html =404;
        }
     }
}

Inside of the BlazorApiACR solution folder, add a text file named docker-compose.yml with this code:

services:
  webapi:
    build:
      context: ./WeatherApiBackend
      dockerfile: Dockerfile
    ports:
      - "5025:80"
    restart: always
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
    networks:
      - blazor-network
  wasm:
    build:
      context: ./BlazorFrontend
      dockerfile: Dockerfile
    depends_on:
      - webapi
    ports:
      - 8888:80
    networks:
      - blazor-network

networks:
  blazor-network:
    driver: bridge

# docker compose up --build
# docker compose down

NOTE: In the docker-compose.yml, change the port 5025 to match the port number for the WebAPI backend in your environment. 

The docker-compose.yml will build Docker images for both the backend and frontend projects and orchestrate them to work together in a virtual network. 

Make sure you start Docker Desktop. You will then run this command from inside of the BlazorApiACR solution folder:

docker compose up --build

Wait until activity in the terminal window settles down. Thereafter, visit http://localhost:8888 in your browser. Thiw is what the Weather tab in the Blazor app will look like:

Prepare Deployment to Azure Container Apps

We will be using  The Azure CLI tool az. 

a. Login to Azure using the az tool:

az login

b. Create Azure Resource Group:

az group create --name <RESOURCE_GROUP> --location <LOCATION>

az group create --name blazor-api-acr-RG --location "East US"

c. Create Azure Container Registry (ACR):

az acr create --resource-group <RESOURCE_GROUP> --name <ACR_NAME> --sku Basic

az acr create --resource-group blazor-api-acr-RG --name blapiacr --sku Basic

d. Login to Azure Container Registry:

az acr login --name <ACR_NAME>

az acr login --name blapiacr

Build and Push Backend WebAPI Image

a. Build backend WebAPI image:

docker build --platform linux/amd64 -t <ACR_NAME>.azurecr.io/backend:latest ./backend

docker build --platform linux/amd64 -t blapiacr.azurecr.io/weatherapi:latest ./WeatherApiBackend

b. Push backend WebAPI image to ACR:

docker push <ACR_NAME>.azurecr.io/backend:latest

docker push blapiacr.azurecr.io/weatherapi:latest

c. Register required providers:

az provider register --namespace Microsoft.App

az provider register --namespace Microsoft.OperationalInsights

d. Create an environment for Container Apps:

az containerapp env create --name <ENV_NAME> --resource-group <RESOURCE_GROUP> --location <LOCATION>

az containerapp env create --name blapi-container-app-env --resource-group blazor-api-acr-RG --location "East US"

NOTE: This takes a while to process, so be patient.

e. Get admin credentials (username and password) to access the registry.

az acr update -n <ACR_NAME>--admin-enabled true

az acr update -n blapiacr --admin-enabled true

f. Deploy backend app (public):

az containerapp create \
  --name backend-app \
  --resource-group <RESOURCE_GROUP> \
  --environment <ENV_NAME> \
  --image <ACR_NAME>.azurecr.io/backend:latest \
  --target-port 4000 \
  --ingress external \
  --registry-server <ACR_NAME>.azurecr.io

az containerapp create \
  --name backend-app \
  --resource-group blazor-api-acr-RG \
  --environment blapi-container-app-env \
  --image blapiacr.azurecr.io/weatherapi:latest \
  --target-port 80 \
  --ingress external \
  --registry-server blapiacr.azurecr.io

g Find backend URL

az containerapp show \
    --name backend-app \
    --resource-group <RESOURCE_GROUP> \
    --query properties.configuration.ingress.fqdn  
 
az containerapp show \
    --name backend-app \
    --resource-group blazor-api-acr-RG  \
    --query properties.configuration.ingress.fqdn 

I got the following response for the WebAPI backend:

This meants that the deployed backend can be accessed at:

https://backend-app.nicebush-967940f0.eastus.azurecontainerapps.io/weatherforecast

I pointed by browser to the above URL and experienced this response:


We now know that our WebAPI is deployed to Azure Container Apps and is alive and well. The next step is to modify our frontend app BaseAddress with the address of the backend.

Build and Push Frontend Blazor Image

a. Edit BlazorFrontend/Program.cs and and update the BaseAddress with the address of the deployed WebAPI backend. In my case I changed the statement to:

BaseAddress = new Uri("https://backend-app.nicebush-967940f0.eastus.azurecontainerapps.io/")

b. Build frontend Blazor image:

docker build - --platform linux/amd64 t <ACR_NAME>.azurecr.io/frontend:latest ./frontend

docker build --platform linux/amd64 -t blapiacr.azurecr.io/blazorclient:latest ./BlazorFrontend

c. Push frontend Blazor image to ACR:

docker push <ACR_NAME>.azurecr.io/frontend:latest

docker push blapiacr.azurecr.io/blazorclient:latest

d. Deploy frontend Blazor app (public):

az containerapp create \
  --name frontend-app \
  --resource-group <RESOURCE_GROUP> \
  --environment <ENV_NAME> \
  --image <ACR_NAME>.azurecr.io/frontend:latest \
  --target-port 80 \
  --ingress external \
  --registry-server <ACR_NAME>.azurecr.io

az containerapp create \
  --name frontend-app \
  --resource-group blazor-api-acr-RG \
  --environment blapi-container-app-env \
  --image blapiacr.azurecr.io/blazorclient:latest \
  --target-port 80 \
  --ingress external \
  --registry-server blapiacr.azurecr.io

e. Get the frontend app’s public URL:

az containerapp show --name frontend-app --resource-group <RESOURCE_GROUP> --query properties.configuration.ingress.fqdn

az containerapp show \
    --name frontend-app \
    --resource-group blazor-api-acr-RG \
    --query properties.configuration.ingress.fqdn

I got the following response for the Blazor frontend:

This meants that the deployed frontendcan be accessed at:

https://frontend-app.nicebush-967940f0.eastus.azurecontainerapps.io

I pointed by browser to the above URL and experienced this response:


Conclusion

In this article we learned how we can deploy a two project solution compring of a WebAPI backend and a Blazor frontend to Azure Container Apps. You can, similarly, deploy and combination of apps with a variety of technolopgies like Node, React, Java, etc....


Friday, June 20, 2025

MCP server that connects VS Code to SQLite Database

In this tutorial we will configure VS Code to use an MCP Server that connects to a SQLite database. In a similar manner, you can also connect to PostgreSQL, MySQL, and SQL Server. This is a very compelling proposition because it allows developers to use AI to assist in generating code that dynamically interacts with data in a relational database.

Prerequisites

You will need to install the following software to proceed:

  • Visual Studio Code with the GitHub Copilot Chat extension
  • Docker Desktop
  • Latest versions of node.js, npm, and npx

The Database MCP Server

We will be using the MCP Server from the mcp-database-server GitHub Repo. Visit https://github.com/executeautomation/mcp-database-server for more details

Install and configure the SQLite MCP server

In a suitable working directory, clone the repo, then build, and publish the code by executing these commands in a terminal window:

git clone https://github.com/executeautomation/mcp-database-server.git
cd mcp-database-server
npm install
npm run build

We will next install the MCP server globally with:

npm install -g @executeautomation/database-server

We will run an SQLite using a sample Northwind database.

To use the MCP server with our SQLite database, run the following terminal window command:

node dist/src/index.js ./northwind-sqlite.db

Keep the above terminal window open and running.

Configuring VS Code

Open VS Code. Click on the settings gear in the bottom-left corner, followed by Settings.

In the search field, enter MCP, then click on "Edit in settings.json".

Under the mcp >> servers section, add the following MCP server settings:

"sqlite": {
  "command": "npx",
  "args": [
    "-y",
    "@executeautomation/database-server",
    "/path/to/your/northwind-sqlite.db"
  ]
},

Click on Start:

Open the GitHub Copilot Chat panel:

In the GitHub Copilot Chat panel, choose any Claude model followed by Agent Mode.

Click on the tools icon in the prompt window.

You will see a list of commands that the MCP server can carry out with SQLite.

We can now start querying the database using natural language. Start with this prompt:


You have access to the Northwind database through an MCP server. What are the tables in the database?

It detects that it can use the list_tables command.

Click on Continue. I got the following output:

Similarly, you can ask another question like: 

Display the contents of the suppliers table.

Yet, another question:

What are the products supplied by "Exotic Liquids"?

Conclusion

It is very easy to connect VS Code with a relational database MCP server. In addition, you can similarly connect MCP Servers any client C# application. MCP Servers open a ton of possibilities for AI aided software development.