Twitter MCP Server
by takiAA
#!/usr/bin/env node
import { FastMCP, UserError } from "fastmcp";
import { Scraper } from 'agent-twitter-client';
import dotenv from 'dotenv';
import { z } from "zod";
// Load environment variables from .env file
dotenv.config();
// Create a new FastMCP server
const server = new FastMCP({
name: "twitter-mcp-server",
version: "1.0.0",
});
// Global scraper instance
let scraper: Scraper | null = null;
// Initialize and authenticate the scraper
async function initScraper() {
if (scraper) return scraper;
// Check for required environment variables
if (!process.env.TWITTER_USERNAME || !process.env.TWITTER_PASSWORD) {
throw new UserError('Missing required environment variables: TWITTER_USERNAME and TWITTER_PASSWORD must be set');
}
scraper = new Scraper();
try {
//console.log('Attempting to login with credentials...');
// Try basic authentication first
//console.log('Using basic authentication');
//console.log(`Username: ${process.env.TWITTER_USERNAME}`);
// Don't log the actual password, just log that we're using it
//console.log('Password: [REDACTED]');
try {
await scraper.login(
process.env.TWITTER_USERNAME,
process.env.TWITTER_PASSWORD,
process.env.TWITTER_EMAIL,
process.env.TWITTER_2FA_SECRET
);
} catch (basicAuthError) {
console.error('Basic authentication failed:', basicAuthError);
// If basic auth fails and we have v2 credentials, try that
if (process.env.TWITTER_API_KEY &&
process.env.TWITTER_API_SECRET_KEY &&
process.env.TWITTER_ACCESS_TOKEN &&
process.env.TWITTER_ACCESS_TOKEN_SECRET) {
//console.log('Falling back to v2 API credentials');
// Login with v2 API credentials
await scraper.login(
process.env.TWITTER_USERNAME,
process.env.TWITTER_PASSWORD,
process.env.TWITTER_EMAIL || undefined,
process.env.TWITTER_API_KEY,
process.env.TWITTER_API_SECRET_KEY,
process.env.TWITTER_ACCESS_TOKEN,
process.env.TWITTER_ACCESS_TOKEN_SECRET
);
} else {
// If we don't have v2 credentials, rethrow the error
throw new UserError(`Authentication failed: ${basicAuthError.message}`);
}
}
//console.log('Login successful');
return scraper;
} catch (authError) {
console.error('Authentication failed:', authError);
throw new UserError(`Authentication failed: ${authError.message}`);
}
}
// Add getTweet tool
server.addTool({
name: "getTweet",
description: "Get a tweet by its ID",
parameters: z.object({
tweetId: z.string().describe("The ID of the tweet to retrieve"),
}),
execute: async (args, { log }) => {
try {
log.info("Initializing Twitter scraper...");
const twitterScraper = await initScraper();
log.info("Fetching tweet...", { tweetId: args.tweetId });
const tweet = await twitterScraper.getTweet(args.tweetId);
log.info("result:",tweet.text);
log.info("Tweet fetched successfully");
return tweet.text;
} catch (error) {
log.error("Failed to get tweet", { error: error.message });
throw new UserError(`Failed to get tweet: ${error.message}`);
}
},
});
// Add sendTweet tool
server.addTool({
name: "sendTweet",
description: "Send a new tweet",
parameters: z.object({
text: z.string().describe("The text content of the tweet to send"),
}),
execute: async (args, { log }) => {
try {
log.info("Initializing Twitter scraper...");
const twitterScraper = await initScraper();
log.info("Sending tweet...");
const result = await twitterScraper.sendTweet(args.text);
log.info("result:",await result.json());
log.info("Tweet sent successfully");
const resultJson = await result.json();
return resultJson;
} catch (error) {
log.error("Failed to send tweet", { error: error.message });
throw new UserError(`Failed to send tweet: ${error.message}`);
}
},
});
// Start the server
server.start({
transportType: "stdio", // Use stdio for direct process communication
});
// log.info("Twitter MCP server started with stdio transport.");