Skip to content

Commit aeacc19

Browse files
authored
Merge pull request Azure-Samples#18 from pamelafox/authserver
Add Entra OAuth proxy (via FastMCP AzureAuthProvider) and automated Entra app registration
1 parent 197d291 commit aeacc19

27 files changed

+2363
-1132
lines changed

.vscode/mcp.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,4 @@
3030
},
3131
},
3232
"inputs": []
33-
}
33+
}

AGENTS.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Instructions for coding agents
2+
3+
## Adding a new azd environment variable
4+
5+
An azd environment variable is stored by the azd CLI for each environment. It is passed to the "azd up" command and can configure both provisioning options and application settings.
6+
7+
When adding new azd environment variables, update these files:
8+
9+
1. **infra/main.parameters.json**: Add the new parameter mapping from azd env variable to Bicep parameter
10+
- Use format `${ENV_VAR_NAME}` for required values
11+
- Use format `${ENV_VAR_NAME=default}` for optional values with defaults
12+
- Example: `"mcpAuthProvider": { "value": "${MCP_AUTH_PROVIDER=none}" }`
13+
14+
2. **infra/main.bicep**: Add the Bicep parameter declaration at the top with `@description`
15+
- Use `@secure()` decorator for sensitive values like passwords/secrets
16+
- Example: `@description('Flag to enable feature X') param useFeatureX bool = false`
17+
18+
3. **infra/server.bicep** (or other module): If the variable needs to be passed to a container app:
19+
- Add a parameter to receive the value from main.bicep
20+
- Add the environment variable to the appropriate `env` array (e.g., `baseEnv`, or a conditional array)
21+
- For secrets, add to a secrets array and reference via `secretRef`
22+
23+
4. **infra/main.bicep**: Pass the parameter value to the module
24+
- Example: `featureXEnabled: useFeatureX ? someValue : ''`
25+
26+
5. **infra/write_env.sh** and **infra/write_env.ps1**: If the variable should be written to `.env` for local development:
27+
- Add a line to echo/write the value from `azd env get-value`
28+
- For conditional values, wrap in an if block to only write when populated
29+
30+
6. **infra/main.bicep outputs**: If the value needs to be stored back in azd env after provisioning:
31+
- Add an output (note: `@secure()` parameters cannot be outputs)

README.md

