Hosting MCP Servers on Azure Functions: GA vs. Preview

The Model Context Protocol (MCP) has become the standard way AI agents and models interact with external systems. If you are building something on Azure and need to expose tools to an AI client, the question is no longer whether to use MCP; it is which hosting option to choose.

Azure Functions supports three distinct approaches. Two of them host MCP servers directly. The third uses message queues instead of MCP calls altogether. Each reflects a different set of trade-offs. This post maps them out so you can pick the right one before you write any code.

Why use Azure Functions for hosting MCP servers?

The doc makes the case concisely: Functions scales efficiently to handle demand and provides binding extensions that simplify AI integration. Both matter for MCP hosting specifically.

MCP servers are called on demand. An AI client sends a request, the server responds, and then goes quiet. That is a bursty, event-driven pattern. Flex Consumption and Elastic Premium both handle it well. Flex Consumption gives you scale-to-zero billing for bursty tool workloads. Elastic Premium gives you pre-warmed instances, predictable latency, and VNet integration for tools that need to reach internal APIs and databases.

Managed identity is the other key piece. Your MCP server likely needs to call downstream Azure services: blob storage, Cosmos DB, and Azure AI Search. With managed identity, you do not need to manage credentials inside your function code, and you do not need to pass secrets through your MCP configuration.

The three ways to host MCP servers on Azure Functions

Comparison matrix with three columns — MCP binding extension (teal, GA), self-hosted MCP SDK (amber, preview), and queue-based tool (coral, GA) and six feature rows: support level, programming model, stateful execution, transport requirement, implementation mechanism, and best-fit scenario.
The three Azure Functions MCP hosting options compared across six features. Amber cells flag preview constraints.

Option 1: MCP binding extension (GA)

This is the right default for most teams. The binding extension lets you build an MCP server using the standard Functions programming model triggers, bindings, local development with Core Tools, deployment via azd. You annotate a function with McpToolTrigger and it is automatically exposed as an MCP tool.

The comparison table from the Microsoft Learn documentation makes the feature set clear:

FeatureMCP binding extension
Support levelGA
Programming modelFunctions triggers and bindings
Stateful executionSupported
LanguagesC# (isolated), Python, TypeScript, JavaScript, Java
Other requirementsNone
ImplementationMCP binding extension

Stateful execution support is the binding extension’s meaningful advantage over the self-hosted SDK option. If your MCP server needs to maintain session state across tool calls, for example, a multi-turn data retrieval scenario, only the binding extension handles that today.

The quickstart template (remote-mcp-functions-dotnet) is a useful starting point. It scaffolds the project via azd init, runs locally with the Azurite storage emulator, and includes a .vscode/mcp.json file that wires the local endpoint directly into GitHub Copilot’s agent mode for testing. That local-to-Copilot loop is genuinely quick; you can test a tool call from Copilot chat without deploying anything.

Before you build: a toolchain issue to know about

There is a confirmed bug in Microsoft.Azure.Functions.Worker.Sdk (all versions to 2.0.7) where the auto-generated WorkerExtensions.csproj is hardcoded to net6.0. The MCP extension package requires net8.0, so the build fails out of the box with NU1202: Package is not compatible with net6.0. The Microsoft Learn quickstart does not mention this.

The workaround that actually works has three parts: add an extensionBundle to host.json so the host loads MCP support at runtime rather than through the build-time generator; mark all extension PackageReference entries with PrivateAssets="all" so their build targets don’t re-trigger the generator; and use 1.0.0-preview.3 rather than 1.0.0 or later preview versions, because the generated project still targets net6.0 and needs a package version it can restore against that framework. The companion repo has all three changes applied and a detailed explanation in the README.

The McpToolTrigger attribute is the core mechanism. Here is what the minimal C# version looks like from the quickstart:

[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!";
}

The trigger attribute registers the function as an MCP tool and handles the protocol negotiation. You write the tool logic; the extension handles the MCP wire format.

Use this when: you are building a new MCP server and do not have an existing codebase using MCP SDKs. It is the fastest path from zero to a deployed, governed MCP server on Azure.

Avoid it when: you already have an MCP server built with an official MCP SDK and want to lift it into Azure without rewriting it in the Functions model.


Option 2: Self-hosted MCP servers via official MCP SDKs (preview)

This option lets you take an existing MCP server built with the official MCP SDKs and host it on Azure Functions without rewriting it as a binding-extension-style function app. Functions acts as the hosting runtime; your MCP SDK code runs via custom handlers.

