Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@
},
},
"inputs": []
}
}
31 changes: 31 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Instructions for coding agents

## Adding a new azd environment variable

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.

When adding new azd environment variables, update these files:

1. **infra/main.parameters.json**: Add the new parameter mapping from azd env variable to Bicep parameter
- Use format `${ENV_VAR_NAME}` for required values
- Use format `${ENV_VAR_NAME=default}` for optional values with defaults
- Example: `"useEntraProxy": { "value": "${USE_ENTRA_PROXY=false}" }`

2. **infra/main.bicep**: Add the Bicep parameter declaration at the top with `@description`
- Use `@secure()` decorator for sensitive values like passwords/secrets
- Example: `@description('Flag to enable feature X') param useFeatureX bool = false`

3. **infra/server.bicep** (or other module): If the variable needs to be passed to a container app:
- Add a parameter to receive the value from main.bicep
- Add the environment variable to the appropriate `env` array (e.g., `baseEnv`, or a conditional array)
- For secrets, add to a secrets array and reference via `secretRef`

4. **infra/main.bicep**: Pass the parameter value to the module
- Example: `featureXEnabled: useFeatureX ? someValue : ''`

5. **infra/write_env.sh** and **infra/write_env.ps1**: If the variable should be written to `.env` for local development:
- Add a line to echo/write the value from `azd env get-value`
- For conditional values, wrap in an if block to only write when populated

