Skip to main content
Glama
cloud-deployment.md12.3 kB
# Cloud Deployment Guide This guide covers deploying the SSO MCP Server in **cloud mode** where the server validates incoming Bearer tokens instead of acquiring tokens via browser SSO. ## Overview In cloud mode, the MCP server acts as an **OAuth 2.1 Resource Server**: - Clients obtain tokens from an identity provider (e.g., Azure Entra ID) - Clients send tokens in the `Authorization: Bearer <token>` header - The server validates tokens using JWKS (JSON Web Key Sets) - No browser interaction required This mode is suitable for: - Multi-tenant deployments - Server-to-server communication - Containerized/cloud-hosted deployments - Integration with existing OAuth infrastructure ## Architecture ``` ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ │ AI Client │ │ MCP Server │ │ Azure Entra ID │ │ (Copilot/Claude)│ │ (Cloud Mode) │ │ (IdP) │ └────────┬────────┘ └────────┬─────────┘ └────────┬────────┘ │ │ │ │ 1. Get token │ │ │───────────────────────┼───────────────────────>│ │ │ │ │ 2. Token response │ │ │<──────────────────────┼────────────────────────│ │ │ │ │ 3. MCP request │ │ │ Authorization: Bearer│ │ │──────────────────────>│ │ │ │ │ │ │ 4. Fetch JWKS │ │ │───────────────────────>│ │ │ │ │ │ 5. JWKS response │ │ │<───────────────────────│ │ │ │ │ │ 6. Validate token │ │ │ (signature, aud, iss) │ │ │ │ │ 7. MCP response │ │ │<──────────────────────│ │ ``` ## Prerequisites 1. **Azure App Registration** configured as an API (Resource Server) 2. **Client App Registration** configured to request tokens for your API 3. Python 3.11+ and uv package manager ## Azure Configuration ### Step 1: Create API App Registration 1. Go to Azure Portal > Azure Active Directory > App registrations 2. Click "New registration" 3. Configure: - Name: `SSO MCP Server API` - Supported account types: Choose based on your needs - Redirect URI: Not needed for API 4. After creation, note the **Application (client) ID** - this becomes your `RESOURCE_IDENTIFIER` ### Step 2: Expose an API 1. Go to your API app registration > "Expose an API" 2. Set the **Application ID URI** (e.g., `api://sso-mcp-server`) 3. Add scopes: - `checklist.read` - Read checklists - `checklist.list` - List checklists - `process.read` - Read processes - `process.search` - Search processes ### Step 3: Configure Token Settings 1. Go to "Manifest" and ensure: ```json { "accessTokenAcceptedVersion": 2 } ``` This ensures v2.0 tokens are issued. ### Step 4: Note Your Issuer URL For Azure Entra ID, the issuer URL format is: ``` https://login.microsoftonline.com/{tenant-id}/v2.0 ``` For multi-tenant apps, you may also allow: ``` https://login.microsoftonline.com/common/v2.0 https://login.microsoftonline.com/organizations/v2.0 ``` ## Server Configuration ### Environment Variables ```bash # Required: Set cloud mode AUTH_MODE=cloud # Required: Your API's Application ID URI or Client ID # This MUST match the 'aud' claim in incoming tokens RESOURCE_IDENTIFIER=api://sso-mcp-server # Required: Allowed token issuers (comma-separated) # Include all tenants that should be able to access your API ALLOWED_ISSUERS=https://login.microsoftonline.com/your-tenant-id/v2.0 # Required: Path to content files CHECKLIST_DIR=/app/checklists PROCESS_DIR=/app/processes # Optional: Server port (default: 8080) MCP_PORT=8080 # Optional: JWKS cache TTL in seconds (default: 3600) JWKS_CACHE_TTL=3600 # Optional: Advertised scopes for Protected Resource Metadata SCOPES_SUPPORTED=checklist.read,checklist.list,process.read,process.search # Optional: Log level LOG_LEVEL=INFO ``` ### Docker Deployment ```dockerfile FROM python:3.11-slim WORKDIR /app # Install uv RUN pip install uv # Copy application COPY pyproject.toml uv.lock ./ COPY src/ ./src/ COPY checklists/ ./checklists/ COPY processes/ ./processes/ # Install dependencies RUN uv sync --frozen # Set environment variables ENV AUTH_MODE=cloud ENV CHECKLIST_DIR=/app/checklists ENV PROCESS_DIR=/app/processes ENV MCP_PORT=8080 EXPOSE 8080 CMD ["uv", "run", "sso-mcp-server"] ``` ```bash # Build and run docker build -t sso-mcp-server . docker run -p 8080:8080 \ -e RESOURCE_IDENTIFIER=api://sso-mcp-server \ -e ALLOWED_ISSUERS=https://login.microsoftonline.com/tenant-id/v2.0 \ sso-mcp-server ``` ### Kubernetes Deployment ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: sso-mcp-server spec: replicas: 2 selector: matchLabels: app: sso-mcp-server template: metadata: labels: app: sso-mcp-server spec: containers: - name: sso-mcp-server image: your-registry/sso-mcp-server:latest ports: - containerPort: 8080 env: - name: AUTH_MODE value: "cloud" - name: RESOURCE_IDENTIFIER valueFrom: secretKeyRef: name: sso-mcp-secrets key: resource-identifier - name: ALLOWED_ISSUERS valueFrom: secretKeyRef: name: sso-mcp-secrets key: allowed-issuers - name: CHECKLIST_DIR value: "/app/checklists" - name: PROCESS_DIR value: "/app/processes" volumeMounts: - name: checklists mountPath: /app/checklists - name: processes mountPath: /app/processes volumes: - name: checklists configMap: name: checklists-config - name: processes configMap: name: processes-config --- apiVersion: v1 kind: Service metadata: name: sso-mcp-server spec: selector: app: sso-mcp-server ports: - port: 80 targetPort: 8080 ``` ## Client Configuration Clients must obtain tokens with the correct audience and send them in the Authorization header. ### Token Requirements The access token must have: - `aud` claim matching `RESOURCE_IDENTIFIER` - `iss` claim matching one of `ALLOWED_ISSUERS` - Valid signature (verifiable via issuer's JWKS) - Not expired (`exp` claim in the future) ### Example: Obtaining a Token (MSAL Python) ```python from msal import ConfidentialClientApplication app = ConfidentialClientApplication( client_id="your-client-app-id", client_credential="your-client-secret", authority="https://login.microsoftonline.com/your-tenant-id" ) # Request token for your API result = app.acquire_token_for_client( scopes=["api://sso-mcp-server/.default"] ) access_token = result["access_token"] ``` ### Example: Calling the MCP Server ```python import httpx async def call_mcp_tool(token: str, tool_name: str, arguments: dict): async with httpx.AsyncClient() as client: response = await client.post( "http://your-server:8080/mcp", headers={ "Authorization": f"Bearer {token}", "Content-Type": "application/json" }, json={ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": tool_name, "arguments": arguments } } ) return response.json() # Usage result = await call_mcp_tool( token=access_token, tool_name="list_checklists", arguments={} ) ``` ## Token Validation Details The server performs the following validations: | Check | Description | Error | |-------|-------------|-------| | Signature | Verified using issuer's JWKS | `INVALID_SIGNATURE` | | Expiration | `exp` claim must be in the future | `TOKEN_EXPIRED` | | Not Before | `nbf` claim (if present) must be in the past | `INVALID_TOKEN` | | Audience | `aud` must contain `RESOURCE_IDENTIFIER` | `INVALID_AUDIENCE` | | Issuer | `iss` must be in `ALLOWED_ISSUERS` | `INVALID_ISSUER` | ### JWKS Caching The server caches JWKS from each issuer to improve performance: - Default TTL: 3600 seconds (1 hour) - Automatic refresh on cache expiry - Automatic retry with cache refresh on key lookup failure (handles key rotation) ## Error Responses Authentication errors return MCP protocol errors: ```json { "jsonrpc": "2.0", "id": 1, "error": { "code": -32002, "message": "The access token has expired.\n\nAction: Obtain a new access token from the authorization server." } } ``` ### Error Codes | HTTP Status | Error Code | Description | |-------------|------------|-------------| | 401 | `MISSING_AUTHORIZATION` | No Authorization header | | 401 | `INVALID_TOKEN` | Malformed or unverifiable token | | 401 | `INVALID_SIGNATURE` | Signature verification failed | | 401 | `TOKEN_EXPIRED` | Token has expired | | 401 | `INVALID_AUDIENCE` | Wrong audience claim | | 401 | `INVALID_ISSUER` | Untrusted issuer | | 403 | `INSUFFICIENT_SCOPE` | Missing required scope | ## Security Best Practices 1. **Use HTTPS in production** - Always deploy behind TLS termination 2. **Restrict allowed issuers** - Only allow trusted identity providers 3. **Use short-lived tokens** - Configure IdP for short token lifetimes 4. **Monitor authentication failures** - Set up alerting for auth errors 5. **Rotate secrets regularly** - If using client credentials flow 6. **Network isolation** - Deploy in private subnet if possible ## Monitoring The server logs authentication events using structured logging: ```json {"event": "token_validated", "sub": "user@example.com", "iss": "https://login.microsoftonline.com/...", "scopes": ["checklist.read"]} {"event": "cloud_auth_failed", "error_code": "TOKEN_EXPIRED", "error": "invalid_token"} ``` Set `LOG_LEVEL=DEBUG` for detailed JWKS and validation logs. ## Troubleshooting ### "Invalid audience" Error 1. Check the `aud` claim in your token (decode at jwt.io) 2. Ensure it matches `RESOURCE_IDENTIFIER` exactly 3. For Azure, the audience is usually `api://your-app-id` or the client ID ### "Invalid issuer" Error 1. Check the `iss` claim in your token 2. Ensure it's in `ALLOWED_ISSUERS` (watch for trailing slashes) 3. Azure v2.0 tokens have issuer: `https://login.microsoftonline.com/{tenant}/v2.0` ### "Invalid signature" Error 1. Ensure token is from the expected issuer 2. Check JWKS endpoint is accessible from the server 3. Try clearing JWKS cache by restarting the server ### JWKS Fetch Failures 1. Check network connectivity to issuer's OIDC endpoints 2. Verify `.well-known/openid-configuration` is accessible 3. Check for firewall rules blocking outbound HTTPS ## Related Documentation - [Quickstart Guide](../specs/001-mcp-sso-checklist/quickstart.md) - [OAuth 2.1 Resource Server Design](../specs/002-oauth21-resource-server/design.md) - [Architecture Overview](./architecture.md)

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/DauQuangThanh/sso-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server