FeatureSelf-hosted MCP servers
Support levelPreview
Programming modelStandard MCP SDKs
Stateful executionNot currently supported
LanguagesC# (isolated), Python, TypeScript, JavaScript, Java
Other requirementsStreamable HTTP transport
ImplementationCustom handlers

Two constraints define whether this option is viable for you right now.

  • First: Streamable HTTP transport is required. The self-hosted option does not support Server-Sent Events (SSE) transport. Suppose your existing MCP server relies on SSE which many early implementations did you need to migrate to Streamable HTTP before hosting on Functions. That is a non-trivial change for an established codebase.
  • Second: stateful execution is not supported during preview. If your MCP server is stateless most tool servers are this is not a problem. But if you need session continuity across calls, stay with the binding extension.

The documentation also flags that configuration details for self-hosted MCP servers change during the preview period. That means operational overhead: you may need to adjust configuration as the feature evolves. Factor that into a production timeline.

The sample (Weather server) shows the self-hosted pattern working correctly for a stateless tool. It is a useful reference for understanding the custom handler wiring, but it does not represent a feature-complete production deployment yet.

Use this when: you have an existing MCP server built with official MCP SDKs, it uses Streamable HTTP transport, it is stateless, and you want to run it on Functions without adopting the binding extension model.

Avoid it when: you need stateful execution, you rely on SSE transport, or you are building from scratch. In those cases the binding extension is the better path.


Option 3: Queue-based Azure Functions tools

This is the option that gets overlooked because it does not follow the MCP pattern at all, and that is sometimes exactly what you want.

Instead of an AI agent calling your function via the MCP protocol, the agent sends a message to a queue (Service Bus or Azure Storage Queue), and a queue-triggered function picks it up asynchronously. Foundry provides Azure Functions-specific tooling for this pattern.

The Microsoft Learn documentation lists the scenarios where this is the right choice:

  • Reliable message delivery and processing
  • Decoupling between AI agents and function execution
  • Built-in retry and error handling
  • Integration with existing Azure messaging infrastructure

That last point is the one that often decides it in enterprise contexts. If your integration platform already uses Service Bus for order processing, claims workflows, or event-driven pipelines, the queue-based tool pattern slots into that infrastructure without adding a new protocol layer. Your AI agent becomes another message producer; your function is another consumer. Operations teams already know how to monitor and manage it.

The decoupling is also genuinely useful for long-running tool operations. An MCP call is synchronous from the agent’s perspective: it sends a request and waits for a response. A queue-based call lets the agent fire and move on; the function processes in the background and the result arrives via a separate callback or polling mechanism. For operations that take seconds or minutes document processing, batch retrieval, report generation that asymmetry matters.

Use this when: the tool operation is long-running, you need reliable delivery with built-in retry, you are integrating with existing Azure messaging infrastructure, or you want to decouple the agent from function execution timing.

Avoid it when: the agent needs a synchronous response immediately, or you are connecting to AI clients that expect the MCP protocol specifically.


How to choose

SituationOption
Building a new MCP server from scratchBinding extension
Need stateful tool executionBinding extension
Have an existing SDK-based MCP server (Streamable HTTP, stateless)Self-hosted SDK
Need async, decoupled, fault-tolerant tool executionQueue-based
Integrating with existing Service Bus infrastructureQueue-based
Building for GitHub Copilot or VS Code agent modeBinding extension
Production deadline in the next quarterBinding extension or queue-based (avoid preview)
I need pre-warmed instances and predictable latencyElastic Premium plan

The preview constraint is the most important practical point here. If you are building for production and your timeline does not allow for configuration changes mid-project, self-hosted MCP servers are not ready. The binding extension and queue-based options are both GA and stable.

APIM as the governance layer

Whichever option you choose, consider putting APIM in front of your MCP server endpoint. This gives you rate limiting, authentication policy, token quota management, and a single point for logging and monitoring across all MCP tool calls.

One practical caveat from real deployment experience: the Azure AI Foundry Agent Service SDK routes LLM calls directly to Azure OpenAI, bypassing APIM entirely. Tool calls to your MCP server do pass through APIM, but model calls do not unless you use the standard OpenAI SDK instead. The Citadel Platform series covers this in detail, including how to structure your APIM policies to handle the tool-call traffic that does flow through it.

What comes next

The next post goes deeper into the serverless agents runtime, the preview programming model that lets you define event-triggered agents as function apps, with .agent.md files, agents.config.yaml, and remote MCP server connections declared in mcp.json.

Up next: The Azure Functions Serverless Agents Runtime: What It Is and When to Use It