6. **infra/main.bicep outputs**: If the value needs to be stored back in azd env after provisioning:
- Add an output (note: `@secure()` parameters cannot be outputs)
81 changes: 81 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ A demonstration project showcasing Model Context Protocol (MCP) implementations
- [Deploy to Azure](#deploy-to-azure)
- [Deploy to Azure with private networking](#deploy-to-azure-with-private-networking)
- [Deploy to Azure with Keycloak authentication](#deploy-to-azure-with-keycloak-authentication)
- [Deploy to Azure with Entra OAuth Proxy](#deploy-to-azure-with-entra-oauth-proxy)

## Getting started

Expand Down Expand Up @@ -360,3 +361,83 @@ This project supports deploying with OAuth 2.0 authentication using Keycloak as
| DCR | Open (anonymous) | Require initial access token | Any client can register without auth |

> **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.

---

## Deploy to Azure with Entra OAuth Proxy

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.

### What gets deployed with Entra OAuth

| Component | Description |
|-----------|-------------|
| **Microsoft Entra App Registration** | Created automatically during provisioning with redirect URIs for local development, VS Code, and production |
| **OAuth-protected MCP Server** | FastMCP with AzureProvider for OAuth authentication |
| **CosmosDB OAuth Client Storage** | Persists OAuth client registrations across server restarts |

### Deployment steps for Entra OAuth

1. Enable Entra OAuth proxy:

```bash
azd env set USE_FASTMCP_AUTH true
```

2. Deploy to Azure:

```bash
azd up
```

During deployment:
- **Preprovision hook**: Creates a Microsoft Entra App Registration with a client secret, and stores the credentials in azd environment variables
- **Postprovision hook**: Updates the App Registration with the deployed server URL as an additional redirect URI

3. Verify deployment by checking the outputs:

```bash
azd env get-value MCP_SERVER_URL
azd env get-value FASTMCP_AUTH_AZURE_CLIENT_ID
```

### Environment variables

The following environment variables are automatically set by the deployment hooks:

| Variable | Description |
|----------|-------------|
| `FASTMCP_AUTH_AZURE_CLIENT_ID` | The App Registration's client ID |
| `FASTMCP_AUTH_AZURE_CLIENT_SECRET` | The App Registration's client secret |
| `FASTMCP_AUTH_AZURE_TENANT_ID` | Your Azure tenant ID |
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The environment variable name is inconsistent. The table lists FASTMCP_AUTH_AZURE_TENANT_ID, but the actual code uses AZURE_TENANT_ID (without the FASTMCP_AUTH prefix).

Looking at the code in auth_mcp.py line 90 and server.bicep line 109-110, the variable is named AZURE_TENANT_ID.

Update the documentation to use AZURE_TENANT_ID instead of FASTMCP_AUTH_AZURE_TENANT_ID.

Suggested change
| `FASTMCP_AUTH_AZURE_TENANT_ID` | Your Azure tenant ID |
| `AZURE_TENANT_ID` | Your Azure tenant ID |

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed


These are written to `.env` by the postprovision hook for local development.

### Testing locally

After deployment, you can test locally with OAuth enabled:

```bash
# Run the MCP server
cd servers && uvicorn deployed_mcp:app --host 0.0.0.0 --port 8000
```

The server will use the Entra App Registration for OAuth and CosmosDB for client storage.

### Connecting VS Code MCP client

The App Registration includes redirect URIs for VS Code:

- `http://localhost:5173/oauth/callback` (VS Code extension localhost)
- `http://localhost:5174/oauth/callback` (VS Code extension localhost alt port)
- `https://vscode.dev/redirect` (VS Code web)

Configure your VS Code MCP client to use the deployed server URL with OAuth.

### Troubleshooting Entra OAuth

If you encounter issues with the OAuth flow in VS Code, you can reset the cached authentication state:

1. Open the Command Palette (`Cmd+Shift+P` on macOS, `Ctrl+Shift+P` on Windows/Linux)
2. Run **"Authentication: Remove Dynamic Authentication Providers"**
3. This clears any cached OAuth tokens and forces a fresh authentication flow
15 changes: 13 additions & 2 deletions azure.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,23 @@ services:
path: ./agents/Dockerfile
context: .
hooks:
preprovision:
windows:
shell: pwsh
run: ./infra/auth_init.ps1
interactive: true
continueOnError: false
posix:
shell: sh
run: ./infra/auth_init.sh
interactive: true
continueOnError: false
postprovision:
posix:
shell: sh
run: ./infra/write_env.sh
run: ./infra/write_env.sh && ./infra/auth_update.sh
continueOnError: true
windows:
shell: pwsh
run: ./infra/write_env.ps1
run: ./infra/write_env.ps1; ./infra/auth_update.ps1
continueOnError: true
11 changes: 11 additions & 0 deletions infra/auth_init.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Pre-provision hook to set up Azure/Entra ID app registration for FastMCP OAuth Proxy

# Check if USE_FASTMCP_AUTH is enabled
$USE_FASTMCP_AUTH = azd env get-value USE_FASTMCP_AUTH 2>$null
if ($USE_FASTMCP_AUTH -ne "true") {
Write-Host "Skipping auth init (USE_FASTMCP_AUTH is not enabled)"
exit 0
}

Write-Host "Setting up Azure/Entra ID app registration for FastMCP OAuth Proxy..."
python ./infra/fastmcp_auth_init.py
12 changes: 12 additions & 0 deletions infra/auth_init.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/bash
# Pre-provision hook to set up Azure/Entra ID app registration for FastMCP OAuth Proxy

# Check if USE_FASTMCP_AUTH is enabled
USE_FASTMCP_AUTH=$(azd env get-value USE_FASTMCP_AUTH 2>/dev/null || echo "false")
if [ "$USE_FASTMCP_AUTH" != "true" ]; then
echo "Skipping auth init (USE_FASTMCP_AUTH is not enabled)"
exit 0
fi

echo "Setting up Azure/Entra ID app registration for FastMCP OAuth Proxy..."
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hit a KeyError: 'AZURE_TENANT_ID' during azd up here. I think it is because the variable isn't exported to pre-provision hooks.

I manually added:

AZURE_TENANT_ID=$(azd env get-value AZURE_TENANT_ID 2>/dev/null)
if [ $? -ne 0 ]; then AZURE_TENANT_ID=""; fi
export AZURE_TENANT_ID

if [ -z "$AZURE_TENANT_ID" ]; then
    # Fallback: Extract tenant ID from azd auth token
    token_json=$(azd auth token --output json 2>/dev/null)
    if [ -n "$token_json" ]; then
        AZURE_TENANT_ID=$(python -c "import sys, json, base64; ... print(json.loads(base64.b64decode(payload).decode('utf-8'))['tid'])" <<< "$token_json")
        export AZURE_TENANT_ID
    fi
fi

and it worked.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I've added that as explicit step to the README, just to have them run "azd env set". That's how we do it in the other repo, as we allow them to set a different tenant ID than the main tenant. I could also do that here, distinguish it as AZURE_AUTH_TENANT_ID, if we want to enable cross-tenant setups. (But that wouldnt work if I manage to move this all to Bicep one day)

python ./infra/fastmcp_auth_init.py
11 changes: 11 additions & 0 deletions infra/auth_update.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Post-provision hook to update Azure app registration redirect URIs with deployed server URL

# Check if USE_FASTMCP_AUTH is enabled
$USE_FASTMCP_AUTH = azd env get-value USE_FASTMCP_AUTH 2>$null
if ($USE_FASTMCP_AUTH -ne "true") {
Write-Host "Skipping auth update (USE_FASTMCP_AUTH is not enabled)"
exit 0
}

Write-Host "Updating FastMCP auth redirect URIs with deployed server URL..."
python ./infra/fastmcp_auth_update.py
12 changes: 12 additions & 0 deletions infra/auth_update.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/bash
# Post-provision hook to update Azure app registration redirect URIs with deployed server URL

# Check if USE_FASTMCP_AUTH is enabled
USE_FASTMCP_AUTH=$(azd env get-value USE_FASTMCP_AUTH 2>/dev/null || echo "false")
if [ "$USE_FASTMCP_AUTH" != "true" ]; then
echo "Skipping auth update (USE_FASTMCP_AUTH is not enabled)"
exit 0
fi

echo "Updating FastMCP auth redirect URIs with deployed server URL..."
python ./infra/fastmcp_auth_update.py
Loading