Skip to content

Commit fcdc03e

Browse files
authored
feat: adds FastMCP On-Behalf-Of token exchange Python example (#81)
* feat: adds fastmcp on-behalf-of token exchange python quickstart * fix: address Starlette routing issue with connection * feat: include temporary auth0-api-python sources * feat: update to reference 1.0.0b9 auth0-api-python sdk
1 parent 678ea3a commit fcdc03e

15 files changed

Lines changed: 2083 additions & 0 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
AUTH0_DOMAIN=
2+
AUTH0_AUDIENCE=http://localhost:3001/
3+
PORT=3001
4+
MCP_SERVER_URL=http://localhost:3001
5+
6+
MCP_AUTH0_CLIENT_ID=
7+
MCP_AUTH0_CLIENT_SECRET=
8+
MCP_AUTH0_EXCHANGE_SCOPE="read:private"
9+
10+
API_AUTH0_AUDIENCE=http://localhost:8787/
11+
API_BASE_URL=http://localhost:8787
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
# Example FastMCP MCP Server with Auth0 for On-Behalf-Of Token Exchange (Python)
2+
3+
## Available Tools
4+
5+
The server exposes the following tools:
6+
7+
- `whoami` - Returns authenticated user information and granted scopes
8+
- `greet` - Personalized greeting demonstrating On-Behalf-Of Token Exchange with upstream API
9+
- `get_datetime` - Returns the current UTC date and time (no scope required)
10+
11+
## Install dependencies
12+
13+
Install the dependencies using Poetry:
14+
15+
```bash
16+
poetry install
17+
```
18+
19+
## Configuration
20+
21+
Create a `.env` file in the project root and configure the following environment variables:
22+
23+
```
24+
# Auth0 tenant domain
25+
AUTH0_DOMAIN=example-tenant.us.auth0.com
26+
27+
# Auth0 API Identifier
28+
AUTH0_AUDIENCE=http://localhost:3001/
29+
30+
# Server port
31+
PORT=3001
32+
33+
# MCP server URL
34+
MCP_SERVER_URL=http://localhost:3001
35+
36+
# MCP Auth0 client credentials and configuration
37+
MCP_AUTH0_CLIENT_ID=your-client-id
38+
MCP_AUTH0_CLIENT_SECRET=your-client-secret
39+
40+
# Scopes to request
41+
MCP_AUTH0_EXCHANGE_SCOPE=read:private
42+
43+
# Upstream API configuration
44+
API_AUTH0_AUDIENCE=your-api-audience
45+
API_BASE_URL=http://localhost:8787
46+
```
47+
48+
## Services
49+
50+
This example consists of two services that work together:
51+
52+
### 1. Upstream API Service (`poetry run python -m src.api.server`)
53+
54+
The upstream API is a Starlette-based service that demonstrates a protected resource requiring specific scopes:
55+
56+
- **Port**: 8787 (configurable via `API_BASE_URL`)
57+
- **Authentication**: Auth0 JWT tokens with `API_AUTH0_AUDIENCE`
58+
- **Endpoints**:
59+
- `GET /api/private-scope` - Protected endpoint requiring `read:private` scope
60+
61+
The API service validates incoming tokens and returns authenticated user information including the subject (`sub`) and granted scopes.
62+
63+
### 2. MCP Server (`poetry run python -m src.server`)
64+
65+
The MCP (Model Context Protocol) server implements on-behalf-of token exchange with Auth0:
66+
67+
- **Port**: 3001 (configurable via `PORT`)
68+
- **Transport**: HTTP streaming at `/` endpoint
69+
- **Authentication**: Auth0 JWT tokens with `AUTH0_AUDIENCE`
70+
71+
## Running the Services
72+
73+
With the configuration in place, start both services:
74+
75+
**1. Start the upstream API:**
76+
```bash
77+
cd /path/to/fastmcp-mcp-on-behalf-of-tokenexchange-python
78+
poetry run python -m src.api.server
79+
```
80+
81+
**2. Start the MCP server (in a new terminal):**
82+
```bash
83+
cd /path/to/fastmcp-mcp-on-behalf-of-tokenexchange-python
84+
poetry run python -m src.server
85+
```
86+
87+
The MCP server will use On-Behalf-Of Token Exchange (OBO) to obtain tokens for calling the upstream API on behalf of authenticated users.
88+
89+
## Testing
90+
91+
Use an MCP client like [MCP Inspector](https://github.com/modelcontextprotocol/inspector) to test your server interactively:
92+
93+
```bash
94+
npx @modelcontextprotocol/inspector
95+
```
96+
97+
The server will start up and the UI will be accessible at http://localhost:6274.
98+
99+
In the MCP Inspector, select `Streamable HTTP` as the `Transport Type`, enter `http://localhost:3001/mcp` as the URL, and select `Via Proxy` for `Connection Type`.
100+
101+
### Using cURL
102+
You can use cURL to verify that the server is running:
103+
104+
```sh
105+
# Test that the server is running and accessible - check OAuth resource metadata
106+
curl -v http://localhost:3001/.well-known/oauth-protected-resource
107+
108+
# Test MCP initialization (requires valid Auth0 access token)
109+
curl -X POST http://localhost:3001/mcp \
110+
-H "Content-Type: application/json" \
111+
-H "Accept: application/json, text/event-stream" \
112+
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
113+
-d '{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2025-06-18", "capabilities": {}, "clientInfo": {"name": "curl-test", "version": "1.0.0"}}}'
114+
115+
# Test get_datetime tool (no scope required) - outputs ISO string like 2025-10-31T14:12:03.123Z
116+
curl -X POST http://localhost:3001/mcp \
117+
-H "Content-Type: application/json" \
118+
-H "Accept: application/json, text/event-stream" \
119+
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
120+
-d '{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "get_datetime", "arguments": {}}}'
121+
122+
# Test greet tool (tool:greet permission required) - outputs Upstream API response when OBO flow succeeds
123+
curl -X POST http://localhost:3001/mcp \
124+
-H "Content-Type: application/json" \
125+
-H "Accept: application/json, text/event-stream" \
126+
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
127+
-d '{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "greet", "arguments": {"name": "World"}}}'
128+
129+
# sample response from greet tool:
130+
event: message
131+
id: b79cc975-0b9a-4ad7-9f0b-60709f33dcaa_1777299634174_47cmz49v
132+
data:
133+
{
134+
"result": {
135+
"content": [
136+
{
137+
"type": "text",
138+
"text": "Hello, World (auth0|69d9483f2d2c4494f9de0d8c)!\n\
139+
\n\
140+
Upstream API Response:\n\
141+
{\n\
142+
\"msg\": \"Hello from upstream API\",\n\
143+
\"sub\": \"auth0|69d9483f2d2c4494f9de0d8c\",\n\
144+
\"scopes\": \"read:private\"\n\
145+
}"
146+
}
147+
]
148+
},
149+
"jsonrpc": "2.0",
150+
"id": 2
151+
}
152+
```
153+
154+
**Note:** Use the MCP Inspector or other MCP-compatible clients for comprehensive testing.
155+
156+
## On-Behalf-Of Token Exchange Flow
157+
158+
This example demonstrates the following On-Behalf-Of (OBO) Token Exchange flow:
159+
160+
1. **Client authenticates** to the MCP server with an Auth0 access token
161+
2. **MCP server validates** the token and creates a session
162+
3. **Client calls `greet` tool** on the MCP server
163+
4. **MCP server exchanges** the original token for a new token with different audience using OBO:
164+
- Subject token: Original MCP access token
165+
- Subject token type: `urn:ietf:params:oauth:token-type:access_token`
166+
- Target audience: Upstream API (`API_AUTH0_AUDIENCE`)
167+
- Requested scope: `read:private`
168+
5. **MCP server calls upstream API** using the exchanged token
169+
6. **Upstream API validates** the token and returns user information
170+
7. **MCP server returns** the result to the client
171+
172+
This pattern is useful for:
173+
- MCP servers that need to call downstream APIs on behalf of the user
174+
- Microservices architectures where each service has its own audience
175+
- Token scoping where different services require different permissions
176+
- Maintaining user identity across service boundaries while respecting audience constraints

0 commit comments

Comments
 (0)