# Directory Structure ``` ├── .env.example ├── .gitignore ├── docker-compose.yml ├── docker-readme.md ├── docker-setup.md ├── Dockerfile ├── fastMcp_readme.md ├── index.ts ├── package-lock.json ├── package.json ├── readme.md ├── stdio-docker-guide.md ├── stdio-docker-setup.md ├── test-mcp-client.js ├── tsconfig.json └── yarn.lock ``` # Files -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` .env /node_modules index.js ``` -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- ``` # Twitter credentials TWITTER_USERNAME=your_twitter_username TWITTER_PASSWORD=your_twitter_password [email protected] TWITTER_2FA_SECRET=your_2fa_secret # Twitter API credentials (optional, used as fallback) TWITTER_API_KEY=your_api_key TWITTER_API_SECRET_KEY=your_api_secret_key TWITTER_ACCESS_TOKEN=your_access_token TWITTER_ACCESS_TOKEN_SECRET=your_access_token_secret # Proxy configuration PROXY_URL=http://host.docker.internal:7890 ``` -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- ```markdown # Twitter MCP Server An MCP (Model Context Protocol) server that provides tools for interacting with Twitter using the agent-twitter-client library. ## Features - **getTweet**: Retrieve a tweet by its ID - **sendTweet**: Post a new tweet to Twitter ## Prerequisites - Node.js (v14 or higher) - npm or yarn - Twitter account credentials ## Installation 1. Clone this repository 2. Install dependencies: ```bash npm install # or yarn install ``` 3. Create a `.env` file in the root directory with your Twitter credentials: ``` TWITTER_USERNAME=your_twitter_username TWITTER_PASSWORD=your_twitter_password [email protected] (optional) TWITTER_2FA_SECRET=your_2fa_secret (optional) # Optional API credentials (used as fallback) TWITTER_API_KEY=your_api_key TWITTER_API_SECRET_KEY=your_api_secret_key TWITTER_ACCESS_TOKEN=your_access_token TWITTER_ACCESS_TOKEN_SECRET=your_access_token_secret ``` ## Usage ### Running the Server You can run the server using the FastMCP CLI tools: ```bash # For development and testing in the terminal npx fastmcp dev # For visual inspection with the MCP Inspector npx fastmcp inspect ``` ### Using the Tools #### getTweet Retrieves a tweet by its ID. Parameters: - `tweetId` (string): The ID of the tweet to retrieve Example: ``` getTweet({"tweetId": "1734609533274853865"}) ``` #### sendTweet Posts a new tweet to Twitter. Parameters: - `text` (string): The text content of the tweet to send Example: ``` sendTweet({"text": "Hello World from MCP!"}) ``` ## Development This server is built using: - [FastMCP](https://github.com/punkpeye/fastmcp) - A TypeScript framework for building MCP servers - [agent-twitter-client](https://www.npmjs.com/package/agent-twitter-client) - A Twitter client library To build the TypeScript code: ```bash npx tsc ``` ## License MIT ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json { "compilerOptions": { "module": "NodeNext", "moduleResolution": "NodeNext", "target": "ES2022", "esModuleInterop": true, "outDir": "dist" } } ``` -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- ```yaml version: '3' services: twitter-scraper-mcp: build: . container_name: twitter-scraper-mcp environment: - HTTP_PROXY=http://host.docker.internal:7890 - HTTPS_PROXY=http://host.docker.internal:7890 - NODE_TLS_REJECT_UNAUTHORIZED=0 # Twitter credentials from .env will be passed through env_file: - .env # No need to expose ports for stdio transport volumes: - .:/app - /app/node_modules extra_hosts: - "host.docker.internal:host-gateway" restart: unless-stopped ``` -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- ```dockerfile FROM node:20-slim # Set working directory WORKDIR /app # Install global proxy tools RUN apt-get update && apt-get install -y \ ca-certificates \ curl \ && rm -rf /var/lib/apt/lists/* # Copy package files COPY package.json yarn.lock ./ # Set proxy environment variables globally ENV HTTP_PROXY=http://host.docker.internal:7890 ENV HTTPS_PROXY=http://host.docker.internal:7890 ENV NODE_TLS_REJECT_UNAUTHORIZED=0 # Install dependencies RUN yarn install # Copy application code COPY . . RUN yarn tsc # Command to run the application CMD ["node", "/app/dist/index.js"] ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json { "name": "twitter-mcp-server", "version": "1.0.0", "main": "index.js", "type": "module", "license": "MIT", "scripts": { "build": "tsc", "start": "node dist/index.js", "dev": "ts-node --esm index.ts", "docker:build": "docker-compose build", "docker:up": "docker-compose up", "docker:down": "docker-compose down", "docker:logs": "docker-compose logs -f", "test:client": "node test-mcp-client.js" }, "dependencies": { "agent-twitter-client": "^0.0.18", "axios": "^1.8.1", "dotenv": "^16.4.7", "fastmcp": "^1.20.2", "zod": "^3.24.2" }, "devDependencies": { "@modelcontextprotocol/sdk": "^1.6.0", "@types/node": "^22.13.9", "ts-node": "^10.9.2", "typescript": "^5.8.2" } } ``` -------------------------------------------------------------------------------- /test-mcp-client.js: -------------------------------------------------------------------------------- ```javascript #!/usr/bin/env node import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'; async function main() { // Create a new MCP client const client = new Client( { name: 'twitter-mcp-test-client', version: '1.0.0', }, { capabilities: {}, } ); try { // Connect to the MCP server console.log('Connecting to MCP server...'); const transport = new SSEClientTransport(new URL('http://localhost:3000/sse')); await client.connect(transport); console.log('Connected to MCP server successfully!'); // List available tools console.log('\nListing available tools:'); const toolsResponse = await client.listTools(); console.log(JSON.stringify(toolsResponse.tools, null, 2)); console.log('\nTesting getTweet tool:'); const tweetId = '1897009050392379653'; // Replace with a valid tweet ID const getTweetResponse = await client.callTool({ name: 'getTweet', arguments: { tweetId, }, }); console.log(JSON.stringify(getTweetResponse, null, 2)); // Test getTweet tool (if you have a tweet ID) // Uncomment and replace with a valid tweet ID /* console.log('\nTesting getTweet tool:'); const tweetId = '1734609533274853865'; // Replace with a valid tweet ID const getTweetResponse = await client.callTool({ name: 'getTweet', arguments: { tweetId, }, }); console.log(JSON.stringify(getTweetResponse, null, 2)); */ // Test sendTweet tool (be careful, this will post to your Twitter account) // Uncomment if you want to test sending a tweet /* console.log('\nTesting sendTweet tool:'); const sendTweetResponse = await client.callTool({ name: 'sendTweet', arguments: { text: 'Test tweet from MCP client ' + new Date().toISOString(), }, }); console.log(JSON.stringify(sendTweetResponse, null, 2)); */ console.log('\nTests completed successfully!'); } catch (error) { console.error('Error:', error); } finally { // Close the client connection await client.close(); } } main().catch(console.error); ``` -------------------------------------------------------------------------------- /docker-readme.md: -------------------------------------------------------------------------------- ```markdown # Docker Setup for Twitter Client with Proxy This setup creates a Docker environment that routes all network traffic through a proxy, solving connection timeout issues when accessing Twitter's API. ## Prerequisites - Docker and Docker Compose installed on your system - A working proxy server (the default configuration uses http://127.0.0.1:7890) ## Configuration 1. The proxy URL is configured in multiple places: - In your `.env` file as `PROXY_URL` - In the Docker container as environment variables `HTTP_PROXY` and `HTTPS_PROXY` 2. The Docker setup uses `host.docker.internal` to access the host machine's proxy. This allows the container to connect to a proxy running on your local machine. ## How to Use ### Building and Running the Container ```bash # Build and start the container docker-compose up --build # Run in detached mode (background) docker-compose up -d --build # View logs docker-compose logs -f ``` ### Stopping the Container ```bash docker-compose down ``` ## How It Works 1. The Dockerfile: - Uses Node.js 18 as the base image - Sets global proxy environment variables - Installs dependencies and compiles TypeScript - Configures the container to use the proxy for all network requests 2. The docker-compose.yml file: - Sets up the service with the necessary environment variables - Mounts the local directory to allow for code changes without rebuilding - Configures host.docker.internal to access the host machine's proxy 3. The modified index.ts: - Checks for proxy configuration from multiple environment variables - Ensures the Scraper instance is properly configured to use the proxy ## Troubleshooting If you still experience connection issues: 1. Verify your proxy is running and accessible 2. Check if the proxy URL is correct in both `.env` and `docker-compose.yml` 3. Try increasing the timeout value in `index.ts` 4. Ensure your proxy allows connections from Docker containers For proxy servers that require authentication, modify the proxy URLs to include credentials: ``` HTTP_PROXY=http://username:[email protected]:7890 HTTPS_PROXY=http://username:[email protected]:7890 ## 开发实践 ### 1.启动交互式终端进入容器,在容器内部进行操作 ``` docker-compose exec twitter-client bash yarn tsc node dist/index.js ``` ``` -------------------------------------------------------------------------------- /docker-setup.md: -------------------------------------------------------------------------------- ```markdown # Docker Setup for Twitter MCP Server This guide explains how to run the Twitter MCP server in Docker and test it from outside the container. ## Prerequisites - Docker and Docker Compose installed on your system - A working proxy server (the default configuration uses http://host.docker.internal:7890) - Twitter credentials (username, password, and optionally API keys) ## Configuration 1. Create a `.env` file in the project root with your Twitter credentials: ``` TWITTER_USERNAME=your_twitter_username TWITTER_PASSWORD=your_twitter_password [email protected] (optional) TWITTER_2FA_SECRET=your_2fa_secret (optional) # Optional API credentials (used as fallback) TWITTER_API_KEY=your_api_key TWITTER_API_SECRET_KEY=your_api_secret_key TWITTER_ACCESS_TOKEN=your_access_token TWITTER_ACCESS_TOKEN_SECRET=your_access_token_secret # Proxy configuration (if needed) PROXY_URL=http://host.docker.internal:7890 ``` 2. The Docker setup uses `host.docker.internal` to access the host machine's proxy. This allows the container to connect to a proxy running on your local machine. ## Running the Server in Docker ### Building and Starting the Container ```bash # Build and start the container in detached mode yarn docker:build yarn docker:up # View logs yarn docker:logs ``` ### Stopping the Container ```bash yarn docker:down ``` ## Testing the MCP Server The MCP server is configured to use SSE (Server-Sent Events) transport and is exposed on port 3000. You can test it using the provided test client or any MCP-compatible client. ### Using the Test Client 1. Install the required dependencies: ```bash yarn install ``` 2. Run the test client: ```bash yarn test:client ``` This will connect to the MCP server running in Docker and list the available tools. ### Using MCP Inspector You can also use the MCP Inspector to test the server: ```bash npx @modelcontextprotocol/inspector http://localhost:3000/sse ``` ## Available MCP Tools The Twitter MCP server provides the following tools: 1. **getTweet**: Retrieve a tweet by its ID - Parameters: `tweetId` (string) - The ID of the tweet to retrieve 2. **sendTweet**: Post a new tweet to Twitter - Parameters: `text` (string) - The text content of the tweet to send ## Troubleshooting If you encounter issues: 1. Check the Docker logs for any error messages: ```bash yarn docker:logs ``` 2. Verify your proxy is running and accessible. 3. Ensure your Twitter credentials are correct in the `.env` file. 4. If you're having network issues, try modifying the proxy settings in `docker-compose.yml` and `Dockerfile`. 5. For proxy servers that require authentication, modify the proxy URLs to include credentials: ``` HTTP_PROXY=http://username:[email protected]:7890 HTTPS_PROXY=http://username:[email protected]:7890 ``` -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- ```typescript #!/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."); ``` -------------------------------------------------------------------------------- /stdio-docker-guide.md: -------------------------------------------------------------------------------- ```markdown # Twitter MCP Server with Stdio Transport in Docker This document explains how to run the Twitter MCP server with stdio transport in Docker and connect to it from Cline. ## Changes Made 1. Modified the server to use stdio transport instead of SSE: - Updated `index.ts` to use `transportType: "stdio"` instead of SSE - Removed port mapping from `docker-compose.yml` as it's no longer needed 2. Updated Cline MCP settings to connect to the Docker container: - Using `docker exec -i` to interact with the container's stdio ## Setup Instructions ### 1. Build and Start the Docker Container ```bash # Build the Docker image npm run docker:build # Start the container npm run docker:up ``` The container will start in the background with the name `twitter-scraper-mcp`. ### 2. Verify Cline MCP Settings The Cline MCP settings have been updated to use stdio transport with the Docker container: ```json { "mcpServers": { "twitter-server": { "command": "docker", "args": ["exec", "-i", "twitter-scraper-mcp", "node", "/app/dist/index.js"], "disabled": false, "autoApprove": [] } } } ``` This configuration: - Uses `docker exec -i` to execute the MCP server inside the container - The `-i` flag keeps stdin open, which is required for stdio transport - Runs the compiled JavaScript file at `/app/dist/index.js` in the container ### 3. Restart Cline After making these changes, restart Cline to apply the new MCP settings. ## Troubleshooting ### Container Not Running If you get an error about the container not being found, make sure the Docker container is running: ```bash docker ps | grep twitter-scraper-mcp ``` If it's not running, start it with: ```bash npm run docker:up ``` ### Checking Container Logs To check the logs from the container: ```bash npm run docker:logs ``` Or directly with Docker: ```bash docker logs twitter-scraper-mcp ``` ### Manual Testing You can manually test the stdio connection by running: ```bash docker exec -i twitter-scraper-mcp node /app/dist/index.js ``` Then type a valid MCP request and press Enter. You should get a response from the server. ## Environment Variables Remember that the Twitter credentials are passed to the container from the `.env` file. Make sure this file exists and contains the required credentials: ``` TWITTER_USERNAME=your_username TWITTER_PASSWORD=your_password [email protected] (optional) TWITTER_2FA_SECRET=your_2fa_secret (optional) # Optional v2 API credentials TWITTER_API_KEY=your_api_key TWITTER_API_SECRET_KEY=your_api_secret_key TWITTER_ACCESS_TOKEN=your_access_token TWITTER_ACCESS_TOKEN_SECRET=your_access_token_secret ``` # Twitter MCP Server with Stdio Transport in Docker This document explains how to run the Twitter MCP server with stdio transport in Docker and connect to it from Cline. ## Changes Made 1. Modified the server to use stdio transport instead of SSE: - Updated `index.ts` to use `transportType: "stdio"` instead of SSE - Removed port mapping from `docker-compose.yml` as it's no longer needed 2. Updated Cline MCP settings to connect to the Docker container: - Using `docker exec -i` to interact with the container's stdio ## Setup Instructions ### 1. Build and Start the Docker Container ```bash # Build the Docker image npm run docker:build # Start the container npm run docker:up ``` The container will start in the background with the name `twitter-scraper-mcp`. ### 2. Verify Cline MCP Settings The Cline MCP settings have been updated to use stdio transport with the Docker container: ```json { "mcpServers": { "twitter-server": { "command": "docker", "args": ["exec", "-i", "twitter-scraper-mcp", "node", "/app/dist/index.js"], "disabled": false, "autoApprove": [] } } } ``` This configuration: - Uses `docker exec -i` to execute the MCP server inside the container - The `-i` flag keeps stdin open, which is required for stdio transport - Runs the compiled JavaScript file at `/app/dist/index.js` in the container ### 3. Restart Cline After making these changes, restart Cline to apply the new MCP settings. ## Troubleshooting ### Container Not Running If you get an error about the container not being found, make sure the Docker container is running: ```bash docker ps | grep twitter-scraper-mcp ``` If it's not running, start it with: ```bash npm run docker:up ``` ### Checking Container Logs To check the logs from the container: ```bash npm run docker:logs ``` Or directly with Docker: ```bash docker logs twitter-scraper-mcp ``` ### Manual Testing You can manually test the stdio connection by running: ```bash docker exec -i twitter-scraper-mcp node /app/dist/index.js ``` Then type a valid MCP request and press Enter. You should get a response from the server. ## Environment Variables Remember that the Twitter credentials are passed to the container from the `.env` file. Make sure this file exists and contains the required credentials: ``` TWITTER_USERNAME=your_username TWITTER_PASSWORD=your_password [email protected] (optional) TWITTER_2FA_SECRET=your_2fa_secret (optional) # Optional v2 API credentials TWITTER_API_KEY=your_api_key TWITTER_API_SECRET_KEY=your_api_secret_key TWITTER_ACCESS_TOKEN=your_access_token # Twitter MCP Server with Stdio Transport in Docker This document explains how to run the Twitter MCP server with stdio transport in Docker and connect to it from Cline. ## Changes Made 1. Modified the server to use stdio transport instead of SSE: - Updated `index.ts` to use `transportType: "stdio"` instead of SSE - Removed port mapping from `docker-compose.yml` as it's no longer needed 2. Updated Cline MCP settings to connect to the Docker container: - Using `docker exec -i` to interact with the container's stdio ## Setup Instructions ### 1. Build and Start the Docker Container ```bash # Build the Docker image npm run docker:build # Start the container npm run docker:up ``` The container will start in the background with the name `twitter-scraper-mcp`. ### 2. Verify Cline MCP Settings The Cline MCP settings have been updated to use stdio transport with the Docker container: ```json { "mcpServers": { "twitter-server": { "command": "docker", "args": ["exec", "-i", "twitter-scraper-mcp", "node", "/app/dist/index.js"], "disabled": false, "autoApprove": [] } } } ``` This configuration: - Uses `docker exec -i` to execute the MCP server inside the container - The `-i` flag keeps stdin open, which is required for stdio transport - Runs the compiled JavaScript file at `/app/dist/index.js` in the container ### 3. Restart Cline After making these changes, restart Cline to apply the new MCP settings. ## Troubleshooting ### Container Not Running If you get an error about the container not being found, make sure the Docker container is running: ```bash docker ps | grep twitter-scraper-mcp ``` If it's not running, start it with: ```bash npm run docker:up ``` ### Checking Container Logs To check the logs from the container: ```bash npm run docker:logs ``` Or directly with Docker: ```bash docker logs twitter-scraper-mcp ``` ### Manual Testing You can manually test the stdio connection by running: ```bash docker exec -i twitter-scraper-mcp node /app/dist/index.js ``` Then type a valid MCP request and press Enter. You should get a response from the server. ## Environment Variables Remember that the Twitter credentials are passed to the container from the `.env` file. Make sure this file exists and contains the required credentials: ``` TWITTER_USERNAME=your_username TWITTER_PASSWORD=your_password [email protected] (optional) TWITTER_2FA_SECRET=your_2fa_secret (optional) # Optional v2 API credentials TWITTER_API_KEY=your_api_key TWITTER_API_SECRET_KEY=your_api_secret_key TWITTER_ACCESS_TOKEN=your_access_token TWITTER_ACCESS_TOKEN_SECRET=your_access_token_secret ``` -------------------------------------------------------------------------------- /stdio-docker-setup.md: -------------------------------------------------------------------------------- ```markdown # Twitter MCP Server with Stdio Transport in Docker This document explains how to run the Twitter MCP server with stdio transport in Docker and connect to it from Cline. ## Changes Made 1. Modified the server to use stdio transport instead of SSE: - Updated `index.ts` to use `transportType: "stdio"` instead of SSE - Removed port mapping from `docker-compose.yml` as it's no longer needed 2. Updated Cline MCP settings to connect to the Docker container: - Using `docker exec -i` to interact with the container's stdio ## Setup Instructions ### 1. Build and Start the Docker Container ```bash # Build the Docker image npm run docker:build # Start the container npm run docker:up ``` The container will start in the background with the name `twitter-scraper-mcp`. ### 2. Verify Cline MCP Settings The Cline MCP settings have been updated to use stdio transport with the Docker container: ```json { "mcpServers": { "twitter-server": { "command": "docker", "args": ["exec", "-i", "twitter-scraper-mcp", "node", "/app/dist/index.js"], "disabled": false, "autoApprove": [] } } } ``` This configuration: - Uses `docker exec -i` to execute the MCP server inside the container - The `-i` flag keeps stdin open, which is required for stdio transport - Runs the compiled JavaScript file at `/app/dist/index.js` in the container ### 3. Restart Cline After making these changes, restart Cline to apply the new MCP settings. ## Troubleshooting ### Container Not Running If you get an error about the container not being found, make sure the Docker container is running: ```bash docker ps | grep twitter-scraper-mcp ``` If it's not running, start it with: ```bash npm run docker:up ``` ### Checking Container Logs To check the logs from the container: ```bash npm run docker:logs ``` Or directly with Docker: ```bash docker logs twitter-scraper-mcp ``` ### Manual Testing You can manually test the stdio connection by running: ```bash docker exec -i twitter-scraper-mcp node /app/dist/index.js ``` Then type a valid MCP request and press Enter. You should get a response from the server. ## Environment Variables Remember that the Twitter credentials are passed to the container from the `.env` file. Make sure this file exists and contains the required credentials: ``` TWITTER_USERNAME=your_username TWITTER_PASSWORD=your_password [email protected] (optional) TWITTER_2FA_SECRET=your_2fa_secret (optional) # Optional v2 API credentials TWITTER_API_KEY=your_api_key TWITTER_API_SECRET_KEY=your_api_secret_key TWITTER_ACCESS_TOKEN=your_access_token TWITTER_ACCESS_TOKEN_SECRET=your_access_token_secret ``` # Twitter MCP Server with Stdio Transport in Docker This document explains how to run the Twitter MCP server with stdio transport in Docker and connect to it from Cline. ## Changes Made 1. Modified the server to use stdio transport instead of SSE: - Updated `index.ts` to use `transportType: "stdio"` instead of SSE - Removed port mapping from `docker-compose.yml` as it's no longer needed 2. Updated Cline MCP settings to connect to the Docker container: - Using `docker exec -i` to interact with the container's stdio ## Setup Instructions ### 1. Build and Start the Docker Container ```bash # Build the Docker image npm run docker:build # Start the container npm run docker:up ``` The container will start in the background with the name `twitter-scraper-mcp`. ### 2. Verify Cline MCP Settings The Cline MCP settings have been updated to use stdio transport with the Docker container: ```json { "mcpServers": { "twitter-server": { "command": "docker", "args": ["exec", "-i", "twitter-scraper-mcp", "node", "/app/dist/index.js"], "disabled": false, "autoApprove": [] } } } ``` This configuration: - Uses `docker exec -i` to execute the MCP server inside the container - The `-i` flag keeps stdin open, which is required for stdio transport - Runs the compiled JavaScript file at `/app/dist/index.js` in the container ### 3. Restart Cline After making these changes, restart Cline to apply the new MCP settings. ## Troubleshooting ### Container Not Running If you get an error about the container not being found, make sure the Docker container is running: ```bash docker ps | grep twitter-scraper-mcp ``` If it's not running, start it with: ```bash npm run docker:up ``` ### Checking Container Logs To check the logs from the container: ```bash npm run docker:logs ``` Or directly with Docker: ```bash docker logs twitter-scraper-mcp ``` ### Manual Testing You can manually test the stdio connection by running: ```bash docker exec -i twitter-scraper-mcp node /app/dist/index.js ``` Then type a valid MCP request and press Enter. You should get a response from the server. ## Environment Variables Remember that the Twitter credentials are passed to the container from the `.env` file. Make sure this file exists and contains the required credentials: ``` TWITTER_USERNAME=your_username TWITTER_PASSWORD=your_password [email protected] (optional) TWITTER_2FA_SECRET=your_2fa_secret (optional) # Optional v2 API credentials TWITTER_API_KEY=your_api_key TWITTER_API_SECRET_KEY=your_api_secret_key TWITTER_ACCESS_TOKEN=your_access_token # Twitter MCP Server with Stdio Transport in Docker This document explains how to run the Twitter MCP server with stdio transport in Docker and connect to it from Cline. ## Changes Made 1. Modified the server to use stdio transport instead of SSE: - Updated `index.ts` to use `transportType: "stdio"` instead of SSE - Removed port mapping from `docker-compose.yml` as it's no longer needed 2. Updated Cline MCP settings to connect to the Docker container: - Using `docker exec -i` to interact with the container's stdio ## Setup Instructions ### 1. Build and Start the Docker Container ```bash # Build the Docker image npm run docker:build # Start the container npm run docker:up ``` The container will start in the background with the name `twitter-scraper-mcp`. ### 2. Verify Cline MCP Settings The Cline MCP settings have been updated to use stdio transport with the Docker container: ```json { "mcpServers": { "twitter-server": { "command": "docker", "args": ["exec", "-i", "twitter-scraper-mcp", "node", "/app/dist/index.js"], "disabled": false, "autoApprove": [] } } } ``` This configuration: - Uses `docker exec -i` to execute the MCP server inside the container - The `-i` flag keeps stdin open, which is required for stdio transport - Runs the compiled JavaScript file at `/app/dist/index.js` in the container ### 3. Restart Cline After making these changes, restart Cline to apply the new MCP settings. ## Troubleshooting ### Container Not Running If you get an error about the container not being found, make sure the Docker container is running: ```bash docker ps | grep twitter-scraper-mcp ``` If it's not running, start it with: ```bash npm run docker:up ``` ### Checking Container Logs To check the logs from the container: ```bash npm run docker:logs ``` Or directly with Docker: ```bash docker logs twitter-scraper-mcp ``` ### Manual Testing You can manually test the stdio connection by running: ```bash docker exec -i twitter-scraper-mcp node /app/dist/index.js ``` Then type a valid MCP request and press Enter. You should get a response from the server. ## Environment Variables Remember that the Twitter credentials are passed to the container from the `.env` file. Make sure this file exists and contains the required credentials: ``` TWITTER_USERNAME=your_username TWITTER_PASSWORD=your_password [email protected] (optional) TWITTER_2FA_SECRET=your_2fa_secret (optional) # Optional v2 API credentials TWITTER_API_KEY=your_api_key TWITTER_API_SECRET_KEY=your_api_secret_key TWITTER_ACCESS_TOKEN=your_access_token TWITTER_ACCESS_TOKEN_SECRET=your_access_token_secret ``` -------------------------------------------------------------------------------- /fastMcp_readme.md: -------------------------------------------------------------------------------- ```markdown # FastMCP A TypeScript framework for building [MCP](https://modelcontextprotocol.io/) servers capable of handling client sessions. > [!NOTE] > > For a Python implementation, see [FastMCP](https://github.com/jlowin/fastmcp). ## Features - Simple Tool, Resource, Prompt definition - [Authentication](#authentication) - [Sessions](#sessions) - [Image content](#returning-an-image) - [Logging](#logging) - [Error handling](#errors) - [SSE](#sse) - CORS (enabled by default) - [Progress notifications](#progress) - [Typed server events](#typed-server-events) - [Prompt argument auto-completion](#prompt-argument-auto-completion) - [Sampling](#requestsampling) - Automated SSE pings - Roots - CLI for [testing](#test-with-mcp-cli) and [debugging](#inspect-with-mcp-inspector) ## Installation ```bash npm install fastmcp ``` ## Quickstart ```ts import { FastMCP } from "fastmcp"; import { z } from "zod"; const server = new FastMCP({ name: "My Server", version: "1.0.0", }); server.addTool({ name: "add", description: "Add two numbers", parameters: z.object({ a: z.number(), b: z.number(), }), execute: async (args) => { return String(args.a + args.b); }, }); server.start({ transportType: "stdio", }); ``` _That's it!_ You have a working MCP server. You can test the server in terminal with: ```bash git clone https://github.com/punkpeye/fastmcp.git cd fastmcp npm install # Test the addition server example using CLI: npx fastmcp dev src/examples/addition.ts # Test the addition server example using MCP Inspector: npx fastmcp inspect src/examples/addition.ts ``` ### SSE You can also run the server with SSE support: ```ts server.start({ transportType: "sse", sse: { endpoint: "/sse", port: 8080, }, }); ``` This will start the server and listen for SSE connections on `http://localhost:8080/sse`. You can then use `SSEClientTransport` to connect to the server: ```ts import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"; const client = new Client( { name: "example-client", version: "1.0.0", }, { capabilities: {}, }, ); const transport = new SSEClientTransport(new URL(`http://localhost:8080/sse`)); await client.connect(transport); ``` ## Core Concepts ### Tools [Tools](https://modelcontextprotocol.io/docs/concepts/tools) in MCP allow servers to expose executable functions that can be invoked by clients and used by LLMs to perform actions. ```js server.addTool({ name: "fetch", description: "Fetch the content of a url", parameters: z.object({ url: z.string(), }), execute: async (args) => { return await fetchWebpageContent(args.url); }, }); ``` #### Returning a string `execute` can return a string: ```js server.addTool({ name: "download", description: "Download a file", parameters: z.object({ url: z.string(), }), execute: async (args) => { return "Hello, world!"; }, }); ``` The latter is equivalent to: ```js server.addTool({ name: "download", description: "Download a file", parameters: z.object({ url: z.string(), }), execute: async (args) => { return { content: [ { type: "text", text: "Hello, world!", }, ], }; }, }); ``` #### Returning a list If you want to return a list of messages, you can return an object with a `content` property: ```js server.addTool({ name: "download", description: "Download a file", parameters: z.object({ url: z.string(), }), execute: async (args) => { return { content: [ { type: "text", text: "First message" }, { type: "text", text: "Second message" }, ], }; }, }); ``` #### Returning an image Use the `imageContent` to create a content object for an image: ```js import { imageContent } from "fastmcp"; server.addTool({ name: "download", description: "Download a file", parameters: z.object({ url: z.string(), }), execute: async (args) => { return imageContent({ url: "https://example.com/image.png", }); // or... // return imageContent({ // path: "/path/to/image.png", // }); // or... // return imageContent({ // buffer: Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=", "base64"), // }); // or... // return { // content: [ // await imageContent(...) // ], // }; }, }); ``` The `imageContent` function takes the following options: - `url`: The URL of the image. - `path`: The path to the image file. - `buffer`: The image data as a buffer. Only one of `url`, `path`, or `buffer` must be specified. The above example is equivalent to: ```js server.addTool({ name: "download", description: "Download a file", parameters: z.object({ url: z.string(), }), execute: async (args) => { return { content: [ { type: "image", data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=", mimeType: "image/png", }, ], }; }, }); ``` #### Logging Tools can log messages to the client using the `log` object in the context object: ```js server.addTool({ name: "download", description: "Download a file", parameters: z.object({ url: z.string(), }), execute: async (args, { log }) => { log.info("Downloading file...", { url, }); // ... log.info("Downloaded file"); return "done"; }, }); ``` The `log` object has the following methods: - `debug(message: string, data?: SerializableValue)` - `error(message: string, data?: SerializableValue)` - `info(message: string, data?: SerializableValue)` - `warn(message: string, data?: SerializableValue)` #### Errors The errors that are meant to be shown to the user should be thrown as `UserError` instances: ```js import { UserError } from "fastmcp"; server.addTool({ name: "download", description: "Download a file", parameters: z.object({ url: z.string(), }), execute: async (args) => { if (args.url.startsWith("https://example.com")) { throw new UserError("This URL is not allowed"); } return "done"; }, }); ``` #### Progress Tools can report progress by calling `reportProgress` in the context object: ```js server.addTool({ name: "download", description: "Download a file", parameters: z.object({ url: z.string(), }), execute: async (args, { reportProgress }) => { reportProgress({ progress: 0, total: 100, }); // ... reportProgress({ progress: 100, total: 100, }); return "done"; }, }); ``` ### Resources [Resources](https://modelcontextprotocol.io/docs/concepts/resources) represent any kind of data that an MCP server wants to make available to clients. This can include: - File contents - Screenshots and images - Log files - And more Each resource is identified by a unique URI and can contain either text or binary data. ```ts server.addResource({ uri: "file:///logs/app.log", name: "Application Logs", mimeType: "text/plain", async load() { return { text: await readLogFile(), }; }, }); ``` > [!NOTE] > > `load` can return multiple resources. This could be used, for example, to return a list of files inside a directory when the directory is read. > > ```ts > async load() { > return [ > { > text: "First file content", > }, > { > text: "Second file content", > }, > ]; > } > ``` You can also return binary contents in `load`: ```ts async load() { return { blob: 'base64-encoded-data' }; } ``` ### Resource templates You can also define resource templates: ```ts server.addResourceTemplate({ uriTemplate: "file:///logs/{name}.log", name: "Application Logs", mimeType: "text/plain", arguments: [ { name: "name", description: "Name of the log", required: true, }, ], async load({ name }) { return { text: `Example log content for ${name}`, }; }, }); ``` #### Resource template argument auto-completion Provide `complete` functions for resource template arguments to enable automatic completion: ```ts server.addResourceTemplate({ uriTemplate: "file:///logs/{name}.log", name: "Application Logs", mimeType: "text/plain", arguments: [ { name: "name", description: "Name of the log", required: true, complete: async (value) => { if (value === "Example") { return { values: ["Example Log"], }; } return { values: [], }; }, }, ], async load({ name }) { return { text: `Example log content for ${name}`, }; }, }); ``` ### Prompts [Prompts](https://modelcontextprotocol.io/docs/concepts/prompts) enable servers to define reusable prompt templates and workflows that clients can easily surface to users and LLMs. They provide a powerful way to standardize and share common LLM interactions. ```ts server.addPrompt({ name: "git-commit", description: "Generate a Git commit message", arguments: [ { name: "changes", description: "Git diff or description of changes", required: true, }, ], load: async (args) => { return `Generate a concise but descriptive commit message for these changes:\n\n${args.changes}`; }, }); ``` #### Prompt argument auto-completion Prompts can provide auto-completion for their arguments: ```js server.addPrompt({ name: "countryPoem", description: "Writes a poem about a country", load: async ({ name }) => { return `Hello, ${name}!`; }, arguments: [ { name: "name", description: "Name of the country", required: true, complete: async (value) => { if (value === "Germ") { return { values: ["Germany"], }; } return { values: [], }; }, }, ], }); ``` #### Prompt argument auto-completion using `enum` If you provide an `enum` array for an argument, the server will automatically provide completions for the argument. ```js server.addPrompt({ name: "countryPoem", description: "Writes a poem about a country", load: async ({ name }) => { return `Hello, ${name}!`; }, arguments: [ { name: "name", description: "Name of the country", required: true, enum: ["Germany", "France", "Italy"], }, ], }); ``` ### Authentication FastMCP allows you to `authenticate` clients using a custom function: ```ts import { AuthError } from "fastmcp"; const server = new FastMCP({ name: "My Server", version: "1.0.0", authenticate: ({request}) => { const apiKey = request.headers["x-api-key"]; if (apiKey !== '123') { throw new Response(null, { status: 401, statusText: "Unauthorized", }); } // Whatever you return here will be accessible in the `context.session` object. return { id: 1, } }, }); ``` Now you can access the authenticated session data in your tools: ```ts server.addTool({ name: "sayHello", execute: async (args, { session }) => { return `Hello, ${session.id}!`; }, }); ``` ### Sessions The `session` object is an instance of `FastMCPSession` and it describes active client sessions. ```ts server.sessions; ``` We allocate a new server instance for each client connection to enable 1:1 communication between a client and the server. ### Typed server events You can listen to events emitted by the server using the `on` method: ```ts server.on("connect", (event) => { console.log("Client connected:", event.session); }); server.on("disconnect", (event) => { console.log("Client disconnected:", event.session); }); ``` ## `FastMCPSession` `FastMCPSession` represents a client session and provides methods to interact with the client. Refer to [Sessions](#sessions) for examples of how to obtain a `FastMCPSession` instance. ### `requestSampling` `requestSampling` creates a [sampling](https://modelcontextprotocol.io/docs/concepts/sampling) request and returns the response. ```ts await session.requestSampling({ messages: [ { role: "user", content: { type: "text", text: "What files are in the current directory?", }, }, ], systemPrompt: "You are a helpful file system assistant.", includeContext: "thisServer", maxTokens: 100, }); ``` ### `clientCapabilities` The `clientCapabilities` property contains the client capabilities. ```ts session.clientCapabilities; ``` ### `loggingLevel` The `loggingLevel` property describes the logging level as set by the client. ```ts session.loggingLevel; ``` ### `roots` The `roots` property contains the roots as set by the client. ```ts session.roots; ``` ### `server` The `server` property contains an instance of MCP server that is associated with the session. ```ts session.server; ``` ### Typed session events You can listen to events emitted by the session using the `on` method: ```ts session.on("rootsChanged", (event) => { console.log("Roots changed:", event.roots); }); session.on("error", (event) => { console.error("Error:", event.error); }); ``` ## Running Your Server ### Test with `mcp-cli` The fastest way to test and debug your server is with `fastmcp dev`: ```bash npx fastmcp dev server.js npx fastmcp dev server.ts ``` This will run your server with [`mcp-cli`](https://github.com/wong2/mcp-cli) for testing and debugging your MCP server in the terminal. ### Inspect with `MCP Inspector` Another way is to use the official [`MCP Inspector`](https://modelcontextprotocol.io/docs/tools/inspector) to inspect your server with a Web UI: ```bash npx fastmcp inspect server.ts ``` ## Showcase > [!NOTE] > > If you've developed a server using FastMCP, please [submit a PR](https://github.com/punkpeye/fastmcp) to showcase it here! - https://github.com/apinetwork/piapi-mcp-server ## Acknowledgements - FastMCP is inspired by the [Python implementation](https://github.com/jlowin/fastmcp) by [Jonathan Lowin](https://github.com/jlowin). - Parts of codebase were adopted from [LiteMCP](https://github.com/wong2/litemcp). - Parts of codebase were adopted from [Model Context protocolでSSEをやってみる](https://dev.classmethod.jp/articles/mcp-sse/). ```