Lines changed: 113 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ A demonstration project showcasing Model Context Protocol (MCP) implementations
1717
- [Deploy to Azure](#deploy-to-azure)
1818
- [Deploy to Azure with private networking](#deploy-to-azure-with-private-networking)
1919
- [Deploy to Azure with Keycloak authentication](#deploy-to-azure-with-keycloak-authentication)
20+
- [Deploy to Azure with Entra OAuth Proxy](#deploy-to-azure-with-entra-oauth-proxy)
2021

2122
## Getting started
2223

@@ -332,35 +333,41 @@ This project supports deploying with OAuth 2.0 authentication using Keycloak as
332333

333334
### Deployment steps
334335

335-
1. Set the Keycloak admin password (required):
336+
1. Enable Keycloak authentication:
337+
338+
```bash
339+
azd env set MCP_AUTH_PROVIDER keycloak
340+
```
341+
342+
2. Set the Keycloak admin password (required):
336343

337344
```bash
338345
azd env set KEYCLOAK_ADMIN_PASSWORD "YourSecurePassword123!"
339346
```
340347

341-
2. Optionally customize the realm name (default: `mcp`):
348+
3. Optionally customize the realm name (default: `mcp`):
342349

343350
```bash
344351
azd env set KEYCLOAK_REALM_NAME "mcp"
345352
```
346353

347-
3. Deploy to Azure:
354+
4. Deploy to Azure:
348355

349356
```bash
350357
azd up
351358
```
352359

353360
This will create the Azure Container Apps environment, deploy Keycloak with the pre-configured realm, deploy the MCP server with OAuth validation, and configure HTTP route-based routing.
354361

355-
4. Verify deployment by checking the outputs:
362+
5. Verify deployment by checking the outputs:
356363

357364
```bash
358365
azd env get-value MCP_SERVER_URL
359366
azd env get-value KEYCLOAK_DIRECT_URL
360367
azd env get-value KEYCLOAK_ADMIN_CONSOLE
361368
```
362369

363-
5. Visit the Keycloak admin console to verify the realm is configured:
370+
6. Visit the Keycloak admin console to verify the realm is configured:
364371

365372
```text
366373
https://<your-mcproutes-url>/auth/admin
@@ -397,3 +404,104 @@ This project supports deploying with OAuth 2.0 authentication using Keycloak as
397404
| DCR | Open (anonymous) | Require initial access token | Any client can register without auth |
398405

399406
> **Note:** Keycloak must be publicly accessible because its URL is dynamically generated by Azure. Token issuer validation requires a known URL, but the mcproutes URL isn't available until after deployment. Using a custom domain would fix this.
407+
408+
---
409+
410+
## Deploy to Azure with Entra OAuth Proxy
411+
412+
This project supports deploying with Microsoft Entra ID (Azure AD) authentication using FastMCP's built-in Azure OAuth proxy. This is an alternative to Keycloak that uses Microsoft Entra with your Azure tenant for identity management.
413+
414+
### What gets deployed with Entra OAuth
415+
416+
| Component | Description |
417+
|-----------|-------------|
418+
| **Microsoft Entra App Registration** | Created automatically during provisioning with redirect URIs for local development, VS Code, and production |
419+
| **OAuth-protected MCP Server** | FastMCP with AzureProvider for OAuth authentication |
420+
| **CosmosDB OAuth Client Storage** | Persists OAuth client registrations across server restarts |
421+
422+
### Deployment steps for Entra OAuth
423+
424+
1. Enable Entra OAuth proxy:
425+
426+
```bash
427+
azd env set MCP_AUTH_PROVIDER entra_proxy
428+
```
429+
430+
2. Set your tenant ID so that the App Registration is created in the correct tenant:
431+
432+
```bash
433+
azd env set AZURE_TENANT_ID "<your-tenant-id>"
434+
```
435+
436+
3. Deploy to Azure:
437+
438+
```bash
439+
azd up
440+
```
441+
442+
During deployment:
443+
- **Preprovision hook**: Creates a Microsoft Entra App Registration with a client secret, and stores the credentials in azd environment variables
444+
- **Postprovision hook**: Updates the App Registration with the deployed server URL as an additional redirect URI
445+
446+
4. Verify deployment by checking the outputs:
447+
448+
```bash
449+
azd env get-value MCP_SERVER_URL
450+
azd env get-value ENTRA_PROXY_AZURE_CLIENT_ID
451+
```
452+
453+
### Environment variables
454+
455+
The following environment variables are automatically set by the deployment hooks:
456+
457+
| Variable | Description |
458+
|----------|-------------|
459+
| `ENTRA_PROXY_AZURE_CLIENT_ID` | The App Registration's client ID |
460+
| `ENTRA_PROXY_AZURE_CLIENT_SECRET` | The App Registration's client secret |
461+
462+
These are then written to `.env` by the postprovision hook for local development.
463+
464+
### Testing locally
465+
466+
After deployment, you can test locally with OAuth enabled:
467+
468+
```bash
469+
# Run the MCP server
470+
cd servers && uvicorn auth_mcp:app --host 0.0.0.0 --port 8000
471+
```
472+
473+
The server will use the Entra App Registration for OAuth and CosmosDB for client storage.
474+
475+
### Use Entra OAuth MCP server with GitHub Copilot
476+
477+
The Entra App Registration includes these redirect URIs for VS Code:
478+
479+
- `https://vscode.dev/redirect` (VS Code web)
480+
- `http://127.0.0.1:{33418-33427}` (VS Code desktop local auth helper, 10 ports)
481+
482+
To use the deployed MCP server with GitHub Copilot Chat:
483+
484+
1. To avoid conflicts, stop the MCP servers from `mcp.json` and disable the expense MCP servers in GitHub Copilot Chat tools.
485+
2. Select "MCP: Add Server" from the VS Code Command Palette
486+
3. Select "HTTP" as the server type
487+
4. Enter the URL of the MCP server, either from `MCP_SERVER_URL` environment variable or `http://localhost:8000/mcp` if running locally.
488+
5. If you get an error about "Client ID not found", open the Command Palette, run **"Authentication: Remove Dynamic Authentication Providers"**, and select the MCP server URL. This clears any cached OAuth tokens and forces a fresh authentication flow. Then restart the server to prompt the OAuth flow again.
489+
6. You should see a FastMCP authentication screen open in your browser. Select "Allow access":
490+
491+
![FastMCP authentication screen](readme_appaccess.png)
492+
493+
7. After granting access, the browser will redirect to a VS Code "Sign-in successful!" page and then bring focus back to VS Code.
494+
495+
![VS Code sign-in successful page](readme_signedin.png)
496+
497+
8. Enable the MCP server in GitHub Copilot Chat tools and test it with an expense tracking query:
498+
499+
```text
500+
Log expense for 75 dollars of office supplies on my visa last Friday
501+
```
502+
503+
![Example GitHub Copilot Chat Input](readme_authquery.png)
504+
505+
9. Verify the expense was added by checking the Cosmos DB `user-expenses` container in the Azure Portal.
506+
507+
![Cosmos DB user-expenses container](readme_userexpenses.png)

azure.yaml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,23 @@ services:
2929
path: ./agents/Dockerfile
3030
context: .
3131
hooks:
32+
preprovision:
33+
windows:
34+
shell: pwsh
35+
run: ./infra/auth_init.ps1
36+
interactive: true
37+
continueOnError: false
38+
posix:
39+
shell: sh
40+
run: ./infra/auth_init.sh
41+
interactive: true
42+
continueOnError: false
3243
postprovision:
3344
posix:
3445
shell: sh
35-
run: ./infra/write_env.sh
46+
run: ./infra/write_env.sh && ./infra/auth_update.sh
3647
continueOnError: true
3748
windows:
3849
shell: pwsh
39-
run: ./infra/write_env.ps1
50+
run: ./infra/write_env.ps1; ./infra/auth_update.ps1
4051
continueOnError: true

infra/auth_init.ps1

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Pre-provision hook to set up Azure/Entra ID app registration for FastMCP OAuth Proxy
2+
3+
# Check if MCP_AUTH_PROVIDER is entra_proxy
4+
$MCP_AUTH_PROVIDER = azd env get-value MCP_AUTH_PROVIDER 2>$null
5+
if ($MCP_AUTH_PROVIDER -ne "entra_proxy") {
6+
Write-Host "Skipping auth init (MCP_AUTH_PROVIDER is not entra_proxy)"
7+
exit 0
8+
}
9+
10+
Write-Host "Setting up Azure/Entra ID app registration for FastMCP OAuth Proxy..."
11+
python ./infra/auth_init.py

0 commit comments

Comments
 (0)