Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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: `"mcpAuthProvider": { "value": "${MCP_AUTH_PROVIDER=none}" }`

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)
118 changes: 113 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,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 @@ -332,35 +333,41 @@ This project supports deploying with OAuth 2.0 authentication using Keycloak as

### Deployment steps

1. Set the Keycloak admin password (required):
1. Enable Keycloak authentication:

```bash
azd env set MCP_AUTH_PROVIDER keycloak
```

2. Set the Keycloak admin password (required):

```bash
azd env set KEYCLOAK_ADMIN_PASSWORD "YourSecurePassword123!"
```

2. Optionally customize the realm name (default: `mcp`):
3. Optionally customize the realm name (default: `mcp`):

```bash
azd env set KEYCLOAK_REALM_NAME "mcp"
```

3. Deploy to Azure:
4. Deploy to Azure:

```bash
azd up
```

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.

4. Verify deployment by checking the outputs:
5. Verify deployment by checking the outputs:

```bash
azd env get-value MCP_SERVER_URL
azd env get-value KEYCLOAK_DIRECT_URL
azd env get-value KEYCLOAK_ADMIN_CONSOLE
```

5. Visit the Keycloak admin console to verify the realm is configured:
6. Visit the Keycloak admin console to verify the realm is configured:

```text
https://<your-mcproutes-url>/auth/admin
Expand Down Expand Up @@ -397,3 +404,104 @@ 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 MCP_AUTH_PROVIDER entra_proxy
```

2. Set your tenant ID so that the App Registration is created in the correct tenant:

```bash
azd env set AZURE_TENANT_ID "<your-tenant-id>"
```

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

4. Verify deployment by checking the outputs:

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

### Environment variables

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

| Variable | Description |
|----------|-------------|
| `ENTRA_PROXY_AZURE_CLIENT_ID` | The App Registration's client ID |
| `ENTRA_PROXY_AZURE_CLIENT_SECRET` | The App Registration's client secret |

These are then 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 auth_mcp:app --host 0.0.0.0 --port 8000
```

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

### Use Entra OAuth MCP server with GitHub Copilot

The Entra App Registration includes these redirect URIs for VS Code:

- `https://vscode.dev/redirect` (VS Code web)
- `http://127.0.0.1:{33418-33427}` (VS Code desktop local auth helper, 10 ports)

To use the deployed MCP server with GitHub Copilot Chat:

1. To avoid conflicts, stop the MCP servers from `mcp.json` and disable the expense MCP servers in GitHub Copilot Chat tools.
2. Select "MCP: Add Server" from the VS Code Command Palette
3. Select "HTTP" as the server type
4. Enter the URL of the MCP server, either from `MCP_SERVER_URL` environment variable or `http://localhost:8000/mcp` if running locally.
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.
6. You should see a FastMCP authentication screen open in your browser. Select "Allow access":

![FastMCP authentication screen](readme_appaccess.png)

7. After granting access, the browser will redirect to a VS Code "Sign-in successful!" page and then bring focus back to VS Code.

![VS Code sign-in successful page](readme_signedin.png)

8. Enable the MCP server in GitHub Copilot Chat tools and test it with an expense tracking query:

```text
Log expense for 75 dollars of office supplies on my visa last Friday
```

![Example GitHub Copilot Chat Input](readme_authquery.png)

9. Verify the expense was added by checking the Cosmos DB `user-expenses` container in the Azure Portal.

![Cosmos DB user-expenses container](readme_userexpenses.png)
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 MCP_AUTH_PROVIDER is entra_proxy
$MCP_AUTH_PROVIDER = azd env get-value MCP_AUTH_PROVIDER 2>$null
if ($MCP_AUTH_PROVIDER -ne "entra_proxy") {
Write-Host "Skipping auth init (MCP_AUTH_PROVIDER is not entra_proxy)"
exit 0
}

Write-Host "Setting up Azure/Entra ID app registration for FastMCP OAuth Proxy..."
python ./infra/auth_init.py
Loading