logo
Free, unlimited AI code reviews that run on commit
git-lrc git-lrc GitHub Install Now We'd appreciate a star git-lrc - Free, unlimited AI code reviews that run on commit | Product Hunt git-lrc - Free, unlimited AI code reviews that run on commit | Product Hunt

typescript-remote-mcp-host-azure-functions

A solution for deploying a custom Model Context Protocol (MCP) backend service utilizing Azure Functions, written in TypeScript. It incorporates robust security features like OAuth integration via EasyAuth and network segmentation using VNETs, facilitating streamlined cloud deployment and management.

Author

typescript-remote-mcp-host-azure-functions logo

Azure-Samples

MIT License

Quick Info

GitHub GitHub Stars 41
NPM Weekly Downloads 0
Tools 1
Last Updated 2026-02-19

Tags

azureapisoauthazure functionsrequests azureusing azure

Establishing a Remote MCP Endpoint with Azure Functions (TypeScript/Node.js)

This quickstart template simplifies the construction and provisioning of a bespoke remote MCP service leveraging Azure Functions infrastructure. You can easily clone, restore, and debug this solution locally, followed by deployment to the cloud within minutes using azd up. The MCP endpoint inherently prioritizes security through key-based authentication and HTTPS, while offering extended capabilities for user authorization via Azure EasyAuth or API Management, alongside network confinement through Virtual Networks (VNETs).

View the solution overview video here

For implementations in other programming languages, refer to the .NET/C# and Python variants of this sample.

Open in GitHub Codespaces

The architectural blueprint for the Azure Functions-based Remote MCP Service is illustrated below:

Necessary Prerequisites

Local Environment Setup

This specific sample mandates an Azure Storage Emulator for persistent storage and retrieval of snippets within blob storage.

  1. Initiate Azurite

    shell docker run -p 10000:10000 -p 10001:10001 -p 10002:10002 \ mcr.microsoft.com/azure-storage/azurite

Observation If Azurite is managed by the VS Code extension, ensure you execute the Azurite: Start command now to circumvent potential errors.

Executing the MCP Host Locally via Terminal

  1. Install Project Dependencies shell npm install

  2. Compile the Project shell npm run build

  3. Launch the Functions Host Locally: shell func start

Note By default, the system employs the Server-Sent Events (SSE) route: /runtime/webhooks/mcp/sse. This route will subsequently require the system key when invoked by the client/host in Azure: /runtime/webhooks/mcp/sse?code=<system_key>

Interfacing with the Local MCP Host from a Client/Host

VS Code - Copilot Editing Integration

  1. Access the command palette, select Add MCP Server, and input the SSE endpoint URL of your active Function app instance: shell http://0.0.0.0:7071/runtime/webhooks/mcp/sse

  2. Designate HTTP (Server-Sent-Events) as the type for the MCP host to be added.

  3. Provide the URL pointing to your operational function app's SSE endpoint.
  4. Assign a unique identifier (Server ID) to this host.
  5. Decide whether to persist this configuration in your global User settings (available universally) or scoped to the current Workspace settings (app-specific).
  6. From the command palette, select List MCP Servers and initiate the connection to the host. (If the previous step automatically started the local host, you may skip this command).
  7. In the Copilot chat interface, submit a query designed to invoke the tool, for instance:

    plaintext Say Hello

    plaintext Save this snippet as snippet1

    plaintext Retrieve snippet1 and apply to newFile.ts

  8. When prompted to execute the tool, affirm by clicking Continue.

  9. Conclude operations by pressing Ctrl+C in the terminal to halt the func.exe process, and utilize the command palette again to List MCP Servers and terminate the local connection.

MCP Inspector Utility

  1. In a new terminal instance, install and execute the MCP Inspector utility:

    shell npx @modelcontextprotocol/inspector node build/index.js

  2. If the function app was previously shut down, restart the Functions host locally:

