MCP - From Development to Azure Governance Series: Part 2 - Creating MCP Server with Azure Functions
In Part 1, you created a .NET-based MCP server called LunchTimeMCP that exposed tools over STDIO. In this part, you’ll take the same restaurant logic and host it in Azure Functions so that MCP clients can call it over HTTP as cloud-hosted tools.
This article focuses on:
- Adapting the
RestaurantServicedomain logic from Part 1 to use Azure Table Storage for persistent state. - Creating a .NET 10 isolated Azure Functions app.
- Using the Model Context Protocol (MCP) bindings for Azure Functions to expose tools.
- Running everything locally so an MCP client can call the Functions app.
By the end, you’ll have an MCP server with persistent storage that scales automatically. Note that authentication and authorization are not covered here — those will be addressed in a later part of the series.
Why Azure Functions for MCP-style tools?
Even though MCP is often demonstrated with local STDIO servers, many real-world use cases benefit from putting tool logic behind HTTP in the cloud. The Azure Functions MCP extension lets you expose Azure Functions as a remote MCP server that language models and agents can call directly:
- Serverless scaling: Only pay for executions and automatically handle bursts.
- Proximity to data/services: Keep MCP tools close to APIs, databases, and queues that already live in Azure.
- MCP-aware surface: MCP clients connect to a single
/runtime/webhooks/mcpendpoint and discover the tools you define with the MCP bindings. - Same .NET code, new host: You reuse the
RestaurantServiceas-is and only change the hosting + binding model.
In later parts of the series, you’ll look at deployment options and governance. For now, you’ll get the Function app running locally and callable from an MCP-capable client.
Starting Point: LunchTimeMCP Domain Logic
From Part 1, you already have the RestaurantService and related models that manage:
- A list of restaurants.
- Adding new restaurants.
- Picking a random restaurant and tracking visits.
In Part 1, the RestaurantService used in-memory lists, which is fine for a local STDIO server. For Azure Functions, you’ll update it to use Azure Table Storage so restaurant data persists across executions and scaling events.
To share domain logic between the console app from Part 1 and the new Functions app, you’ll introduce a shared class library called LunchTimeMCP.Core and copy RestaurantService.cs into it.
Start by creating a fresh solution folder that will host both the shared library and the Functions app:
| |
Now create the shared class library and register it in the solution:
| |
Copy RestaurantService.cs from the Part 1 console project into the Core library (replace <path-to-part1> with the actual path):
| |
Note:
RestaurantTools.csstays in the Part 1 console project. It contains STDIO-specific MCP tool definitions using theModelContextProtocolSDK, which don’t apply here. The Functions app will get its ownLunchTimeMcpTools.csthat uses the Azure Functions MCP bindings instead.
Next, add the Azure Data Tables SDK to the Core library:
| |
With the shared library in place, you’ll update RestaurantService in LunchTimeMCP.Core to use Azure Table Storage, giving the Functions app access to a persistent implementation.
Create the Azure Functions Project (Isolated .NET 10)
You’ll use the .NET isolated worker model, which aligns well with modern .NET and gives you full control over the hosting and DI pipeline.
From the solution folder (LunchTimeMcpSolution), run:
| |
Then add a reference to the shared Core library:
| |
Restore and build once to be sure everything compiles:
| |
Add Persistent Storage with Azure Table Storage
Since Azure Functions can scale to multiple instances and instances can be recycled at any time, storing restaurant data in memory won’t work reliably in production. Instead, you’ll use Azure Table Storage to persist the restaurant list and visit counts.
The beauty of this approach is that Azure Functions already uses Azure Storage for its own runtime needs (AzureWebJobsStorage), so you can reuse the same storage account without additional configuration.
Note: The
Azure.Data.Tablespackage was already added toLunchTimeMCP.Corein the previous step. No additional package installation is needed here.
Next, create a Table Storage entity model for restaurants. Add a new file RestaurantEntity.cs to LunchTimeMCP.Core (alongside RestaurantService.cs):
| |
Now update your RestaurantService to use Table Storage instead of in-memory lists. Here’s an updated implementation:
| |
This implementation:
- Uses
TableClientto interact with Azure Table Storage - Stores each restaurant as a
RestaurantEntitywith a uniqueRowKey - Automatically creates the table if it doesn’t exist
- Persists visit counts and timestamps across function executions
- Works consistently even when Azure Functions scales to multiple instances
Wire Up Program.cs with RestaurantService and MCP
In a .NET 10 isolated Functions app, Program.cs controls host configuration, dependency injection, and now also MCP tool metadata.
First, install the MCP extension NuGet package in the Functions project:
| |
Then replace the default Program.cs with something like this:
| |
The important parts are:
- Registering
TableServiceClientthat connects to the storage account specified in theAzureWebJobsStorageenvironment variable (the same storage Azure Functions already uses). - Registering
RestaurantServiceso your Functions can receive it via constructor injection.
Implement MCP Tool Triggers for Your LunchTime Tools
Instead of regular HTTP triggers, you’ll use the MCP tool trigger so that Azure Functions itself behaves as an MCP server. Each function becomes a tool endpoint that MCP clients can call.
Add a new class (for example, LunchTimeMcpTools.cs) to expose tools that map 1:1 to the ones from Part 1.
| |
A few notes about this implementation:
[McpToolTrigger]: Marks each function as an MCP tool endpoint with atoolNameand description that MCP clients see.[McpToolProperty]: Describes input properties (name, location, foodType) so the client knows what arguments to collect.- Return values: You return CLR objects and anonymous objects; the MCP extension serializes them to JSON for the client.
These functions are now first-class MCP tools hosted by Azure Functions, instead of plain HTTP endpoints you have to wire up manually.
Configure local.settings.json and host.json
For local development, make sure you have a local.settings.json similar to this (do not check secrets into source control):
| |
The AzureWebJobsStorage setting does double duty here:
- Azure Functions runtime uses it for internal state and triggers.
- Your
RestaurantServiceuses it to persist restaurant data in Table Storage.
For local testing, "UseDevelopmentStorage=true" points to Azurite, the local storage emulator. Make sure you have Azurite running before starting the Functions app:
| |
When you deploy to Azure, the AzureWebJobsStorage app setting will automatically point to a real Azure Storage account, and your restaurant data will persist there.
Next, configure MCP-specific settings in host.json so the Functions runtime knows how to describe your MCP server:
| |
Note: The
extensionBundleblock is not needed here. Extension bundles are an alternative way to add extensions without NuGet, but since you already referencedMicrosoft.Azure.Functions.Worker.Extensions.Mcpas a NuGet package, including it would be redundant and can cause conflicts.
The extensions.mcp section is optional but useful — it gives MCP clients human-readable instructions and a friendly server name.
Run the Azure Functions App Locally
From the Functions project folder, start the runtime:
| |
You should see your MCP tool functions listed in the output. Behind the scenes, the MCP extension also exposes a single MCP server endpoint at:
http://localhost:7071/runtime/webhooks/mcp(Streamable HTTP transport)
You don’t call this endpoint directly with curl—instead, MCP-aware clients use it to discover and invoke your tools.
Connecting an MCP Client to the Azure Functions MCP Server
With the MCP bindings in place, the Functions app itself is the MCP server. MCP clients just need to know how to reach it.
For example, to configure GitHub Copilot in Visual Studio Code, you could add a server entry like this in your mcp.json:
| |
When Copilot connects to this server, it discovers the tools you defined with [McpToolTrigger] (get_restaurants, add_restaurant, delete_restaurant, pick_random_restaurant) and can invoke them directly—no separate HTTP wiring or per-tool URLs required.
Where This Fits in the Series
At this point you have:
- A console-based MCP server using STDIO (Part 1).
- A serverless Azure Functions MCP server with persistent storage using Azure Table Storage (this part). Authentication and authorization are not yet covered.
Both expose similar capabilities:
- Get all restaurants.
- Add a new restaurant.
- Delete a restaurant.
- Pick a random restaurant and track visits.
The difference is about where and how the tools run:
- Local STDIO server – Great for development, local CLI tools, and quick experiments. Uses in-memory state that resets when the process stops.
- Azure Functions MCP server – Cloud-hosted, MCP-aware tools that MCP clients can call from anywhere. Uses Azure Table Storage for persistence across function executions, instance scaling, and cold starts.
The Table Storage integration ensures that:
- Restaurant data persists even when Azure Functions scales out or scales down to zero.
- Multiple concurrent function instances see a consistent view of the data.
- You can track visit history over time without losing state.
In future parts of the series, you’ll look at:
- Different hosting options for MCP-style tools across Azure.
- How to factor configuration and secrets when running in the cloud.
- Governance and monitoring once these tools are deployed broadly.
For now, you have a clean, serverless host for your LunchTime MCP logic running on Azure Functions, ready to be wired into your favorite MCP-capable client. Authentication and authorization are intentionally out of scope here — they’ll be covered in a later part of the series.
Check the PlayGoKids repository for this article demo.

Join the conversation! Share your thoughts and connect with other readers.