Blog Post

Apps on Azure Blog
6 MIN READ

Build AI agent tools using remote MCP with Azure Functions

MatthewHenderson's avatar
Apr 04, 2025

Model Context Protocol (MCP) is a way for apps to provide capabilities and context to a large language model. A key feature of MCP is the ability to define tools that AI agents can leverage to accomplish whatever tasks they’ve been given. MCP servers can be run locally, but remote MCP servers are important for sharing tools that work at cloud scale. 

Today, we’re pleased to share an early experimental preview of triggers and bindings that allow you to build tools using remote MCP with server-sent events (SSE) with Azure Functions. Azure Functions lets you author focused, event-driven logic that scales automatically in response to demand. You just write code reflecting unique requirements of your tools, and Functions will take care of the rest.

Remote MCP quickstarts for Azure Functions are now available in Python, Node.js (TypeScript), and .NET (C#). With a func start, you’ll have a local MCP server ready for testing. And with an azd up, you’ll easily have your own remote MCP server running in the cloud on our Flex Consumption plan. The app will then rapidly scale to meet demand, with controllable concurrency and built-in session handling. Your tools have key-based access control by default, but you easily add networking rules and identity-based access control to meet your security requirements. This is the easiest way to get started with remote MCP, and it’s even better together with other Azure services like API Management and Logic Apps.

Introducing the MCP tool trigger

You can declare functions that act as tools with the new preview MCP tool trigger. To configure a tool, you give it a name and description through the trigger, and then you define any properties it exposes. When an agent uses MCP, it first lists the tools, gathering this metadata. When the agent decides to invoke a tool, it populates the tool properties based on the operating context as part of its request. That’s when your MCP tool trigger will fire, and your function code will be run!

As an example, let’s consider some tools to extend the GitHub Copilot experience in VS Code. We can use the MCP tool trigger to add an experience around saving and retrieving code snippets. We have full samples for this scenario in C#, TypeScript and Python, but this post will just use C# examples. The following diagram shows the architecture for the solution:

Here’s the C# code to define the first tool, which saves code snippets into blob storage:

[Function(nameof(SaveSnippet))] [BlobOutput("snippets/{mcptoolargs.snippetname}.json")] public string SaveSnippet( [McpToolTrigger("savesnippet", "Saves a code snippet into your snippet collection.")] ToolInvocationContext context, [McpToolProperty("snippetname", "string", "The name of the snippet.")] string name, [McpToolProperty("snippet", "string", "The code snippet.")] string snippet) { return snippet; }

Tool properties are available as binding data, which you can see being used to set the path for the output blob. You can also use the McpToolProperty input binding to bring them directly into your function. Here, the snippet is being passed in by the agent, and we just pass that along to the blob output by returning it.

We’re also exploring property definition through service configuration. Now that we have a tool to save a snippet, we need its counterpart, a tool to retrieve a saved snippet. For that, we’ll first define the function, without the input bindings:

[Function(nameof(GetSnippet))] public object GetSnippet( [McpToolTrigger("getsnippets", "Gets code snippets from your snippet collection.")] ToolInvocationContext context, [BlobInput("snippets/{mcptoolargs.snippetname}.json")] string snippetContent) { return snippetContent; }

This function doesn’t need to bind to the tool properties at all, so it doesn’t declare McpToolProperty input bindings. Instead, we define the properties for “getsnippets” in Program.cs with these lines:

builder.EnableMcpToolMetadata(); builder.ConfigureMcpTool("getsnippets") .WithProperty("snippetname", "string", "The name of the snippet.");

Both approaches are valid, and we’ll be looking for feedback as we continue experimenting with this exciting new model!

Get started

To get started with this early version of the MCP tool trigger, you’ll need to include a package reference for your choice of .NET, Node, and Python. For each of these, you can also find a full, ready-to-run sample for the example snippet scenario.

StackPreview packageSample
.NETNuGetSnippet tool (C#)
Node

npm (requires experimental bundle reference)

Snippet tool (TypeScript)
PythonNo additional package (requires experimental bundle reference)Snippet tool (Python)

For Node and Python, your apps also need to reference a new experimental version of the extension bundle. Your host.json file should include the following bundle reference:

"extensionBundle": { "id": "Microsoft.Azure.Functions.ExtensionBundle.Experimental", "version": "[4.*, 5.0.0)" }

The samples are a great way to see it all come together quickly. Just clone the repo and run azd up to get your own MCP server with all the power of the Azure Functions platform behind it!

Using a tool in GitHub Copilot

Once you have tools written in Functions, you can run them locally or in the cloud. Try it out using the Azure Functions Core Tools locally (func start).

VS Code now has preview support for MCP servers . Update to latest, open the command palette, and run the MCP: Add Server command. Choose the “HTTP (server sent events)” option, and then enter http://localhost:7071/runtime/webhooks/mcp/sse, which is the server sent events endpoint exposed by your local app. Give your server a name, and save it to your workspace. Now, when you use GitHub Copilot in agent mode, your tools will be loaded, and you can give instructions that leverage the tool. Using the snippet example, you could say “save the selected code as a snippet named myfirstsnippet”. Try setting a breakpoint in your function and see it hit as you work with GitHub Copilot!

Note that if you want to instead connect to a deployed version of the app, you’ll need to obtain the mcp_extension system key for your app and add as a header in your mcp.json.  The following example uses an input and will prompt you to provide the key when you start the server from VS Code.

{ "inputs": [ { "type": "promptString", "id": "functions-mcp-extension-system-key", "description": "Azure Functions MCP Extension System Key", "password": true } ], "servers": { "my-mcp-server": { "type": "sse", "url": "https://<app-name>.azurewebsites.net/runtime/webhooks/mcp/sse", "headers": { "x-functions-key": "${input:functions-mcp-extension-system-key}" } } } }

Try using Application Insights Live Metrics in the Azure Portal to watch your app respond to sessions as you connect to it. You’ll see new instances coming up in response to the demand. The SSE connection remains active from the initial instance that fielded it, but any available instance can field subsequent message requests from your agent. Intelligent message routing coordinates those sessions for you, so the app can run at cloud scale.

Next steps

This initial preview supports tools, but there are other features of MCP, such as prompts, which will be added over time. MCP is an evolving standard, and we expect that there could be some breaking changes to the extension as we move through the preview and process community feedback, so please keep that in mind as you try things out. Although the extension can be deployed to the Azure Functions service, it doesn’t qualify for support plans yet.

As with the other Functions extensions, this will be an open-source project on GitHub. The repository isn’t ready yet, so until it is, the best place to provide feedback is https://github.com/Azure/Azure-Functions/issues. You can find additional samples and resources at https://aka.ms/mcp-remote, and we also have a video overview for C# and a video overview for TypeScript on Microsoft Azure Developers!

We’re so excited to see what kinds of tools the community can build using Functions. Please try out this early preview and let us know what you think!

 

 

Updated Apr 09, 2025
Version 4.0

3 Comments

  • nlucero's avatar
    nlucero
    Copper Contributor

    I haven't been able to figure out how to make a tool property "optional", in the sense that the invocation doesn't expect it and it gets populated by a default value. 

    My code looks something like the following, where, in a context outside of MCP, MyMcpTool runs correctly without supplying optionalInt:

            public async Task<List<string>> MyMcpTool(
                [McpToolTrigger("my tool", "my description)] ToolInvocationContext context,
                [McpToolProperty("my optional property", "integer", "description")] int? optionalInt)
            {
               if(optionalInt.HasValue) ...
               else ...
                ...
            }

    when I try to invoke the function without specifying an int for the property, I get an error. And, in tools like Postman's MCP inspector, this tool method is being reported as having a required integer parameter. How can I make it optional so that the MCP server doesn't complain when it's not provided and it doesn't show up as a required parameter in tool inspectors?

  • jigarchhadwa's avatar
    jigarchhadwa
    Copper Contributor

    Azure functions are generally stateless. How does this manages the state of the connection that is requured for MCP with SSE protocol?

    • Anirudh Garg's avatar
      Anirudh Garg
      Icon for Microsoft rankMicrosoft

      Thats a great question jigar. We have implemented backplane support - so that we maintain persistent connection across multiple instances using Azure Storage.. Using Azure queues we forward requests to the right instance based on the client.

OSZAR »