shell func start

  1. Use Ctrl-click to open the MCP Inspector web interface from the displayed URL (e.g., http://0.0.0.0:5173/#resources).
  2. Set the communication protocol transport type to SSE.
  3. Configure the endpoint URL to your active function app's SSE path and click Connect: shell http://0.0.0.0:7071/runtime/webhooks/mcp/sse

  4. Invoke List Tools. Select a desired tool and click Run Tool.

  5. To finalize, press Ctrl+C in the terminal running the func.exe host and again in the terminal running the @modelcontextprotocol/inspector process.

Validating Local Blob Storage Persistence in Azurite

Following successful local testing of the snippet saving feature, confirm that data is correctly persisted in your local Azurite storage emulator.

Using Azure Storage Explorer

  1. Launch Azure Storage Explorer.
  2. In the left navigation pane, expand Emulator & AttachedStorage Accounts(Emulator - Default Ports) (Key).
  3. Navigate to Blob Containerssnippets.
  4. All saved snippets should appear as individual blob files within this container.
  5. Double-click any blob file to inspect its contents and verify the integrity of the saved snippet data.

Using Azure CLI (Alternative Method)

For command-line verification, use the Azure CLI configured for the storage emulator:

shell

Command to enumerate blobs in the snippets container

az storage blob list --container-name snippets --connection-string "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;"

shell

Command to retrieve a specific blob for content inspection

az storage blob download --container-name snippets --name --file --connection-string "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;"

This validation step confirms that the MCP host is successfully interfacing with the local storage emulator, ensuring blob storage functionality is sound prior to cloud deployment.

Deployment to Azure for Remote MCP Service

You have the option to enable VNet integration in this sample. (If desired, configure this setting before executing azd up)

bash azd env set VNET_ENABLED true

Execute this azd command to orchestrate the provisioning of the necessary Azure resources (including the function app) and deploy the codebase:

shell azd up

Insight For enhanced security, API Management can proxy your MCP Server, applying policies. Furthermore, App Service's integrated authentication supports configuration with your preferred OAuth identity provider, such as Entra ID.

Connecting to the Remote MCP Host Function App from a Client

Clients must possess an authorization key to invoke the newly hosted SSE endpoint, which will resemble https://<funcappname>.azurewebsites.net/runtime/webhooks/mcp/sse. By default, the hosted function requires a system key, obtainable via the Azure Portal or the CLI (az functionapp keys list --resource-group <resource_group> --name <function_app_name>). Specifically, retrieve the system key named mcp_extension.

Connecting via MCP Inspector to the Remote MCP Host

For MCP Inspector usage, embed the key directly into the URL: plaintext https://.azurewebsites.net/runtime/webhooks/mcp/sse?code=

Connecting via VS Code - GitHub Copilot to the Remote MCP Host

For GitHub Copilot usage within VS Code, the key must be set as the x-functions-key header within the mcp.json configuration file, while the URL remains https://<funcappname>.azurewebsites.net/runtime/webhooks/mcp/sse. The subsequent example demonstrates the structure of the mcp.json file included in this repository, which utilizes an input prompt to request the key upon starting the server from VS Code. Your mcp.json file configuration will look like this:

{ "inputs": [ { "type": "promptString", "id": "functions-mcp-extension-system-key", "description": "Azure Functions MCP Extension System Key", "password": true }, { "type": "promptString", "id": "functionapp-name", "description": "Azure Functions App Name" } ], "servers": { "remote-mcp-function": { "type": "sse", "url": "https://${input:functionapp-name}.azurewebsites.net/runtime/webhooks/mcp/sse", "headers": { "x-functions-key": "${input:functions-mcp-extension-system-key}" } }, "local-mcp-function": { "type": "sse", "url": "http://0.0.0.0:7071/runtime/webhooks/mcp/sse" } } }

  1. Initiate the connection for the server named remote-mcp-function within the mcp.json file.

  2. When prompted by VS Code, input the name of the Azure Function App you deployed.

  3. Enter the required Azure Functions MCP Extension System Key when prompted. This key can be retrieved from the Azure portal by navigating to the Functions menu, selecting App Keys, and copying the value associated with the mcp_extension key under System Keys.

  4. In the Copilot chat interface, provide a prompt intended to trigger tool execution, such as:

    plaintext Say Hello

    plaintext Save this snippet as snippet1

    plaintext Retrieve snippet1 and apply to newFile.ts

Re-deploying Code Iterations

The azd up command can be executed repeatedly to manage both the initial provisioning of Azure assets and subsequent deployments of code modifications to your function app.

[!NOTE] Any deployed file content is superseded by the contents of the most recent deployment package.

Resource Decommissioning

Once your development with the function app and associated Azure infrastructure is complete, execute the following command to remove the function app and all related resources from Azure, thereby preventing ongoing charges:

shell azd down

Source Code Details

The implementation logic for the getSnippet and saveSnippet endpoints resides within the TypeScript files located in the src directory. The MCP function annotations expose these methods as callable tools on the MCP Server.

This section illustrates the TypeScript code defining several sample MCP server tools (retrieving a string, an object, and saving an object):

typescript // Hello function - responds with hello message export async function mcpToolHello(context: InvocationContext): Promise { return "Hello I am MCP Tool!"; }

// Register the hello tool app.mcpTool('hello', { toolName: 'hello', description: 'Simple hello world MCP Tool that responses with a hello message.', handler: mcpToolHello });

// GetSnippet function - retrieves a snippet by name export async function getSnippet(_message: unknown, context: InvocationContext): Promise { console.info('Getting snippet');

// Get snippet name from the tool arguments
const mcptoolargs = context.triggerMetadata.mcptoolargs as { snippetname?: string };
const snippetName = mcptoolargs?.snippetname;

console.info(`Snippet name: ${snippetName}`);

if (!snippetName) {
    return "No snippet name provided";
}

// Get the content from blob binding - properly retrieving from extraInputs
const snippetContent = context.extraInputs.get(blobInputBinding);

if (!snippetContent) {
    return `Snippet '${snippetName}' not found`;
}

console.info(`Retrieved snippet: ${snippetName}`);
return snippetContent as string;

}

