mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-22 13:34:17 +02:00
Adds PropelAuth as an AuthProvider
Adds the PropelAuthProvider which delegates to the IntrospectionTokenVerifier and optionally does an additional resource check. Adds an example server and client which makes an authenticated request and gets information from the token. Updates the documentation (but only for v3 as this isn't in v2).
This commit is contained in:
parent
8ad4eb8321
commit
5fb72c7200
7 changed files with 888 additions and 0 deletions
67
examples/auth/propelauth_oauth/README.md
Normal file
67
examples/auth/propelauth_oauth/README.md
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# PropelAuth OAuth Example
|
||||
|
||||
Demonstrates FastMCP server protection with PropelAuth OAuth.
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Configure MCP Authentication in PropelAuth
|
||||
|
||||
**Create a PropelAuth Account**:
|
||||
|
||||
- Go to [PropelAuth Dashboard](https://www.propelauth.com)
|
||||
- Navigate to the **MCP** section and click **Enable MCP**
|
||||
|
||||
**Configure Allowed MCP Clients**:
|
||||
|
||||
- Under **MCP > Allowed MCP Clients**, add redirect URIs for each MCP client you want to allow
|
||||
- PropelAuth provides templates for popular clients like Claude, Cursor, and ChatGPT
|
||||
|
||||
**Configure Scopes**:
|
||||
|
||||
- Under **MCP > Scopes**, define the permissions available to MCP clients (e.g., `read:user_data`)
|
||||
|
||||
**Generate Introspection Credentials**:
|
||||
|
||||
- Go to **MCP > Request Validation** and click **Create Credentials**
|
||||
- Note the **Client ID** and **Client Secret**
|
||||
|
||||
**Note Your Auth URL**:
|
||||
|
||||
- Find your Auth URL in the **Backend Integration** section (e.g., `https://auth.yourdomain.com`)
|
||||
|
||||
Create a `.env` file:
|
||||
|
||||
```bash
|
||||
# Required PropelAuth credentials
|
||||
PROPELAUTH_AUTH_URL=https://auth.yourdomain.com
|
||||
PROPELAUTH_INTROSPECTION_CLIENT_ID=your-client-id
|
||||
PROPELAUTH_INTROSPECTION_CLIENT_SECRET=your-client-secret
|
||||
BASE_URL=http://localhost:8000/
|
||||
# Optional: additional scopes tokens must include (comma-separated)
|
||||
# PROPELAUTH_REQUIRED_SCOPES=read:user_data
|
||||
```
|
||||
|
||||
### 2. Run the Example
|
||||
|
||||
Start the server:
|
||||
|
||||
```bash
|
||||
# From this directory
|
||||
uv run python server.py
|
||||
```
|
||||
|
||||
The server will start on `http://localhost:8000/mcp` with PropelAuth OAuth authentication enabled.
|
||||
|
||||
Test with client:
|
||||
|
||||
```bash
|
||||
uv run python client.py
|
||||
```
|
||||
|
||||
The `client.py` will:
|
||||
|
||||
1. Attempt to connect to the server
|
||||
2. Detect that OAuth authentication is required
|
||||
3. Open a browser for PropelAuth authentication
|
||||
4. Complete the OAuth flow and connect to the server
|
||||
5. Demonstrate calling authenticated tools (echo and whoami)
|
||||
43
examples/auth/propelauth_oauth/client.py
Normal file
43
examples/auth/propelauth_oauth/client.py
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
"""OAuth client example for connecting to PropelAuth-protected FastMCP servers.
|
||||
|
||||
This example demonstrates how to connect to a PropelAuth OAuth-protected FastMCP server.
|
||||
|
||||
To run:
|
||||
python client.py
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
from fastmcp.client import Client
|
||||
|
||||
SERVER_URL = "http://127.0.0.1:8000/mcp"
|
||||
|
||||
|
||||
async def main():
|
||||
try:
|
||||
async with Client(SERVER_URL, auth="oauth") as client:
|
||||
assert await client.ping()
|
||||
print("✅ Successfully authenticated with PropelAuth!")
|
||||
|
||||
tools = await client.list_tools()
|
||||
print(f"🔧 Available tools ({len(tools)}):")
|
||||
for tool in tools:
|
||||
print(f" - {tool.name}: {tool.description}")
|
||||
|
||||
# Test calling a tool
|
||||
result = await client.call_tool(
|
||||
"echo", {"message": "Hello from PropelAuth!"}
|
||||
)
|
||||
print(f"🎯 Echo result: {result}")
|
||||
|
||||
# Test calling whoami tool
|
||||
whoami = await client.call_tool("whoami", {})
|
||||
print(f"👤 Who am I: {whoami}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Authentication failed: {e}")
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
54
examples/auth/propelauth_oauth/server.py
Normal file
54
examples/auth/propelauth_oauth/server.py
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
"""PropelAuth OAuth server example for FastMCP.
|
||||
|
||||
This example demonstrates how to protect a FastMCP server with PropelAuth OAuth.
|
||||
|
||||
Required environment variables:
|
||||
- PROPELAUTH_AUTH_URL: Your PropelAuth Auth URL (from Backend Integration page)
|
||||
- PROPELAUTH_INTROSPECTION_CLIENT_ID: Introspection Client ID (from MCP > Request Validation)
|
||||
- PROPELAUTH_INTROSPECTION_CLIENT_SECRET: Introspection Client Secret (from MCP > Request Validation)
|
||||
|
||||
Optional:
|
||||
- PROPELAUTH_REQUIRED_SCOPES: Comma-separated scopes tokens must include
|
||||
- BASE_URL: Public URL where the FastMCP server is exposed (defaults to `http://localhost:8000/`)
|
||||
|
||||
To run:
|
||||
python server.py
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.auth.providers.propelauth import PropelAuthProvider
|
||||
from fastmcp.server.dependencies import get_access_token
|
||||
|
||||
load_dotenv()
|
||||
|
||||
auth = PropelAuthProvider(
|
||||
auth_url=os.environ["PROPELAUTH_AUTH_URL"],
|
||||
introspection_client_id=os.environ["PROPELAUTH_INTROSPECTION_CLIENT_ID"],
|
||||
introspection_client_secret=os.environ["PROPELAUTH_INTROSPECTION_CLIENT_SECRET"],
|
||||
base_url=os.getenv("BASE_URL", "http://localhost:8000/"),
|
||||
)
|
||||
|
||||
mcp = FastMCP("PropelAuth OAuth Example Server", auth=auth)
|
||||
|
||||
|
||||
@mcp.tool
|
||||
def echo(message: str) -> str:
|
||||
"""Echo the provided message."""
|
||||
return message
|
||||
|
||||
|
||||
@mcp.tool
|
||||
def whoami() -> dict:
|
||||
"""Return the authenticated user's ID."""
|
||||
token = get_access_token()
|
||||
if token is None:
|
||||
return {"error": "Not authenticated"}
|
||||
return {"user_id": token.claims.get("sub")}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="http", port=8000)
|
||||
Loading…
Add table
Add a link
Reference in a new issue