You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: src/FunctionsMcpTool/README.md
+93-5Lines changed: 93 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
# FunctionsMcpTool — Remote MCP Server on Azure Functions (Python)
2
2
3
-
This project is a Python Azure Function app that exposes multiple MCP (Model Context Protocol) tools as a remote MCP server. It includes tools for snippets, QR code generation, structured metadata, batch operations, and more.
3
+
This project is a Python Azure Function app that exposes multiple MCP (Model Context Protocol) tools as a remote MCP server. It includes tools for snippets, QR code generation, badges, structured metadata, batch operations, and a **hello with auth** tool that demonstrates the On-Behalf-Of (OBO) flow to call Microsoft Graph as the signed-in user.
4
4
5
5
> **Note:** MCP resources are in the [FunctionsMcpResources](../FunctionsMcpResources/) project, and prompts are in the [FunctionsMcpPrompts](../FunctionsMcpPrompts/) project.
6
6
@@ -120,7 +120,7 @@ This also becomes the resource group name.
120
120
121
121
### Step 3: Provision and deploy
122
122
123
-
By default, OAuth-based authentication is enabled using the [built-in MCP auth feature](https://learn.microsoft.com/azure/app-service/configure-authentication-mcp?toc=/azure/azure-functions/toc.json&bc=/azure/azure-functions/breadcrumb/toc.json) with Microsoft Entra as the identity provider.
123
+
This project requires OAuth-based authentication through the [built-in MCP auth feature](https://learn.microsoft.com/azure/app-service/configure-authentication-mcp?toc=/azure/azure-functions/toc.json&bc=/azure/azure-functions/breadcrumb/toc.json) with Microsoft Entra as the identity provider, and it is enabled by default. Do not disable authentication for this project.
124
124
125
125
Configure VS Code as an allowed client application for Microsoft Entra:
126
126
@@ -140,11 +140,25 @@ Deploy the project. When prompted, pick your subscription and an Azure region.
140
140
azd up
141
141
```
142
142
143
-
### Step 4: Connect to the remote MCP server
143
+
### Step 4: Consent to the application
144
144
145
-
Open **`.vscode/mcp.json`** and click **Start** above **`remote-mcp-function`**. You'll be prompted for `functionapp-name` — find it in your `azd` command output or the `.azure/<env>/.env` file. Since authentication is enabled, you'll also be prompted to sign in with Microsoft.
145
+
The `hello_tool_with_auth` tool requires consent for delegated permission to access Microsoft Graph. For testing, you can grant consent just for yourself by logging into the application in a browser. See [Consent authoring](#consent-authoring) for how you would handle this for production scenarios.
146
146
147
-
> **Tip:** Click **More... → Show Output** above the server name to see request/response details.
147
+
Navigate to the `/.auth/login/aad` endpoint of your deployed function app. For example, if your function app is at `https://my-mcp-function-app.azurewebsites.net`, navigate to:
Sign in with your Azure subscription email and accept the permissions prompt. This completes the consent flow for you.
154
+
155
+
### Step 5: Connect to the remote MCP server
156
+
157
+
Open **`.vscode/mcp.json`** and click **Start** above **`remote-mcp-function`**. You'll be prompted for `functionapp-name` — find it in your `azd` command output or the `.azure/<env>/.env` file. You'll also be prompted to authenticate with Microsoft — click **Allow** and sign in.
158
+
159
+
> **Tip:** A successful connection shows the number of tools the server exposes. Click **More... → Show Output** above the server name to see request/response details.
160
+
161
+
> If you run into issues, see the [Troubleshooting](#troubleshooting) section below.
### Calling Microsoft Graph with the On-Behalf-Of flow (`hello_tool_with_auth`)
208
+
209
+
The `hello_tool_with_auth` tool (in [`hello_tool_with_auth.py`](hello_tool_with_auth.py)) demonstrates how to call a downstream API (Microsoft Graph) **as the signed-in user** using the On-Behalf-Of (OBO) flow.
210
+
211
+
**Local development** falls back to your local developer identity (Azure CLI, azd, etc.):
212
+
213
+
```python
214
+
if is_local:
215
+
credential = ChainedTokenCredential(
216
+
AzureCliCredential(),
217
+
AzureDeveloperCliCredential(),
218
+
)
219
+
else:
220
+
credential = _build_obo_credential(context)
221
+
```
222
+
223
+
**In production**, the `_build_obo_credential` function exchanges the user's auth token for a Microsoft Graph token using three pieces of information:
224
+
225
+
1.**The user's bearer token** — extracted from the `X-MS-TOKEN-AAD-ACCESS-TOKEN` header (or `Authorization` fallback)
226
+
2.**The user's tenant ID** — decoded from the `X-MS-CLIENT-PRINCIPAL` header
227
+
3.**A client assertion** — obtained from a managed identity with a federated credential, proving the app's identity without a client secret
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:
266
+
267
+
-**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.
268
+
269
+
-**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.
270
+
271
+
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.
272
+
273
+
The main difference is that because Visual Studio Code is using a single sign-on flow, 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.
274
+
275
+
See [Overview of permissions and consent in the Microsoft identity platform](https://learn.microsoft.com/en-us/entra/identity-platform/permissions-consent-overview) for additional information on how Entra ID handles consent.
276
+
193
277
## Troubleshooting
194
278
195
279
| Problem | Solution |
196
280
|---------|----------|
197
281
| Connection refused locally | Ensure Azurite is running (`docker run -p 10000:10000 ...`) |
198
282
| API version not supported by Azurite | Add `--skipApiVersionCheck` flag to the Azurite command, or pull the latest image |
283
+
|`hello_tool_with_auth` fails locally | Ensure you're signed in with `az login` or `azd auth login`|
284
+
| OBO errors in production | Verify that consent has been granted (see Step 4) and that the Entra app registration is configured correctly |
285
+
|`An error occurred invoking 'hello_tool_with_auth'` right after `azd up`| Restart the function app: `az functionapp restart -g <resource-group> -n <function-app-name>`. The OBO flow signs a client assertion with the user-assigned managed identity via a federated identity credential (FIC). Right after provisioning, the auth runtime can hold a stale signing credential while the FIC propagates in Entra. Check **Application Insights > Logs** for `AADSTS50013: Assertion failed signature validation` to confirm. |
286
+
| Generic "An error occurred invoking" with no details | Check **Application Insights > Logs** and query `exceptions \| where timestamp > ago(1h) \| project timestamp, outerMessage, innermostMessage` to find the actual error. |
199
287
|`AttributeError: 'FunctionApp' object has no attribute 'mcp_resource_trigger'`| Python 3.13 is required. Verify with `python3 --version`. |
200
288
|`azd up` provision succeeded but deploy failed | Transient error — run `azd deploy` again |
0 commit comments