// Register the GetSnippet tool app.mcpTool('getsnippet', { toolName: GET_SNIPPET_TOOL_NAME, description: GET_SNIPPET_TOOL_DESCRIPTION, toolProperties: [ { propertyName: SNIPPET_NAME_PROPERTY_NAME, propertyValue: PROPERTY_TYPE, description: SNIPPET_NAME_PROPERTY_DESCRIPTION, } ], extraInputs: [blobInputBinding], handler: getSnippet });

// SaveSnippet function - saves a snippet with a name export async function saveSnippet(_message: unknown, context: InvocationContext): Promise { console.info('Saving snippet');

// Get snippet name and content from the tool arguments
const mcptoolargs = context.triggerMetadata.mcptoolargs as { 
    snippetname?: string;
    snippet?: string;
};

const snippetName = mcptoolargs?.snippetname;
const snippet = mcptoolargs?.snippet;

if (!snippetName) {
    return "No snippet name provided";
}

if (!snippet) {
    return "No snippet content provided";
}

// Save the snippet to blob storage using the output binding
context.extraOutputs.set(blobOutputBinding, snippet);

console.info(`Saved snippet: ${snippetName}`);
return snippet;

}

// Register the SaveSnippet tool app.mcpTool('savesnippet', { toolName: SAVE_SNIPPET_TOOL_NAME, description: SAVE_SNIPPET_TOOL_DESCRIPTION, toolProperties: [ { propertyName: SNIPPET_NAME_PROPERTY_NAME, propertyValue: PROPERTY_TYPE, description: SNIPPET_NAME_PROPERTY_DESCRIPTION, }, { propertyName: SNIPPET_PROPERTY_NAME, propertyValue: PROPERTY_TYPE, description: SNIPPET_PROPERTY_DESCRIPTION, } ], extraOutputs: [blobOutputBinding], handler: saveSnippet });

The host.json configuration file also requires a reference to the experimental bundle, which is a prerequisite for utilizing this feature:

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

Subsequent Actions

See Also

`