Spaces:
Sleeping
Sleeping
Abdelkarim Bengrine commited on
Commit Β·
532ff49
1
Parent(s): be9510b
fix: oauth
Browse files- TWITTER_OAUTH_README.md +177 -0
- __pycache__/echo_server.cpython-311.pyc +0 -0
- __pycache__/math_server.cpython-311.pyc +0 -0
- echo_server.py +172 -3
- requirements.txt +2 -1
- templates/index.html +28 -5
- twitter_config.py +51 -0
- twitter_oauth_example.py +138 -0
TWITTER_OAUTH_README.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Twitter OAuth Integration with FastMCP
|
| 2 |
+
|
| 3 |
+
This project demonstrates how to integrate Twitter OAuth 2.1 authentication with FastMCP using the Model Context Protocol (MCP).
|
| 4 |
+
|
| 5 |
+
## Features
|
| 6 |
+
|
| 7 |
+
- **Twitter OAuth 2.1 Authentication**: Secure authentication using OAuth 2.1 with PKCE
|
| 8 |
+
- **Twitter API v2 Integration**: Full access to Twitter's API v2 endpoints
|
| 9 |
+
- **MCP Tools**: Ready-to-use tools for common Twitter operations
|
| 10 |
+
- **Token Management**: Automatic token caching and refresh
|
| 11 |
+
- **FastMCP Integration**: Seamless integration with FastMCP's OAuth helper
|
| 12 |
+
|
| 13 |
+
## Available Tools
|
| 14 |
+
|
| 15 |
+
The Echo server now includes the following Twitter OAuth tools:
|
| 16 |
+
|
| 17 |
+
1. **`authenticate_twitter()`** - Authenticate with Twitter using OAuth 2.1
|
| 18 |
+
2. **`post_tweet(text: str)`** - Post tweets to Twitter
|
| 19 |
+
3. **`get_twitter_profile()`** - Get user's Twitter profile information
|
| 20 |
+
4. **`search_tweets(query: str, max_results: int)`** - Search for tweets on Twitter
|
| 21 |
+
|
| 22 |
+
## Setup Instructions
|
| 23 |
+
|
| 24 |
+
### 1. Twitter Developer Account Setup
|
| 25 |
+
|
| 26 |
+
1. Go to [Twitter Developer Portal](https://developer.twitter.com/)
|
| 27 |
+
2. Create a new app or use an existing one
|
| 28 |
+
3. Note down your **Client ID** and **Client Secret**
|
| 29 |
+
4. Set up a redirect URI (e.g., `http://localhost:8080/callback`)
|
| 30 |
+
|
| 31 |
+
### 2. Environment Configuration
|
| 32 |
+
|
| 33 |
+
Create a `.env` file in the project root with the following variables:
|
| 34 |
+
|
| 35 |
+
```bash
|
| 36 |
+
# Twitter OAuth Configuration
|
| 37 |
+
TWITTER_CLIENT_ID=your_twitter_client_id_here
|
| 38 |
+
TWITTER_CLIENT_SECRET=your_twitter_client_secret_here
|
| 39 |
+
TWITTER_REDIRECT_URI=http://localhost:8080/callback
|
| 40 |
+
|
| 41 |
+
# Optional: Custom OAuth server URL
|
| 42 |
+
TWITTER_OAUTH_SERVER_URL=https://api.twitter.com/2
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
### 3. Install Dependencies
|
| 46 |
+
|
| 47 |
+
```bash
|
| 48 |
+
pip install -r requirements.txt
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
### 4. Run the Server
|
| 52 |
+
|
| 53 |
+
```bash
|
| 54 |
+
python server.py
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
The server will be available at `http://localhost:10000` with the Echo MCP server (including Twitter OAuth) at `/echo/mcp`.
|
| 58 |
+
|
| 59 |
+
## Usage Examples
|
| 60 |
+
|
| 61 |
+
### Method 1: Simple OAuth (Default Settings)
|
| 62 |
+
|
| 63 |
+
```python
|
| 64 |
+
from fastmcp import Client
|
| 65 |
+
|
| 66 |
+
# Uses default OAuth settings
|
| 67 |
+
async with Client("https://api.twitter.com/2", auth="oauth") as client:
|
| 68 |
+
await client.ping()
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
### Method 2: Advanced OAuth (Custom Configuration)
|
| 72 |
+
|
| 73 |
+
```python
|
| 74 |
+
from fastmcp import Client
|
| 75 |
+
from fastmcp.client.auth import OAuth
|
| 76 |
+
|
| 77 |
+
# Configure OAuth with custom settings
|
| 78 |
+
oauth = OAuth(
|
| 79 |
+
mcp_url="https://api.twitter.com/2",
|
| 80 |
+
scopes=["tweet.read", "tweet.write", "users.read"],
|
| 81 |
+
client_name="FastMCP Twitter Client"
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
async with Client("https://api.twitter.com/2", auth=oauth) as client:
|
| 85 |
+
await client.ping()
|
| 86 |
+
```
|
| 87 |
+
|
| 88 |
+
### Method 3: Using MCP Tools
|
| 89 |
+
|
| 90 |
+
```python
|
| 91 |
+
from echo_server import authenticate_twitter, post_tweet, get_twitter_profile, search_tweets
|
| 92 |
+
|
| 93 |
+
# Authenticate with Twitter
|
| 94 |
+
auth_result = await authenticate_twitter()
|
| 95 |
+
print(auth_result)
|
| 96 |
+
|
| 97 |
+
# Get user profile
|
| 98 |
+
profile = await get_twitter_profile()
|
| 99 |
+
print(profile)
|
| 100 |
+
|
| 101 |
+
# Search for tweets
|
| 102 |
+
tweets = await search_tweets("FastMCP", max_results=10)
|
| 103 |
+
print(tweets)
|
| 104 |
+
|
| 105 |
+
# Post a tweet
|
| 106 |
+
tweet_result = await post_tweet("Hello from FastMCP Twitter OAuth! π¦")
|
| 107 |
+
print(tweet_result)
|
| 108 |
+
```
|
| 109 |
+
|
| 110 |
+
## Running the Example Script
|
| 111 |
+
|
| 112 |
+
A complete example script is provided in `twitter_oauth_example.py`:
|
| 113 |
+
|
| 114 |
+
```bash
|
| 115 |
+
python twitter_oauth_example.py
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
This script demonstrates all three methods of using Twitter OAuth with FastMCP.
|
| 119 |
+
|
| 120 |
+
## OAuth Flow
|
| 121 |
+
|
| 122 |
+
The OAuth flow follows these steps:
|
| 123 |
+
|
| 124 |
+
1. **Token Check**: Check for existing valid tokens in the cache
|
| 125 |
+
2. **OAuth Server Discovery**: Discover Twitter's OAuth endpoints
|
| 126 |
+
3. **Dynamic Client Registration**: Register the client with Twitter (if needed)
|
| 127 |
+
4. **Local Callback Server**: Start a temporary local server for OAuth callback
|
| 128 |
+
5. **Browser Interaction**: Open user's browser for authentication
|
| 129 |
+
6. **Authorization Code Exchange**: Exchange authorization code for access token
|
| 130 |
+
7. **Token Caching**: Save tokens for future use
|
| 131 |
+
8. **Authenticated Requests**: Use access token for API requests
|
| 132 |
+
9. **Token Refresh**: Automatically refresh expired tokens
|
| 133 |
+
|
| 134 |
+
## Token Management
|
| 135 |
+
|
| 136 |
+
- Tokens are automatically cached in `~/.fastmcp/oauth-mcp-client-cache/`
|
| 137 |
+
- Tokens persist between application runs
|
| 138 |
+
- Automatic token refresh when expired
|
| 139 |
+
- Clear tokens using the `FileTokenStorage` class if needed
|
| 140 |
+
|
| 141 |
+
## Error Handling
|
| 142 |
+
|
| 143 |
+
All tools return structured responses with:
|
| 144 |
+
- `status`: "success" or "error"
|
| 145 |
+
- `message`: Human-readable message
|
| 146 |
+
- `data`: Response data (on success) or error details (on failure)
|
| 147 |
+
|
| 148 |
+
## Security Notes
|
| 149 |
+
|
| 150 |
+
- Never commit your `.env` file to version control
|
| 151 |
+
- Use environment variables for sensitive configuration
|
| 152 |
+
- The OAuth flow uses PKCE for enhanced security
|
| 153 |
+
- Tokens are stored securely in the user's home directory
|
| 154 |
+
|
| 155 |
+
## Troubleshooting
|
| 156 |
+
|
| 157 |
+
### Common Issues
|
| 158 |
+
|
| 159 |
+
1. **Missing Environment Variables**: Ensure all required environment variables are set
|
| 160 |
+
2. **Invalid Client Credentials**: Verify your Twitter app credentials
|
| 161 |
+
3. **Redirect URI Mismatch**: Ensure the redirect URI matches your Twitter app configuration
|
| 162 |
+
4. **Network Issues**: Check your internet connection and firewall settings
|
| 163 |
+
|
| 164 |
+
### Debug Mode
|
| 165 |
+
|
| 166 |
+
Enable debug logging by setting the environment variable:
|
| 167 |
+
```bash
|
| 168 |
+
export FASTMCP_DEBUG=1
|
| 169 |
+
```
|
| 170 |
+
|
| 171 |
+
## API Reference
|
| 172 |
+
|
| 173 |
+
For more details about FastMCP OAuth, see the [official documentation](https://gofastmcp.com/clients/auth/oauth).
|
| 174 |
+
|
| 175 |
+
## License
|
| 176 |
+
|
| 177 |
+
This project is part of the FastMCP ecosystem and follows the same licensing terms.
|
__pycache__/echo_server.cpython-311.pyc
ADDED
|
Binary file (8.19 kB). View file
|
|
|
__pycache__/math_server.cpython-311.pyc
ADDED
|
Binary file (1.78 kB). View file
|
|
|
echo_server.py
CHANGED
|
@@ -1,7 +1,176 @@
|
|
| 1 |
from mcp.server.fastmcp import FastMCP
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
-
mcp = FastMCP(name=
|
| 4 |
|
| 5 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
def echo(message: str) -> str:
|
| 7 |
-
return f
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from mcp.server.fastmcp import FastMCP
|
| 2 |
+
from fastmcp import Client
|
| 3 |
+
from fastmcp.client.auth import OAuth
|
| 4 |
+
import os
|
| 5 |
+
from typing import Optional, Dict, Any
|
| 6 |
|
| 7 |
+
mcp = FastMCP(name='EchoServer', stateless_http=True)
|
| 8 |
|
| 9 |
+
# Twitter OAuth configuration
|
| 10 |
+
TWITTER_CLIENT_ID = os.getenv('TWITTER_CLIENT_ID')
|
| 11 |
+
TWITTER_CLIENT_SECRET = os.getenv('TWITTER_CLIENT_SECRET')
|
| 12 |
+
TWITTER_REDIRECT_URI = os.getenv(
|
| 13 |
+
'TWITTER_REDIRECT_URI', 'http://localhost:8080/callback'
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
# Global Twitter client instance
|
| 17 |
+
twitter_client: Optional[Client] = None
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
async def get_twitter_client() -> Client:
|
| 21 |
+
"""Get authenticated Twitter client using OAuth"""
|
| 22 |
+
global twitter_client
|
| 23 |
+
|
| 24 |
+
if twitter_client is None:
|
| 25 |
+
# Configure OAuth for Twitter
|
| 26 |
+
oauth = OAuth(
|
| 27 |
+
mcp_url='https://api.twitter.com/2', # Twitter API v2 endpoint
|
| 28 |
+
scopes=['tweet.read', 'tweet.write', 'users.read'],
|
| 29 |
+
client_name='FastMCP Twitter Client',
|
| 30 |
+
additional_client_metadata={
|
| 31 |
+
'client_id': TWITTER_CLIENT_ID,
|
| 32 |
+
'client_secret': TWITTER_CLIENT_SECRET,
|
| 33 |
+
'redirect_uri': TWITTER_REDIRECT_URI
|
| 34 |
+
}
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
twitter_client = Client('https://api.twitter.com/2', auth=oauth)
|
| 38 |
+
await twitter_client.__aenter__()
|
| 39 |
+
|
| 40 |
+
return twitter_client
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
@mcp.tool(description='A simple echo tool')
|
| 44 |
def echo(message: str) -> str:
|
| 45 |
+
return f'Echo: {message}'
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
@mcp.tool(description='Authenticate with Twitter using OAuth 2.1')
|
| 49 |
+
async def authenticate_twitter() -> Dict[str, Any]:
|
| 50 |
+
"""Authenticate with Twitter and return authentication status"""
|
| 51 |
+
try:
|
| 52 |
+
client = await get_twitter_client()
|
| 53 |
+
# Test the connection by making a simple API call
|
| 54 |
+
response = await client.request('GET', '/users/me')
|
| 55 |
+
return {
|
| 56 |
+
'status': 'success',
|
| 57 |
+
'message': 'Successfully authenticated with Twitter',
|
| 58 |
+
'user_data': (
|
| 59 |
+
response.json() if hasattr(response, 'json') else str(response)
|
| 60 |
+
)
|
| 61 |
+
}
|
| 62 |
+
except Exception as e:
|
| 63 |
+
return {
|
| 64 |
+
'status': 'error',
|
| 65 |
+
'message': f'Failed to authenticate with Twitter: {str(e)}',
|
| 66 |
+
'error': str(e)
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
@mcp.tool(description='Post a tweet using Twitter API v2')
|
| 71 |
+
async def post_tweet(text: str) -> Dict[str, Any]:
|
| 72 |
+
"""Post a tweet to Twitter"""
|
| 73 |
+
try:
|
| 74 |
+
client = await get_twitter_client()
|
| 75 |
+
|
| 76 |
+
# Twitter API v2 endpoint for posting tweets
|
| 77 |
+
tweet_data = {
|
| 78 |
+
'text': text
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
response = await client.request('POST', '/tweets', json=tweet_data)
|
| 82 |
+
|
| 83 |
+
return {
|
| 84 |
+
'status': 'success',
|
| 85 |
+
'message': 'Tweet posted successfully',
|
| 86 |
+
'tweet_id': (
|
| 87 |
+
response.json().get('data', {}).get('id')
|
| 88 |
+
if hasattr(response, 'json') else None
|
| 89 |
+
),
|
| 90 |
+
'response': (
|
| 91 |
+
response.json() if hasattr(response, 'json') else str(response)
|
| 92 |
+
)
|
| 93 |
+
}
|
| 94 |
+
except Exception as e:
|
| 95 |
+
return {
|
| 96 |
+
'status': 'error',
|
| 97 |
+
'message': f'Failed to post tweet: {str(e)}',
|
| 98 |
+
'error': str(e)
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
@mcp.tool(description="Get user's Twitter profile information")
|
| 103 |
+
async def get_twitter_profile() -> Dict[str, Any]:
|
| 104 |
+
"""Get the authenticated user's Twitter profile"""
|
| 105 |
+
try:
|
| 106 |
+
client = await get_twitter_client()
|
| 107 |
+
|
| 108 |
+
# Get user profile information
|
| 109 |
+
response = await client.request(
|
| 110 |
+
'GET', '/users/me?user.fields=id,name,username,public_metrics'
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
return {
|
| 114 |
+
'status': 'success',
|
| 115 |
+
'message': 'Profile retrieved successfully',
|
| 116 |
+
'profile': (
|
| 117 |
+
response.json() if hasattr(response, 'json') else str(response)
|
| 118 |
+
)
|
| 119 |
+
}
|
| 120 |
+
except Exception as e:
|
| 121 |
+
return {
|
| 122 |
+
'status': 'error',
|
| 123 |
+
'message': f'Failed to get profile: {str(e)}',
|
| 124 |
+
'error': str(e)
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
@mcp.tool(description='Search for tweets using Twitter API v2')
|
| 129 |
+
async def search_tweets(query: str, max_results: int = 10) -> Dict[str, Any]:
|
| 130 |
+
"""Search for tweets on Twitter"""
|
| 131 |
+
try:
|
| 132 |
+
client = await get_twitter_client()
|
| 133 |
+
|
| 134 |
+
# Twitter API v2 search endpoint
|
| 135 |
+
params = {
|
| 136 |
+
'query': query,
|
| 137 |
+
'max_results': min(max_results, 100), # Twitter API limit
|
| 138 |
+
'tweet.fields': 'created_at,author_id,public_metrics'
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
response = await client.request(
|
| 142 |
+
'GET', '/tweets/search/recent', params=params
|
| 143 |
+
)
|
| 144 |
+
|
| 145 |
+
return {
|
| 146 |
+
'status': 'success',
|
| 147 |
+
'message': f'Found tweets for query: {query}',
|
| 148 |
+
'tweets': (
|
| 149 |
+
response.json() if hasattr(response, 'json') else str(response)
|
| 150 |
+
)
|
| 151 |
+
}
|
| 152 |
+
except Exception as e:
|
| 153 |
+
return {
|
| 154 |
+
'status': 'error',
|
| 155 |
+
'message': f'Failed to search tweets: {str(e)}',
|
| 156 |
+
'error': str(e)
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
# Example usage functions
|
| 161 |
+
async def example_twitter_oauth_usage():
|
| 162 |
+
"""Example of how to use Twitter OAuth with FastMCP"""
|
| 163 |
+
|
| 164 |
+
# Method 1: Using default OAuth settings
|
| 165 |
+
async with Client('https://api.twitter.com/2', auth='oauth') as client:
|
| 166 |
+
await client.ping()
|
| 167 |
+
|
| 168 |
+
# Method 2: Using OAuth helper with custom configuration
|
| 169 |
+
oauth = OAuth(
|
| 170 |
+
mcp_url='https://api.twitter.com/2',
|
| 171 |
+
scopes=['tweet.read', 'tweet.write', 'users.read'],
|
| 172 |
+
client_name='FastMCP Twitter Client'
|
| 173 |
+
)
|
| 174 |
+
|
| 175 |
+
async with Client('https://api.twitter.com/2', auth=oauth) as client:
|
| 176 |
+
await client.ping()
|
requirements.txt
CHANGED
|
@@ -3,4 +3,5 @@ httpx>=0.27.0
|
|
| 3 |
pydantic>=2.7.0
|
| 4 |
selectolax>=0.3.15
|
| 5 |
fastapi
|
| 6 |
-
jinja2
|
|
|
|
|
|
| 3 |
pydantic>=2.7.0
|
| 4 |
selectolax>=0.3.15
|
| 5 |
fastapi
|
| 6 |
+
jinja2
|
| 7 |
+
python-dotenv>=1.0.0
|
templates/index.html
CHANGED
|
@@ -46,13 +46,13 @@
|
|
| 46 |
<div class="card">
|
| 47 |
<span class="badge">FastAPI β’ MCP</span>
|
| 48 |
<h1>Host multiple MCP servers on a single app</h1>
|
| 49 |
-
<p class="subtitle">This template mounts multiple Model Context Protocol (MCP) servers under one FastAPI instance.</p>
|
| 50 |
|
| 51 |
<div class="grid">
|
| 52 |
<div class="tile">
|
| 53 |
<h3>Available servers</h3>
|
| 54 |
<ul class="muted">
|
| 55 |
-
<li><a href="/echo/mcp" target="_blank" rel="noopener noreferrer">{{ base_url }}/echo/mcp</a> β Echo MCP server</li>
|
| 56 |
<li><a href="/math/mcp" target="_blank" rel="noopener noreferrer">{{ base_url }}/math/mcp</a> β Math MCP server</li>
|
| 57 |
</ul>
|
| 58 |
</div>
|
|
@@ -62,9 +62,19 @@
|
|
| 62 |
</div>
|
| 63 |
</div>
|
| 64 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
<h3 style="margin:16px 0 8px">How to get your {base_URL}</h3>
|
| 66 |
<ol class="muted" style="margin:0 0 12px 18px">
|
| 67 |
-
<li>Open your Space and click <strong>
|
| 68 |
<li>Copy the <strong>iframe</strong> code and take the value of the <strong>src</strong> attribute.</li>
|
| 69 |
<li>That origin (e.g. <code>https://your-space.hf.space</code>) is your <code>{base_URL}</code>.</li>
|
| 70 |
</ol>
|
|
@@ -73,8 +83,21 @@
|
|
| 73 |
|
| 74 |
base_URL = {{ base_url }}
|
| 75 |
|
| 76 |
-
Echo MCP
|
| 77 |
-
Math MCP
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
</code></pre>
|
| 79 |
|
| 80 |
<p class="muted" style="margin:10px 0 8px">Illustration of the βEmbed this Spaceβ dialog:</p>
|
|
|
|
| 46 |
<div class="card">
|
| 47 |
<span class="badge">FastAPI β’ MCP</span>
|
| 48 |
<h1>Host multiple MCP servers on a single app</h1>
|
| 49 |
+
<p class="subtitle">This template mounts multiple Model Context Protocol (MCP) servers under one FastAPI instance, including Twitter OAuth integration.</p>
|
| 50 |
|
| 51 |
<div class="grid">
|
| 52 |
<div class="tile">
|
| 53 |
<h3>Available servers</h3>
|
| 54 |
<ul class="muted">
|
| 55 |
+
<li><a href="/echo/mcp" target="_blank" rel="noopener noreferrer">{{ base_url }}/echo/mcp</a> β Echo MCP server with Twitter OAuth</li>
|
| 56 |
<li><a href="/math/mcp" target="_blank" rel="noopener noreferrer">{{ base_url }}/math/mcp</a> β Math MCP server</li>
|
| 57 |
</ul>
|
| 58 |
</div>
|
|
|
|
| 62 |
</div>
|
| 63 |
</div>
|
| 64 |
|
| 65 |
+
<h3 style="margin:16px 0 8px">Twitter OAuth Integration</h3>
|
| 66 |
+
<p class="muted" style="margin:0 0 12px">The Echo server now includes Twitter OAuth 2.1 authentication with the following tools:</p>
|
| 67 |
+
<ul class="muted" style="margin:0 0 12px 18px">
|
| 68 |
+
<li><strong>authenticate_twitter</strong> β Authenticate with Twitter using OAuth 2.1</li>
|
| 69 |
+
<li><strong>post_tweet</strong> β Post tweets to Twitter</li>
|
| 70 |
+
<li><strong>get_twitter_profile</strong> β Get user's Twitter profile information</li>
|
| 71 |
+
<li><strong>search_tweets</strong> β Search for tweets on Twitter</li>
|
| 72 |
+
</ul>
|
| 73 |
+
<p class="muted" style="margin:0 0 12px">Configure environment variables: <code>TWITTER_CLIENT_ID</code>, <code>TWITTER_CLIENT_SECRET</code>, <code>TWITTER_REDIRECT_URI</code></p>
|
| 74 |
+
|
| 75 |
<h3 style="margin:16px 0 8px">How to get your {base_URL}</h3>
|
| 76 |
<ol class="muted" style="margin:0 0 12px 18px">
|
| 77 |
+
<li>Open your Space and click <strong>"Embed this Space"</strong>.</li>
|
| 78 |
<li>Copy the <strong>iframe</strong> code and take the value of the <strong>src</strong> attribute.</li>
|
| 79 |
<li>That origin (e.g. <code>https://your-space.hf.space</code>) is your <code>{base_URL}</code>.</li>
|
| 80 |
</ol>
|
|
|
|
| 83 |
|
| 84 |
base_URL = {{ base_url }}
|
| 85 |
|
| 86 |
+
Echo MCP (with Twitter OAuth) = {{ base_url }}/echo/mcp
|
| 87 |
+
Math MCP = {{ base_url }}/math/mcp
|
| 88 |
+
|
| 89 |
+
# Twitter OAuth Example
|
| 90 |
+
from fastmcp import Client
|
| 91 |
+
from fastmcp.client.auth import OAuth
|
| 92 |
+
|
| 93 |
+
# Simple OAuth
|
| 94 |
+
async with Client("https://api.twitter.com/2", auth="oauth") as client:
|
| 95 |
+
await client.ping()
|
| 96 |
+
|
| 97 |
+
# Advanced OAuth
|
| 98 |
+
oauth = OAuth(mcp_url="https://api.twitter.com/2")
|
| 99 |
+
async with Client("https://api.twitter.com/2", auth=oauth) as client:
|
| 100 |
+
await client.ping()
|
| 101 |
</code></pre>
|
| 102 |
|
| 103 |
<p class="muted" style="margin:10px 0 8px">Illustration of the βEmbed this Spaceβ dialog:</p>
|
twitter_config.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Twitter OAuth Configuration
|
| 3 |
+
|
| 4 |
+
To use this Twitter OAuth implementation, you need to:
|
| 5 |
+
|
| 6 |
+
1. Create a Twitter Developer account at https://developer.twitter.com/
|
| 7 |
+
2. Create a new app in the Twitter Developer Portal
|
| 8 |
+
3. Get your Client ID and Client Secret
|
| 9 |
+
4. Set up the following environment variables:
|
| 10 |
+
|
| 11 |
+
Required Environment Variables:
|
| 12 |
+
- TWITTER_CLIENT_ID: Your Twitter app's client ID
|
| 13 |
+
- TWITTER_CLIENT_SECRET: Your Twitter app's client secret
|
| 14 |
+
- TWITTER_REDIRECT_URI: The redirect URI for OAuth (default: http://localhost:8080/callback)
|
| 15 |
+
|
| 16 |
+
Optional Environment Variables:
|
| 17 |
+
- TWITTER_OAUTH_SERVER_URL: Custom OAuth server URL (default: https://api.twitter.com/2)
|
| 18 |
+
|
| 19 |
+
Example .env file:
|
| 20 |
+
TWITTER_CLIENT_ID=your_client_id_here
|
| 21 |
+
TWITTER_CLIENT_SECRET=your_client_secret_here
|
| 22 |
+
TWITTER_REDIRECT_URI=http://localhost:8080/callback
|
| 23 |
+
"""
|
| 24 |
+
|
| 25 |
+
import os
|
| 26 |
+
from typing import Optional
|
| 27 |
+
|
| 28 |
+
class TwitterConfig:
|
| 29 |
+
"""Configuration class for Twitter OAuth"""
|
| 30 |
+
|
| 31 |
+
def __init__(self):
|
| 32 |
+
self.client_id = os.getenv("TWITTER_CLIENT_ID")
|
| 33 |
+
self.client_secret = os.getenv("TWITTER_CLIENT_SECRET")
|
| 34 |
+
self.redirect_uri = os.getenv("TWITTER_REDIRECT_URI", "http://localhost:8080/callback")
|
| 35 |
+
self.oauth_server_url = os.getenv("TWITTER_OAUTH_SERVER_URL", "https://api.twitter.com/2")
|
| 36 |
+
|
| 37 |
+
def validate(self) -> bool:
|
| 38 |
+
"""Validate that required configuration is present"""
|
| 39 |
+
return bool(self.client_id and self.client_secret)
|
| 40 |
+
|
| 41 |
+
def get_missing_config(self) -> list[str]:
|
| 42 |
+
"""Get list of missing configuration variables"""
|
| 43 |
+
missing = []
|
| 44 |
+
if not self.client_id:
|
| 45 |
+
missing.append("TWITTER_CLIENT_ID")
|
| 46 |
+
if not self.client_secret:
|
| 47 |
+
missing.append("TWITTER_CLIENT_SECRET")
|
| 48 |
+
return missing
|
| 49 |
+
|
| 50 |
+
# Global config instance
|
| 51 |
+
twitter_config = TwitterConfig()
|
twitter_oauth_example.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Twitter OAuth Example using FastMCP
|
| 4 |
+
|
| 5 |
+
This script demonstrates how to use Twitter OAuth authentication with FastMCP.
|
| 6 |
+
It shows both the simple "oauth" string method and the advanced OAuth helper method.
|
| 7 |
+
|
| 8 |
+
Before running this script:
|
| 9 |
+
1. Set up your Twitter Developer account and create an app
|
| 10 |
+
2. Set the required environment variables (see twitter_config.py)
|
| 11 |
+
3. Install dependencies: pip install -r requirements.txt
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
import asyncio
|
| 15 |
+
import os
|
| 16 |
+
from dotenv import load_dotenv
|
| 17 |
+
from fastmcp import Client
|
| 18 |
+
from fastmcp.client.auth import OAuth
|
| 19 |
+
from twitter_config import twitter_config
|
| 20 |
+
|
| 21 |
+
# Load environment variables from .env file
|
| 22 |
+
load_dotenv()
|
| 23 |
+
|
| 24 |
+
async def example_simple_oauth():
|
| 25 |
+
"""Example using simple OAuth string configuration"""
|
| 26 |
+
print("=== Simple OAuth Example ===")
|
| 27 |
+
|
| 28 |
+
try:
|
| 29 |
+
# Method 1: Using default OAuth settings
|
| 30 |
+
async with Client("https://api.twitter.com/2", auth="oauth") as client:
|
| 31 |
+
print("β Successfully connected to Twitter API with simple OAuth")
|
| 32 |
+
|
| 33 |
+
# Test the connection
|
| 34 |
+
response = await client.request("GET", "/users/me")
|
| 35 |
+
print(f"β User data: {response.json() if hasattr(response, 'json') else str(response)}")
|
| 36 |
+
|
| 37 |
+
except Exception as e:
|
| 38 |
+
print(f"β Simple OAuth failed: {e}")
|
| 39 |
+
|
| 40 |
+
async def example_advanced_oauth():
|
| 41 |
+
"""Example using advanced OAuth helper configuration"""
|
| 42 |
+
print("\n=== Advanced OAuth Example ===")
|
| 43 |
+
|
| 44 |
+
# Validate configuration
|
| 45 |
+
if not twitter_config.validate():
|
| 46 |
+
missing = twitter_config.get_missing_config()
|
| 47 |
+
print(f"β Missing configuration: {', '.join(missing)}")
|
| 48 |
+
print("Please set the required environment variables (see twitter_config.py)")
|
| 49 |
+
return
|
| 50 |
+
|
| 51 |
+
try:
|
| 52 |
+
# Method 2: Using OAuth helper with custom configuration
|
| 53 |
+
oauth = OAuth(
|
| 54 |
+
mcp_url=twitter_config.oauth_server_url,
|
| 55 |
+
scopes=["tweet.read", "tweet.write", "users.read"],
|
| 56 |
+
client_name="FastMCP Twitter Client",
|
| 57 |
+
additional_client_metadata={
|
| 58 |
+
"client_id": twitter_config.client_id,
|
| 59 |
+
"client_secret": twitter_config.client_secret,
|
| 60 |
+
"redirect_uri": twitter_config.redirect_uri
|
| 61 |
+
}
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
async with Client(twitter_config.oauth_server_url, auth=oauth) as client:
|
| 65 |
+
print("β Successfully connected to Twitter API with advanced OAuth")
|
| 66 |
+
|
| 67 |
+
# Test the connection
|
| 68 |
+
response = await client.request("GET", "/users/me")
|
| 69 |
+
print(f"β User data: {response.json() if hasattr(response, 'json') else str(response)}")
|
| 70 |
+
|
| 71 |
+
# Example: Post a tweet
|
| 72 |
+
print("\n--- Posting a test tweet ---")
|
| 73 |
+
tweet_data = {
|
| 74 |
+
"text": "Hello from FastMCP Twitter OAuth! π¦"
|
| 75 |
+
}
|
| 76 |
+
tweet_response = await client.request("POST", "/tweets", json=tweet_data)
|
| 77 |
+
print(f"β Tweet posted: {tweet_response.json() if hasattr(tweet_response, 'json') else str(tweet_response)}")
|
| 78 |
+
|
| 79 |
+
except Exception as e:
|
| 80 |
+
print(f"β Advanced OAuth failed: {e}")
|
| 81 |
+
|
| 82 |
+
async def example_using_mcp_tools():
|
| 83 |
+
"""Example using the MCP tools from echo_server.py"""
|
| 84 |
+
print("\n=== MCP Tools Example ===")
|
| 85 |
+
|
| 86 |
+
try:
|
| 87 |
+
# Import the MCP tools
|
| 88 |
+
from echo_server import authenticate_twitter, post_tweet, get_twitter_profile, search_tweets
|
| 89 |
+
|
| 90 |
+
# Authenticate
|
| 91 |
+
auth_result = await authenticate_twitter()
|
| 92 |
+
print(f"Authentication result: {auth_result}")
|
| 93 |
+
|
| 94 |
+
if auth_result["status"] == "success":
|
| 95 |
+
# Get profile
|
| 96 |
+
profile_result = await get_twitter_profile()
|
| 97 |
+
print(f"Profile result: {profile_result}")
|
| 98 |
+
|
| 99 |
+
# Search tweets
|
| 100 |
+
search_result = await search_tweets("FastMCP", max_results=5)
|
| 101 |
+
print(f"Search result: {search_result}")
|
| 102 |
+
|
| 103 |
+
# Post a tweet (uncomment to actually post)
|
| 104 |
+
# tweet_result = await post_tweet("Testing FastMCP Twitter OAuth integration! π")
|
| 105 |
+
# print(f"Tweet result: {tweet_result}")
|
| 106 |
+
|
| 107 |
+
except Exception as e:
|
| 108 |
+
print(f"β MCP tools example failed: {e}")
|
| 109 |
+
|
| 110 |
+
async def main():
|
| 111 |
+
"""Main function to run all examples"""
|
| 112 |
+
print("Twitter OAuth with FastMCP Examples")
|
| 113 |
+
print("=" * 50)
|
| 114 |
+
|
| 115 |
+
# Check if configuration is available
|
| 116 |
+
if not twitter_config.validate():
|
| 117 |
+
print("β οΈ Twitter configuration not found!")
|
| 118 |
+
print("Please set the following environment variables:")
|
| 119 |
+
for var in twitter_config.get_missing_config():
|
| 120 |
+
print(f" - {var}")
|
| 121 |
+
print("\nSee twitter_config.py for more details.")
|
| 122 |
+
return
|
| 123 |
+
|
| 124 |
+
print("β Twitter configuration found")
|
| 125 |
+
print(f" Client ID: {twitter_config.client_id[:10]}..." if twitter_config.client_id else "Not set")
|
| 126 |
+
print(f" Redirect URI: {twitter_config.redirect_uri}")
|
| 127 |
+
print(f" OAuth Server: {twitter_config.oauth_server_url}")
|
| 128 |
+
|
| 129 |
+
# Run examples
|
| 130 |
+
await example_simple_oauth()
|
| 131 |
+
await example_advanced_oauth()
|
| 132 |
+
await example_using_mcp_tools()
|
| 133 |
+
|
| 134 |
+
print("\n" + "=" * 50)
|
| 135 |
+
print("Examples completed!")
|
| 136 |
+
|
| 137 |
+
if __name__ == "__main__":
|
| 138 |
+
asyncio.run(main())
|