From 7d6aa3bd66a6019004851ca12bba95ebef137fbf Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Mon, 8 Dec 2025 23:08:02 -0800 Subject: [PATCH 1/9] Add Entra auth proxy --- .vscode/mcp.json | 4 +- AGENTS.md | 31 +++ README.md | 81 ++++++++ azure.yaml | 15 +- infra/auth_init.ps1 | 11 + infra/auth_init.sh | 12 ++ infra/auth_update.ps1 | 11 + infra/auth_update.sh | 12 ++ infra/fastmcp_auth_init.py | 219 ++++++++++++++++++++ infra/fastmcp_auth_update.py | 103 ++++++++++ infra/main.bicep | 49 ++++- infra/main.parameters.json | 10 + infra/server.bicep | 53 ++++- infra/write_env.ps1 | 13 +- infra/write_env.sh | 13 +- pyproject.toml | 4 +- servers/Dockerfile | 4 +- servers/auth_mcp.py | 241 ++++++++++++++++++++++ servers/cosmosdb_store.py | 262 ++++++++++++++++++++++++ servers/deployed_mcp.py | 59 +++--- servers/expenses.csv | 1 + uv.lock | 381 +++++++++++++++++++++-------------- 22 files changed, 1399 insertions(+), 190 deletions(-) create mode 100644 AGENTS.md create mode 100644 infra/auth_init.ps1 create mode 100755 infra/auth_init.sh create mode 100644 infra/auth_update.ps1 create mode 100755 infra/auth_update.sh create mode 100644 infra/fastmcp_auth_init.py create mode 100644 infra/fastmcp_auth_update.py create mode 100644 servers/auth_mcp.py create mode 100644 servers/cosmosdb_store.py diff --git a/.vscode/mcp.json b/.vscode/mcp.json index 4b87ffb..3de95ed 100644 --- a/.vscode/mcp.json +++ b/.vscode/mcp.json @@ -27,7 +27,7 @@ "0.0.0.0:5678", "servers/basic_mcp_stdio.py" ] - }, + } }, "inputs": [] -} \ No newline at end of file +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..2b031a6 --- /dev/null +++ b/AGENTS.md @@ -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) diff --git a/README.md b/README.md index 291e4fd..03ffadf 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 | + +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 diff --git a/azure.yaml b/azure.yaml index 1cab741..6981c01 100644 --- a/azure.yaml +++ b/azure.yaml @@ -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 diff --git a/infra/auth_init.ps1 b/infra/auth_init.ps1 new file mode 100644 index 0000000..129c292 --- /dev/null +++ b/infra/auth_init.ps1 @@ -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 diff --git a/infra/auth_init.sh b/infra/auth_init.sh new file mode 100755 index 0000000..aed72e2 --- /dev/null +++ b/infra/auth_init.sh @@ -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..." +python ./infra/fastmcp_auth_init.py diff --git a/infra/auth_update.ps1 b/infra/auth_update.ps1 new file mode 100644 index 0000000..6a67f55 --- /dev/null +++ b/infra/auth_update.ps1 @@ -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 diff --git a/infra/auth_update.sh b/infra/auth_update.sh new file mode 100755 index 0000000..74fbf1c --- /dev/null +++ b/infra/auth_update.sh @@ -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 diff --git a/infra/fastmcp_auth_init.py b/infra/fastmcp_auth_init.py new file mode 100644 index 0000000..c020431 --- /dev/null +++ b/infra/fastmcp_auth_init.py @@ -0,0 +1,219 @@ +import asyncio +import datetime +import os +import random +import subprocess +import uuid + +from azure.identity.aio import AzureDeveloperCliCredential +from dotenv_azd import load_azd_env +from msgraph import GraphServiceClient +from msgraph.generated.applications.item.add_password.add_password_post_request_body import ( + AddPasswordPostRequestBody, +) +from msgraph.generated.models.api_application import ApiApplication +from msgraph.generated.models.application import Application +from msgraph.generated.models.password_credential import PasswordCredential +from msgraph.generated.models.permission_scope import PermissionScope +from msgraph.generated.models.service_principal import ServicePrincipal +from msgraph.generated.models.web_application import WebApplication + + +async def get_application(graph_client: GraphServiceClient, app_id: str) -> str | None: + """Get an application's object ID by its client/app ID.""" + try: + apps = await graph_client.applications.get() + if apps and apps.value: + for app in apps.value: + if app.app_id == app_id: + return app.id + except Exception: + pass + return None + + +def update_azd_env(name: str, val: str) -> None: + """Update an Azure Developer CLI environment variable.""" + subprocess.run(f'azd env set {name} "{val}"', shell=True) + + +def random_app_identifier() -> int: + """Generate a random identifier for the app name.""" + rand = random.Random() + rand.seed(datetime.datetime.now().timestamp()) + return rand.randint(1000, 100000) + + +async def create_application(graph_client: GraphServiceClient, request_app: Application) -> tuple[str, str]: + """Create an Entra ID application and its service principal.""" + app = await graph_client.applications.post(request_app) + if app is None: + raise ValueError("Failed to create application") + object_id = app.id + client_id = app.app_id + if object_id is None or client_id is None: + raise ValueError("Created application has no ID or client ID") + + # Create a service principal for the application + request_principal = ServicePrincipal(app_id=client_id, display_name=app.display_name) + await graph_client.service_principals.post(request_principal) + return object_id, client_id + + +async def add_client_secret(graph_client: GraphServiceClient, app_object_id: str) -> str: + """Add a client secret to an application.""" + request_password = AddPasswordPostRequestBody( + password_credential=PasswordCredential(display_name="FastMCPSecret"), + ) + password_credential = await graph_client.applications.by_application_id(app_object_id).add_password.post( + request_password + ) + if password_credential is None: + raise ValueError("Failed to create client secret") + if password_credential.secret_text is None: + raise ValueError("Created client secret has no secret text") + return password_credential.secret_text + + +def fastmcp_app_redirect_uris_update(redirect_uri: str) -> Application: + """ + Create an Application object with just redirect URIs for updating existing apps. + + This is used when we only need to update redirect URIs without touching permission scopes. + """ + # Include redirect URIs for VS Code MCP client (localhost ports and vscode.dev) + redirect_uris = [ + redirect_uri, + "https://vscode.dev/redirect", + ] + # Add common localhost ports used by VS Code for OAuth callbacks + for port in range(33418, 33428): + redirect_uris.append(f"http://127.0.0.1:{port}") + + return Application( + web=WebApplication( + redirect_uris=redirect_uris, + ), + ) + + +def fastmcp_app_registration(identifier: int, redirect_uri: str, scope_name: str = "mcp-access") -> Application: + """ + Create an Application object configured for FastMCP Azure OAuth. + + This creates a single app registration with: + - Web redirect URI for OAuth callback + - An exposed API scope for FastMCP + - Access token version 2 (required by FastMCP) + """ + # Include redirect URIs for VS Code MCP client (localhost ports and vscode.dev) + redirect_uris = [ + redirect_uri, + "https://vscode.dev/redirect", + ] + # Add common localhost ports used by VS Code for OAuth callbacks + for port in range(33418, 33428): + redirect_uris.append(f"http://127.0.0.1:{port}") + + return Application( + display_name=f"FastMCP Server App {identifier}", + sign_in_audience="AzureADMyOrg", # Single tenant - change if needed + web=WebApplication( + redirect_uris=redirect_uris, + ), + api=ApiApplication( + oauth2_permission_scopes=[ + PermissionScope( + id=uuid.UUID("{" + str(uuid.uuid4()) + "}"), + admin_consent_display_name="Access FastMCP Server", + admin_consent_description="Allows access to the FastMCP server as the signed-in user.", + user_consent_display_name="Access FastMCP Server", + user_consent_description="Allow access to the FastMCP server on your behalf", + is_enabled=True, + value=scope_name, + type="User", + ) + ], + requested_access_token_version=2, # Required by FastMCP + ), + ) + + +def update_app_with_identifier_uri(client_id: str) -> Application: + """Update application with identifier URI after we have the client ID.""" + return Application( + identifier_uris=[f"api://{client_id}"], + ) + + +async def create_or_update_fastmcp_app( + graph_client: GraphServiceClient, + redirect_uri: str, + scope_name: str = "mcp-access", +) -> None: + """Create or update a FastMCP app registration.""" + app_id_env_var = "FASTMCP_AUTH_AZURE_CLIENT_ID" + app_secret_env_var = "FASTMCP_AUTH_AZURE_CLIENT_SECRET" + + app_id = os.getenv(app_id_env_var, "no-id") + object_id = None + + if app_id != "no-id": + print(f"Checking if application {app_id} exists...") + object_id = await get_application(graph_client, app_id) + + identifier = random_app_identifier() + + if object_id: + print("Application already exists, updating redirect URIs...") + request_app = fastmcp_app_redirect_uris_update(redirect_uri) + await graph_client.applications.by_application_id(object_id).patch(request_app) + else: + print("Creating new FastMCP application registration...") + request_app = fastmcp_app_registration(identifier, redirect_uri, scope_name) + object_id, app_id = await create_application(graph_client, request_app) + update_azd_env(app_id_env_var, app_id) + print(f"Created application with Client ID: {app_id}") + + # Update with identifier URI now that we have the client ID + await graph_client.applications.by_application_id(object_id).patch(update_app_with_identifier_uri(app_id)) + print(f"Set Application ID URI to: api://{app_id}") + + # Create client secret if not already set + client_secret = os.getenv(app_secret_env_var, "no-secret") + if client_secret == "no-secret": + print("Adding client secret...") + client_secret = await add_client_secret(graph_client, object_id) + update_azd_env(app_secret_env_var, client_secret) + print("Client secret created and saved to environment.") + + +async def main(): + # Configuration - customize these as needed + base_url = os.getenv("FASTMCP_AUTH_AZURE_BASE_URL", "http://localhost:8000") + redirect_path = os.getenv("FASTMCP_AUTH_AZURE_REDIRECT_PATH", "/auth/callback") + redirect_uri = f"{base_url}{redirect_path}" + scope_name = os.getenv("FASTMCP_SCOPE_NAME", "mcp-access") + + auth_tenant = os.environ["AZURE_TENANT_ID"] + + print(f"Setting up FastMCP authentication for tenant: {auth_tenant}") + print(f"Redirect URI: {redirect_uri}") + print(f"Scope name: {scope_name}") + print() + + credential = AzureDeveloperCliCredential(tenant_id=auth_tenant) + scopes = ["https://graph.microsoft.com/.default"] + graph_client = GraphServiceClient(credentials=credential, scopes=scopes) + + await create_or_update_fastmcp_app( + graph_client, + redirect_uri=redirect_uri, + scope_name=scope_name, + ) + print("Setup complete!") + + +if __name__ == "__main__": + load_azd_env() + asyncio.run(main()) diff --git a/infra/fastmcp_auth_update.py b/infra/fastmcp_auth_update.py new file mode 100644 index 0000000..da58b6c --- /dev/null +++ b/infra/fastmcp_auth_update.py @@ -0,0 +1,103 @@ +""" +Post-provision script to update Azure app registration redirect URIs with deployed server URL. + +This script runs after provisioning to update the FastMCP app registration with the +actual deployed server URL as a redirect URI. +""" + +import asyncio +import os + +from azure.identity.aio import AzureDeveloperCliCredential +from dotenv_azd import load_azd_env +from msgraph import GraphServiceClient +from msgraph.generated.models.application import Application +from msgraph.generated.models.web_application import WebApplication + + +async def get_application(graph_client: GraphServiceClient, app_id: str) -> str | None: + """Get an application's object ID by its client/app ID.""" + try: + apps = await graph_client.applications.get() + if apps and apps.value: + for app in apps.value: + if app.app_id == app_id: + return app.id + return None + except Exception as e: + print(f"Error getting application: {e}") + return None + + +async def get_existing_redirect_uris(graph_client: GraphServiceClient, object_id: str) -> list[str]: + """Get existing redirect URIs from an application.""" + try: + app = await graph_client.applications.by_application_id(object_id).get() + if app and app.web and app.web.redirect_uris: + return list(app.web.redirect_uris) + return [] + except Exception as e: + print(f"Error getting existing redirect URIs: {e}") + return [] + + +async def main(): + load_azd_env() + + # Check if FastMCP auth is enabled + use_fastmcp_auth = os.getenv("USE_FASTMCP_AUTH", "false").lower() == "true" + if not use_fastmcp_auth: + print("FastMCP auth not enabled, skipping redirect URI update.") + return + + client_id = os.getenv("FASTMCP_AUTH_AZURE_CLIENT_ID") + if not client_id: + print("No FASTMCP_AUTH_AZURE_CLIENT_ID found, skipping redirect URI update.") + return + + # Get the deployed server URL from MCP_SERVER_URL output + server_url = os.getenv("MCP_SERVER_URL", "").rstrip("/mcp").rstrip("/") + if not server_url: + print("No MCP_SERVER_URL found, skipping redirect URI update.") + return + + auth_tenant = os.environ["AZURE_TENANT_ID"] + redirect_uri = f"{server_url}/auth/callback" + + print("Updating redirect URIs for FastMCP app registration...") + print(f" Client ID: {client_id}") + print(f" Server URL: {server_url}") + print(f" Redirect URI: {redirect_uri}") + + credential = AzureDeveloperCliCredential(tenant_id=auth_tenant) + scopes = ["https://graph.microsoft.com/.default"] + graph_client = GraphServiceClient(credentials=credential, scopes=scopes) + + # Get the application object ID + object_id = await get_application(graph_client, client_id) + if not object_id: + print(f"Could not find application with client ID {client_id}") + return + + # Get existing redirect URIs and add the deployed URL + existing_uris = await get_existing_redirect_uris(graph_client, object_id) + print(f" Existing redirect URIs: {len(existing_uris)}") + + # Add only the deployed server redirect URI to existing URIs + # (local/VS Code URIs are already set by fastmcp_auth_init.py during preprovision) + redirect_uris = set(existing_uris) + redirect_uris.add(redirect_uri) + + # Update the application + app = Application( + web=WebApplication( + redirect_uris=list(redirect_uris), + ), + ) + await graph_client.applications.by_application_id(object_id).patch(app) + print(f"Updated redirect URIs ({len(redirect_uris)} total)") + print("Redirect URI update complete!") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/infra/main.bicep b/infra/main.bicep index adf1774..50d694e 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -68,6 +68,16 @@ param usePrivateAcr bool = false @description('Flag to restrict Log Analytics public query access for increased security') param usePrivateLogAnalytics bool = false +@description('Flag to enable Azure/Entra ID OAuth Proxy authentication for the MCP server') +param useFastMcpAuth bool = false + +@description('Azure/Entra ID app registration client ID for OAuth Proxy - required when useEntraProxy is true') +param entraProxyClientId string = '' + +@secure() +@description('Azure/Entra ID app registration client secret for OAuth Proxy - required when useEntraProxy is true') +param entraProxyClientSecret string = '' + var resourceToken = toLower(uniqueString(subscription().id, name, location)) var tags = { 'azd-env-name': name } @@ -85,6 +95,8 @@ var openAiModelName = 'gpt-4o-mini' // Cosmos DB configuration var cosmosDbDatabaseName = 'expenses-database' var cosmosDbContainerName = 'expenses' +var cosmosDbOAuthContainerName = 'oauth-clients' +var cosmosDbUserContainerName = 'user-expenses' module openAi 'br/public:avm/res/cognitive-services/account:0.7.2' = { name: 'openai' @@ -158,6 +170,20 @@ module cosmosDb 'br/public:avm/res/document-db/database-account:0.6.1' = { '/category' ] } + { + name: cosmosDbUserContainerName + kind: 'Hash' + paths: [ + '/user_id' + ] + } + { + name: cosmosDbOAuthContainerName + kind: 'Hash' + paths: [ + '/collection' + ] + } ] } ] @@ -708,12 +734,19 @@ module server 'server.bicep' = { cosmosDbAccount: cosmosDb.outputs.name cosmosDbDatabase: cosmosDbDatabaseName cosmosDbContainer: cosmosDbContainerName + cosmosDbUserContainer: cosmosDbUserContainerName + cosmosDbOAuthContainer: cosmosDbOAuthContainerName applicationInsightsConnectionString: useMonitoring ? applicationInsights!.outputs.connectionString : '' exists: serverExists // Keycloak authentication configuration (only when enabled) keycloakRealmUrl: useKeycloak ? '${keycloak!.outputs.uri}/realms/${keycloakRealmName}' : '' mcpServerBaseUrl: useKeycloak ? 'https://mcproutes.${containerApps.outputs.defaultDomain}' : '' keycloakMcpServerAudience: keycloakMcpServerAudience + // Azure/Entra ID OAuth Proxy authentication configuration (only when enabled) + entraProxyClientId: useFastMcpAuth ? entraProxyClientId : '' + entraProxyClientSecret: useFastMcpAuth ? entraProxyClientSecret : '' + entraProxyBaseUrl: useFastMcpAuth ? 'https://${replace('${take(prefix,15)}-server', '--', '-')}.${containerApps.outputs.defaultDomain}' : '' + tenantId: useFastMcpAuth ? tenant().tenantId : '' } } @@ -848,14 +881,24 @@ output AZURE_COSMOSDB_ACCOUNT string = cosmosDb.outputs.name output AZURE_COSMOSDB_ENDPOINT string = cosmosDb.outputs.endpoint output AZURE_COSMOSDB_DATABASE string = cosmosDbDatabaseName output AZURE_COSMOSDB_CONTAINER string = cosmosDbContainerName +output AZURE_COSMOSDB_USER_CONTAINER string = cosmosDbUserContainerName +output AZURE_COSMOSDB_OAUTH_CONTAINER string = cosmosDbOAuthContainerName // We typically do not output sensitive values, but App Insights connection strings are not considered highly sensitive output APPLICATIONINSIGHTS_CONNECTION_STRING string = useMonitoring ? applicationInsights!.outputs.connectionString : '' +// Entry selection for MCP server (auth-enabled when Keycloak or FastMCP auth is used) +// Use server module's computed entry selection (checks URLs/clientId) +output MCP_ENTRY string = server.outputs.mcpEntry + +// Output the deployed MCP server HOST only (no /mcp). Python app will append /mcp as needed. +output MCP_SERVER_URL string = useKeycloak ? httpRoutes!.outputs.routeConfigUrl : server.outputs.uri + // Keycloak and MCP Server routing outputs (only populated when useKeycloak is true) -output HTTP_ROUTES_URL string = useKeycloak ? httpRoutes!.outputs.routeConfigUrl : '' -output KEYCLOAK_URL string = useKeycloak ? '${httpRoutes!.outputs.routeConfigUrl}/auth' : '' output KEYCLOAK_REALM_URL string = useKeycloak ? '${httpRoutes!.outputs.routeConfigUrl}/auth/realms/${keycloakRealmName}' : '' -output MCP_SERVER_URL string = useKeycloak ? httpRoutes!.outputs.routeConfigUrl : server.outputs.uri output KEYCLOAK_ADMIN_CONSOLE string = useKeycloak ? '${httpRoutes!.outputs.routeConfigUrl}/auth/admin' : '' output KEYCLOAK_DIRECT_URL string = keycloak.outputs.uri + +// Auth feature flags for env scripts +output USE_KEYCLOAK bool = useKeycloak +output USE_FASTMCP_AUTH bool = useFastMcpAuth diff --git a/infra/main.parameters.json b/infra/main.parameters.json index 8622eeb..fa13baa 100644 --- a/infra/main.parameters.json +++ b/infra/main.parameters.json @@ -49,6 +49,16 @@ }, "keycloakMcpServerAudience": { "value": "${KEYCLOAK_MCP_SERVER_AUDIENCE=mcp-server}" + }, + "useFastMcpAuth": { + "value": "${USE_FASTMCP_AUTH=false}" + }, + + "entraProxyClientId": { + "value": "${FASTMCP_AUTH_AZURE_CLIENT_ID}" + }, + "entraProxyClientSecret": { + "value": "${FASTMCP_AUTH_AZURE_CLIENT_SECRET}" } } } diff --git a/infra/server.bicep b/infra/server.bicep index caad60d..b510fbe 100644 --- a/infra/server.bicep +++ b/infra/server.bicep @@ -12,12 +12,21 @@ param openAiEndpoint string param cosmosDbAccount string param cosmosDbDatabase string param cosmosDbContainer string +param cosmosDbUserContainer string +param cosmosDbOAuthContainer string param applicationInsightsConnectionString string = '' param keycloakRealmUrl string = '' param mcpServerBaseUrl string = '' param keycloakMcpServerAudience string = 'mcp-server' +param entraProxyClientId string = '' +@secure() +param entraProxyClientSecret string = '' +param entraProxyBaseUrl string = '' +param tenantId string = '' // Base environment variables +// Select MCP entrypoint based on configured auth (Keycloak or FastMCP Azure auth) +var mcpEntry = (!empty(keycloakRealmUrl) || !empty(entraProxyClientId)) ? 'auth' : 'deployed' var baseEnv = [ { name: 'AZURE_OPENAI_CHAT_DEPLOYMENT' @@ -47,11 +56,23 @@ var baseEnv = [ name: 'AZURE_COSMOSDB_CONTAINER' value: cosmosDbContainer } + { + name: 'AZURE_COSMOSDB_USER_CONTAINER' + value: cosmosDbUserContainer + } + { + name: 'AZURE_COSMOSDB_OAUTH_CONTAINER' + value: cosmosDbOAuthContainer + } // We typically store sensitive values in secrets, but App Insights connection strings are not considered highly sensitive { name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' value: applicationInsightsConnectionString } + { + name: 'MCP_ENTRY' + value: mcpEntry + } ] // Keycloak authentication environment variables (only added when configured) @@ -70,6 +91,34 @@ var keycloakEnv = !empty(keycloakRealmUrl) ? [ } ] : [] +// Azure/Entra ID OAuth Proxy environment variables (only added when configured) +var entraProxyEnv = !empty(entraProxyClientId) ? [ + { + name: 'FASTMCP_AUTH_AZURE_CLIENT_ID' + value: entraProxyClientId + } + { + name: 'FASTMCP_AUTH_AZURE_CLIENT_SECRET' + secretRef: 'entra-proxy-client-secret' + } + { + name: 'FASTMCP_AUTH_AZURE_BASE_URL' + value: entraProxyBaseUrl + } + { + name: 'AZURE_TENANT_ID' + value: tenantId + } +] : [] + +// Secrets for sensitive values +var entraProxySecrets = !empty(entraProxyClientSecret) ? [ + { + name: 'entra-proxy-client-secret' + value: entraProxyClientSecret + } +] : [] + resource serverIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { name: identityName @@ -87,7 +136,8 @@ module app 'core/host/container-app-upsert.bicep' = { containerAppsEnvironmentName: containerAppsEnvironmentName containerRegistryName: containerRegistryName ingressEnabled: true - env: concat(baseEnv, keycloakEnv) + env: concat(baseEnv, keycloakEnv, entraProxyEnv) + secrets: entraProxySecrets targetPort: 8000 probes: [ { @@ -128,3 +178,4 @@ output name string = app.outputs.name output hostName string = app.outputs.hostName output uri string = app.outputs.uri output imageName string = app.outputs.imageName +output mcpEntry string = mcpEntry diff --git a/infra/write_env.ps1 b/infra/write_env.ps1 index 5f03da8..f86aaea 100644 --- a/infra/write_env.ps1 +++ b/infra/write_env.ps1 @@ -11,10 +11,21 @@ Add-Content -Path $ENV_FILE_PATH -Value "AZURE_TENANT_ID=$(azd env get-value AZU Add-Content -Path $ENV_FILE_PATH -Value "AZURE_COSMOSDB_ACCOUNT=$(azd env get-value AZURE_COSMOSDB_ACCOUNT)" Add-Content -Path $ENV_FILE_PATH -Value "AZURE_COSMOSDB_DATABASE=$(azd env get-value AZURE_COSMOSDB_DATABASE)" Add-Content -Path $ENV_FILE_PATH -Value "AZURE_COSMOSDB_CONTAINER=$(azd env get-value AZURE_COSMOSDB_CONTAINER)" +Add-Content -Path $ENV_FILE_PATH -Value "AZURE_COSMOSDB_USER_CONTAINER=$(azd env get-value AZURE_COSMOSDB_USER_CONTAINER)" +Add-Content -Path $ENV_FILE_PATH -Value "AZURE_COSMOSDB_OAUTH_CONTAINER=$(azd env get-value AZURE_COSMOSDB_OAUTH_CONTAINER)" Add-Content -Path $ENV_FILE_PATH -Value "APPLICATIONINSIGHTS_CONNECTION_STRING=$(azd env get-value APPLICATIONINSIGHTS_CONNECTION_STRING)" +# Auth feature flags +Add-Content -Path $ENV_FILE_PATH -Value "USE_FASTMCP_AUTH=$(azd env get-value USE_FASTMCP_AUTH)" +Add-Content -Path $ENV_FILE_PATH -Value "USE_KEYCLOAK=$(azd env get-value USE_KEYCLOAK)" $KEYCLOAK_REALM_URL = azd env get-value KEYCLOAK_REALM_URL 2>$null if ($KEYCLOAK_REALM_URL -and $KEYCLOAK_REALM_URL -ne "") { Add-Content -Path $ENV_FILE_PATH -Value "KEYCLOAK_REALM_URL=$KEYCLOAK_REALM_URL" } -Add-Content -Path $ENV_FILE_PATH -Value "MCP_SERVER_URL=$(azd env get-value MCP_SERVER_URL)/mcp" +$FASTMCP_AUTH_AZURE_CLIENT_ID = azd env get-value FASTMCP_AUTH_AZURE_CLIENT_ID 2>$null +if ($FASTMCP_AUTH_AZURE_CLIENT_ID -and $FASTMCP_AUTH_AZURE_CLIENT_ID -ne "") { + Add-Content -Path $ENV_FILE_PATH -Value "FASTMCP_AUTH_AZURE_CLIENT_ID=$FASTMCP_AUTH_AZURE_CLIENT_ID" + Add-Content -Path $ENV_FILE_PATH -Value "FASTMCP_AUTH_AZURE_CLIENT_SECRET=$(azd env get-value FASTMCP_AUTH_AZURE_CLIENT_SECRET)" +} +Add-Content -Path $ENV_FILE_PATH -Value "MCP_ENTRY=$(azd env get-value MCP_ENTRY)" +Add-Content -Path $ENV_FILE_PATH -Value "MCP_SERVER_URL=$(azd env get-value MCP_SERVER_URL)" Add-Content -Path $ENV_FILE_PATH -Value "API_HOST=azure" diff --git a/infra/write_env.sh b/infra/write_env.sh index d4de852..bc0b00a 100755 --- a/infra/write_env.sh +++ b/infra/write_env.sh @@ -15,10 +15,21 @@ echo "AZURE_TENANT_ID=$(azd env get-value AZURE_TENANT_ID)" >> "$ENV_FILE_PATH" echo "AZURE_COSMOSDB_ACCOUNT=$(azd env get-value AZURE_COSMOSDB_ACCOUNT)" >> "$ENV_FILE_PATH" echo "AZURE_COSMOSDB_DATABASE=$(azd env get-value AZURE_COSMOSDB_DATABASE)" >> "$ENV_FILE_PATH" echo "AZURE_COSMOSDB_CONTAINER=$(azd env get-value AZURE_COSMOSDB_CONTAINER)" >> "$ENV_FILE_PATH" +echo "AZURE_COSMOSDB_USER_CONTAINER=$(azd env get-value AZURE_COSMOSDB_USER_CONTAINER)" >> "$ENV_FILE_PATH" +echo "AZURE_COSMOSDB_OAUTH_CONTAINER=$(azd env get-value AZURE_COSMOSDB_OAUTH_CONTAINER)" >> "$ENV_FILE_PATH" echo "APPLICATIONINSIGHTS_CONNECTION_STRING=$(azd env get-value APPLICATIONINSIGHTS_CONNECTION_STRING)" >> "$ENV_FILE_PATH" +# Auth feature flags +echo "USE_FASTMCP_AUTH=$(azd env get-value USE_FASTMCP_AUTH)" >> "$ENV_FILE_PATH" +echo "USE_KEYCLOAK=$(azd env get-value USE_KEYCLOAK)" >> "$ENV_FILE_PATH" KEYCLOAK_REALM_URL=$(azd env get-value KEYCLOAK_REALM_URL 2>/dev/null || echo "") if [ -n "$KEYCLOAK_REALM_URL" ] && [ "$KEYCLOAK_REALM_URL" != "" ]; then echo "KEYCLOAK_REALM_URL=${KEYCLOAK_REALM_URL}" >> "$ENV_FILE_PATH" fi -echo "MCP_SERVER_URL=$(azd env get-value MCP_SERVER_URL)/mcp" >> "$ENV_FILE_PATH" +FASTMCP_AUTH_AZURE_CLIENT_ID=$(azd env get-value FASTMCP_AUTH_AZURE_CLIENT_ID 2>/dev/null || echo "") +if [ -n "$FASTMCP_AUTH_AZURE_CLIENT_ID" ] && [ "$FASTMCP_AUTH_AZURE_CLIENT_ID" != "" ]; then + echo "FASTMCP_AUTH_AZURE_CLIENT_ID=${FASTMCP_AUTH_AZURE_CLIENT_ID}" >> "$ENV_FILE_PATH" + echo "FASTMCP_AUTH_AZURE_CLIENT_SECRET=$(azd env get-value FASTMCP_AUTH_AZURE_CLIENT_SECRET)" >> "$ENV_FILE_PATH" +fi +echo "MCP_ENTRY=$(azd env get-value MCP_ENTRY)" >> "$ENV_FILE_PATH" +echo "MCP_SERVER_URL=$(azd env get-value MCP_SERVER_URL)" >> "$ENV_FILE_PATH" echo "API_HOST=azure" >> "$ENV_FILE_PATH" diff --git a/pyproject.toml b/pyproject.toml index c7196ff..6ee9a4e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,11 +5,13 @@ description = "Demonstration of Python FastMCP servers" readme = "README.md" requires-python = "==3.13.*" dependencies = [ - "fastmcp>=2.12.5", + "fastmcp>=2.13.3", "debugpy>=1.8.0", "langchain-core>=0.3.0", "mcp>=1.3.0", "azure-identity>=1.25.1", + "msgraph-sdk>=1.0.0", + "dotenv-azd>=0.1.0", "langchain==1.0.0a5", "langchain-openai>=1.0.1", "langchain-mcp-adapters>=0.1.11", diff --git a/servers/Dockerfile b/servers/Dockerfile index 89b474c..0fe6feb 100644 --- a/servers/Dockerfile +++ b/servers/Dockerfile @@ -37,4 +37,6 @@ ENV PATH="/code/.venv/bin:$PATH" EXPOSE 8000 -ENTRYPOINT ["uvicorn", "deployed_mcp:app", "--host", "0.0.0.0", "--port", "8000"] +ENV MCP_ENTRY=deployed + +CMD ["uvicorn", "${MCP_ENTRY}_mcp:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/servers/auth_mcp.py b/servers/auth_mcp.py new file mode 100644 index 0000000..99d540c --- /dev/null +++ b/servers/auth_mcp.py @@ -0,0 +1,241 @@ +"""Run with: cd servers && uvicorn auth_mcp:app --host 0.0.0.0 --port 8000""" + +import logging +import os +import uuid +from datetime import date +from enum import Enum +from typing import Annotated + +import logfire +from azure.core.settings import settings +from azure.cosmos.aio import CosmosClient +from azure.identity.aio import ChainedTokenCredential, DefaultAzureCredential, ManagedIdentityCredential +from azure.monitor.opentelemetry import configure_azure_monitor +from cosmosdb_store import CosmosDBStore +from dotenv import load_dotenv +from fastmcp import Context, FastMCP +from fastmcp.server.auth import RemoteAuthProvider +from fastmcp.server.auth.providers.azure import AzureProvider +from fastmcp.server.auth.providers.jwt import JWTVerifier +from fastmcp.server.dependencies import get_access_token +from fastmcp.server.middleware import Middleware, MiddlewareContext +from key_value.aio.stores.memory import MemoryStore +from opentelemetry.instrumentation.starlette import StarletteInstrumentor +from opentelemetry_middleware import OpenTelemetryMiddleware +from pydantic import AnyHttpUrl +from rich.console import Console +from rich.logging import RichHandler +from starlette.responses import JSONResponse + +RUNNING_IN_PRODUCTION = os.getenv("RUNNING_IN_PRODUCTION", "false").lower() == "true" + +if not RUNNING_IN_PRODUCTION: + load_dotenv(override=True) + +logging.basicConfig( + level=logging.WARNING, + format="%(message)s", + handlers=[ + RichHandler( + console=Console(stderr=True), + show_path=False, + show_level=False, + rich_tracebacks=True, + ) + ], +) +logger = logging.getLogger("ExpensesMCP") +logger.setLevel(logging.INFO) + +# Configure Azure SDK OpenTelemetry to use OTEL +settings.tracing_implementation = "opentelemetry" + +# Configure OpenTelemetry exporters (App Insights or Logfire) +if os.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING"): + logger.info("Setting up Azure Monitor instrumentation") + configure_azure_monitor() +elif os.getenv("LOGFIRE_PROJECT_NAME"): + logger.info("Setting up Logfire instrumentation") + logfire.configure(service_name="expenses-mcp", send_to_logfire=True) + +# Configure Cosmos DB client +azure_credential = ChainedTokenCredential(ManagedIdentityCredential(), DefaultAzureCredential()) +cosmos_client = CosmosClient( + url=f"https://{os.environ['AZURE_COSMOSDB_ACCOUNT']}.documents.azure.com:443/", + credential=azure_credential, +) +cosmos_db = cosmos_client.get_database_client(os.environ["AZURE_COSMOSDB_DATABASE"]) +cosmos_container = cosmos_db.get_container_client(os.environ["AZURE_COSMOSDB_USER_CONTAINER"]) + +# Configure authentication provider +if RUNNING_IN_PRODUCTION: + MCP_SERVER_HOST = os.environ["MCP_SERVER_URL"] +else: + MCP_SERVER_HOST = "http://localhost:8000" +auth = None +if os.getenv("USE_FASTMCP_AUTH", "false").lower() == "true": + # Azure/Entra ID authentication using AzureProvider + # When running locally, always use localhost for base URL (OAuth redirects need to match) + oauth_client_store = None + if RUNNING_IN_PRODUCTION: + oauth_container = cosmos_db.get_container_client(os.environ["AZURE_COSMOSDB_OAUTH_CONTAINER"]) + oauth_client_store = CosmosDBStore(container=oauth_container, default_collection="oauth-clients") + else: + oauth_client_store = MemoryStore() + + auth = AzureProvider( + client_id=os.environ["FASTMCP_AUTH_AZURE_CLIENT_ID"], + client_secret=os.environ["FASTMCP_AUTH_AZURE_CLIENT_SECRET"], + tenant_id=os.environ["AZURE_TENANT_ID"], + base_url=MCP_SERVER_HOST, + required_scopes=["mcp-access"], + client_storage=oauth_client_store, + ) + logger.info( + "Using Entra OAuth Proxy for server %s and %s storage", MCP_SERVER_HOST, type(oauth_client_store).__name__ + ) +elif os.getenv("USE_KEYCLOAK", "false").lower() == "true": + # Keycloak authentication using RemoteAuthProvider with JWT verification + KEYCLOAK_REALM_URL = os.environ["KEYCLOAK_REALM_URL"] + token_verifier = JWTVerifier( + jwks_uri=f"{KEYCLOAK_REALM_URL}/protocol/openid-connect/certs", + issuer=KEYCLOAK_REALM_URL, + audience=os.getenv("KEYCLOAK_MCP_SERVER_AUDIENCE", "mcp-server"), + ) + auth = RemoteAuthProvider( + token_verifier=token_verifier, + authorization_servers=[AnyHttpUrl(KEYCLOAK_REALM_URL)], + base_url=f"{MCP_SERVER_HOST}/mcp", + ) + logger.info("Using Keycloak auth for server %s and realm %s", MCP_SERVER_HOST, KEYCLOAK_REALM_URL) +else: + logger.error("No authentication configured for MCP server, exiting.") + raise SystemExit(1) + + +# Middleware to populate user_id in per-request context state +class UserAuthMiddleware(Middleware): + def _get_user_id(self): + token = get_access_token() + if not (token and hasattr(token, "claims")): + return None + # Return 'oid' claim if present (for Entra), otherwise fallback to 'sub' (for KeyCloak) + return token.claims.get("oid", token.claims.get("sub")) + + async def on_call_tool(self, context: MiddlewareContext, call_next): + user_id = self._get_user_id() + if context.fastmcp_context is not None: + context.fastmcp_context.set_state("user_id", user_id) + return await call_next(context) + + async def on_read_resource(self, context: MiddlewareContext, call_next): + user_id = self._get_user_id() + if context.fastmcp_context is not None: + context.fastmcp_context.set_state("user_id", user_id) + return await call_next(context) + + +# Create the MCP server +mcp = FastMCP("Expenses Tracker", auth=auth, middleware=[OpenTelemetryMiddleware("ExpensesMCP"), UserAuthMiddleware()]) + +# Configure Starlette middleware for OpenTelemetry +app = mcp.http_app() +StarletteInstrumentor.instrument_app(app) + +"""Expense tracking MCP server with authentication and Cosmos DB storage.""" + + +class PaymentMethod(Enum): + AMEX = "amex" + VISA = "visa" + CASH = "cash" + + +class Category(Enum): + FOOD = "food" + TRANSPORT = "transport" + ENTERTAINMENT = "entertainment" + SHOPPING = "shopping" + GADGET = "gadget" + OTHER = "other" + + +@mcp.tool +async def add_user_expense( + date: Annotated[date, "Date of the expense in YYYY-MM-DD format"], + amount: Annotated[float, "Positive numeric amount of the expense"], + category: Annotated[Category, "Category label"], + description: Annotated[str, "Human-readable description of the expense"], + payment_method: Annotated[PaymentMethod, "Payment method used"], + ctx: Context, +): + """Add a new expense to Cosmos DB.""" + if amount <= 0: + return "Error: Amount must be positive" + + date_iso = date.isoformat() + logger.info(f"Adding expense: ${amount} for {description} on {date_iso}") + + try: + # Read user_id stored by middleware + user_id = ctx.get_state("user_id") + if not user_id: + return "Error: Authentication required (no user_id present)" + expense_id = str(uuid.uuid4()) + expense_item = { + "id": expense_id, + "user_id": user_id, + "date": date_iso, + "amount": amount, + "category": category.value, + "description": description, + "payment_method": payment_method.value, + } + await cosmos_container.create_item(body=expense_item) + return f"Successfully added expense: ${amount} for {description} on {date_iso}" + + except Exception as e: + logger.error(f"Error adding expense: {str(e)}") + return f"Error: Unable to add expense - {str(e)}" + + +@mcp.tool +async def get_user_expenses(ctx: Context): + """Get the authenticated user's expense data from Cosmos DB.""" + + try: + user_id = ctx.get_state("user_id") + if not user_id: + return "Error: Authentication required (no user_id present)" + query = "SELECT * FROM c WHERE c.user_id = @uid ORDER BY c.date DESC" + parameters = [{"name": "@uid", "value": user_id}] + expenses_data = [] + + async for item in cosmos_container.query_items(query=query, parameters=parameters, partition_key=user_id): + expenses_data.append(item) + + if not expenses_data: + return "No expenses found." + + csv_content = f"Expense data ({len(expenses_data)} entries):\n\n" + for expense in expenses_data: + csv_content += ( + f"Date: {expense.get('date', 'N/A')}, " + f"Amount: ${expense.get('amount', 0)}, " + f"Category: {expense.get('category', 'N/A')}, " + f"Description: {expense.get('description', 'N/A')}, " + f"Payment: {expense.get('payment_method', 'N/A')}\n" + ) + + return csv_content + + except Exception as e: + logger.error(f"Error reading expenses: {str(e)}") + return f"Error: Unable to retrieve expense data - {str(e)}" + + +@mcp.custom_route("/health", methods=["GET"]) +async def health_check(_request): + """Health check endpoint for service availability.""" + return JSONResponse({"status": "healthy", "service": "mcp-server"}) diff --git a/servers/cosmosdb_store.py b/servers/cosmosdb_store.py new file mode 100644 index 0000000..68dbc75 --- /dev/null +++ b/servers/cosmosdb_store.py @@ -0,0 +1,262 @@ +""" +Azure Cosmos DB Store for py-key-value AsyncKeyValue protocol. + +This module provides a Cosmos DB implementation of the AsyncKeyValue protocol, +suitable for use with FastMCP's OAuth proxy client storage. + +Based on the proposal in https://github.com/strawgate/py-key-value/issues/44 +""" + +import logging +from collections.abc import Mapping, Sequence +from datetime import datetime, timezone +from typing import Any, SupportsFloat + +from azure.cosmos.aio import ContainerProxy +from azure.cosmos.exceptions import CosmosResourceNotFoundError + +logger = logging.getLogger(__name__) + + +class ManagedEntry: + """A managed entry with value and expiration tracking.""" + + def __init__( + self, + value: dict[str, Any], + created_at: datetime | None = None, + expires_at: datetime | None = None, + ): + self.value = value + self.created_at = created_at or datetime.now(timezone.utc) + self.expires_at = expires_at + + @property + def is_expired(self) -> bool: + """Check if the entry has expired.""" + if self.expires_at is None: + return False + return datetime.now(timezone.utc) > self.expires_at + + @property + def ttl_seconds(self) -> float | None: + """Get remaining TTL in seconds, or None if no expiration.""" + if self.expires_at is None: + return None + remaining = (self.expires_at - datetime.now(timezone.utc)).total_seconds() + return max(0.0, remaining) + + def to_dict(self) -> dict[str, Any]: + """Serialize to dictionary for storage.""" + return { + "value": self.value, + "created_at": self.created_at.isoformat() if self.created_at else None, + "expires_at": self.expires_at.isoformat() if self.expires_at else None, + } + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> "ManagedEntry": + """Deserialize from dictionary.""" + created_at = None + expires_at = None + if data.get("created_at"): + created_at = datetime.fromisoformat(data["created_at"]) + if data.get("expires_at"): + expires_at = datetime.fromisoformat(data["expires_at"]) + return cls( + value=data.get("value", {}), + created_at=created_at, + expires_at=expires_at, + ) + + +class CosmosDBStore: + """ + Azure Cosmos DB implementation of the AsyncKeyValue protocol. + + This store uses Cosmos DB's NoSQL API to store key-value pairs. + Documents are stored with: + - id: The key + - collection: Partition key for logical grouping + - value: The stored value (as JSON) + - created_at: Creation timestamp + - expires_at: Expiration timestamp (optional) + - ttl: Cosmos DB native TTL in seconds (optional) + + Usage: + from azure.cosmos.aio import CosmosClient + from azure.identity.aio import DefaultAzureCredential + + credential = DefaultAzureCredential() + cosmos_client = CosmosClient(url="https://...", credential=credential) + container = cosmos_client.get_database_client("mydb").get_container_client("oauth-clients") + + store = CosmosDBStore(container=container) + + # Use with FastMCP OAuth proxy + auth = AzureProvider( + client_id="...", + client_secret="...", + client_storage=store, + ) + """ + + def __init__( + self, + container: ContainerProxy, + default_collection: str = "default", + ): + """ + Initialize the Cosmos DB store. + + Args: + container: An Azure Cosmos DB container proxy (async). + default_collection: Default collection/partition key to use. + """ + self._container = container + self.default_collection = default_collection + + def _make_document_id(self, collection: str, key: str) -> str: + """Create a unique document ID from collection and key.""" + # Use a compound key to ensure uniqueness across collections + return f"{collection}:{key}" + + async def get( + self, + key: str, + *, + collection: str | None = None, + ) -> dict[str, Any] | None: + """Retrieve a value by key from the specified collection.""" + collection = collection or self.default_collection + doc_id = self._make_document_id(collection, key) + + try: + item = await self._container.read_item(item=doc_id, partition_key=collection) + entry = ManagedEntry.from_dict(item.get("entry", {})) + + if entry.is_expired: + # Clean up expired entry + await self.delete(key=key, collection=collection) + return None + + return dict(entry.value) + except CosmosResourceNotFoundError: + return None + except Exception as e: + logger.error(f"Error reading from Cosmos DB: {e}") + return None + + async def get_many(self, keys: Sequence[str], *, collection: str | None = None) -> list[dict[str, Any] | None]: + """Retrieve multiple values by key from the specified collection.""" + return [await self.get(key=key, collection=collection) for key in keys] + + async def ttl(self, key: str, *, collection: str | None = None) -> tuple[dict[str, Any] | None, float | None]: + """Retrieve the value and TTL information for a key.""" + collection = collection or self.default_collection + doc_id = self._make_document_id(collection, key) + + try: + item = await self._container.read_item(item=doc_id, partition_key=collection) + entry = ManagedEntry.from_dict(item.get("entry", {})) + + if entry.is_expired: + await self.delete(key=key, collection=collection) + return (None, None) + + return (dict(entry.value), entry.ttl_seconds) + except CosmosResourceNotFoundError: + return (None, None) + except Exception as e: + logger.error(f"Error reading TTL from Cosmos DB: {e}") + return (None, None) + + async def ttl_many( + self, keys: Sequence[str], *, collection: str | None = None + ) -> list[tuple[dict[str, Any] | None, float | None]]: + """Retrieve multiple values and TTL information by key.""" + return [await self.ttl(key=key, collection=collection) for key in keys] + + async def put( + self, + key: str, + value: Mapping[str, Any], + *, + collection: str | None = None, + ttl: SupportsFloat | None = None, + ) -> None: + """Store a key-value pair in the specified collection with optional TTL.""" + collection = collection or self.default_collection + doc_id = self._make_document_id(collection, key) + + now = datetime.now(timezone.utc) + expires_at = None + cosmos_ttl = None + + if ttl is not None: + ttl_seconds = float(ttl) + if ttl_seconds > 0: + from datetime import timedelta + + expires_at = now + timedelta(seconds=ttl_seconds) + cosmos_ttl = int(ttl_seconds) + + entry = ManagedEntry( + value=dict(value), + created_at=now, + expires_at=expires_at, + ) + + document = { + "id": doc_id, + "collection": collection, + "key": key, + "entry": entry.to_dict(), + } + + # Add Cosmos DB native TTL if specified + if cosmos_ttl is not None: + document["ttl"] = cosmos_ttl + + try: + await self._container.upsert_item(body=document) + except Exception as e: + logger.error(f"Error writing to Cosmos DB: {e}") + raise + + async def put_many( + self, + keys: Sequence[str], + values: Sequence[Mapping[str, Any]], + *, + collection: str | None = None, + ttl: SupportsFloat | None = None, + ) -> None: + """Store multiple key-value pairs in the specified collection.""" + if len(keys) != len(values): + raise ValueError("Number of keys must match number of values") + + for key, value in zip(keys, values): + await self.put(key=key, value=value, collection=collection, ttl=ttl) + + async def delete(self, key: str, *, collection: str | None = None) -> bool: + """Delete a key-value pair from the specified collection.""" + collection = collection or self.default_collection + doc_id = self._make_document_id(collection, key) + + try: + await self._container.delete_item(item=doc_id, partition_key=collection) + return True + except CosmosResourceNotFoundError: + return False + except Exception as e: + logger.error(f"Error deleting from Cosmos DB: {e}") + return False + + async def delete_many(self, keys: Sequence[str], *, collection: str | None = None) -> int: + """Delete multiple key-value pairs from the specified collection.""" + deleted_count = 0 + for key in keys: + if await self.delete(key=key, collection=collection): + deleted_count += 1 + return deleted_count diff --git a/servers/deployed_mcp.py b/servers/deployed_mcp.py index 6e400d5..4a2baa6 100644 --- a/servers/deployed_mcp.py +++ b/servers/deployed_mcp.py @@ -14,10 +14,8 @@ from azure.monitor.opentelemetry import configure_azure_monitor from dotenv import load_dotenv from fastmcp import FastMCP -from fastmcp.server.auth import RemoteAuthProvider -from fastmcp.server.auth.providers.jwt import JWTVerifier +from fastmcp.server.dependencies import get_access_token from opentelemetry.instrumentation.starlette import StarletteInstrumentor -from pydantic import AnyHttpUrl from starlette.responses import JSONResponse try: @@ -50,28 +48,8 @@ AZURE_COSMOSDB_CONTAINER = os.environ["AZURE_COSMOSDB_CONTAINER"] AZURE_CLIENT_ID = os.getenv("AZURE_CLIENT_ID", "") -# Optional: Keycloak authentication (enabled if KEYCLOAK_REALM_URL is set) -KEYCLOAK_REALM_URL = os.getenv("KEYCLOAK_REALM_URL") -MCP_SERVER_BASE_URL = os.getenv("MCP_SERVER_BASE_URL") -KEYCLOAK_MCP_SERVER_AUDIENCE = os.getenv("KEYCLOAK_MCP_SERVER_AUDIENCE", "mcp-server") -auth = None -if KEYCLOAK_REALM_URL and MCP_SERVER_BASE_URL: - token_verifier = JWTVerifier( - jwks_uri=f"{KEYCLOAK_REALM_URL}/protocol/openid-connect/certs", - issuer=KEYCLOAK_REALM_URL, - audience=KEYCLOAK_MCP_SERVER_AUDIENCE, - ) - auth = RemoteAuthProvider( - token_verifier=token_verifier, - authorization_servers=[AnyHttpUrl(KEYCLOAK_REALM_URL)], - base_url=MCP_SERVER_BASE_URL, - ) - logger.info(f"Keycloak auth enabled: realm={KEYCLOAK_REALM_URL}, audience={KEYCLOAK_MCP_SERVER_AUDIENCE}") -else: - logger.info("No authentication configured (set KEYCLOAK_REALM_URL and MCP_SERVER_BASE_URL to enable)") - -# Configure Cosmos DB client and container +# Configure Cosmos DB client and container for expenses data if RUNNING_IN_PRODUCTION and AZURE_CLIENT_ID: credential = ManagedIdentityCredential(client_id=AZURE_CLIENT_ID) else: @@ -85,10 +63,33 @@ cosmos_container = cosmos_db.get_container_client(AZURE_COSMOSDB_CONTAINER) logger.info(f"Connected to Cosmos DB: {AZURE_COSMOSDB_ACCOUNT}") +# No OAuth client storage used in non-auth deployment + +# Authentication disabled in this deployment +auth = None + +# Create the MCP server mcp = FastMCP("Expenses Tracker", auth=auth) + +# Add OpenTelemetry middleware for distributed tracing mcp.add_middleware(OpenTelemetryMiddleware("ExpensesMCP")) +def get_user_id_from_token() -> str | None: + """Extract the authenticated user's object ID (oid) from access token claims. + + Returns None if no token is present or oid is missing. + """ + try: + token = get_access_token() + if token and hasattr(token, "claims"): + claims = token.claims or {} + return claims.get("oid") + return None + except Exception: + return None + + @mcp.custom_route("/health", methods=["GET"]) async def health_check(_request): """ @@ -135,6 +136,9 @@ async def add_expense( logger.info(f"Adding expense: ${amount} for {description} on {date_iso}") try: + # Extract user_id from token via helper (no verbose logging) + user_id = get_user_id_from_token() + expense_id = str(uuid.uuid4()) expense_item = { "id": expense_id, @@ -145,6 +149,10 @@ async def add_expense( "payment_method": payment_method.value, } + # Attach user context based on token claims when available + if user_id: + expense_item["user_id"] = user_id + await cosmos_container.create_item(body=expense_item) return f"Successfully added expense: ${amount} for {description} on {date_iso}" @@ -156,13 +164,12 @@ async def add_expense( @mcp.resource("resource://expenses") async def get_expenses_data(): """Get raw expense data from Cosmos DB.""" - logger.info("Expenses data accessed") try: query = "SELECT * FROM c ORDER BY c.date DESC" expenses_data = [] - async for item in cosmos_container.query_items(query=query, enable_cross_partition_query=True): + async for item in cosmos_container.query_items(query=query): expenses_data.append(item) if not expenses_data: diff --git a/servers/expenses.csv b/servers/expenses.csv index 7caec9d..45aa7a4 100644 --- a/servers/expenses.csv +++ b/servers/expenses.csv @@ -19,3 +19,4 @@ date,amount,category,description,payment_method 2025-08-27,50.0,gadget,phone case,AMEX 2025-10-25,50.0,shopping,stuff,AMEX 2025-10-21,1200.0,gadget,Laptop purchase,VISA +2025-12-08,75.0,food,pizza,amex diff --git a/uv.lock b/uv.lock index 1062ddb..c788e1d 100644 --- a/uv.lock +++ b/uv.lock @@ -424,6 +424,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148 }, ] +[[package]] +name = "beartype" +version = "0.22.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/1d/794ae2acaa67c8b216d91d5919da2606c2bb14086849ffde7f5555f3a3a5/beartype-0.22.8.tar.gz", hash = "sha256:b19b21c9359722ee3f7cc433f063b3e13997b27ae8226551ea5062e621f61165", size = 1602262 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/2a/fbcbf5a025d3e71ddafad7efd43e34ec4362f4d523c3c471b457148fb211/beartype-0.22.8-py3-none-any.whl", hash = "sha256:b832882d04e41a4097bab9f63e6992bc6de58c414ee84cba9b45b67314f5ab2e", size = 1331895 }, +] + [[package]] name = "cachetools" version = "6.2.1" @@ -563,7 +572,7 @@ wheels = [ [[package]] name = "cyclopts" -version = "3.24.0" +version = "4.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, @@ -571,9 +580,9 @@ dependencies = [ { name = "rich" }, { name = "rich-rst" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/30/ca/7782da3b03242d5f0a16c20371dff99d4bd1fedafe26bc48ff82e42be8c9/cyclopts-3.24.0.tar.gz", hash = "sha256:de6964a041dfb3c57bf043b41e68c43548227a17de1bad246e3a0bfc5c4b7417", size = 76131 } +sdist = { url = "https://files.pythonhosted.org/packages/1b/0f/fe026df2ab8301e30a2b0bd425ff1462ad858fd4f991c1ac0389c2059c24/cyclopts-4.3.0.tar.gz", hash = "sha256:e95179cd0a959ce250ecfb2f0262a5996a92c1f9467bccad2f3d829e6833cef5", size = 151411 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/8b/2c95f0645c6f40211896375e6fa51f504b8ccb29c21f6ae661fe87ab044e/cyclopts-3.24.0-py3-none-any.whl", hash = "sha256:809d04cde9108617106091140c3964ee6fceb33cecdd537f7ffa360bde13ed71", size = 86154 }, + { url = "https://files.pythonhosted.org/packages/7a/e8/77a231ae531cf38765b75ddf27dae28bb5f70b41d8bb4f15ce1650e93f57/cyclopts-4.3.0-py3-none-any.whl", hash = "sha256:91a30b69faf128ada7cfeaefd7d9649dc222e8b2a8697f1fc99e4ee7b7ca44f3", size = 187184 }, ] [[package]] @@ -589,6 +598,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-py2.py3-none-any.whl", hash = "sha256:60c7dca6571efe660ccb7a9508d73ca14b8796c4ed484c2002abba714226cfef", size = 5283210 }, ] +[[package]] +name = "diskcache" +version = "5.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/21/1c1ffc1a039ddcc459db43cc108658f32c57d271d7289a2794e401d0fdb6/diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc", size = 67916 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/27/4570e78fc0bf5ea0ca45eb1de3818a23787af9b390c0b0a0033a1b8236f9/diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19", size = 45550 }, +] + [[package]] name = "distlib" version = "0.4.0" @@ -634,6 +652,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/66/dd/f95350e853a4468ec37478414fc04ae2d61dad7a947b3015c3dcc51a09b9/docutils-0.22.2-py3-none-any.whl", hash = "sha256:b0e98d679283fc3bb0ead8a5da7f501baa632654e7056e9c5846842213d674d8", size = 632667 }, ] +[[package]] +name = "dotenv-azd" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dotenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/95/61a3b01f6fccd2b3d4df619ecb91e986f88eedf18cf6ab4799cf21cf7025/dotenv_azd-0.3.0.tar.gz", hash = "sha256:6905c9b7f57e795d66eccb3951814fc62c53fa1d0b22574ee1f726565e543026", size = 9879 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/f6/35a119a3dcfc8995542fee35c83e800a144890337c9a8377d22a2052fa1d/dotenv_azd-0.3.0-py3-none-any.whl", hash = "sha256:db57c4cba883662f23a64d86bc3dd1bdf91bcbf13f6452d3db4c156c203657a4", size = 4528 }, +] + [[package]] name = "email-validator" version = "2.3.0" @@ -681,24 +711,28 @@ wheels = [ [[package]] name = "fastmcp" -version = "2.12.5" +version = "2.13.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "authlib" }, { name = "cyclopts" }, { name = "exceptiongroup" }, { name = "httpx" }, + { name = "jsonschema-path" }, { name = "mcp" }, - { name = "openapi-core" }, { name = "openapi-pydantic" }, + { name = "platformdirs" }, + { name = "py-key-value-aio", extra = ["disk", "memory"] }, { name = "pydantic", extra = ["email"] }, { name = "pyperclip" }, { name = "python-dotenv" }, { name = "rich" }, + { name = "uvicorn" }, + { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/00/a6/e3b46cd3e228635e0064c2648788b6f66a53bf0d0ddbf5fb44cca951f908/fastmcp-2.12.5.tar.gz", hash = "sha256:2dfd02e255705a4afe43d26caddbc864563036e233dbc6870f389ee523b39a6a", size = 7190263 } +sdist = { url = "https://files.pythonhosted.org/packages/21/a1/a507bfb73f51983759cbbc3702b6f4780128cff68ebbc51db2f10170c950/fastmcp-2.13.3.tar.gz", hash = "sha256:ebca59e99412c596dd75ebdd5147800f6abc2490d025af76fa8ea4fc5f68781d", size = 8185958 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/c1/9fb98c9649e15ea8cc691b4b09558b61dafb3dc0345f7322f8c4a8991ade/fastmcp-2.12.5-py3-none-any.whl", hash = "sha256:b1e542f9b83dbae7cecfdc9c73b062f77074785abda9f2306799116121344133", size = 329099 }, + { url = "https://files.pythonhosted.org/packages/db/bc/56925f1202357dbfcfdfd0c75afc6c27ec1e6ef1d89b7e7410df3945ceb4/fastmcp-2.13.3-py3-none-any.whl", hash = "sha256:5173d335f4e6aabcfb5a5131af3fa092f604b303130fd3a49226b7a844a48e65", size = 385644 }, ] [[package]] @@ -1234,27 +1268,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/14/e8/edff4de49cf364eb9ee88d13da0a555844df32438413bf53d90d507b97cd/langsmith-0.4.37-py3-none-any.whl", hash = "sha256:e34a94ce7277646299e4703a0f6e2d2c43647a28e8b800bb7ef82fd87a0ec766", size = 396111 }, ] -[[package]] -name = "lazy-object-proxy" -version = "1.12.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/08/a2/69df9c6ba6d316cfd81fe2381e464db3e6de5db45f8c43c6a23504abf8cb/lazy_object_proxy-1.12.0.tar.gz", hash = "sha256:1f5a462d92fd0cfb82f1fab28b51bfb209fabbe6aabf7f0d51472c0c124c0c61", size = 43681 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/26/b74c791008841f8ad896c7f293415136c66cc27e7c7577de4ee68040c110/lazy_object_proxy-1.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:86fd61cb2ba249b9f436d789d1356deae69ad3231dc3c0f17293ac535162672e", size = 26745 }, - { url = "https://files.pythonhosted.org/packages/9b/52/641870d309e5d1fb1ea7d462a818ca727e43bfa431d8c34b173eb090348c/lazy_object_proxy-1.12.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:81d1852fb30fab81696f93db1b1e55a5d1ff7940838191062f5f56987d5fcc3e", size = 71537 }, - { url = "https://files.pythonhosted.org/packages/47/b6/919118e99d51c5e76e8bf5a27df406884921c0acf2c7b8a3b38d847ab3e9/lazy_object_proxy-1.12.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be9045646d83f6c2664c1330904b245ae2371b5c57a3195e4028aedc9f999655", size = 71141 }, - { url = "https://files.pythonhosted.org/packages/e5/47/1d20e626567b41de085cf4d4fb3661a56c159feaa73c825917b3b4d4f806/lazy_object_proxy-1.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:67f07ab742f1adfb3966c40f630baaa7902be4222a17941f3d85fd1dae5565ff", size = 69449 }, - { url = "https://files.pythonhosted.org/packages/58/8d/25c20ff1a1a8426d9af2d0b6f29f6388005fc8cd10d6ee71f48bff86fdd0/lazy_object_proxy-1.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:75ba769017b944fcacbf6a80c18b2761a1795b03f8899acdad1f1c39db4409be", size = 70744 }, - { url = "https://files.pythonhosted.org/packages/c0/67/8ec9abe15c4f8a4bcc6e65160a2c667240d025cbb6591b879bea55625263/lazy_object_proxy-1.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:7b22c2bbfb155706b928ac4d74c1a63ac8552a55ba7fff4445155523ea4067e1", size = 26568 }, - { url = "https://files.pythonhosted.org/packages/23/12/cd2235463f3469fd6c62d41d92b7f120e8134f76e52421413a0ad16d493e/lazy_object_proxy-1.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4a79b909aa16bde8ae606f06e6bbc9d3219d2e57fb3e0076e17879072b742c65", size = 27391 }, - { url = "https://files.pythonhosted.org/packages/60/9e/f1c53e39bbebad2e8609c67d0830cc275f694d0ea23d78e8f6db526c12d3/lazy_object_proxy-1.12.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:338ab2f132276203e404951205fe80c3fd59429b3a724e7b662b2eb539bb1be9", size = 80552 }, - { url = "https://files.pythonhosted.org/packages/4c/b6/6c513693448dcb317d9d8c91d91f47addc09553613379e504435b4cc8b3e/lazy_object_proxy-1.12.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c40b3c9faee2e32bfce0df4ae63f4e73529766893258eca78548bac801c8f66", size = 82857 }, - { url = "https://files.pythonhosted.org/packages/12/1c/d9c4aaa4c75da11eb7c22c43d7c90a53b4fca0e27784a5ab207768debea7/lazy_object_proxy-1.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:717484c309df78cedf48396e420fa57fc8a2b1f06ea889df7248fdd156e58847", size = 80833 }, - { url = "https://files.pythonhosted.org/packages/0b/ae/29117275aac7d7d78ae4f5a4787f36ff33262499d486ac0bf3e0b97889f6/lazy_object_proxy-1.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a6b7ea5ea1ffe15059eb44bcbcb258f97bcb40e139b88152c40d07b1a1dfc9ac", size = 79516 }, - { url = "https://files.pythonhosted.org/packages/19/40/b4e48b2c38c69392ae702ae7afa7b6551e0ca5d38263198b7c79de8b3bdf/lazy_object_proxy-1.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:08c465fb5cd23527512f9bd7b4c7ba6cec33e28aad36fbbe46bf7b858f9f3f7f", size = 27656 }, - { url = "https://files.pythonhosted.org/packages/41/a0/b91504515c1f9a299fc157967ffbd2f0321bce0516a3d5b89f6f4cad0355/lazy_object_proxy-1.12.0-pp39.pp310.pp311.graalpy311-none-any.whl", hash = "sha256:c3b2e0af1f7f77c4263759c4824316ce458fabe0fceadcd24ef8ca08b2d1e402", size = 15072 }, -] - [[package]] name = "logfire" version = "4.15.1" @@ -1285,39 +1298,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321 }, ] -[[package]] -name = "markupsafe" -version = "3.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622 }, - { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029 }, - { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374 }, - { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980 }, - { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990 }, - { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784 }, - { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588 }, - { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041 }, - { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543 }, - { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113 }, - { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911 }, - { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658 }, - { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066 }, - { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639 }, - { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569 }, - { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284 }, - { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801 }, - { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769 }, - { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642 }, - { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612 }, - { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200 }, - { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973 }, -] - [[package]] name = "mcp" -version = "1.16.0" +version = "1.22.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1326,15 +1309,18 @@ dependencies = [ { name = "jsonschema" }, { name = "pydantic" }, { name = "pydantic-settings" }, + { name = "pyjwt", extra = ["crypto"] }, { name = "python-multipart" }, { name = "pywin32", marker = "sys_platform == 'win32'" }, { name = "sse-starlette" }, { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3d/a1/b1f328da3b153683d2ec34f849b4b6eac2790fb240e3aef06ff2fab3df9d/mcp-1.16.0.tar.gz", hash = "sha256:39b8ca25460c578ee2cdad33feeea122694cfdf73eef58bee76c42f6ef0589df", size = 472918 } +sdist = { url = "https://files.pythonhosted.org/packages/a3/a2/c5ec0ab38b35ade2ae49a90fada718fbc76811dc5aa1760414c6aaa6b08a/mcp-1.22.0.tar.gz", hash = "sha256:769b9ac90ed42134375b19e777a2858ca300f95f2e800982b3e2be62dfc0ba01", size = 471788 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/0e/7cebc88e17daf94ebe28c95633af595ccb2864dc2ee7abd75542d98495cc/mcp-1.16.0-py3-none-any.whl", hash = "sha256:ec917be9a5d31b09ba331e1768aa576e0af45470d657a0319996a20a57d7d633", size = 167266 }, + { url = "https://files.pythonhosted.org/packages/a9/bb/711099f9c6bb52770f56e56401cdfb10da5b67029f701e0df29362df4c8e/mcp-1.22.0-py3-none-any.whl", hash = "sha256:bed758e24df1ed6846989c909ba4e3df339a27b4f30f1b8b627862a4bade4e98", size = 175489 }, ] [package.optional-dependencies] @@ -1409,6 +1395,99 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ce/87/35b5a065a2b104d802abd028957bcc159d88ce7a77586f09d07fbb4dc221/microsoft_agents_hosting_core-0.5.0.dev19-py3-none-any.whl", hash = "sha256:aa8937ec10e8fbd50d359654a03187915fd4e02b997aae50098b2aebda0903bf", size = 120113 }, ] +[[package]] +name = "microsoft-kiota-abstractions" +version = "1.9.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-sdk" }, + { name = "std-uritemplate" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/6c/fd855a03545ae261b28d179b206e5f80a0e7c95fac5a580514c4dabedca0/microsoft_kiota_abstractions-1.9.7.tar.gz", hash = "sha256:731ed60c2df74ca80d1bf36d40a4c390aab353db3a76796c63ea9e9a220ce65c", size = 24447 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/d8/d699a2cb209c72f1258af5f582a7868d1b006e57cc4394b68b0f996ba370/microsoft_kiota_abstractions-1.9.7-py3-none-any.whl", hash = "sha256:8add66c38d05ab9a496c1c843bb16e04b70edc4651dc290b9629b14009f5c0c0", size = 44404 }, +] + +[[package]] +name = "microsoft-kiota-authentication-azure" +version = "1.9.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "azure-core" }, + { name = "microsoft-kiota-abstractions" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/03/9a/3deb5d951e55e059fbde93deaf1b3fdd1ec3a6e8bdac01280c640dac7b8c/microsoft_kiota_authentication_azure-1.9.7.tar.gz", hash = "sha256:1ecef94097ca8029e5b903bfef8dbbf47ba75bc1521907164a84b6617226696b", size = 4987 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/49/d12e7eabd6fc7039bfa301dfff26f0fced9bd164564b96b6d99fffcb020b/microsoft_kiota_authentication_azure-1.9.7-py3-none-any.whl", hash = "sha256:a2d776bef22d10be65df1ea9e8f1737e46981bd14cdb70e3fe4f4a066e92b139", size = 6908 }, +] + +[[package]] +name = "microsoft-kiota-http" +version = "1.9.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx", extra = ["http2"] }, + { name = "microsoft-kiota-abstractions" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-sdk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/a9/7efe67311902394a208545ae067dfc7e957383939b0ee6ff43e1955afbe7/microsoft_kiota_http-1.9.7.tar.gz", hash = "sha256:abcacca784649308ab93d8578c2afb581a42deed048b183d7bbdc48c325dd6a1", size = 21249 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/99/1d625b9353cabb3aaddb468c379b1e1fc726795281e94437096846b434b1/microsoft_kiota_http-1.9.7-py3-none-any.whl", hash = "sha256:14ce6b14c4fa93608f535f2c6ae21d35b1d0e2635ab70501fa3a3afc90135261", size = 31577 }, +] + +[[package]] +name = "microsoft-kiota-serialization-form" +version = "1.9.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "microsoft-kiota-abstractions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/d7/dc6d782f75608be4b1733df6592e4c7e819b6e32b290ac45304f74c0c0cf/microsoft_kiota_serialization_form-1.9.7.tar.gz", hash = "sha256:d3297a60778c0437513334b703225ce108fd109f13c1993afea599b85dc5a528", size = 8999 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/62/9929fc1fe0ff76af5ff6dd8179b71c58105675465536141cd491e03f5a1d/microsoft_kiota_serialization_form-1.9.7-py3-none-any.whl", hash = "sha256:72d2dc5e57a993145702870ad89c85cebe3336d4d34f231d951ee1bc83ad11b9", size = 10671 }, +] + +[[package]] +name = "microsoft-kiota-serialization-json" +version = "1.9.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "microsoft-kiota-abstractions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/f8/e13c48e610a00f2abfa9fa19f03e2cf21fe98486dfc5a453ce6c0490d3f2/microsoft_kiota_serialization_json-1.9.7.tar.gz", hash = "sha256:1e54ff90b185fe21cca94ebbf8468bf44a2ca5f082c4cf04dbd2d42a9472837a", size = 9416 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/6b/761e45c91086fb45e69ee1b85d538f6e5fc89b86f6ade148e8c5575259ce/microsoft_kiota_serialization_json-1.9.7-py3-none-any.whl", hash = "sha256:6f44012f00cf7c4c4d8b9195e7f8a691d186021b5d9a20e791a77c800b5be531", size = 11056 }, +] + +[[package]] +name = "microsoft-kiota-serialization-multipart" +version = "1.9.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "microsoft-kiota-abstractions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/23/31b11fd0e44bb79923ea8310c2f3d0bc1e16f56d35d2fc73203a260a0a73/microsoft_kiota_serialization_multipart-1.9.7.tar.gz", hash = "sha256:1a13d193d078dea86711d8c6e89ac142aff5033079c7be4061279b2da5c83ef8", size = 5150 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/ca/98efd66c8e7180928fe2901f4c766799991836e536a8a0aca9186b5d7c7a/microsoft_kiota_serialization_multipart-1.9.7-py3-none-any.whl", hash = "sha256:cd72ee004039ee64a35bd5254afd3f8bc89877e948282ab0fe0a7efab75f68bb", size = 6651 }, +] + +[[package]] +name = "microsoft-kiota-serialization-text" +version = "1.9.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "microsoft-kiota-abstractions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/24/5c/479378981c7b8fb22d6ba693f07db457a18d3efc86dd083ebe31d6192d37/microsoft_kiota_serialization_text-1.9.7.tar.gz", hash = "sha256:d57a082d5c6ea1e650286314cac9a9e7a2662aa4beb80635bf4addd33d252bd5", size = 7306 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/8b/b8b6482719d9ecc4d87f07aa8726d33c18004e0630ef5cd2891ee8bf2ada/microsoft_kiota_serialization_text-1.9.7-py3-none-any.whl", hash = "sha256:47c4d774883bec269a6eb077a5ca2f26ae6715986c8defa374d536a9664dc43e", size = 8840 }, +] + [[package]] name = "ml-dtypes" version = "0.5.3" @@ -1428,15 +1507,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5b/38/6266604dffb43378055394ea110570cf261a49876fc48f548dfe876f34cc/ml_dtypes-0.5.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdf40d2aaabd3913dec11840f0d0ebb1b93134f99af6a0a4fd88ffe924928ab4", size = 5285422 }, ] -[[package]] -name = "more-itertools" -version = "10.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ea/5d/38b681d3fce7a266dd9ab73c66959406d565b3e85f21d5e66e1181d93721/more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd", size = 137431 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667 }, -] - [[package]] name = "msal" version = "1.34.0" @@ -1463,6 +1533,38 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5e/75/bd9b7bb966668920f06b200e84454c8f3566b102183bc55c5473d96cb2b9/msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca", size = 20583 }, ] +[[package]] +name = "msgraph-core" +version = "1.3.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx", extra = ["http2"] }, + { name = "microsoft-kiota-abstractions" }, + { name = "microsoft-kiota-authentication-azure" }, + { name = "microsoft-kiota-http" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/4e/123f9530ec43b306c597bb830c62bedab830ffa76e0edf33ea88a26f756e/msgraph_core-1.3.8.tar.gz", hash = "sha256:6e883f9d4c4ad57501234749e07b010478c1a5f19550ef4cf005bbcac4a63ae7", size = 25506 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/4d/01432f60727ae452787014cad0d5bc9e035c6e11a670f12c23f7fc926d90/msgraph_core-1.3.8-py3-none-any.whl", hash = "sha256:86d83edcf62119946f201d13b7e857c947ef67addb088883940197081de85bea", size = 34473 }, +] + +[[package]] +name = "msgraph-sdk" +version = "1.50.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "azure-identity" }, + { name = "microsoft-kiota-serialization-form" }, + { name = "microsoft-kiota-serialization-json" }, + { name = "microsoft-kiota-serialization-multipart" }, + { name = "microsoft-kiota-serialization-text" }, + { name = "msgraph-core" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/48/268cc9df923f5108e7a66292a4e2ada406c55647ba8373611fbd932b8236/msgraph_sdk-1.50.0.tar.gz", hash = "sha256:ff537ce4a3e94f0cffb95a81ee01a87cf3c154f8ad70c761ae9ce739c83f942f", size = 6207420 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/d0/be4250abb5cec445474a8e05b9b9e93b93844f5a73778c3d3c2c1bded93a/msgraph_sdk-1.50.0-py3-none-any.whl", hash = "sha256:09f40855418e3c1c7f7b04167aaf40bab4dbdc6daa3111bcbd4d84ca311e7d6c", size = 25425463 }, +] + [[package]] name = "msrest" version = "0.7.1" @@ -1591,26 +1693,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1d/2a/7dd3d207ec669cacc1f186fd856a0f61dbc255d24f6fdc1a6715d6051b0f/openai-1.109.1-py3-none-any.whl", hash = "sha256:6bcaf57086cf59159b8e27447e4e7dd019db5d29a438072fbd49c290c7e65315", size = 948627 }, ] -[[package]] -name = "openapi-core" -version = "0.19.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "isodate" }, - { name = "jsonschema" }, - { name = "jsonschema-path" }, - { name = "more-itertools" }, - { name = "openapi-schema-validator" }, - { name = "openapi-spec-validator" }, - { name = "parse" }, - { name = "typing-extensions" }, - { name = "werkzeug" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b1/35/1acaa5f2fcc6e54eded34a2ec74b479439c4e469fc4e8d0e803fda0234db/openapi_core-0.19.5.tar.gz", hash = "sha256:421e753da56c391704454e66afe4803a290108590ac8fa6f4a4487f4ec11f2d3", size = 103264 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/6f/83ead0e2e30a90445ee4fc0135f43741aebc30cca5b43f20968b603e30b6/openapi_core-0.19.5-py3-none-any.whl", hash = "sha256:ef7210e83a59394f46ce282639d8d26ad6fc8094aa904c9c16eb1bac8908911f", size = 106595 }, -] - [[package]] name = "openapi-pydantic" version = "0.5.1" @@ -1623,35 +1705,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/cf/03675d8bd8ecbf4445504d8071adab19f5f993676795708e36402ab38263/openapi_pydantic-0.5.1-py3-none-any.whl", hash = "sha256:a3a09ef4586f5bd760a8df7f43028b60cafb6d9f61de2acba9574766255ab146", size = 96381 }, ] -[[package]] -name = "openapi-schema-validator" -version = "0.6.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jsonschema" }, - { name = "jsonschema-specifications" }, - { name = "rfc3339-validator" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8b/f3/5507ad3325169347cd8ced61c232ff3df70e2b250c49f0fe140edb4973c6/openapi_schema_validator-0.6.3.tar.gz", hash = "sha256:f37bace4fc2a5d96692f4f8b31dc0f8d7400fd04f3a937798eaf880d425de6ee", size = 11550 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/21/c6/ad0fba32775ae749016829dace42ed80f4407b171da41313d1a3a5f102e4/openapi_schema_validator-0.6.3-py3-none-any.whl", hash = "sha256:f3b9870f4e556b5a62a1c39da72a6b4b16f3ad9c73dc80084b1b11e74ba148a3", size = 8755 }, -] - -[[package]] -name = "openapi-spec-validator" -version = "0.7.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jsonschema" }, - { name = "jsonschema-path" }, - { name = "lazy-object-proxy" }, - { name = "openapi-schema-validator" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/82/af/fe2d7618d6eae6fb3a82766a44ed87cd8d6d82b4564ed1c7cfb0f6378e91/openapi_spec_validator-0.7.2.tar.gz", hash = "sha256:cc029309b5c5dbc7859df0372d55e9d1ff43e96d678b9ba087f7c56fc586f734", size = 36855 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/dd/b3fd642260cb17532f66cc1e8250f3507d1e580483e209dc1e9d13bd980d/openapi_spec_validator-0.7.2-py3-none-any.whl", hash = "sha256:4bbdc0894ec85f1d1bea1d6d9c8b2c3c8d7ccaa13577ef40da9c006c9fd0eb60", size = 39713 }, -] - [[package]] name = "opentelemetry-api" version = "1.38.0" @@ -2018,21 +2071,21 @@ wheels = [ ] [[package]] -name = "parse" -version = "1.20.2" +name = "pathable" +version = "0.4.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4f/78/d9b09ba24bb36ef8b83b71be547e118d46214735b6dfb39e4bfde0e9b9dd/parse-1.20.2.tar.gz", hash = "sha256:b41d604d16503c79d81af5165155c0b20f6c8d6c559efa66b4b695c3e5a0a0ce", size = 29391 } +sdist = { url = "https://files.pythonhosted.org/packages/67/93/8f2c2075b180c12c1e9f6a09d1a985bc2036906b13dff1d8917e395f2048/pathable-0.4.4.tar.gz", hash = "sha256:6905a3cd17804edfac7875b5f6c9142a218c7caef78693c2dbbbfbac186d88b2", size = 8124 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/31/ba45bf0b2aa7898d81cbbfac0e88c267befb59ad91a19e36e1bc5578ddb1/parse-1.20.2-py2.py3-none-any.whl", hash = "sha256:967095588cb802add9177d0c0b6133b5ba33b1ea9007ca800e526f42a85af558", size = 20126 }, + { url = "https://files.pythonhosted.org/packages/7d/eb/b6260b31b1a96386c0a880edebe26f89669098acea8e0318bff6adb378fd/pathable-0.4.4-py3-none-any.whl", hash = "sha256:5ae9e94793b6ef5a4cbe0a7ce9dbbefc1eec38df253763fd0aeeacf2762dbbc2", size = 9592 }, ] [[package]] -name = "pathable" -version = "0.4.4" +name = "pathvalidate" +version = "3.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/67/93/8f2c2075b180c12c1e9f6a09d1a985bc2036906b13dff1d8917e395f2048/pathable-0.4.4.tar.gz", hash = "sha256:6905a3cd17804edfac7875b5f6c9142a218c7caef78693c2dbbbfbac186d88b2", size = 8124 } +sdist = { url = "https://files.pythonhosted.org/packages/fa/2a/52a8da6fe965dea6192eb716b357558e103aea0a1e9a8352ad575a8406ca/pathvalidate-3.3.1.tar.gz", hash = "sha256:b18c07212bfead624345bb8e1d6141cdcf15a39736994ea0b94035ad2b1ba177", size = 63262 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7d/eb/b6260b31b1a96386c0a880edebe26f89669098acea8e0318bff6adb378fd/pathable-0.4.4-py3-none-any.whl", hash = "sha256:5ae9e94793b6ef5a4cbe0a7ce9dbbefc1eec38df253763fd0aeeacf2762dbbc2", size = 9592 }, + { url = "https://files.pythonhosted.org/packages/9a/70/875f4a23bfc4731703a5835487d0d2fb999031bd415e7d17c0ae615c18b7/pathvalidate-3.3.1-py3-none-any.whl", hash = "sha256:5263baab691f8e1af96092fa5137ee17df5bdfbd6cff1fcac4d6ef4bc2e1735f", size = 24305 }, ] [[package]] @@ -2179,6 +2232,41 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0a/8d/8a9a45c8b655851f216c1d44f68e3533dc8d2c752ccd0f61f1aa73be4893/psutil-7.1.1-cp37-abi3-win_arm64.whl", hash = "sha256:5457cf741ca13da54624126cd5d333871b454ab133999a9a103fb097a7d7d21a", size = 243944 }, ] +[[package]] +name = "py-key-value-aio" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beartype" }, + { name = "py-key-value-shared" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/93/ce/3136b771dddf5ac905cc193b461eb67967cf3979688c6696e1f2cdcde7ea/py_key_value_aio-0.3.0.tar.gz", hash = "sha256:858e852fcf6d696d231266da66042d3355a7f9871650415feef9fca7a6cd4155", size = 50801 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/10/72f6f213b8f0bce36eff21fda0a13271834e9eeff7f9609b01afdc253c79/py_key_value_aio-0.3.0-py3-none-any.whl", hash = "sha256:1c781915766078bfd608daa769fefb97e65d1d73746a3dfb640460e322071b64", size = 96342 }, +] + +[package.optional-dependencies] +disk = [ + { name = "diskcache" }, + { name = "pathvalidate" }, +] +memory = [ + { name = "cachetools" }, +] + +[[package]] +name = "py-key-value-shared" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beartype" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7b/e4/1971dfc4620a3a15b4579fe99e024f5edd6e0967a71154771a059daff4db/py_key_value_shared-0.3.0.tar.gz", hash = "sha256:8fdd786cf96c3e900102945f92aa1473138ebe960ef49da1c833790160c28a4b", size = 11666 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/e4/b8b0a03ece72f47dce2307d36e1c34725b7223d209fc679315ffe6a4e2c3/py_key_value_shared-0.3.0-py3-none-any.whl", hash = "sha256:5b0efba7ebca08bb158b1e93afc2f07d30b8f40c2fc12ce24a4c0d84f42f9298", size = 19560 }, +] + [[package]] name = "pyasn1" version = "0.6.1" @@ -2346,6 +2434,7 @@ dependencies = [ { name = "azure-identity" }, { name = "azure-monitor-opentelemetry" }, { name = "debugpy" }, + { name = "dotenv-azd" }, { name = "fastmcp" }, { name = "langchain" }, { name = "langchain-core" }, @@ -2353,6 +2442,7 @@ dependencies = [ { name = "langchain-openai" }, { name = "logfire" }, { name = "mcp" }, + { name = "msgraph-sdk" }, { name = "opentelemetry-instrumentation-starlette" }, ] @@ -2371,13 +2461,15 @@ requires-dist = [ { name = "azure-identity", specifier = ">=1.25.1" }, { name = "azure-monitor-opentelemetry", specifier = ">=1.6.4" }, { name = "debugpy", specifier = ">=1.8.0" }, - { name = "fastmcp", specifier = ">=2.12.5" }, + { name = "dotenv-azd", specifier = ">=0.1.0" }, + { name = "fastmcp", specifier = ">=2.13.3" }, { name = "langchain", specifier = "==1.0.0a5" }, { name = "langchain-core", specifier = ">=0.3.0" }, { name = "langchain-mcp-adapters", specifier = ">=0.1.11" }, { name = "langchain-openai", specifier = ">=1.0.1" }, { name = "logfire", specifier = ">=4.15.1" }, { name = "mcp", specifier = ">=1.3.0" }, + { name = "msgraph-sdk", specifier = ">=1.0.0" }, { name = "opentelemetry-instrumentation-starlette", specifier = ">=0.49b0" }, ] @@ -2577,18 +2669,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481 }, ] -[[package]] -name = "rfc3339-validator" -version = "0.1.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490 }, -] - [[package]] name = "rich" version = "14.2.0" @@ -2753,6 +2833,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/be/72/2db2f49247d0a18b4f1bb9a5a39a0162869acf235f3a96418363947b3d46/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659", size = 73736 }, ] +[[package]] +name = "std-uritemplate" +version = "2.0.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/93/62/61866776cd32df3f984ff2f79b1428e10700e0a33ca7a7536e3fcba3cf2a/std_uritemplate-2.0.8.tar.gz", hash = "sha256:138ceff2c5bfef18a650372a5e8c82fe7f780c87235513de6c342fb5f7e18347", size = 6018 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/97/b4f2f442fee92a1406f08b4fbc990bd7d02dc84b3b5e6315a59fa9b2a9f4/std_uritemplate-2.0.8-py3-none-any.whl", hash = "sha256:839807a7f9d07f0bad1a88977c3428bd97b9ff0d229412a0bf36123d8c724257", size = 6512 }, +] + [[package]] name = "tenacity" version = "9.1.2" @@ -2936,18 +3025,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743 }, ] -[[package]] -name = "werkzeug" -version = "3.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markupsafe" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/32/af/d4502dc713b4ccea7175d764718d5183caf8d0867a4f0190d5d4a45cea49/werkzeug-3.1.1.tar.gz", hash = "sha256:8cd39dfbdfc1e051965f156163e2974e52c210f130810e9ad36858f0fd3edad4", size = 806453 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/ea/c67e1dee1ba208ed22c06d1d547ae5e293374bfc43e0eb0ef5e262b68561/werkzeug-3.1.1-py3-none-any.whl", hash = "sha256:a71124d1ef06008baafa3d266c02f56e1836a5984afd6dd6c9230669d60d9fb5", size = 224371 }, -] - [[package]] name = "wrapt" version = "1.17.3" From 22b03c1daaf2ae0113b9c4b9d53054be11f10dcc Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Mon, 8 Dec 2025 23:13:49 -0800 Subject: [PATCH 2/9] Reverting unneeded changes --- .vscode/mcp.json | 2 +- servers/deployed_mcp.py | 68 ++++++++++++----------------------------- servers/expenses.csv | 1 - 3 files changed, 20 insertions(+), 51 deletions(-) diff --git a/.vscode/mcp.json b/.vscode/mcp.json index 3de95ed..fe05888 100644 --- a/.vscode/mcp.json +++ b/.vscode/mcp.json @@ -27,7 +27,7 @@ "0.0.0.0:5678", "servers/basic_mcp_stdio.py" ] - } + }, }, "inputs": [] } diff --git a/servers/deployed_mcp.py b/servers/deployed_mcp.py index 4a2baa6..1281a7b 100644 --- a/servers/deployed_mcp.py +++ b/servers/deployed_mcp.py @@ -14,7 +14,6 @@ from azure.monitor.opentelemetry import configure_azure_monitor from dotenv import load_dotenv from fastmcp import FastMCP -from fastmcp.server.dependencies import get_access_token from opentelemetry.instrumentation.starlette import StarletteInstrumentor from starlette.responses import JSONResponse @@ -63,46 +62,8 @@ cosmos_container = cosmos_db.get_container_client(AZURE_COSMOSDB_CONTAINER) logger.info(f"Connected to Cosmos DB: {AZURE_COSMOSDB_ACCOUNT}") -# No OAuth client storage used in non-auth deployment - -# Authentication disabled in this deployment -auth = None - -# Create the MCP server -mcp = FastMCP("Expenses Tracker", auth=auth) - -# Add OpenTelemetry middleware for distributed tracing -mcp.add_middleware(OpenTelemetryMiddleware("ExpensesMCP")) - - -def get_user_id_from_token() -> str | None: - """Extract the authenticated user's object ID (oid) from access token claims. - - Returns None if no token is present or oid is missing. - """ - try: - token = get_access_token() - if token and hasattr(token, "claims"): - claims = token.claims or {} - return claims.get("oid") - return None - except Exception: - return None - - -@mcp.custom_route("/health", methods=["GET"]) -async def health_check(_request): - """ - Health check endpoint for service availability. - - This endpoint is used by Azure Container Apps health probes to verify that the service is running. - Returns a JSON response with the following format: - { - "status": "healthy", - "service": "mcp-server" - } - """ - return JSONResponse({"status": "healthy", "service": "mcp-server"}) +# Create the MCP server with OpenTelemetry middleware +mcp = FastMCP("Expenses Tracker", middleware=[OpenTelemetryMiddleware("ExpensesMCP")]) class PaymentMethod(Enum): @@ -136,9 +97,6 @@ async def add_expense( logger.info(f"Adding expense: ${amount} for {description} on {date_iso}") try: - # Extract user_id from token via helper (no verbose logging) - user_id = get_user_id_from_token() - expense_id = str(uuid.uuid4()) expense_item = { "id": expense_id, @@ -149,10 +107,6 @@ async def add_expense( "payment_method": payment_method.value, } - # Attach user context based on token claims when available - if user_id: - expense_item["user_id"] = user_id - await cosmos_container.create_item(body=expense_item) return f"Successfully added expense: ${amount} for {description} on {date_iso}" @@ -164,12 +118,13 @@ async def add_expense( @mcp.resource("resource://expenses") async def get_expenses_data(): """Get raw expense data from Cosmos DB.""" + logger.info("Expenses data accessed") try: query = "SELECT * FROM c ORDER BY c.date DESC" expenses_data = [] - async for item in cosmos_container.query_items(query=query): + async for item in cosmos_container.query_items(query=query, enable_cross_partition_query=True): expenses_data.append(item) if not expenses_data: @@ -222,6 +177,21 @@ def analyze_spending_prompt( """ +@mcp.custom_route("/health", methods=["GET"]) +async def health_check(_request): + """ + Health check endpoint for service availability. + + This endpoint is used by Azure Container Apps health probes to verify that the service is running. + Returns a JSON response with the following format: + { + "status": "healthy", + "service": "mcp-server" + } + """ + return JSONResponse({"status": "healthy", "service": "mcp-server"}) + + # ASGI application for uvicorn app = mcp.http_app() StarletteInstrumentor.instrument_app(app) diff --git a/servers/expenses.csv b/servers/expenses.csv index 45aa7a4..7caec9d 100644 --- a/servers/expenses.csv +++ b/servers/expenses.csv @@ -19,4 +19,3 @@ date,amount,category,description,payment_method 2025-08-27,50.0,gadget,phone case,AMEX 2025-10-25,50.0,shopping,stuff,AMEX 2025-10-21,1200.0,gadget,Laptop purchase,VISA -2025-12-08,75.0,food,pizza,amex From e0c8f28b8078d8082b615c8a768f9075ea8b3e59 Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Tue, 9 Dec 2025 23:43:35 -0800 Subject: [PATCH 3/9] Fixes to env vars and readme --- .vscode/mcp.json | 8 +++ README.md | 56 ++++++++++++------ infra/auth_init.ps1 | 10 ++-- infra/{fastmcp_auth_init.py => auth_init.py} | 45 +++++--------- infra/auth_init.sh | 13 ++-- infra/auth_update.ps1 | 10 ++-- ...{fastmcp_auth_update.py => auth_update.py} | 10 ++-- infra/auth_update.sh | 10 ++-- infra/main.bicep | 30 ++++++---- infra/main.parameters.json | 8 +-- infra/server.bicep | 26 +++++--- infra/write_env.ps1 | 15 +++-- infra/write_env.sh | 15 +++-- readme_appaccess.png | Bin 0 -> 59415 bytes readme_authquery.png | Bin 0 -> 54913 bytes readme_signedin.png | Bin 0 -> 34043 bytes readme_userexpenses.png | Bin 0 -> 100986 bytes servers/Dockerfile | 6 +- servers/auth_mcp.py | 26 ++++---- servers/entrypoint.sh | 11 ++++ servers/expenses.csv | 1 + 21 files changed, 180 insertions(+), 120 deletions(-) rename infra/{fastmcp_auth_init.py => auth_init.py} (82%) rename infra/{fastmcp_auth_update.py => auth_update.py} (90%) create mode 100644 readme_appaccess.png create mode 100644 readme_authquery.png create mode 100644 readme_signedin.png create mode 100644 readme_userexpenses.png create mode 100644 servers/entrypoint.sh diff --git a/.vscode/mcp.json b/.vscode/mcp.json index fe05888..8596506 100644 --- a/.vscode/mcp.json +++ b/.vscode/mcp.json @@ -28,6 +28,14 @@ "servers/basic_mcp_stdio.py" ] }, + "my-mcp-server-54ab02db": { + "url": "http://localhost:8000/mcp", + "type": "http" + }, + "my-mcp-server-87f05b06": { + "url": "https://pf-mcp-python-a-server.nicedesert-911988d8.westus.azurecontainerapps.io/mcp", + "type": "http" + } }, "inputs": [] } diff --git a/README.md b/README.md index 03ffadf..a8fe5c3 100644 --- a/README.md +++ b/README.md @@ -381,10 +381,16 @@ This project supports deploying with Microsoft Entra ID (Azure AD) authenticatio 1. Enable Entra OAuth proxy: ```bash - azd env set USE_FASTMCP_AUTH true + azd env set USE_ENTRA_PROXY true ``` -2. Deploy to Azure: +2. Set your tenant ID so that the App Registration is created in the correct tenant: + + ```bash + azd env set AZURE_TENANT_ID "" + ``` + +3. Deploy to Azure: ```bash azd up @@ -394,11 +400,11 @@ This project supports deploying with Microsoft Entra ID (Azure AD) authenticatio - **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: +4. Verify deployment by checking the outputs: ```bash azd env get-value MCP_SERVER_URL - azd env get-value FASTMCP_AUTH_AZURE_CLIENT_ID + azd env get-value ENTRA_PROXY_AZURE_CLIENT_ID ``` ### Environment variables @@ -407,11 +413,10 @@ The following environment variables are automatically set by the deployment hook | 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 | +| `ENTRA_PROXY_AZURE_CLIENT_ID` | The App Registration's client ID | +| `ENTRA_PROXY_AZURE_CLIENT_SECRET` | The App Registration's client secret | -These are written to `.env` by the postprovision hook for local development. +These are then written to `.env` by the postprovision hook for local development. ### Testing locally @@ -419,25 +424,42 @@ 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 +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. -### Connecting VS Code MCP client +### Use Entra OAuth MCP server with GitHub Copilot -The App Registration includes redirect URIs for VS Code: +The Entra App Registration includes these 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. +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 + ``` -### Troubleshooting Entra OAuth + ![Example GitHub Copilot Chat Input](readme_authquery.png) -If you encounter issues with the OAuth flow in VS Code, you can reset the cached authentication state: +9. Verify the expense was added by checking the Cosmos DB `user-expenses` container in the Azure Portal. -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 + ![Cosmos DB user-expenses container](readme_userexpenses.png) diff --git a/infra/auth_init.ps1 b/infra/auth_init.ps1 index 129c292..eb5caea 100644 --- a/infra/auth_init.ps1 +++ b/infra/auth_init.ps1 @@ -1,11 +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)" +# Check if USE_ENTRA_PROXY is enabled +$USE_ENTRA_PROXY = azd env get-value USE_ENTRA_PROXY 2>$null +if ($USE_ENTRA_PROXY -ne "true") { + Write-Host "Skipping auth init (USE_ENTRA_PROXY is not enabled)" exit 0 } Write-Host "Setting up Azure/Entra ID app registration for FastMCP OAuth Proxy..." -python ./infra/fastmcp_auth_init.py +python ./infra/ENTRA_PROXY_init.py diff --git a/infra/fastmcp_auth_init.py b/infra/auth_init.py similarity index 82% rename from infra/fastmcp_auth_init.py rename to infra/auth_init.py index c020431..13e0645 100644 --- a/infra/fastmcp_auth_init.py +++ b/infra/auth_init.py @@ -75,15 +75,16 @@ async def add_client_secret(graph_client: GraphServiceClient, app_object_id: str return password_credential.secret_text -def fastmcp_app_redirect_uris_update(redirect_uri: str) -> Application: +def fastmcp_app_redirect_uris_update() -> Application: """ Create an Application object with just redirect URIs for updating existing apps. This is used when we only need to update redirect URIs without touching permission scopes. """ - # Include redirect URIs for VS Code MCP client (localhost ports and vscode.dev) redirect_uris = [ - redirect_uri, + # Include the main redirect URI for local development + "http://localhost:8000/auth/callback", + # Include redirect URIs for VS Code MCP client (localhost ports and vscode.dev) "https://vscode.dev/redirect", ] # Add common localhost ports used by VS Code for OAuth callbacks @@ -97,7 +98,7 @@ def fastmcp_app_redirect_uris_update(redirect_uri: str) -> Application: ) -def fastmcp_app_registration(identifier: int, redirect_uri: str, scope_name: str = "mcp-access") -> Application: +def fastmcp_app_registration(identifier: int) -> Application: """ Create an Application object configured for FastMCP Azure OAuth. @@ -108,7 +109,7 @@ def fastmcp_app_registration(identifier: int, redirect_uri: str, scope_name: str """ # Include redirect URIs for VS Code MCP client (localhost ports and vscode.dev) redirect_uris = [ - redirect_uri, + "http://localhost:8000/auth/callback", "https://vscode.dev/redirect", ] # Add common localhost ports used by VS Code for OAuth callbacks @@ -130,7 +131,7 @@ def fastmcp_app_registration(identifier: int, redirect_uri: str, scope_name: str user_consent_display_name="Access FastMCP Server", user_consent_description="Allow access to the FastMCP server on your behalf", is_enabled=True, - value=scope_name, + value="mcp-access", type="User", ) ], @@ -146,14 +147,10 @@ def update_app_with_identifier_uri(client_id: str) -> Application: ) -async def create_or_update_fastmcp_app( - graph_client: GraphServiceClient, - redirect_uri: str, - scope_name: str = "mcp-access", -) -> None: +async def create_or_update_fastmcp_app(graph_client: GraphServiceClient) -> None: """Create or update a FastMCP app registration.""" - app_id_env_var = "FASTMCP_AUTH_AZURE_CLIENT_ID" - app_secret_env_var = "FASTMCP_AUTH_AZURE_CLIENT_SECRET" + app_id_env_var = "ENTRA_PROXY_AZURE_CLIENT_ID" + app_secret_env_var = "ENTRA_PROXY_AZURE_CLIENT_SECRET" app_id = os.getenv(app_id_env_var, "no-id") object_id = None @@ -165,12 +162,10 @@ async def create_or_update_fastmcp_app( identifier = random_app_identifier() if object_id: - print("Application already exists, updating redirect URIs...") - request_app = fastmcp_app_redirect_uris_update(redirect_uri) - await graph_client.applications.by_application_id(object_id).patch(request_app) + print("Application already exists, skipping creation.") else: print("Creating new FastMCP application registration...") - request_app = fastmcp_app_registration(identifier, redirect_uri, scope_name) + request_app = fastmcp_app_registration(identifier) object_id, app_id = await create_application(graph_client, request_app) update_azd_env(app_id_env_var, app_id) print(f"Created application with Client ID: {app_id}") @@ -190,27 +185,15 @@ async def create_or_update_fastmcp_app( async def main(): # Configuration - customize these as needed - base_url = os.getenv("FASTMCP_AUTH_AZURE_BASE_URL", "http://localhost:8000") - redirect_path = os.getenv("FASTMCP_AUTH_AZURE_REDIRECT_PATH", "/auth/callback") - redirect_uri = f"{base_url}{redirect_path}" - scope_name = os.getenv("FASTMCP_SCOPE_NAME", "mcp-access") - auth_tenant = os.environ["AZURE_TENANT_ID"] - print(f"Setting up FastMCP authentication for tenant: {auth_tenant}") - print(f"Redirect URI: {redirect_uri}") - print(f"Scope name: {scope_name}") - print() + print(f"Setting up FastMCP Entra proxy authentication for tenant: {auth_tenant}") credential = AzureDeveloperCliCredential(tenant_id=auth_tenant) scopes = ["https://graph.microsoft.com/.default"] graph_client = GraphServiceClient(credentials=credential, scopes=scopes) - await create_or_update_fastmcp_app( - graph_client, - redirect_uri=redirect_uri, - scope_name=scope_name, - ) + await create_or_update_fastmcp_app(graph_client) print("Setup complete!") diff --git a/infra/auth_init.sh b/infra/auth_init.sh index aed72e2..48b96c0 100755 --- a/infra/auth_init.sh +++ b/infra/auth_init.sh @@ -1,12 +1,11 @@ #!/bin/bash -# Pre-provision hook to set up Azure/Entra ID app registration for FastMCP OAuth Proxy +# Pre-provision hook to set up Azure/Entra ID app registration for FastMCP Entra 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)" +USE_ENTRA_PROXY=$(azd env get-value USE_ENTRA_PROXY 2>/dev/null || echo "false") +if [ "$USE_ENTRA_PROXY" != "true" ]; then + echo "Skipping auth init (USE_ENTRA_PROXY is not enabled)" exit 0 fi -echo "Setting up Azure/Entra ID app registration for FastMCP OAuth Proxy..." -python ./infra/fastmcp_auth_init.py +echo "Setting up Entra ID app registration for FastMCP Entra OAuth Proxy..." +python ./infra/auth_init.py diff --git a/infra/auth_update.ps1 b/infra/auth_update.ps1 index 6a67f55..7468de4 100644 --- a/infra/auth_update.ps1 +++ b/infra/auth_update.ps1 @@ -1,11 +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)" +# Check if USE_ENTRA_PROXY is enabled +$USE_ENTRA_PROXY = azd env get-value USE_ENTRA_PROXY 2>$null +if ($USE_ENTRA_PROXY -ne "true") { + Write-Host "Skipping auth update (USE_ENTRA_PROXY is not enabled)" exit 0 } Write-Host "Updating FastMCP auth redirect URIs with deployed server URL..." -python ./infra/fastmcp_auth_update.py +python ./infra/ENTRA_PROXY_update.py diff --git a/infra/fastmcp_auth_update.py b/infra/auth_update.py similarity index 90% rename from infra/fastmcp_auth_update.py rename to infra/auth_update.py index da58b6c..9268053 100644 --- a/infra/fastmcp_auth_update.py +++ b/infra/auth_update.py @@ -45,14 +45,14 @@ async def main(): load_azd_env() # Check if FastMCP auth is enabled - use_fastmcp_auth = os.getenv("USE_FASTMCP_AUTH", "false").lower() == "true" - if not use_fastmcp_auth: + USE_ENTRA_PROXY = os.getenv("USE_ENTRA_PROXY", "false").lower() == "true" + if not USE_ENTRA_PROXY: print("FastMCP auth not enabled, skipping redirect URI update.") return - client_id = os.getenv("FASTMCP_AUTH_AZURE_CLIENT_ID") + client_id = os.getenv("ENTRA_PROXY_AZURE_CLIENT_ID") if not client_id: - print("No FASTMCP_AUTH_AZURE_CLIENT_ID found, skipping redirect URI update.") + print("No ENTRA_PROXY_AZURE_CLIENT_ID found, skipping redirect URI update.") return # Get the deployed server URL from MCP_SERVER_URL output @@ -84,7 +84,7 @@ async def main(): print(f" Existing redirect URIs: {len(existing_uris)}") # Add only the deployed server redirect URI to existing URIs - # (local/VS Code URIs are already set by fastmcp_auth_init.py during preprovision) + # (local/VS Code URIs are already set by ENTRA_PROXY_init.py during preprovision) redirect_uris = set(existing_uris) redirect_uris.add(redirect_uri) diff --git a/infra/auth_update.sh b/infra/auth_update.sh index 74fbf1c..481b3f2 100755 --- a/infra/auth_update.sh +++ b/infra/auth_update.sh @@ -1,12 +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)" +# Check if USE_ENTRA_PROXY is enabled +USE_ENTRA_PROXY=$(azd env get-value USE_ENTRA_PROXY 2>/dev/null || echo "false") +if [ "$USE_ENTRA_PROXY" != "true" ]; then + echo "Skipping auth update (USE_ENTRA_PROXY is not enabled)" exit 0 fi echo "Updating FastMCP auth redirect URIs with deployed server URL..." -python ./infra/fastmcp_auth_update.py +python ./infra/ENTRA_PROXY_update.py diff --git a/infra/main.bicep b/infra/main.bicep index 50d694e..4398e9e 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -69,7 +69,7 @@ param usePrivateAcr bool = false param usePrivateLogAnalytics bool = false @description('Flag to enable Azure/Entra ID OAuth Proxy authentication for the MCP server') -param useFastMcpAuth bool = false +param useEntraProxy bool = false @description('Azure/Entra ID app registration client ID for OAuth Proxy - required when useEntraProxy is true') param entraProxyClientId string = '' @@ -719,11 +719,15 @@ module cosmosDbPrivateEndpoint 'br/public:avm/res/network/private-endpoint:0.11. } // Container app for MCP server +var containerAppDomain = replace('${take(prefix,15)}-server', '--', '-') +// DRY base URLs for auth providers +var keycloakMcpServerBaseUrl = 'https://mcproutes.${containerApps.outputs.defaultDomain}' +var entraProxyMcpServerBaseUrl = 'https://${containerAppDomain}.${containerApps.outputs.defaultDomain}' module server 'server.bicep' = { name: 'server' scope: resourceGroup params: { - name: replace('${take(prefix,15)}-server', '--', '-') + name: containerAppDomain location: location tags: tags identityName: '${prefix}-id-server' @@ -740,13 +744,15 @@ module server 'server.bicep' = { exists: serverExists // Keycloak authentication configuration (only when enabled) keycloakRealmUrl: useKeycloak ? '${keycloak!.outputs.uri}/realms/${keycloakRealmName}' : '' - mcpServerBaseUrl: useKeycloak ? 'https://mcproutes.${containerApps.outputs.defaultDomain}' : '' + keycloakMcpServerBaseUrl: useKeycloak ? keycloakMcpServerBaseUrl : '' keycloakMcpServerAudience: keycloakMcpServerAudience // Azure/Entra ID OAuth Proxy authentication configuration (only when enabled) - entraProxyClientId: useFastMcpAuth ? entraProxyClientId : '' - entraProxyClientSecret: useFastMcpAuth ? entraProxyClientSecret : '' - entraProxyBaseUrl: useFastMcpAuth ? 'https://${replace('${take(prefix,15)}-server', '--', '-')}.${containerApps.outputs.defaultDomain}' : '' - tenantId: useFastMcpAuth ? tenant().tenantId : '' + entraProxyClientId: useEntraProxy ? entraProxyClientId : '' + entraProxyClientSecret: useEntraProxy ? entraProxyClientSecret : '' + entraProxyBaseUrl: useEntraProxy ? entraProxyMcpServerBaseUrl : '' + tenantId: useEntraProxy ? tenant().tenantId : '' + useKeycloak: useKeycloak + useEntraProxy: useEntraProxy } } @@ -891,8 +897,12 @@ output APPLICATIONINSIGHTS_CONNECTION_STRING string = useMonitoring ? applicatio // Use server module's computed entry selection (checks URLs/clientId) output MCP_ENTRY string = server.outputs.mcpEntry -// Output the deployed MCP server HOST only (no /mcp). Python app will append /mcp as needed. -output MCP_SERVER_URL string = useKeycloak ? httpRoutes!.outputs.routeConfigUrl : server.outputs.uri +// Convenience output so developer can find MCP server URL easily +output MCP_SERVER_URL string = useKeycloak ? '${httpRoutes!.outputs.routeConfigUrl}/mcp' : '${server.outputs.uri}/mcp' + +// Provider-specific base URLs for MCP server (exposed for local env writing) +output ENTRA_PROXY_MCP_SERVER_BASE_URL string = useEntraProxy ? entraProxyMcpServerBaseUrl : '' +output KEYCLOAK_MCP_SERVER_BASE_URL string = useKeycloak ? keycloakMcpServerBaseUrl : '' // Keycloak and MCP Server routing outputs (only populated when useKeycloak is true) output KEYCLOAK_REALM_URL string = useKeycloak ? '${httpRoutes!.outputs.routeConfigUrl}/auth/realms/${keycloakRealmName}' : '' @@ -901,4 +911,4 @@ output KEYCLOAK_DIRECT_URL string = keycloak.outputs.uri // Auth feature flags for env scripts output USE_KEYCLOAK bool = useKeycloak -output USE_FASTMCP_AUTH bool = useFastMcpAuth +output USE_ENTRA_PROXY bool = useEntraProxy diff --git a/infra/main.parameters.json b/infra/main.parameters.json index fa13baa..9aecbff 100644 --- a/infra/main.parameters.json +++ b/infra/main.parameters.json @@ -50,15 +50,15 @@ "keycloakMcpServerAudience": { "value": "${KEYCLOAK_MCP_SERVER_AUDIENCE=mcp-server}" }, - "useFastMcpAuth": { - "value": "${USE_FASTMCP_AUTH=false}" + "useEntraProxy": { + "value": "${USE_ENTRA_PROXY=false}" }, "entraProxyClientId": { - "value": "${FASTMCP_AUTH_AZURE_CLIENT_ID}" + "value": "${ENTRA_PROXY_AZURE_CLIENT_ID}" }, "entraProxyClientSecret": { - "value": "${FASTMCP_AUTH_AZURE_CLIENT_SECRET}" + "value": "${ENTRA_PROXY_AZURE_CLIENT_SECRET}" } } } diff --git a/infra/server.bicep b/infra/server.bicep index b510fbe..2b7b593 100644 --- a/infra/server.bicep +++ b/infra/server.bicep @@ -16,13 +16,15 @@ param cosmosDbUserContainer string param cosmosDbOAuthContainer string param applicationInsightsConnectionString string = '' param keycloakRealmUrl string = '' -param mcpServerBaseUrl string = '' param keycloakMcpServerAudience string = 'mcp-server' +param keycloakMcpServerBaseUrl string = '' param entraProxyClientId string = '' @secure() param entraProxyClientSecret string = '' param entraProxyBaseUrl string = '' param tenantId string = '' +param useKeycloak bool = false +param useEntraProxy bool = false // Base environment variables // Select MCP entrypoint based on configured auth (Keycloak or FastMCP Azure auth) @@ -40,6 +42,14 @@ var baseEnv = [ name: 'RUNNING_IN_PRODUCTION' value: 'true' } + { + name: 'USE_KEYCLOAK' + value: useKeycloak + } + { + name: 'USE_ENTRA_PROXY' + value: useEntraProxy + } { name: 'AZURE_CLIENT_ID' value: serverIdentity.properties.clientId @@ -81,28 +91,28 @@ var keycloakEnv = !empty(keycloakRealmUrl) ? [ name: 'KEYCLOAK_REALM_URL' value: keycloakRealmUrl } - { - name: 'MCP_SERVER_BASE_URL' - value: mcpServerBaseUrl - } { name: 'KEYCLOAK_MCP_SERVER_AUDIENCE' value: keycloakMcpServerAudience } + { + name: 'KEYCLOAK_MCP_SERVER_BASE_URL' + value: keycloakMcpServerBaseUrl + } ] : [] // Azure/Entra ID OAuth Proxy environment variables (only added when configured) var entraProxyEnv = !empty(entraProxyClientId) ? [ { - name: 'FASTMCP_AUTH_AZURE_CLIENT_ID' + name: 'ENTRA_PROXY_AZURE_CLIENT_ID' value: entraProxyClientId } { - name: 'FASTMCP_AUTH_AZURE_CLIENT_SECRET' + name: 'ENTRA_PROXY_AZURE_CLIENT_SECRET' secretRef: 'entra-proxy-client-secret' } { - name: 'FASTMCP_AUTH_AZURE_BASE_URL' + name: 'ENTRA_PROXY_MCP_SERVER_BASE_URL' value: entraProxyBaseUrl } { diff --git a/infra/write_env.ps1 b/infra/write_env.ps1 index f86aaea..d4017bf 100644 --- a/infra/write_env.ps1 +++ b/infra/write_env.ps1 @@ -15,17 +15,22 @@ Add-Content -Path $ENV_FILE_PATH -Value "AZURE_COSMOSDB_USER_CONTAINER=$(azd env Add-Content -Path $ENV_FILE_PATH -Value "AZURE_COSMOSDB_OAUTH_CONTAINER=$(azd env get-value AZURE_COSMOSDB_OAUTH_CONTAINER)" Add-Content -Path $ENV_FILE_PATH -Value "APPLICATIONINSIGHTS_CONNECTION_STRING=$(azd env get-value APPLICATIONINSIGHTS_CONNECTION_STRING)" # Auth feature flags -Add-Content -Path $ENV_FILE_PATH -Value "USE_FASTMCP_AUTH=$(azd env get-value USE_FASTMCP_AUTH)" +Add-Content -Path $ENV_FILE_PATH -Value "USE_ENTRA_PROXY=$(azd env get-value USE_ENTRA_PROXY)" Add-Content -Path $ENV_FILE_PATH -Value "USE_KEYCLOAK=$(azd env get-value USE_KEYCLOAK)" $KEYCLOAK_REALM_URL = azd env get-value KEYCLOAK_REALM_URL 2>$null if ($KEYCLOAK_REALM_URL -and $KEYCLOAK_REALM_URL -ne "") { Add-Content -Path $ENV_FILE_PATH -Value "KEYCLOAK_REALM_URL=$KEYCLOAK_REALM_URL" } -$FASTMCP_AUTH_AZURE_CLIENT_ID = azd env get-value FASTMCP_AUTH_AZURE_CLIENT_ID 2>$null -if ($FASTMCP_AUTH_AZURE_CLIENT_ID -and $FASTMCP_AUTH_AZURE_CLIENT_ID -ne "") { - Add-Content -Path $ENV_FILE_PATH -Value "FASTMCP_AUTH_AZURE_CLIENT_ID=$FASTMCP_AUTH_AZURE_CLIENT_ID" - Add-Content -Path $ENV_FILE_PATH -Value "FASTMCP_AUTH_AZURE_CLIENT_SECRET=$(azd env get-value FASTMCP_AUTH_AZURE_CLIENT_SECRET)" +$ENTRA_PROXY_AZURE_CLIENT_ID = azd env get-value ENTRA_PROXY_AZURE_CLIENT_ID 2>$null +if ($ENTRA_PROXY_AZURE_CLIENT_ID -and $ENTRA_PROXY_AZURE_CLIENT_ID -ne "") { + Add-Content -Path $ENV_FILE_PATH -Value "ENTRA_PROXY_AZURE_CLIENT_ID=$ENTRA_PROXY_AZURE_CLIENT_ID" + Add-Content -Path $ENV_FILE_PATH -Value "ENTRA_PROXY_AZURE_CLIENT_SECRET=$(azd env get-value ENTRA_PROXY_AZURE_CLIENT_SECRET)" + # Specific base URL for Entra proxy from azd outputs + $ENTRA_PROXY_MCP_SERVER_BASE_URL = azd env get-value ENTRA_PROXY_MCP_SERVER_BASE_URL 2>$null + Add-Content -Path $ENV_FILE_PATH -Value "ENTRA_PROXY_MCP_SERVER_BASE_URL=$ENTRA_PROXY_MCP_SERVER_BASE_URL" } Add-Content -Path $ENV_FILE_PATH -Value "MCP_ENTRY=$(azd env get-value MCP_ENTRY)" Add-Content -Path $ENV_FILE_PATH -Value "MCP_SERVER_URL=$(azd env get-value MCP_SERVER_URL)" + $KEYCLOAK_MCP_SERVER_BASE_URL = azd env get-value KEYCLOAK_MCP_SERVER_BASE_URL 2>$null + Add-Content -Path $ENV_FILE_PATH -Value "KEYCLOAK_MCP_SERVER_BASE_URL=$KEYCLOAK_MCP_SERVER_BASE_URL" Add-Content -Path $ENV_FILE_PATH -Value "API_HOST=azure" diff --git a/infra/write_env.sh b/infra/write_env.sh index bc0b00a..6119bca 100755 --- a/infra/write_env.sh +++ b/infra/write_env.sh @@ -19,17 +19,22 @@ echo "AZURE_COSMOSDB_USER_CONTAINER=$(azd env get-value AZURE_COSMOSDB_USER_CONT echo "AZURE_COSMOSDB_OAUTH_CONTAINER=$(azd env get-value AZURE_COSMOSDB_OAUTH_CONTAINER)" >> "$ENV_FILE_PATH" echo "APPLICATIONINSIGHTS_CONNECTION_STRING=$(azd env get-value APPLICATIONINSIGHTS_CONNECTION_STRING)" >> "$ENV_FILE_PATH" # Auth feature flags -echo "USE_FASTMCP_AUTH=$(azd env get-value USE_FASTMCP_AUTH)" >> "$ENV_FILE_PATH" +echo "USE_ENTRA_PROXY=$(azd env get-value USE_ENTRA_PROXY)" >> "$ENV_FILE_PATH" echo "USE_KEYCLOAK=$(azd env get-value USE_KEYCLOAK)" >> "$ENV_FILE_PATH" KEYCLOAK_REALM_URL=$(azd env get-value KEYCLOAK_REALM_URL 2>/dev/null || echo "") if [ -n "$KEYCLOAK_REALM_URL" ] && [ "$KEYCLOAK_REALM_URL" != "" ]; then echo "KEYCLOAK_REALM_URL=${KEYCLOAK_REALM_URL}" >> "$ENV_FILE_PATH" fi -FASTMCP_AUTH_AZURE_CLIENT_ID=$(azd env get-value FASTMCP_AUTH_AZURE_CLIENT_ID 2>/dev/null || echo "") -if [ -n "$FASTMCP_AUTH_AZURE_CLIENT_ID" ] && [ "$FASTMCP_AUTH_AZURE_CLIENT_ID" != "" ]; then - echo "FASTMCP_AUTH_AZURE_CLIENT_ID=${FASTMCP_AUTH_AZURE_CLIENT_ID}" >> "$ENV_FILE_PATH" - echo "FASTMCP_AUTH_AZURE_CLIENT_SECRET=$(azd env get-value FASTMCP_AUTH_AZURE_CLIENT_SECRET)" >> "$ENV_FILE_PATH" +ENTRA_PROXY_AZURE_CLIENT_ID=$(azd env get-value ENTRA_PROXY_AZURE_CLIENT_ID 2>/dev/null || echo "") +if [ -n "$ENTRA_PROXY_AZURE_CLIENT_ID" ] && [ "$ENTRA_PROXY_AZURE_CLIENT_ID" != "" ]; then + echo "ENTRA_PROXY_AZURE_CLIENT_ID=${ENTRA_PROXY_AZURE_CLIENT_ID}" >> "$ENV_FILE_PATH" + echo "ENTRA_PROXY_AZURE_CLIENT_SECRET=$(azd env get-value ENTRA_PROXY_AZURE_CLIENT_SECRET)" >> "$ENV_FILE_PATH" + # Specific base URL for Entra proxy from azd outputs + ENTRA_PROXY_MCP_SERVER_BASE_URL=$(azd env get-value ENTRA_PROXY_MCP_SERVER_BASE_URL 2>/dev/null || echo "") + echo "ENTRA_PROXY_MCP_SERVER_BASE_URL=${ENTRA_PROXY_MCP_SERVER_BASE_URL}" >> "$ENV_FILE_PATH" fi echo "MCP_ENTRY=$(azd env get-value MCP_ENTRY)" >> "$ENV_FILE_PATH" echo "MCP_SERVER_URL=$(azd env get-value MCP_SERVER_URL)" >> "$ENV_FILE_PATH" +KEYCLOAK_MCP_SERVER_BASE_URL=$(azd env get-value KEYCLOAK_MCP_SERVER_BASE_URL 2>/dev/null || echo "") +echo "KEYCLOAK_MCP_SERVER_BASE_URL=${KEYCLOAK_MCP_SERVER_BASE_URL}" >> "$ENV_FILE_PATH" echo "API_HOST=azure" >> "$ENV_FILE_PATH" diff --git a/readme_appaccess.png b/readme_appaccess.png new file mode 100644 index 0000000000000000000000000000000000000000..b73c14d4669dc71e24f978031bc60153577047c4 GIT binary patch literal 59415 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 P@JCKcS+Ydj$nXCFt2P?|^S?6YG62F!&lv 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 HH}d~qdp2 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 u Date: Wed, 10 Dec 2025 10:58:59 -0800 Subject: [PATCH 4/9] Fix ManagedIdentityCredential in prod so it knows to use user-assigned --- servers/auth_mcp.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/servers/auth_mcp.py b/servers/auth_mcp.py index f3649b3..617351a 100644 --- a/servers/auth_mcp.py +++ b/servers/auth_mcp.py @@ -10,7 +10,7 @@ import logfire from azure.core.settings import settings from azure.cosmos.aio import CosmosClient -from azure.identity.aio import ChainedTokenCredential, DefaultAzureCredential, ManagedIdentityCredential +from azure.identity.aio import DefaultAzureCredential, ManagedIdentityCredential from azure.monitor.opentelemetry import configure_azure_monitor from cosmosdb_store import CosmosDBStore from dotenv import load_dotenv @@ -60,7 +60,12 @@ logfire.configure(service_name="expenses-mcp", send_to_logfire=True) # Configure Cosmos DB client -azure_credential = ChainedTokenCredential(ManagedIdentityCredential(), DefaultAzureCredential()) +if RUNNING_IN_PRODUCTION: + azure_credential = ManagedIdentityCredential(client_id=os.environ["AZURE_CLIENT_ID"]) + logger.info("Using Managed Identity Credential for Azure authentication") +else: + azure_credential = DefaultAzureCredential() + logger.info("Using Default Azure Credential for Azure authentication") cosmos_client = CosmosClient( url=f"https://{os.environ['AZURE_COSMOSDB_ACCOUNT']}.documents.azure.com:443/", credential=azure_credential, @@ -141,10 +146,6 @@ async def on_read_resource(self, context: MiddlewareContext, call_next): # Create the MCP server mcp = FastMCP("Expenses Tracker", auth=auth, middleware=[OpenTelemetryMiddleware("ExpensesMCP"), UserAuthMiddleware()]) -# Configure Starlette middleware for OpenTelemetry -app = mcp.http_app() -StarletteInstrumentor.instrument_app(app) - """Expense tracking MCP server with authentication and Cosmos DB storage.""" @@ -241,3 +242,9 @@ async def get_user_expenses(ctx: Context): async def health_check(_request): """Health check endpoint for service availability.""" return JSONResponse({"status": "healthy", "service": "mcp-server"}) + + +# Configure Starlette middleware for OpenTelemetry +# We must do this *after* defining all the MCP server routes +app = mcp.http_app() +StarletteInstrumentor.instrument_app(app) From eb4c563c7e88680efeef4aeb079573ce98bfb32d Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Wed, 10 Dec 2025 11:37:44 -0800 Subject: [PATCH 5/9] Address PR feedback --- README.md | 3 +-- infra/auth_init.py | 27 ++++++++------------------- infra/auth_update.py | 16 ++++------------ infra/main.parameters.json | 1 - servers/cosmosdb_store.py | 4 +--- 5 files changed, 14 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index a8fe5c3..0c9170b 100644 --- a/README.md +++ b/README.md @@ -433,9 +433,8 @@ The server will use the Entra App Registration for OAuth and CosmosDB for client The Entra App Registration includes these 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) +- `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: diff --git a/infra/auth_init.py b/infra/auth_init.py index 13e0645..a4197d5 100644 --- a/infra/auth_init.py +++ b/infra/auth_init.py @@ -1,5 +1,4 @@ import asyncio -import datetime import os import random import subprocess @@ -7,7 +6,6 @@ from azure.identity.aio import AzureDeveloperCliCredential from dotenv_azd import load_azd_env -from msgraph import GraphServiceClient from msgraph.generated.applications.item.add_password.add_password_post_request_body import ( AddPasswordPostRequestBody, ) @@ -17,31 +15,22 @@ from msgraph.generated.models.permission_scope import PermissionScope from msgraph.generated.models.service_principal import ServicePrincipal from msgraph.generated.models.web_application import WebApplication +from msgraph.graph_service_client import GraphServiceClient async def get_application(graph_client: GraphServiceClient, app_id: str) -> str | None: """Get an application's object ID by its client/app ID.""" - try: - apps = await graph_client.applications.get() - if apps and apps.value: - for app in apps.value: - if app.app_id == app_id: - return app.id - except Exception: - pass + apps = await graph_client.applications.get() + if apps and apps.value: + for app in apps.value: + if app.app_id == app_id: + return app.id return None def update_azd_env(name: str, val: str) -> None: """Update an Azure Developer CLI environment variable.""" - subprocess.run(f'azd env set {name} "{val}"', shell=True) - - -def random_app_identifier() -> int: - """Generate a random identifier for the app name.""" - rand = random.Random() - rand.seed(datetime.datetime.now().timestamp()) - return rand.randint(1000, 100000) + subprocess.run(["azd", "env", "set", name, val], check=True) async def create_application(graph_client: GraphServiceClient, request_app: Application) -> tuple[str, str]: @@ -159,7 +148,7 @@ async def create_or_update_fastmcp_app(graph_client: GraphServiceClient) -> None print(f"Checking if application {app_id} exists...") object_id = await get_application(graph_client, app_id) - identifier = random_app_identifier() + identifier = random.randint(1000, 100000) if object_id: print("Application already exists, skipping creation.") diff --git a/infra/auth_update.py b/infra/auth_update.py index 9268053..150b907 100644 --- a/infra/auth_update.py +++ b/infra/auth_update.py @@ -10,9 +10,9 @@ from azure.identity.aio import AzureDeveloperCliCredential from dotenv_azd import load_azd_env -from msgraph import GraphServiceClient from msgraph.generated.models.application import Application from msgraph.generated.models.web_application import WebApplication +from msgraph.graph_service_client import GraphServiceClient async def get_application(graph_client: GraphServiceClient, app_id: str) -> str | None: @@ -50,18 +50,10 @@ async def main(): print("FastMCP auth not enabled, skipping redirect URI update.") return - client_id = os.getenv("ENTRA_PROXY_AZURE_CLIENT_ID") - if not client_id: - print("No ENTRA_PROXY_AZURE_CLIENT_ID found, skipping redirect URI update.") - return - - # Get the deployed server URL from MCP_SERVER_URL output - server_url = os.getenv("MCP_SERVER_URL", "").rstrip("/mcp").rstrip("/") - if not server_url: - print("No MCP_SERVER_URL found, skipping redirect URI update.") - return - + client_id = os.environ["ENTRA_PROXY_AZURE_CLIENT_ID"] + server_url = os.environ["ENTRA_PROXY_MCP_SERVER_BASE_URL"] auth_tenant = os.environ["AZURE_TENANT_ID"] + redirect_uri = f"{server_url}/auth/callback" print("Updating redirect URIs for FastMCP app registration...") diff --git a/infra/main.parameters.json b/infra/main.parameters.json index 9aecbff..affb984 100644 --- a/infra/main.parameters.json +++ b/infra/main.parameters.json @@ -53,7 +53,6 @@ "useEntraProxy": { "value": "${USE_ENTRA_PROXY=false}" }, - "entraProxyClientId": { "value": "${ENTRA_PROXY_AZURE_CLIENT_ID}" }, diff --git a/servers/cosmosdb_store.py b/servers/cosmosdb_store.py index 68dbc75..0bc5dfb 100644 --- a/servers/cosmosdb_store.py +++ b/servers/cosmosdb_store.py @@ -9,7 +9,7 @@ import logging from collections.abc import Mapping, Sequence -from datetime import datetime, timezone +from datetime import datetime, timedelta, timezone from typing import Any, SupportsFloat from azure.cosmos.aio import ContainerProxy @@ -196,8 +196,6 @@ async def put( if ttl is not None: ttl_seconds = float(ttl) if ttl_seconds > 0: - from datetime import timedelta - expires_at = now + timedelta(seconds=ttl_seconds) cosmos_ttl = int(ttl_seconds) From 15fdc9886d7436bfcac14423ead902732a1ac65d Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Wed, 10 Dec 2025 11:59:33 -0800 Subject: [PATCH 6/9] Move to a single variable for auth control --- AGENTS.md | 2 +- README.md | 18 ++++++++++++------ infra/auth_init.ps1 | 8 ++++---- infra/auth_init.sh | 6 +++--- infra/auth_update.ps1 | 10 +++++----- infra/auth_update.py | 8 ++++---- infra/auth_update.sh | 10 +++++----- infra/main.bicep | 34 +++++++++++++++++++--------------- infra/main.parameters.json | 8 +++----- infra/server.bicep | 16 ++++++++-------- infra/write_env.ps1 | 8 ++------ infra/write_env.sh | 11 +++-------- servers/auth_mcp.py | 5 +++-- 13 files changed, 72 insertions(+), 72 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 2b031a6..7831d5f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,7 +9,7 @@ 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}" }` + - 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 diff --git a/README.md b/README.md index 0c9170b..5ccff7f 100644 --- a/README.md +++ b/README.md @@ -296,19 +296,25 @@ 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 @@ -316,7 +322,7 @@ This project supports deploying with OAuth 2.0 authentication using Keycloak as 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 @@ -324,7 +330,7 @@ This project supports deploying with OAuth 2.0 authentication using Keycloak as 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:///auth/admin @@ -381,7 +387,7 @@ This project supports deploying with Microsoft Entra ID (Azure AD) authenticatio 1. Enable Entra OAuth proxy: ```bash - azd env set USE_ENTRA_PROXY true + azd env set MCP_AUTH_PROVIDER entra_proxy ``` 2. Set your tenant ID so that the App Registration is created in the correct tenant: diff --git a/infra/auth_init.ps1 b/infra/auth_init.ps1 index eb5caea..2e80d04 100644 --- a/infra/auth_init.ps1 +++ b/infra/auth_init.ps1 @@ -1,9 +1,9 @@ # Pre-provision hook to set up Azure/Entra ID app registration for FastMCP OAuth Proxy -# Check if USE_ENTRA_PROXY is enabled -$USE_ENTRA_PROXY = azd env get-value USE_ENTRA_PROXY 2>$null -if ($USE_ENTRA_PROXY -ne "true") { - Write-Host "Skipping auth init (USE_ENTRA_PROXY is not enabled)" +# 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 } diff --git a/infra/auth_init.sh b/infra/auth_init.sh index 48b96c0..9138112 100755 --- a/infra/auth_init.sh +++ b/infra/auth_init.sh @@ -1,9 +1,9 @@ #!/bin/bash # Pre-provision hook to set up Azure/Entra ID app registration for FastMCP Entra OAuth Proxy -USE_ENTRA_PROXY=$(azd env get-value USE_ENTRA_PROXY 2>/dev/null || echo "false") -if [ "$USE_ENTRA_PROXY" != "true" ]; then - echo "Skipping auth init (USE_ENTRA_PROXY is not enabled)" +MCP_AUTH_PROVIDER=$(azd env get-value MCP_AUTH_PROVIDER 2>/dev/null || echo "none") +if [ "$MCP_AUTH_PROVIDER" != "entra_proxy" ]; then + echo "Skipping auth init (MCP_AUTH_PROVIDER is not entra_proxy)" exit 0 fi diff --git a/infra/auth_update.ps1 b/infra/auth_update.ps1 index 7468de4..751ebd0 100644 --- a/infra/auth_update.ps1 +++ b/infra/auth_update.ps1 @@ -1,11 +1,11 @@ # Post-provision hook to update Azure app registration redirect URIs with deployed server URL -# Check if USE_ENTRA_PROXY is enabled -$USE_ENTRA_PROXY = azd env get-value USE_ENTRA_PROXY 2>$null -if ($USE_ENTRA_PROXY -ne "true") { - Write-Host "Skipping auth update (USE_ENTRA_PROXY is not enabled)" +# 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 update (MCP_AUTH_PROVIDER is not entra_proxy)" exit 0 } Write-Host "Updating FastMCP auth redirect URIs with deployed server URL..." -python ./infra/ENTRA_PROXY_update.py +python ./infra/auth_update.py diff --git a/infra/auth_update.py b/infra/auth_update.py index 150b907..7113b98 100644 --- a/infra/auth_update.py +++ b/infra/auth_update.py @@ -44,10 +44,10 @@ async def get_existing_redirect_uris(graph_client: GraphServiceClient, object_id async def main(): load_azd_env() - # Check if FastMCP auth is enabled - USE_ENTRA_PROXY = os.getenv("USE_ENTRA_PROXY", "false").lower() == "true" - if not USE_ENTRA_PROXY: - print("FastMCP auth not enabled, skipping redirect URI update.") + # Check if MCP auth provider is entra_proxy + MCP_AUTH_PROVIDER = os.getenv("MCP_AUTH_PROVIDER", "none") + if MCP_AUTH_PROVIDER != "entra_proxy": + print("MCP auth provider is not entra_proxy, skipping redirect URI update.") return client_id = os.environ["ENTRA_PROXY_AZURE_CLIENT_ID"] diff --git a/infra/auth_update.sh b/infra/auth_update.sh index 481b3f2..6931147 100755 --- a/infra/auth_update.sh +++ b/infra/auth_update.sh @@ -1,12 +1,12 @@ #!/bin/bash # Post-provision hook to update Azure app registration redirect URIs with deployed server URL -# Check if USE_ENTRA_PROXY is enabled -USE_ENTRA_PROXY=$(azd env get-value USE_ENTRA_PROXY 2>/dev/null || echo "false") -if [ "$USE_ENTRA_PROXY" != "true" ]; then - echo "Skipping auth update (USE_ENTRA_PROXY is not enabled)" +# Check if MCP_AUTH_PROVIDER is entra_proxy +MCP_AUTH_PROVIDER=$(azd env get-value MCP_AUTH_PROVIDER 2>/dev/null || echo "none") +if [ "$MCP_AUTH_PROVIDER" != "entra_proxy" ]; then + echo "Skipping auth update (MCP_AUTH_PROVIDER is not entra_proxy)" exit 0 fi echo "Updating FastMCP auth redirect URIs with deployed server URL..." -python ./infra/ENTRA_PROXY_update.py +python ./infra/auth_update.py diff --git a/infra/main.bicep b/infra/main.bicep index 4398e9e..655cf3b 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -46,20 +46,25 @@ param useVnet bool = false @description('Flag to enable or disable public ingress') param usePrivateIngress bool = false -@description('Flag to enable or disable Keycloak authentication for the MCP server') -param useKeycloak bool = false +@description('Authentication provider for the MCP server') +@allowed([ + 'none' + 'keycloak' + 'entra_proxy' +]) +param mcpAuthProvider string = 'none' @description('Keycloak admin username') param keycloakAdminUser string = 'admin' @secure() -@description('Keycloak admin password - required when useKeycloak is true') +@description('Keycloak admin password - required when mcpAuthProvider is keycloak') param keycloakAdminPassword string = '' @description('Keycloak realm name for MCP authentication') param keycloakRealmName string = 'mcp' -@description('Audience claim for MCP server tokens (only used when useKeycloak is true)') +@description('Audience claim for MCP server tokens (only used when mcpAuthProvider is keycloak)') param keycloakMcpServerAudience string = 'mcp-server' @description('Flag to restrict ACR public network access (requires VPN for local image push when true)') @@ -68,16 +73,17 @@ param usePrivateAcr bool = false @description('Flag to restrict Log Analytics public query access for increased security') param usePrivateLogAnalytics bool = false -@description('Flag to enable Azure/Entra ID OAuth Proxy authentication for the MCP server') -param useEntraProxy bool = false - -@description('Azure/Entra ID app registration client ID for OAuth Proxy - required when useEntraProxy is true') +@description('Azure/Entra ID app registration client ID for OAuth Proxy - required when mcpAuthProvider is entra_proxy') param entraProxyClientId string = '' @secure() -@description('Azure/Entra ID app registration client secret for OAuth Proxy - required when useEntraProxy is true') +@description('Azure/Entra ID app registration client secret for OAuth Proxy - required when mcpAuthProvider is entra_proxy') param entraProxyClientSecret string = '' +// Derived booleans for backward compatibility in bicep modules +var useKeycloak = mcpAuthProvider == 'keycloak' +var useEntraProxy = mcpAuthProvider == 'entra_proxy' + var resourceToken = toLower(uniqueString(subscription().id, name, location)) var tags = { 'azd-env-name': name } @@ -751,8 +757,7 @@ module server 'server.bicep' = { entraProxyClientSecret: useEntraProxy ? entraProxyClientSecret : '' entraProxyBaseUrl: useEntraProxy ? entraProxyMcpServerBaseUrl : '' tenantId: useEntraProxy ? tenant().tenantId : '' - useKeycloak: useKeycloak - useEntraProxy: useEntraProxy + mcpAuthProvider: mcpAuthProvider } } @@ -904,11 +909,10 @@ output MCP_SERVER_URL string = useKeycloak ? '${httpRoutes!.outputs.routeConfigU output ENTRA_PROXY_MCP_SERVER_BASE_URL string = useEntraProxy ? entraProxyMcpServerBaseUrl : '' output KEYCLOAK_MCP_SERVER_BASE_URL string = useKeycloak ? keycloakMcpServerBaseUrl : '' -// Keycloak and MCP Server routing outputs (only populated when useKeycloak is true) +// Keycloak and MCP Server routing outputs (only populated when mcpAuthProvider is keycloak) output KEYCLOAK_REALM_URL string = useKeycloak ? '${httpRoutes!.outputs.routeConfigUrl}/auth/realms/${keycloakRealmName}' : '' output KEYCLOAK_ADMIN_CONSOLE string = useKeycloak ? '${httpRoutes!.outputs.routeConfigUrl}/auth/admin' : '' output KEYCLOAK_DIRECT_URL string = keycloak.outputs.uri -// Auth feature flags for env scripts -output USE_KEYCLOAK bool = useKeycloak -output USE_ENTRA_PROXY bool = useEntraProxy +// Auth provider for env scripts +output MCP_AUTH_PROVIDER string = mcpAuthProvider diff --git a/infra/main.parameters.json b/infra/main.parameters.json index affb984..a95fffb 100644 --- a/infra/main.parameters.json +++ b/infra/main.parameters.json @@ -26,8 +26,8 @@ "usePrivateLogAnalytics": { "value": "${USE_PRIVATE_LOGANALYTICS=false}" }, - "useKeycloak": { - "value": "${USE_KEYCLOAK=false}" + "mcpAuthProvider": { + "value": "${MCP_AUTH_PROVIDER=none}" }, "serverExists": { "value": "${SERVICE_SERVER_RESOURCE_EXISTS=false}" @@ -50,9 +50,7 @@ "keycloakMcpServerAudience": { "value": "${KEYCLOAK_MCP_SERVER_AUDIENCE=mcp-server}" }, - "useEntraProxy": { - "value": "${USE_ENTRA_PROXY=false}" - }, + "entraProxyClientId": { "value": "${ENTRA_PROXY_AZURE_CLIENT_ID}" }, diff --git a/infra/server.bicep b/infra/server.bicep index 2b7b593..15ab677 100644 --- a/infra/server.bicep +++ b/infra/server.bicep @@ -23,8 +23,12 @@ param entraProxyClientId string = '' param entraProxyClientSecret string = '' param entraProxyBaseUrl string = '' param tenantId string = '' -param useKeycloak bool = false -param useEntraProxy bool = false +@allowed([ + 'none' + 'keycloak' + 'entra_proxy' +]) +param mcpAuthProvider string = 'none' // Base environment variables // Select MCP entrypoint based on configured auth (Keycloak or FastMCP Azure auth) @@ -43,12 +47,8 @@ var baseEnv = [ value: 'true' } { - name: 'USE_KEYCLOAK' - value: useKeycloak - } - { - name: 'USE_ENTRA_PROXY' - value: useEntraProxy + name: 'MCP_AUTH_PROVIDER' + value: mcpAuthProvider } { name: 'AZURE_CLIENT_ID' diff --git a/infra/write_env.ps1 b/infra/write_env.ps1 index d4017bf..a8c4579 100644 --- a/infra/write_env.ps1 +++ b/infra/write_env.ps1 @@ -14,9 +14,7 @@ Add-Content -Path $ENV_FILE_PATH -Value "AZURE_COSMOSDB_CONTAINER=$(azd env get- Add-Content -Path $ENV_FILE_PATH -Value "AZURE_COSMOSDB_USER_CONTAINER=$(azd env get-value AZURE_COSMOSDB_USER_CONTAINER)" Add-Content -Path $ENV_FILE_PATH -Value "AZURE_COSMOSDB_OAUTH_CONTAINER=$(azd env get-value AZURE_COSMOSDB_OAUTH_CONTAINER)" Add-Content -Path $ENV_FILE_PATH -Value "APPLICATIONINSIGHTS_CONNECTION_STRING=$(azd env get-value APPLICATIONINSIGHTS_CONNECTION_STRING)" -# Auth feature flags -Add-Content -Path $ENV_FILE_PATH -Value "USE_ENTRA_PROXY=$(azd env get-value USE_ENTRA_PROXY)" -Add-Content -Path $ENV_FILE_PATH -Value "USE_KEYCLOAK=$(azd env get-value USE_KEYCLOAK)" +Add-Content -Path $ENV_FILE_PATH -Value "MCP_AUTH_PROVIDER=$(azd env get-value MCP_AUTH_PROVIDER)" $KEYCLOAK_REALM_URL = azd env get-value KEYCLOAK_REALM_URL 2>$null if ($KEYCLOAK_REALM_URL -and $KEYCLOAK_REALM_URL -ne "") { Add-Content -Path $ENV_FILE_PATH -Value "KEYCLOAK_REALM_URL=$KEYCLOAK_REALM_URL" @@ -25,12 +23,10 @@ $ENTRA_PROXY_AZURE_CLIENT_ID = azd env get-value ENTRA_PROXY_AZURE_CLIENT_ID 2>$ if ($ENTRA_PROXY_AZURE_CLIENT_ID -and $ENTRA_PROXY_AZURE_CLIENT_ID -ne "") { Add-Content -Path $ENV_FILE_PATH -Value "ENTRA_PROXY_AZURE_CLIENT_ID=$ENTRA_PROXY_AZURE_CLIENT_ID" Add-Content -Path $ENV_FILE_PATH -Value "ENTRA_PROXY_AZURE_CLIENT_SECRET=$(azd env get-value ENTRA_PROXY_AZURE_CLIENT_SECRET)" - # Specific base URL for Entra proxy from azd outputs $ENTRA_PROXY_MCP_SERVER_BASE_URL = azd env get-value ENTRA_PROXY_MCP_SERVER_BASE_URL 2>$null Add-Content -Path $ENV_FILE_PATH -Value "ENTRA_PROXY_MCP_SERVER_BASE_URL=$ENTRA_PROXY_MCP_SERVER_BASE_URL" } Add-Content -Path $ENV_FILE_PATH -Value "MCP_ENTRY=$(azd env get-value MCP_ENTRY)" Add-Content -Path $ENV_FILE_PATH -Value "MCP_SERVER_URL=$(azd env get-value MCP_SERVER_URL)" - $KEYCLOAK_MCP_SERVER_BASE_URL = azd env get-value KEYCLOAK_MCP_SERVER_BASE_URL 2>$null - Add-Content -Path $ENV_FILE_PATH -Value "KEYCLOAK_MCP_SERVER_BASE_URL=$KEYCLOAK_MCP_SERVER_BASE_URL" +Add-Content -Path $ENV_FILE_PATH -Value "KEYCLOAK_MCP_SERVER_BASE_URL=$(azd env get-value KEYCLOAK_MCP_SERVER_BASE_URL)" Add-Content -Path $ENV_FILE_PATH -Value "API_HOST=azure" diff --git a/infra/write_env.sh b/infra/write_env.sh index 6119bca..7b60b51 100755 --- a/infra/write_env.sh +++ b/infra/write_env.sh @@ -18,9 +18,7 @@ echo "AZURE_COSMOSDB_CONTAINER=$(azd env get-value AZURE_COSMOSDB_CONTAINER)" >> echo "AZURE_COSMOSDB_USER_CONTAINER=$(azd env get-value AZURE_COSMOSDB_USER_CONTAINER)" >> "$ENV_FILE_PATH" echo "AZURE_COSMOSDB_OAUTH_CONTAINER=$(azd env get-value AZURE_COSMOSDB_OAUTH_CONTAINER)" >> "$ENV_FILE_PATH" echo "APPLICATIONINSIGHTS_CONNECTION_STRING=$(azd env get-value APPLICATIONINSIGHTS_CONNECTION_STRING)" >> "$ENV_FILE_PATH" -# Auth feature flags -echo "USE_ENTRA_PROXY=$(azd env get-value USE_ENTRA_PROXY)" >> "$ENV_FILE_PATH" -echo "USE_KEYCLOAK=$(azd env get-value USE_KEYCLOAK)" >> "$ENV_FILE_PATH" +echo "MCP_AUTH_PROVIDER=$(azd env get-value MCP_AUTH_PROVIDER)" >> "$ENV_FILE_PATH" KEYCLOAK_REALM_URL=$(azd env get-value KEYCLOAK_REALM_URL 2>/dev/null || echo "") if [ -n "$KEYCLOAK_REALM_URL" ] && [ "$KEYCLOAK_REALM_URL" != "" ]; then echo "KEYCLOAK_REALM_URL=${KEYCLOAK_REALM_URL}" >> "$ENV_FILE_PATH" @@ -29,12 +27,9 @@ ENTRA_PROXY_AZURE_CLIENT_ID=$(azd env get-value ENTRA_PROXY_AZURE_CLIENT_ID 2>/d if [ -n "$ENTRA_PROXY_AZURE_CLIENT_ID" ] && [ "$ENTRA_PROXY_AZURE_CLIENT_ID" != "" ]; then echo "ENTRA_PROXY_AZURE_CLIENT_ID=${ENTRA_PROXY_AZURE_CLIENT_ID}" >> "$ENV_FILE_PATH" echo "ENTRA_PROXY_AZURE_CLIENT_SECRET=$(azd env get-value ENTRA_PROXY_AZURE_CLIENT_SECRET)" >> "$ENV_FILE_PATH" - # Specific base URL for Entra proxy from azd outputs - ENTRA_PROXY_MCP_SERVER_BASE_URL=$(azd env get-value ENTRA_PROXY_MCP_SERVER_BASE_URL 2>/dev/null || echo "") - echo "ENTRA_PROXY_MCP_SERVER_BASE_URL=${ENTRA_PROXY_MCP_SERVER_BASE_URL}" >> "$ENV_FILE_PATH" + echo "ENTRA_PROXY_MCP_SERVER_BASE_URL=$(azd env get-value ENTRA_PROXY_MCP_SERVER_BASE_URL)" >> "$ENV_FILE_PATH" fi echo "MCP_ENTRY=$(azd env get-value MCP_ENTRY)" >> "$ENV_FILE_PATH" echo "MCP_SERVER_URL=$(azd env get-value MCP_SERVER_URL)" >> "$ENV_FILE_PATH" -KEYCLOAK_MCP_SERVER_BASE_URL=$(azd env get-value KEYCLOAK_MCP_SERVER_BASE_URL 2>/dev/null || echo "") -echo "KEYCLOAK_MCP_SERVER_BASE_URL=${KEYCLOAK_MCP_SERVER_BASE_URL}" >> "$ENV_FILE_PATH" +echo "KEYCLOAK_MCP_SERVER_BASE_URL=$(azd env get-value KEYCLOAK_MCP_SERVER_BASE_URL)" >> "$ENV_FILE_PATH" echo "API_HOST=azure" >> "$ENV_FILE_PATH" diff --git a/servers/auth_mcp.py b/servers/auth_mcp.py index 617351a..7865f67 100644 --- a/servers/auth_mcp.py +++ b/servers/auth_mcp.py @@ -75,7 +75,8 @@ # Configure authentication provider auth = None -if os.getenv("USE_ENTRA_PROXY", "false").lower() == "true": +mcp_auth_provider = os.getenv("MCP_AUTH_PROVIDER", "none").lower() +if mcp_auth_provider == "entra_proxy": # Azure/Entra ID authentication using AzureProvider # When running locally, always use localhost for base URL (OAuth redirects need to match) oauth_client_store = None @@ -97,7 +98,7 @@ logger.info( "Using Entra OAuth Proxy for server %s and %s storage", entra_base_url, type(oauth_client_store).__name__ ) -elif os.getenv("USE_KEYCLOAK", "false").lower() == "true": +elif mcp_auth_provider == "keycloak": # Keycloak authentication using RemoteAuthProvider with JWT verification KEYCLOAK_REALM_URL = os.environ["KEYCLOAK_REALM_URL"] token_verifier = JWTVerifier( From c33c03195a53f5ead7fa41c4c86a9f23a0f41b63 Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Wed, 10 Dec 2025 12:15:12 -0800 Subject: [PATCH 7/9] Fix ruff error --- servers/auth_mcp.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/servers/auth_mcp.py b/servers/auth_mcp.py index 7865f67..de71672 100644 --- a/servers/auth_mcp.py +++ b/servers/auth_mcp.py @@ -22,12 +22,13 @@ from fastmcp.server.middleware import Middleware, MiddlewareContext from key_value.aio.stores.memory import MemoryStore from opentelemetry.instrumentation.starlette import StarletteInstrumentor -from opentelemetry_middleware import OpenTelemetryMiddleware from pydantic import AnyHttpUrl from rich.console import Console from rich.logging import RichHandler from starlette.responses import JSONResponse +from opentelemetry_middleware import OpenTelemetryMiddleware + RUNNING_IN_PRODUCTION = os.getenv("RUNNING_IN_PRODUCTION", "false").lower() == "true" if not RUNNING_IN_PRODUCTION: From 8652a4f692ec52db0d8ef42155ad704203f82e02 Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Wed, 10 Dec 2025 12:16:37 -0800 Subject: [PATCH 8/9] Revert mcp.json changes --- .vscode/mcp.json | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.vscode/mcp.json b/.vscode/mcp.json index 8596506..fe05888 100644 --- a/.vscode/mcp.json +++ b/.vscode/mcp.json @@ -28,14 +28,6 @@ "servers/basic_mcp_stdio.py" ] }, - "my-mcp-server-54ab02db": { - "url": "http://localhost:8000/mcp", - "type": "http" - }, - "my-mcp-server-87f05b06": { - "url": "https://pf-mcp-python-a-server.nicedesert-911988d8.westus.azurecontainerapps.io/mcp", - "type": "http" - } }, "inputs": [] } From cb31c05cc020de7f0c4e88add87a9048e590895a Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Wed, 10 Dec 2025 14:04:14 -0800 Subject: [PATCH 9/9] Fix ps1 auth init --- infra/auth_init.ps1 | 2 +- infra/auth_update.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/infra/auth_init.ps1 b/infra/auth_init.ps1 index 2e80d04..76c6b7d 100644 --- a/infra/auth_init.ps1 +++ b/infra/auth_init.ps1 @@ -8,4 +8,4 @@ if ($MCP_AUTH_PROVIDER -ne "entra_proxy") { } Write-Host "Setting up Azure/Entra ID app registration for FastMCP OAuth Proxy..." -python ./infra/ENTRA_PROXY_init.py +python ./infra/auth_init.py diff --git a/infra/auth_update.py b/infra/auth_update.py index 7113b98..92d8f14 100644 --- a/infra/auth_update.py +++ b/infra/auth_update.py @@ -76,7 +76,7 @@ async def main(): print(f" Existing redirect URIs: {len(existing_uris)}") # Add only the deployed server redirect URI to existing URIs - # (local/VS Code URIs are already set by ENTRA_PROXY_init.py during preprovision) + # (local/VS Code URIs are already set by auth_init.py during preprovision) redirect_uris = set(existing_uris) redirect_uris.add(redirect_uri)