|
| 1 | +# Authenticated MCP server (Python) |
| 2 | + |
| 3 | +This example shows how to build an authenticated app with the OpenAI Apps SDK. |
| 4 | +It demonstrates triggering the ChatGPT authentication UI by responding with MCP |
| 5 | +authorization metadata and follows the same OAuth flow described in the [MCP |
| 6 | +authorization spec](https://modelcontextprotocol.io/docs/tutorials/security/authorization#the-authorization-flow:-step-by-step). |
| 7 | + |
| 8 | +The Apps SDK [auth guide](https://developers.openai.com/apps-sdk/build/auth#triggering-authentication-ui) covers how the UI is triggered. |
| 9 | + |
| 10 | +The server exposes two tools: |
| 11 | + |
| 12 | +1. `search_pizza_sf` (mixed auth, returns the `mixed-auth-search` widget) |
| 13 | +2. `see_past_orders` (OAuth required, returns the `mixed-auth-past-orders` widget with past orders data) |
| 14 | + |
| 15 | +If a request is missing a token for the protected tool, the server returns an `mcp/www_authenticate` hint (backed by `WWW-Authenticate`) plus `/.well-known/oauth-protected-resource` metadata so ChatGPT knows which authorization server to use. With a valid token, the tools return widget markup or structured results. |
| 16 | + |
| 17 | +## Set up |
| 18 | + |
| 19 | +### 1. Configure the authorization server (Auth0) |
| 20 | + |
| 21 | +> This example uses an Auth0 tenant as a default authorization server. To set up your own, follow the instructions below: |
| 22 | +
|
| 23 | +1. **Create an API** |
| 24 | + |
| 25 | + - Auth0 Dashboard → _Applications_ → _APIs_ → _Create API_ |
| 26 | + - Name it (e.g., `mcp-python-server`) |
| 27 | + - Identifier → `https://your-domain.example.com/mcp` (add this to your `JWT_AUDIENCES` environment variable) |
| 28 | + - (JWT) Profile → Auth0 |
| 29 | + |
| 30 | +2. **Enable a default audience for your tenant** (per [this community post](https://community.auth0.com/t/rfc-8707-implementation-audience-vs-resource/188990/4)) so that Auth0 issues an unencrypted RS256 JWT. |
| 31 | + |
| 32 | + - Tenant settings > Default Audience > Add the API identifier you created in step 1. |
| 33 | + |
| 34 | +3. **Enable Dynamic Client Registration** |
| 35 | + |
| 36 | + - Go to Dashboard > Settings > Advanced and enable the [OIDC Dynamic Application Registration](https://auth0.com/docs/get-started/applications/dynamic-client-registration?tenant=openai-mcpkit-trial%40prod-us-5&locale=en-us). |
| 37 | + |
| 38 | +4. **Add a social connection to the tenant** for example Google oauth2 to provide a social login mechanism for uers. |
| 39 | + - Authentication > Social > google-oauth2 > Advanced > Promote Connection to Domain Level |
| 40 | + |
| 41 | +### 2. Set the environment variables |
| 42 | + |
| 43 | +Create `authenticated_server_python/.env` with the values below: |
| 44 | + |
| 45 | +```env |
| 46 | +AUTHORIZATION_SERVER_URL=https://your-domain.example.com |
| 47 | +RESOURCE_SERVER_URL=https://your-domain.example.com/mcp |
| 48 | +``` |
| 49 | + |
| 50 | +- `AUTHORIZATION_SERVER_URL`: Base URL for your OAuth authorization server (Auth0 tenant). This is what ChatGPT uses to start the OAuth flow. |
| 51 | +- `RESOURCE_SERVER_URL`: Public URL to this MCP server's `/mcp` endpoint. This is the protected resource URL advertised in the OAuth metadata. |
| 52 | + |
| 53 | +### 3. Customize the app |
| 54 | + |
| 55 | +Adjust the `WWW-Authenticate` construction or scopes to match your security model. |
| 56 | + |
| 57 | +## Run the app |
| 58 | + |
| 59 | +### Serve the static assets |
| 60 | + |
| 61 | +In a separate terminal, from the root of the directory, build and serve the widget assets (required for the UI): |
| 62 | + |
| 63 | +```bash |
| 64 | +pnpm run build |
| 65 | +pnpm run serve |
| 66 | +``` |
| 67 | + |
| 68 | +### Install python requirements |
| 69 | + |
| 70 | +```bash |
| 71 | +cd authenticated_server_python/ |
| 72 | +python -m venv .venv |
| 73 | +source .venv/bin/activate |
| 74 | +pip install -r requirements.txt |
| 75 | +``` |
| 76 | + |
| 77 | +### Run the MCP server |
| 78 | + |
| 79 | +Run the following command to start the MCP server: |
| 80 | + |
| 81 | +```bash |
| 82 | +uvicorn authenticated_server_python.main:app --port 8000 |
| 83 | +``` |
| 84 | + |
| 85 | +### Tunnel with ngrok |
| 86 | + |
| 87 | +In a separate tab, to expose the server publicly (required for ChatGPT to reach it), tunnel the local port with ngrok: |
| 88 | + |
| 89 | +```bash |
| 90 | +ngrok http 8000 |
| 91 | +``` |
| 92 | + |
| 93 | +Copy the `https://...ngrok-free.app` URL and set `RESOURCE_SERVER_URL` in `.env` to `https://...ngrok-free.app/mcp` |
| 94 | + |
| 95 | +The server listens on `http://127.0.0.1:8000` and exposes the standard MCP endpoint at `GET /mcp`. |
| 96 | + |
| 97 | +The `search_pizza_sf` tool echoes the optional `searchTerm` argument as a topping and returns structured content plus widget markup. Unauthenticated calls return the MCP auth hint so the Apps SDK can start the OAuth flow. |
| 98 | + |
| 99 | +The `see_past_orders` tool requires OAuth and returns the `mixed-auth-past-orders` widget with past orders data. |
0 commit comments