Fix CI/CD: Add Gitea Actions workflow and fix linting issues
Some checks failed
CI / test (push) Failing after 13s
Some checks failed
CI / test (push) Failing after 13s
This commit is contained in:
176
mcp_servers/x_mcp.py
Normal file
176
mcp_servers/x_mcp.py
Normal file
@@ -0,0 +1,176 @@
|
||||
"""
|
||||
X/Twitter MCP Server for 7000%AUTO
|
||||
Provides Twitter posting and search functionality
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
import tweepy
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
mcp = FastMCP("X API Server")
|
||||
|
||||
# Twitter API credentials from environment
|
||||
API_KEY = os.getenv("X_API_KEY", "")
|
||||
API_SECRET = os.getenv("X_API_SECRET", "")
|
||||
ACCESS_TOKEN = os.getenv("X_ACCESS_TOKEN", "")
|
||||
ACCESS_TOKEN_SECRET = os.getenv("X_ACCESS_TOKEN_SECRET", "")
|
||||
BEARER_TOKEN = os.getenv("X_BEARER_TOKEN", "")
|
||||
|
||||
|
||||
def get_client() -> Optional[tweepy.Client]:
|
||||
"""Get authenticated Twitter client"""
|
||||
if not all([API_KEY, API_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET]):
|
||||
return None
|
||||
|
||||
return tweepy.Client(
|
||||
consumer_key=API_KEY,
|
||||
consumer_secret=API_SECRET,
|
||||
access_token=ACCESS_TOKEN,
|
||||
access_token_secret=ACCESS_TOKEN_SECRET,
|
||||
bearer_token=BEARER_TOKEN if BEARER_TOKEN else None,
|
||||
wait_on_rate_limit=True
|
||||
)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def post_tweet(text: str) -> dict:
|
||||
"""
|
||||
Post a tweet to X/Twitter.
|
||||
|
||||
Args:
|
||||
text: Tweet text (max 280 characters)
|
||||
|
||||
Returns:
|
||||
Dictionary with tweet URL and status
|
||||
"""
|
||||
try:
|
||||
if len(text) > 280:
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"Tweet exceeds 280 characters (got {len(text)})"
|
||||
}
|
||||
|
||||
client = get_client()
|
||||
if not client:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Twitter API credentials not configured"
|
||||
}
|
||||
|
||||
response = client.create_tweet(text=text)
|
||||
tweet_id = response.data["id"]
|
||||
|
||||
# Get username for URL construction
|
||||
me = client.get_me()
|
||||
username = me.data.username if me.data else "user"
|
||||
|
||||
tweet_url = f"https://twitter.com/{username}/status/{tweet_id}"
|
||||
|
||||
logger.info(f"Posted tweet: {tweet_url}")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"tweet_id": tweet_id,
|
||||
"url": tweet_url,
|
||||
"text": text,
|
||||
"character_count": len(text)
|
||||
}
|
||||
|
||||
except tweepy.TweepyException as e:
|
||||
logger.error(f"Twitter API error: {e}")
|
||||
return {"success": False, "error": str(e)}
|
||||
except Exception as e:
|
||||
logger.error(f"Error posting tweet: {e}")
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def search_tweets(query: str, max_results: int = 10) -> dict:
|
||||
"""
|
||||
Search recent tweets on X/Twitter.
|
||||
|
||||
Args:
|
||||
query: Search query string
|
||||
max_results: Maximum number of results (default 10, max 100)
|
||||
|
||||
Returns:
|
||||
Dictionary with list of tweets
|
||||
"""
|
||||
try:
|
||||
client = get_client()
|
||||
if not client:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Twitter API credentials not configured"
|
||||
}
|
||||
|
||||
max_results = min(max_results, 100)
|
||||
|
||||
response = client.search_recent_tweets(
|
||||
query=query,
|
||||
max_results=max_results,
|
||||
tweet_fields=["created_at", "public_metrics", "author_id"]
|
||||
)
|
||||
|
||||
tweets = []
|
||||
if response.data:
|
||||
for tweet in response.data:
|
||||
tweets.append({
|
||||
"id": tweet.id,
|
||||
"text": tweet.text,
|
||||
"created_at": tweet.created_at.isoformat() if tweet.created_at else "",
|
||||
"metrics": tweet.public_metrics if hasattr(tweet, "public_metrics") else {}
|
||||
})
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"tweets": tweets,
|
||||
"count": len(tweets)
|
||||
}
|
||||
|
||||
except tweepy.TweepyException as e:
|
||||
logger.error(f"Twitter search error: {e}")
|
||||
return {"success": False, "error": str(e), "tweets": []}
|
||||
except Exception as e:
|
||||
logger.error(f"Error searching tweets: {e}")
|
||||
return {"success": False, "error": str(e), "tweets": []}
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def get_rate_limit_status() -> dict:
|
||||
"""
|
||||
Get current rate limit status for the Twitter API.
|
||||
|
||||
Returns:
|
||||
Dictionary with rate limit information
|
||||
"""
|
||||
try:
|
||||
client = get_client()
|
||||
if not client:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Twitter API credentials not configured"
|
||||
}
|
||||
|
||||
# Basic check by getting user info
|
||||
me = client.get_me()
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"authenticated": True,
|
||||
"username": me.data.username if me.data else None
|
||||
}
|
||||
|
||||
except tweepy.TweepyException as e:
|
||||
return {"success": False, "error": str(e)}
|
||||
except Exception as e:
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run()
|
||||
Reference in New Issue
Block a user