This sample shows how to use App Service Authentication and Authorization for Model Context Protocol (MCP) server authorization. The MCP server is implemented using the Azure Functions MCP Extension with TypeScript/Node.js. The sample also shows how to call the Microsoft Graph on behalf of the signed-in user.
The sample uses a TypeScript project. There are two ways to get started:
- You can deploy the application to Azure using the Azure Developer CLI (azd) - this is the recommended way to get started with the sample.
- You can run the application locally - this option does not use any MCP server authorization. Instead of using the identity of the authorized user, it uses your developer credentials for outbound calls to the Microsoft Graph. The option is covered for completeness and to show how you might build your own applications to work locally, but it is not the core focus of the sample.
You can run it locally or quickly deploy it to Azure using the Azure Developer CLI (azd).
- An Azure subscription - Create an Azure free account
- Azure CLI - Install the Azure CLI
- Azure Developer CLI (azd) - Install the Azure Developer CLI
- Azure Functions Core Tools - Install the Azure Functions Core Tools
- Node.js 20 LTS or later - Install Node.js
- Visual Studio Code - Install Visual Studio Code
- Azurite - Install Azurite
You must also have an Entra client ID that can be used by your MCP client. For basic testing, you can use Visual Studio Code, which has a known client ID explained in the setup instructions below.
-
Clone this repository.
-
Sign in to Azure and initialize azd:
az login azd auth login -
Create a new azd project environment, specifying a location (
westus2is recommended), the subscription ID you want to use, and a name for the environment:azd env new <environment-name> --location westus2 --subscription <subscription-id> -
This sample uses Visual Studio Code as the main client. Configure the it as an allowed client application:
azd env set PRE_AUTHORIZED_CLIENT_IDS aebc6443-996d-45c2-90f0-388ff96faa56If you have other MCP clients that will call your server, provide all of the client IDs as a comma-separated list:
azd env set PRE_AUTHORIZED_CLIENT_IDS <comma-separated-list-of-client-ids> -
Specify a service management reference if required by your organization. If you're not a Microsoft employee and don't know that you need to set this, you can skip this step. However, if provisioning fails with an error about a missing service management reference, you may need to revisit this step. Microsoft employees using a Microsoft tenant must provide a service management reference (your Service Tree ID). Without this, you won't be able to create the Entra app registration, and provisioning will fail.
azd env set SERVICE_MANAGEMENT_REFERENCE <service-management-reference>If you don't know what to set here, check with your organization's tenant administrator.
-
(Optional) If you are deploying to a sovereign cloud, set the token exchange audience used for that cloud.
For Entra ID US Government:
azd env set TOKEN_EXCHANGE_AUDIENCE api://AzureADTokenExchangeUSGovFor Entra ID China operated by 21Vianet:
azd env set TOKEN_EXCHANGE_AUDIENCE api://AzureADTokenExchangeChina -
Create the Azure resources and deploy the application:
azd upEnsure all resources show as "Succeeded" status before proceeding.
-
Consent to the application so your MCP client can successfully sign you in. For testing, you'll author consent just for yourself by logging into the application in a browser. See Consent authoring for how you would handle this for production scenarios.
-
Navigate to the
/.auth/login/aadendpoint of your deployed function app. For example, if your function app is athttps://my-mcp-function-app.azurewebsites.net, navigate tohttps://my-mcp-function-app.azurewebsites.net/.auth/login/aad.Your function app base URL should be shown in the output of the
azd upcommand. If you missed it in the deployment output, you can retrieve it with:azd env get-value SERVICE_MCP_DEFAULT_HOSTNAMEOr find it in the Azure portal under your Function App resource.
-
You should be redirected to a login prompt. Sign in with the account you will use from the MCP client for testing.
-
After signing in, you will be prompted to consent to the application. Review the permissions requested and click "Accept" to grant consent.
-
You should be redirected back to a page hosted at the function app URL saying "you have successfully signed in". You can close the page at this point.
-
-
Get your Function App's MCP extension system key for connecting MCP clients. Your client will need this key to call your MCP server, in addition to server authorization with Entra ID.
You can obtain the system key named
mcp_extensionusing the Azure CLI. First, get the resource group and function app names from your azd deployment:# Get resource group name azd env get-value AZURE_RESOURCE_GROUP_NAME # Get function app name azd env get-value SERVICE_MCP_NAMEThen use these values to retrieve the system key:
az functionapp keys list --resource-group <resource_group> --name <function_app_name> --query "systemKeys.mcp_extension" -o tsvAlternatively, you can retrieve it from the Azure portal:
- Navigate to your Function App in the Azure portal
- Go to Functions → App keys
- Copy the value for the
mcp_extensionsystem key
-
Test your MCP server by following the instructions in the Test your MCP server section below. You'll need your function app URL and the
mcp_extensionsystem key from the previous step.
Note
When running locally, the MCP server will use your developer credentials (from Azure CLI or Visual Studio Code) for outbound calls to Microsoft Graph instead of an authorized user's identity.
-
Clone this repository.
-
(Optional) Update the
local.settings.jsonfile in thesrc/mcp-serverfolder to include your Microsoft Entra tenant ID. This helps the application correctly access your developer identity, even when you can sign into multiple tenants.-
Obtain the tenant ID from the Azure CLI:
az account show --query tenantId -o tsv -
Update
local.settings.jsonto include the tenant ID:{ "IsEncrypted": false, "Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", "FUNCTIONS_WORKER_RUNTIME": "node", "AZURE_TENANT_ID": "<your-tenant-id>" } }
-
-
Start Azurite. If you are using the CLI to start it up, with an
azuriteor adocker runcommand, for example, do so in a separate background terminal. -
Move your main terminal to the
src/mcp-serverfolder. -
Install dependencies:
npm install -
Start the project:
npm start -
Test your MCP server by following the instructions in the Test your MCP server section below.
Once you've completed one of the "Get started" options above, you can test your resulting MCP server by connecting to it with an MCP client.
The instructions provided below are for testing with GitHub Copilot in Visual Studio Code, which supports both the MCP protocol and Entra ID authentication. This setup also provides logs that make it easy to see how the client and server interact for server authorization.
-
Create a new workspace in VS Code and install the MCP extension.
-
Create or update your MCP configuration in VS Code. Add this to your
mcp.jsonfile (or create one):{ "inputs": [ { "type": "promptString", "id": "functions-mcp-extension-system-key", "description": "Azure Functions MCP Extension System Key", "password": true }, { "type": "promptString", "id": "functionapp-host", "description": "The host domain of the function app." } ], "servers": { "remote-mcp-function": { "type": "http", "url": "https://${input:functionapp-host}/runtime/webhooks/mcp", "headers": { "x-functions-key": "${input:functions-mcp-extension-system-key}" } }, "local-mcp-function": { "type": "http", "url": "http://localhost:7071/runtime/webhooks/mcp" } } }This creates two server configurations, one for each "Get started" option above. For subsequent steps, choose the server that matches how you set up your MCP server, local or remote.
-
(Optional) Show the output logs:
- In VS Code, open the Command Palette:
- On Windows/Linux: Press
Ctrl+Shift+P - On Mac: Press
Cmd+Shift+P
- On Windows/Linux: Press
- Type "MCP: List Servers" and press Enter
- Select the server you want to start (either
remote-mcp-functionorlocal-mcp-function) - Choose "Show Output" - this will open the output pane.
- Select the gear icon in the output pane and select "Debug" or higher. "Trace" provides the most detail but might include additional noise.
- In VS Code, open the Command Palette:
-
Start the MCP server:
- In VS Code, open the Command Palette:
- On Windows/Linux: Press
Ctrl+Shift+P - On Mac: Press
Cmd+Shift+P
- On Windows/Linux: Press
- Type "MCP: List Servers" and press Enter
- Select the server you want to start (either
remote-mcp-functionorlocal-mcp-function) - Choose "Start Server".
- If you chose
remote-mcp-function, you will be prompted for:- Your Function App URL
- Your
mcp_extensionsystem key
- In VS Code, open the Command Palette:
-
If you're connecting to the app hosted behind App Service Authentication and Authorization, VS Code will prompt you to allow GitHub Copilot to access your Microsoft account. Follow the prompts to sign in.
If you configured debug output logs, you should see how the MCP client and server interact during the sign-in process. See Server authorization protocol for more details.
-
Open the GitHub Copilot chat pane (
Ctrl+Alt+I) and enter a prompt to use the tool. The easiest way to ensure it invokes the tool is to send the message#HelloTool. -
GitHub Copilot should prompt you to allow it to call the tool. Verify that it's going to the right server and then allow it to proceed.
-
You should see a response from the tool in the chat pane. It should greet you by name and show your email. This information comes from the Microsoft Graph, which the MCP server calls.
The code for the MCP server is in the src/mcp-server project folder. This is an Azure Functions project using TypeScript with Node.js that uses the MCP Extension for Azure Functions and a custom handler. The MCP handler is defined in mcp-handler/function.json and implements one tool in index.ts. The goal of this function is to call the Microsoft Graph and return a simple greeting. When hosted in Azure, it will call the Graph on behalf of the signed-in user for the request. When run locally, it will use your developer credentials.
To meet these requirements, the handler uses the @azure/identity package to obtain a token for the correct user. The index.ts file configures an appropriate TokenCredential instance based on the context. In local contexts, this is a simple ChainedTokenCredential. However, when hosted in Azure with App Service Authentication and Authorization, the handler uses the MCP tool trigger's ToolInvocationContext to access the headers from the underlying HTTP transport. It uses these headers to craft a custom token credential implementation. This implementation uses the OnBehalfOfCredential, authenticating as the app registration using a managed identity as a federated identity credential, which was set up during provisioning.
The approach of using a custom handler with the @azure/identity package keeps the tool implementation simple and focused on its core purpose, without introducing unnecessary complexity. It also allows the same setup to be used across multiple tools if needed.
In the steps described for this example, you consented to the application by signing into it in a browser. This allowed the application to request delegated permissions to the Microsoft Graph. There are two main ways that consent can be handled:
- User consent - This is the approach used in the example above. Each user signs into the application and consents to the permissions requested. They can only do this for themselves, unless they are a tenant administrator with the ability to consent on behalf of others. In this sample, user consent is appropriate because it allows you to quickly test things without impacting other users. However, the way user consent is authored in this sample does not reflect how you would typically do it in a production scenario. This is described in more detail below.
- Admin consent - A tenant administrator can consent to the application on behalf of all users when they sign in and review the permissions. Once this is done, individual users can sign in without needing to consent themselves. This approach is more scalable and ensures that all users can access the application without running into consent issues. For the purposes of a sample, admin consent is not appropriate, but it is a great choice for production scenarios.
The user consent approach for this sample is a separate login because the sample uses Visual Studio Code as the client. Although Visual Studio Code is pre-authorized to our application, that only creates consent for the user to call the MCP server. It doesn't create consent for the MCP server to call the Microsoft Graph on behalf of the user. When we log into the application directly, we request Microsoft Graph permissions as part of a combined consent experience.
The main difference is that because Visual Studio Code is using a single sign-on flow, so it only requests a token for the MCP server. It does not present an opportunity for the user to interactively consent to any permissions needed for or by the MCP server. If you built a client that used an interactive login of some kind, you could have it all handled entirely by that client. It would not be necessary to have a separate browser login.
See Overview of permissions and consent in the Microsoft identity platform for additional information on how Entra ID handles consent.
In the debug output from VS Code, you'll see a series of requests and responses as the MCP client and server interact. When MCP server authorization is used, you should see the following sequence of events:
- The editor sends an initialization request to the MCP server.
- The MCP server responds with an error indicating that authorization is required. The response includes a pointer to the protected resource metadata (PRM) for the application. The App Service Authentication and Authorization feature generates the PRM for an app built with this sample.
- The editor fetches the PRM and uses it to identify the authorization server.
- The editor attempts to obtain authorization server metadata (ASM) from a well-known endpoint on the authorization server.
- Microsoft Entra ID doesn't support ASM on the well-known endpoint, so the editor falls back to using the OpenID Connect metadata endpoint to obtain the ASM. It tries to discover this using by inserting the well-known endpoint before any other path information.
- The OpenID Connect specifications actually defined the well-known endpoint as being after path information, and that is where Microsoft Entra ID hosts it. So the editor tries again with that format.
- The editor successfully retrieves the ASM. It then can then use this information in conjunction with its own client ID to perform a login. At this point, the editor prompts you to sign in and consent to the application.
- Assuming you successfully sign in and consent, the editor completes the login. It repeats the intialization request to the MCP server, this time including an authorization token in the request. This reattempt isn't visible at the Debug output level, but you can see it in the Trace output level.
- The MCP server validates the token and responds with a successful response to the initialization request. The standard MCP flow continues from this point, ultimately resulting in discovery of the MCP tool defined in this sample.
You can learn more about the full protocol in the MCP specification.