If you already have a working server locally, the next question is how to deploy MCP server to Azure without accidentally choosing the wrong hosting model. For C# developers, the practical choice usually comes down to two paths: Azure Functions with the MCP extension, or Azure Container Apps running an ASP.NET Core MCP server over Streamable HTTP.
This is a MOFU/BOFU decision guide. We are not going to rebuild the first local MCP server from scratch, and we are not going deep on authentication setup. The goal is narrower: compare the two Azure hosting paths, show what the deployment shape looks like, and help you decide where an already-built C# MCP server belongs.
Verified on July 5, 2026, the latest stable Azure Functions MCP package I found on NuGet is Microsoft.Azure.Functions.Worker.Extensions.Mcp 1.5.1. For the official C# MCP SDK packages, ModelContextProtocol and ModelContextProtocol.AspNetCore are stable at 1.4.0, with a 2.0.0 preview also visible. Check NuGet before you publish, because MCP package versions move quickly.
Deploy MCP Server to Azure: The Decision in One Pass
Azure Functions gives you an MCP-specific extension. You expose tools, resources, and prompts using Functions triggers. The extension handles the remote MCP endpoint for you, and the Azure Functions MCP bindings docs document /runtime/webhooks/mcp as the Streamable HTTP endpoint for clients. If you want to deploy MCP server to Azure with minimal infrastructure and your tools map naturally to independent function invocations, this is a strong path.
Azure Container Apps gives you a more general container hosting model. You run an ASP.NET Core app that references ModelContextProtocol.AspNetCore, registers .WithHttpTransport(...), and maps app.MapMcp("/mcp"). If your server already uses ASP.NET Core middleware, richer routing, custom dependencies, or non-Functions hosting assumptions, Container Apps is usually the more direct path.
I would not frame either as universally better. Functions reduces MCP hosting ceremony. Container Apps preserves ASP.NET Core control. That tradeoff matters more than the label on the Azure service.
If your server calls downstream APIs, the production concerns are still familiar .NET concerns: timeouts, retries, logging, and observability. The patterns in HttpClient in C#: The Complete Guide for .NET Developers still apply once your MCP tool implementation leaves the process.
Path A: Deploy MCP Server to Azure with Azure Functions MCP Extension
The Azure Functions path is not just "host my ASP.NET Core MCP app in Functions." It is a different programming model. You install Microsoft.Azure.Functions.Worker.Extensions.Mcp, use the isolated worker model for C#, and define MCP capabilities through Functions trigger attributes.
The Azure Functions MCP bindings docs document three MCP binding actions: tool triggers, resource triggers, and prompt triggers. The tool trigger runs a function from an MCP tool call. The resource trigger exposes information for context. The prompt trigger exposes reusable prompts that clients can select. That maps nicely when your MCP server is mostly a set of callable operations and context providers.
Before you deploy MCP server to Azure this way, keep the requirements tight: the Azure Functions MCP bindings docs list C# support as isolated worker only, the package as Microsoft.Azure.Functions.Worker.Extensions.Mcp, local development as Azure Functions Core Tools 4.0.7030 or later, and the C# package baselines as Microsoft.Azure.Functions.Worker 2.1.0 or later plus Microsoft.Azure.Functions.Worker.Sdk 2.0.2 or later. The Streamable HTTP endpoint exposed by the extension is /runtime/webhooks/mcp.
That endpoint detail is important. With Functions, you do not map app.MapMcp("/mcp") yourself. The extension exposes the runtime webhook endpoint. Locally that is typically http://localhost:7071/runtime/webhooks/mcp. In Azure, your client points at https://<function-app-host>/runtime/webhooks/mcp.
Here is the shape of a C# tool trigger. This assumes you already know what your tool does and you are adapting it to the Functions MCP binding model:
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Extensions.Mcp;
using Microsoft.Extensions.Logging;
public sealed class DeploymentTools(ILogger<DeploymentTools> logger)
{
[Function(nameof(GetDeploymentReadiness))]
public string GetDeploymentReadiness(
[McpToolTrigger("get_deployment_readiness", "Checks whether an MCP server is ready for Azure deployment.")]
ToolInvocationContext context,
[McpToolProperty("serviceName", "The logical name of the MCP service.", isRequired: true)]
string serviceName,
[McpToolProperty("targetHost", "The Azure hosting option being evaluated.")]
string? targetHost)
{
logger.LogInformation("Checking deployment readiness for {ServiceName}.", serviceName);
var host = string.IsNullOrWhiteSpace(targetHost) ? "Azure" : targetHost;
return $"{serviceName} is ready for a {host} deployment review.";
}
}
The attributes are doing the MCP exposure work. [McpToolTrigger] names and describes the tool. [McpToolProperty] describes arguments that clients can pass. The function can still use the rest of the Functions ecosystem, including bindings, dependency injection, application settings, and Application Insights.
For resources and prompts, the programming model is similar. You are not building transport plumbing. You are exposing capabilities through trigger endpoints:
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Extensions.Mcp;
public sealed class DeploymentPrompts
{
[Function(nameof(AzureDeploymentChecklist))]
public string AzureDeploymentChecklist(
[McpPromptTrigger("azure_deployment_checklist", Description = "Creates a concise Azure deployment checklist for an MCP server.")]
PromptInvocationContext context,
[McpPromptArgument("hostingPath", "Functions or Container Apps.", isRequired: true)]
string hostingPath)
{
return $"Review configuration, secrets, logs, scaling, and client connectivity for {hostingPath}.";
}
}
Use this path when the extension model fits your server contract. For example, a snippet server, an internal lookup server, or a set of narrowly scoped operations can be represented very naturally as Functions. Microsoft Learn's Azure Functions MCP bindings also document the system key requirement for hosted function apps, unless you set the webhook authorization level differently. Treat that as part of your auth surface, not as your complete security design.
When you deploy MCP server to Azure with Functions, you also inherit Functions deployment practices. Microsoft Learn's quickstart uses azd templates and Flex Consumption for a custom remote MCP server. That is attractive for cost-sensitive workloads because you are paying for usage rather than keeping a web server warm all day. The tradeoff is cold start and less control over the HTTP hosting pipeline.
Path B: Deploy MCP Server to Azure with Azure Container Apps
The Container Apps path starts from a different assumption: you already have an ASP.NET Core MCP server, or you want your MCP server to be a normal web app. The C# SDK transport docs document ModelContextProtocol.AspNetCore for HTTP-based MCP servers and identify Streamable HTTP as the recommended transport for remote servers.
This path is the more direct one when you want to host MCP server Azure workloads that use ASP.NET Core middleware, custom health checks, richer startup configuration, or standard container build pipelines. Azure Container Apps is documented as a serverless platform for containerized applications, API endpoints, background processing, event-driven processing, and microservices. It can scale based on HTTP traffic, CPU or memory, events, or KEDA-supported scalers.
A minimal ASP.NET Core MCP server for Container Apps looks like this:
using ModelContextProtocol.Server;
using System.ComponentModel;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddMcpServer()
.WithHttpTransport(options =>
{
options.Stateless = true;
})
.WithToolsFromAssembly();
var app = builder.Build();
app.MapGet("/healthz", () => Results.Ok("healthy"));
app.MapMcp("/mcp");
app.Run();
[McpServerToolType]
public static class DeploymentStatusTools
{
[McpServerTool]
[Description("Returns the deployment status for a named service.")]
public static string GetStatus(
[Description("The service name to check.")] string serviceName) =>
$"{serviceName} is reachable from the remote MCP server.";
}
For most remote servers, I would start with options.Stateless = true. The SDK transport documentation says the HTTP transport uses stateful sessions by default, but recommends stateless mode for most servers that do not need server-to-client requests. That guidance lines up well with Container Apps horizontal scaling because stateless replicas do not need sticky sessions to find in-memory session state.
A simple Dockerfile for the ASP.NET Core path is ordinary .NET container work:
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish ./src/MyMcpServer/MyMcpServer.csproj -c Release -o /app/publish
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
WORKDIR /app
COPY --from=build /app/publish .
ENV ASPNETCORE_URLS=http://+:8080
EXPOSE 8080
ENTRYPOINT ["dotnet", "MyMcpServer.dll"]
The Azure side is also familiar. Push the image to a registry, then create a container app with HTTP ingress targeting the port your container listens on. The Container Apps ingress docs cover external and internal exposure, TLS termination, IP restrictions, authentication, traffic splitting, CORS, and session affinity.
For an external MCP endpoint, the deployment shape is roughly this:
az containerapp create
--name my-mcp-server
--resource-group rg-mcp-prod
--environment cae-mcp-prod
--image myregistry.azurecr.io/my-mcp-server:1.0.0
--ingress external
--target-port 8080
--min-replicas 0
--max-replicas 5
--scale-rule-name http-mcp
--scale-rule-type http
--scale-rule-http-concurrency 50
A remote MCP server Azure Container Apps deployment is still an HTTP service. Your client URL becomes something like https://<container-app-fqdn>/mcp, depending on your custom domain and ingress configuration.
The main implementation detail is to avoid assuming state you do not actually need. If your tools are stateless, use stateless MCP transport and let Container Apps scale replicas freely. If you genuinely need stateful sessions, Container Apps supports session affinity for HTTP ingress, but you should treat that as a conscious design choice. Sticky sessions can help route a client to the same replica, but they also couple client behavior to replica lifetime.
If your MCP server makes outbound calls, the guidance in IHttpClientFactory in .NET: Named Clients, Typed Clients, and DI Patterns becomes relevant quickly. Remote tools tend to fail in distributed ways: DNS, timeouts, throttling, auth, and dependency outages.
Comparing Azure Functions and Azure Container Apps for MCP
Here is where the deployment decision gets practical. To deploy MCP server to Azure confidently, compare the actual operating shape, not just the sample code.
Cost and scale-to-zero are close, but not identical. Functions on a consumption-style plan is a natural fit for sporadic tool calls. You get a function execution model and a remote MCP endpoint without managing a container. Container Apps can also scale to zero for many application types, and the Container Apps scaling docs list the default minimum replica count as 0. However, if you set minimum replicas to 1 to avoid cold starts, you are choosing availability over the lowest idle cost.
Complexity is different. Functions moves complexity into bindings and host configuration. Container Apps moves complexity into containers, registry, ingress, and ASP.NET Core hosting. If your team already ships containers daily, that may not feel complex at all. If your team is mostly serverless, Functions may be the lower-friction answer.
Session affinity is one of the biggest architectural differences. Functions is best when each tool call can stand alone, or when state lives in durable external services. Container Apps plus ASP.NET Core gives you the option of stateful Streamable HTTP sessions, but the C# SDK recommends stateless mode for most servers. If you enable stateful sessions, think hard about replica scaling, restarts, and whether client reconnection behavior is acceptable.
Auth surface also differs. Functions has its function/system key behavior on the MCP webhook endpoint, plus whatever identity-based layer you add. Container Apps has ingress-level options, application middleware, IP restrictions, and whatever auth you implement in ASP.NET Core. This article is not the authentication deep dive, but I would not deploy either path publicly without a deliberate access-control story.
Cold start is the final practical comparison. Functions can be excellent for low-cost bursty workloads, but cold starts are part of the model. Container Apps with min-replicas 0 can also cold start. Container Apps with min-replicas 1 can reduce that at the cost of idle billing. For interactive agent workflows, perceived latency matters because a model or user may be waiting on the tool call before continuing.
| Decision point | Azure Functions MCP Extension | Azure Container Apps |
|---|---|---|
| Best fit | Independent tools, resources, and prompts | ASP.NET Core MCP servers |
| Endpoint shape | /runtime/webhooks/mcp |
Your mapped route, commonly /mcp |
| Scaling model | Function app hosting plan behavior | Replica-based HTTP/KEDA scaling |
| State preference | Externalized state per invocation | Stateless HTTP unless state is intentional |
| Operational burden | Lower container burden, more binding model constraints | More hosting control, more container responsibility |
This table is not a scoring system. It is a forcing function. If most of your answers land in the left column, Functions is probably the lower-friction first deployment. If most land in the right column, Container Apps will usually preserve more of your existing architecture.
For production troubleshooting, keep logging and telemetry in the plan from the start. Logging in .NET: The Complete Developer's Guide is relevant whether the host is a function app or a containerized ASP.NET Core app.
When Azure Functions Wins for MCP Server Azure Deployments
Choose Azure Functions when your MCP server is mostly a collection of discrete tools, resources, and prompts. The extension is purpose-built for this shape. You write a function, add MCP trigger attributes, configure the function app, and let the Functions runtime expose the MCP endpoint.
This path is especially compelling when each operation is short-lived, you already use Functions bindings, you want a low-ceremony remote endpoint, and you are comfortable with the isolated worker model.
When Azure Container Apps Wins for Remote MCP Server Azure Container Apps Deployments
Choose Azure Container Apps when your remote MCP server is an ASP.NET Core service first. If you need middleware control, custom routing, health endpoints, sidecar-friendly deployment, container image promotion, or a path that looks like the rest of your platform services, Container Apps is a strong fit.
This path is especially compelling when your C# MCP server already uses ModelContextProtocol.AspNetCore, you want app.MapMcp("/mcp"), you need custom middleware or health checks, and your team already has container build and registry practices.
This is also where HTTP/3 in .NET 10: Enabling QUIC with HttpClient and ASP.NET Core can be useful background if you are already thinking deeply about HTTP hosting, even though MCP deployment itself should start with documented supported ingress behavior.
Deployment Checklist Before You Pick a Path
Before you deploy MCP server to Azure, answer a few questions in writing. They are more useful than debating the hosting service in the abstract.
First, is your server contract naturally function-shaped or web-app-shaped? A handful of independent operations points toward Functions. An ASP.NET Core app with middleware and custom hosting points toward Container Apps.
Second, does your MCP server need session state? If no, prefer stateless HTTP in the ASP.NET Core path and externalize durable state. If yes, identify exactly what state exists, how long it lives, and what happens when a replica restarts.
Third, who can call the server, and what latency is acceptable? Your deployment decision should include public versus internal exposure, IP restrictions, auth layers, and whether cold start is acceptable. For HTTP-heavy tools, HttpClient Logging and Observability in .NET 10 is directly relevant.
A Practical Recommendation
If I were starting from a small set of independent C# tools and wanted the fastest supported remote path, I would start with Azure Functions MCP Extension. The package name is explicit, the trigger model is documented, and the transport endpoint is handled by the extension. That is a good way to deploy MCP server to Azure without building extra hosting plumbing.
If I already had an ASP.NET Core MCP server, or I expected the service to grow into a richer web-hosted platform component, I would choose Azure Container Apps. It keeps the server close to the official ModelContextProtocol.AspNetCore guidance, uses Streamable HTTP directly, and lets me scale and secure it like other containerized APIs.
The important part is to avoid a muddled middle. Do not wrap an ASP.NET Core server in Functions just to say it is serverless. Do not containerize a few simple function-shaped tools just because containers feel more "production." Pick the shape that matches your server.
FAQ
How do I deploy MCP server to Azure if I already have an ASP.NET Core server?
Use Azure Container Apps when your server already uses ModelContextProtocol.AspNetCore. Containerize the app, expose the port through HTTP ingress, and point clients at the MapMcp route, commonly /mcp. Start with stateless HTTP transport unless your server truly needs server-to-client requests or in-memory session behavior.
Can Azure Functions host a C# MCP server directly?
Yes, but it uses the Azure Functions MCP extension model, not the ASP.NET Core MapMcp model. For C#, Microsoft Learn documents isolated worker support with Microsoft.Azure.Functions.Worker.Extensions.Mcp. You expose capabilities using tool, resource, and prompt triggers, and clients connect to /runtime/webhooks/mcp for Streamable HTTP.
Which path is cheaper for low-traffic MCP tools?
Azure Functions is often the simpler low-traffic answer because the function execution model fits sporadic independent tool calls. Azure Container Apps can also scale to zero for many apps, so it can be cost-effective too. The better cost answer depends on cold start tolerance, minimum replica settings, execution duration, and how much infrastructure your team already operates.
Should I use session affinity for an MCP server on Azure Container Apps?
Only if you intentionally run stateful Streamable HTTP sessions and understand the consequences. For most ASP.NET Core MCP servers, the C# SDK recommends setting Stateless = true, which avoids the need for sticky sessions and makes horizontal scaling easier. If you need state, consider whether it belongs in external storage instead of in a replica's memory.
Does the Azure Functions MCP extension use Streamable HTTP?
The Azure Functions MCP bindings docs list Streamable HTTP as the endpoint /runtime/webhooks/mcp for the Azure Functions MCP extension. The same docs also list an SSE endpoint for legacy clients, but newer protocol versions deprecate SSE. Unless a specific client requires legacy behavior, deploy MCP server to Azure using the Streamable HTTP endpoint.
Where should authentication fit into this decision?
Authentication should be part of the deployment plan, but it is a separate deep dive. Functions has function or system key behavior for the runtime webhook unless configured otherwise, and Container Apps has ingress and application-level options. In both cases, treat MCP tools as remote operations that need deliberate access control.
Conclusion: Pick the Azure Host That Matches Your MCP Shape
To deploy MCP server to Azure confidently, start from the shape of the server. Azure Functions is best when your C# MCP capabilities are discrete tools, resources, and prompts that fit the Functions trigger model. Azure Container Apps is best when your MCP server is an ASP.NET Core application that should stay containerized and use Streamable HTTP through ModelContextProtocol.AspNetCore.
Both paths can work. The decision is about control, complexity, cost, cold start, and state. Choose Functions for the MCP-specific serverless path. Choose Container Apps for the ASP.NET Core container path. Either way, keep auth, observability, and scaling behavior explicit before you invite clients to depend on the endpoint.

