# 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: -------------------------------------------------------------------------------- ``` 1 | .env 2 | /node_modules 3 | index.js ``` -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- ``` 1 | # Twitter credentials 2 | TWITTER_USERNAME=your_twitter_username 3 | TWITTER_PASSWORD=your_twitter_password 4 | [email protected] 5 | TWITTER_2FA_SECRET=your_2fa_secret 6 | 7 | # Twitter API credentials (optional, used as fallback) 8 | TWITTER_API_KEY=your_api_key 9 | TWITTER_API_SECRET_KEY=your_api_secret_key 10 | TWITTER_ACCESS_TOKEN=your_access_token 11 | TWITTER_ACCESS_TOKEN_SECRET=your_access_token_secret 12 | 13 | # Proxy configuration 14 | PROXY_URL=http://host.docker.internal:7890 15 | ``` -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- ```markdown 1 | # Twitter MCP Server 2 | 3 | An MCP (Model Context Protocol) server that provides tools for interacting with Twitter using the agent-twitter-client library. 4 | 5 | ## Features 6 | 7 | - **getTweet**: Retrieve a tweet by its ID 8 | - **sendTweet**: Post a new tweet to Twitter 9 | 10 | ## Prerequisites 11 | 12 | - Node.js (v14 or higher) 13 | - npm or yarn 14 | - Twitter account credentials 15 | 16 | ## Installation 17 | 18 | 1. Clone this repository 19 | 2. Install dependencies: 20 | 21 | ```bash 22 | npm install 23 | # or 24 | yarn install 25 | ``` 26 | 27 | 3. Create a `.env` file in the root directory with your Twitter credentials: 28 | 29 | ``` 30 | TWITTER_USERNAME=your_twitter_username 31 | TWITTER_PASSWORD=your_twitter_password 32 | [email protected] (optional) 33 | TWITTER_2FA_SECRET=your_2fa_secret (optional) 34 | 35 | # Optional API credentials (used as fallback) 36 | TWITTER_API_KEY=your_api_key 37 | TWITTER_API_SECRET_KEY=your_api_secret_key 38 | TWITTER_ACCESS_TOKEN=your_access_token 39 | TWITTER_ACCESS_TOKEN_SECRET=your_access_token_secret 40 | ``` 41 | 42 | ## Usage 43 | 44 | ### Running the Server 45 | 46 | You can run the server using the FastMCP CLI tools: 47 | 48 | ```bash 49 | # For development and testing in the terminal 50 | npx fastmcp dev 51 | 52 | # For visual inspection with the MCP Inspector 53 | npx fastmcp inspect 54 | ``` 55 | 56 | ### Using the Tools 57 | 58 | #### getTweet 59 | 60 | Retrieves a tweet by its ID. 61 | 62 | Parameters: 63 | - `tweetId` (string): The ID of the tweet to retrieve 64 | 65 | Example: 66 | ``` 67 | getTweet({"tweetId": "1734609533274853865"}) 68 | ``` 69 | 70 | #### sendTweet 71 | 72 | Posts a new tweet to Twitter. 73 | 74 | Parameters: 75 | - `text` (string): The text content of the tweet to send 76 | 77 | Example: 78 | ``` 79 | sendTweet({"text": "Hello World from MCP!"}) 80 | ``` 81 | 82 | ## Development 83 | 84 | This server is built using: 85 | - [FastMCP](https://github.com/punkpeye/fastmcp) - A TypeScript framework for building MCP servers 86 | - [agent-twitter-client](https://www.npmjs.com/package/agent-twitter-client) - A Twitter client library 87 | 88 | To build the TypeScript code: 89 | 90 | ```bash 91 | npx tsc 92 | ``` 93 | 94 | ## License 95 | 96 | MIT 97 | ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "module": "NodeNext", 4 | "moduleResolution": "NodeNext", 5 | "target": "ES2022", 6 | "esModuleInterop": true, 7 | "outDir": "dist" 8 | } 9 | } 10 | ``` -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- ```yaml 1 | version: '3' 2 | 3 | services: 4 | twitter-scraper-mcp: 5 | build: . 6 | container_name: twitter-scraper-mcp 7 | environment: 8 | - HTTP_PROXY=http://host.docker.internal:7890 9 | - HTTPS_PROXY=http://host.docker.internal:7890 10 | - NODE_TLS_REJECT_UNAUTHORIZED=0 11 | # Twitter credentials from .env will be passed through 12 | env_file: 13 | - .env 14 | # No need to expose ports for stdio transport 15 | volumes: 16 | - .:/app 17 | - /app/node_modules 18 | extra_hosts: 19 | - "host.docker.internal:host-gateway" 20 | restart: unless-stopped 21 | ``` -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- ```dockerfile 1 | FROM node:20-slim 2 | 3 | # Set working directory 4 | WORKDIR /app 5 | 6 | # Install global proxy tools 7 | RUN apt-get update && apt-get install -y \ 8 | ca-certificates \ 9 | curl \ 10 | && rm -rf /var/lib/apt/lists/* 11 | 12 | # Copy package files 13 | COPY package.json yarn.lock ./ 14 | 15 | # Set proxy environment variables globally 16 | ENV HTTP_PROXY=http://host.docker.internal:7890 17 | ENV HTTPS_PROXY=http://host.docker.internal:7890 18 | ENV NODE_TLS_REJECT_UNAUTHORIZED=0 19 | 20 | # Install dependencies 21 | RUN yarn install 22 | 23 | # Copy application code 24 | COPY . . 25 | 26 | RUN yarn tsc 27 | 28 | # Command to run the application 29 | CMD ["node", "/app/dist/index.js"] 30 | ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "twitter-mcp-server", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "type": "module", 6 | "license": "MIT", 7 | "scripts": { 8 | "build": "tsc", 9 | "start": "node dist/index.js", 10 | "dev": "ts-node --esm index.ts", 11 | "docker:build": "docker-compose build", 12 | "docker:up": "docker-compose up", 13 | "docker:down": "docker-compose down", 14 | "docker:logs": "docker-compose logs -f", 15 | "test:client": "node test-mcp-client.js" 16 | }, 17 | "dependencies": { 18 | "agent-twitter-client": "^0.0.18", 19 | "axios": "^1.8.1", 20 | "dotenv": "^16.4.7", 21 | "fastmcp": "^1.20.2", 22 | "zod": "^3.24.2" 23 | }, 24 | "devDependencies": { 25 | "@modelcontextprotocol/sdk": "^1.6.0", 26 | "@types/node": "^22.13.9", 27 | "ts-node": "^10.9.2", 28 | "typescript": "^5.8.2" 29 | } 30 | } 31 | ``` -------------------------------------------------------------------------------- /test-mcp-client.js: -------------------------------------------------------------------------------- ```javascript 1 | #!/usr/bin/env node 2 | import { Client } from '@modelcontextprotocol/sdk/client/index.js'; 3 | import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'; 4 | 5 | async function main() { 6 | // Create a new MCP client 7 | const client = new Client( 8 | { 9 | name: 'twitter-mcp-test-client', 10 | version: '1.0.0', 11 | }, 12 | { 13 | capabilities: {}, 14 | } 15 | ); 16 | 17 | try { 18 | // Connect to the MCP server 19 | console.log('Connecting to MCP server...'); 20 | const transport = new SSEClientTransport(new URL('http://localhost:3000/sse')); 21 | await client.connect(transport); 22 | console.log('Connected to MCP server successfully!'); 23 | 24 | // List available tools 25 | console.log('\nListing available tools:'); 26 | const toolsResponse = await client.listTools(); 27 | console.log(JSON.stringify(toolsResponse.tools, null, 2)); 28 | console.log('\nTesting getTweet tool:'); 29 | const tweetId = '1897009050392379653'; // Replace with a valid tweet ID 30 | const getTweetResponse = await client.callTool({ 31 | name: 'getTweet', 32 | arguments: { 33 | tweetId, 34 | }, 35 | }); 36 | console.log(JSON.stringify(getTweetResponse, null, 2)); 37 | 38 | // Test getTweet tool (if you have a tweet ID) 39 | // Uncomment and replace with a valid tweet ID 40 | /* 41 | console.log('\nTesting getTweet tool:'); 42 | const tweetId = '1734609533274853865'; // Replace with a valid tweet ID 43 | const getTweetResponse = await client.callTool({ 44 | name: 'getTweet', 45 | arguments: { 46 | tweetId, 47 | }, 48 | }); 49 | console.log(JSON.stringify(getTweetResponse, null, 2)); 50 | */ 51 | 52 | // Test sendTweet tool (be careful, this will post to your Twitter account) 53 | // Uncomment if you want to test sending a tweet 54 | /* 55 | console.log('\nTesting sendTweet tool:'); 56 | const sendTweetResponse = await client.callTool({ 57 | name: 'sendTweet', 58 | arguments: { 59 | text: 'Test tweet from MCP client ' + new Date().toISOString(), 60 | }, 61 | }); 62 | console.log(JSON.stringify(sendTweetResponse, null, 2)); 63 | */ 64 | 65 | console.log('\nTests completed successfully!'); 66 | } catch (error) { 67 | console.error('Error:', error); 68 | } finally { 69 | // Close the client connection 70 | await client.close(); 71 | } 72 | } 73 | 74 | main().catch(console.error); 75 | ``` -------------------------------------------------------------------------------- /docker-readme.md: -------------------------------------------------------------------------------- ```markdown 1 | # Docker Setup for Twitter Client with Proxy 2 | 3 | This setup creates a Docker environment that routes all network traffic through a proxy, solving connection timeout issues when accessing Twitter's API. 4 | 5 | ## Prerequisites 6 | 7 | - Docker and Docker Compose installed on your system 8 | - A working proxy server (the default configuration uses http://127.0.0.1:7890) 9 | 10 | ## Configuration 11 | 12 | 1. The proxy URL is configured in multiple places: 13 | - In your `.env` file as `PROXY_URL` 14 | - In the Docker container as environment variables `HTTP_PROXY` and `HTTPS_PROXY` 15 | 16 | 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. 17 | 18 | ## How to Use 19 | 20 | ### Building and Running the Container 21 | 22 | ```bash 23 | # Build and start the container 24 | docker-compose up --build 25 | 26 | # Run in detached mode (background) 27 | docker-compose up -d --build 28 | 29 | # View logs 30 | docker-compose logs -f 31 | ``` 32 | 33 | ### Stopping the Container 34 | 35 | ```bash 36 | docker-compose down 37 | ``` 38 | 39 | ## How It Works 40 | 41 | 1. The Dockerfile: 42 | - Uses Node.js 18 as the base image 43 | - Sets global proxy environment variables 44 | - Installs dependencies and compiles TypeScript 45 | - Configures the container to use the proxy for all network requests 46 | 47 | 2. The docker-compose.yml file: 48 | - Sets up the service with the necessary environment variables 49 | - Mounts the local directory to allow for code changes without rebuilding 50 | - Configures host.docker.internal to access the host machine's proxy 51 | 52 | 3. The modified index.ts: 53 | - Checks for proxy configuration from multiple environment variables 54 | - Ensures the Scraper instance is properly configured to use the proxy 55 | 56 | ## Troubleshooting 57 | 58 | If you still experience connection issues: 59 | 60 | 1. Verify your proxy is running and accessible 61 | 2. Check if the proxy URL is correct in both `.env` and `docker-compose.yml` 62 | 3. Try increasing the timeout value in `index.ts` 63 | 4. Ensure your proxy allows connections from Docker containers 64 | 65 | For proxy servers that require authentication, modify the proxy URLs to include credentials: 66 | ``` 67 | HTTP_PROXY=http://username:[email protected]:7890 68 | HTTPS_PROXY=http://username:[email protected]:7890 69 | 70 | ## 开发实践 71 | 72 | ### 1.启动交互式终端进入容器,在容器内部进行操作 73 | ``` 74 | docker-compose exec twitter-client bash 75 | yarn tsc 76 | node dist/index.js 77 | ``` ``` -------------------------------------------------------------------------------- /docker-setup.md: -------------------------------------------------------------------------------- ```markdown 1 | # Docker Setup for Twitter MCP Server 2 | 3 | This guide explains how to run the Twitter MCP server in Docker and test it from outside the container. 4 | 5 | ## Prerequisites 6 | 7 | - Docker and Docker Compose installed on your system 8 | - A working proxy server (the default configuration uses http://host.docker.internal:7890) 9 | - Twitter credentials (username, password, and optionally API keys) 10 | 11 | ## Configuration 12 | 13 | 1. Create a `.env` file in the project root with your Twitter credentials: 14 | 15 | ``` 16 | TWITTER_USERNAME=your_twitter_username 17 | TWITTER_PASSWORD=your_twitter_password 18 | [email protected] (optional) 19 | TWITTER_2FA_SECRET=your_2fa_secret (optional) 20 | 21 | # Optional API credentials (used as fallback) 22 | TWITTER_API_KEY=your_api_key 23 | TWITTER_API_SECRET_KEY=your_api_secret_key 24 | TWITTER_ACCESS_TOKEN=your_access_token 25 | TWITTER_ACCESS_TOKEN_SECRET=your_access_token_secret 26 | 27 | # Proxy configuration (if needed) 28 | PROXY_URL=http://host.docker.internal:7890 29 | ``` 30 | 31 | 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. 32 | 33 | ## Running the Server in Docker 34 | 35 | ### Building and Starting the Container 36 | 37 | ```bash 38 | # Build and start the container in detached mode 39 | yarn docker:build 40 | yarn docker:up 41 | 42 | # View logs 43 | yarn docker:logs 44 | ``` 45 | 46 | ### Stopping the Container 47 | 48 | ```bash 49 | yarn docker:down 50 | ``` 51 | 52 | ## Testing the MCP Server 53 | 54 | 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. 55 | 56 | ### Using the Test Client 57 | 58 | 1. Install the required dependencies: 59 | 60 | ```bash 61 | yarn install 62 | ``` 63 | 64 | 2. Run the test client: 65 | 66 | ```bash 67 | yarn test:client 68 | ``` 69 | 70 | This will connect to the MCP server running in Docker and list the available tools. 71 | 72 | ### Using MCP Inspector 73 | 74 | You can also use the MCP Inspector to test the server: 75 | 76 | ```bash 77 | npx @modelcontextprotocol/inspector http://localhost:3000/sse 78 | ``` 79 | 80 | ## Available MCP Tools 81 | 82 | The Twitter MCP server provides the following tools: 83 | 84 | 1. **getTweet**: Retrieve a tweet by its ID 85 | - Parameters: `tweetId` (string) - The ID of the tweet to retrieve 86 | 87 | 2. **sendTweet**: Post a new tweet to Twitter 88 | - Parameters: `text` (string) - The text content of the tweet to send 89 | 90 | ## Troubleshooting 91 | 92 | If you encounter issues: 93 | 94 | 1. Check the Docker logs for any error messages: 95 | ```bash 96 | yarn docker:logs 97 | ``` 98 | 99 | 2. Verify your proxy is running and accessible. 100 | 101 | 3. Ensure your Twitter credentials are correct in the `.env` file. 102 | 103 | 4. If you're having network issues, try modifying the proxy settings in `docker-compose.yml` and `Dockerfile`. 104 | 105 | 5. For proxy servers that require authentication, modify the proxy URLs to include credentials: 106 | ``` 107 | HTTP_PROXY=http://username:[email protected]:7890 108 | HTTPS_PROXY=http://username:[email protected]:7890 109 | ``` -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- ```typescript 1 | #!/usr/bin/env node 2 | import { FastMCP, UserError } from "fastmcp"; 3 | import { Scraper } from 'agent-twitter-client'; 4 | import dotenv from 'dotenv'; 5 | import { z } from "zod"; 6 | 7 | // Load environment variables from .env file 8 | dotenv.config(); 9 | 10 | // Create a new FastMCP server 11 | const server = new FastMCP({ 12 | name: "twitter-mcp-server", 13 | version: "1.0.0", 14 | }); 15 | 16 | // Global scraper instance 17 | let scraper: Scraper | null = null; 18 | 19 | // Initialize and authenticate the scraper 20 | async function initScraper() { 21 | if (scraper) return scraper; 22 | 23 | // Check for required environment variables 24 | if (!process.env.TWITTER_USERNAME || !process.env.TWITTER_PASSWORD) { 25 | throw new UserError('Missing required environment variables: TWITTER_USERNAME and TWITTER_PASSWORD must be set'); 26 | } 27 | 28 | scraper = new Scraper(); 29 | 30 | try { 31 | //console.log('Attempting to login with credentials...'); 32 | 33 | // Try basic authentication first 34 | //console.log('Using basic authentication'); 35 | //console.log(`Username: ${process.env.TWITTER_USERNAME}`); 36 | // Don't log the actual password, just log that we're using it 37 | //console.log('Password: [REDACTED]'); 38 | 39 | try { 40 | await scraper.login( 41 | process.env.TWITTER_USERNAME, 42 | process.env.TWITTER_PASSWORD, 43 | process.env.TWITTER_EMAIL, 44 | process.env.TWITTER_2FA_SECRET 45 | ); 46 | } catch (basicAuthError) { 47 | console.error('Basic authentication failed:', basicAuthError); 48 | 49 | // If basic auth fails and we have v2 credentials, try that 50 | if (process.env.TWITTER_API_KEY && 51 | process.env.TWITTER_API_SECRET_KEY && 52 | process.env.TWITTER_ACCESS_TOKEN && 53 | process.env.TWITTER_ACCESS_TOKEN_SECRET) { 54 | 55 | //console.log('Falling back to v2 API credentials'); 56 | 57 | // Login with v2 API credentials 58 | await scraper.login( 59 | process.env.TWITTER_USERNAME, 60 | process.env.TWITTER_PASSWORD, 61 | process.env.TWITTER_EMAIL || undefined, 62 | process.env.TWITTER_API_KEY, 63 | process.env.TWITTER_API_SECRET_KEY, 64 | process.env.TWITTER_ACCESS_TOKEN, 65 | process.env.TWITTER_ACCESS_TOKEN_SECRET 66 | ); 67 | } else { 68 | // If we don't have v2 credentials, rethrow the error 69 | throw new UserError(`Authentication failed: ${basicAuthError.message}`); 70 | } 71 | } 72 | 73 | //console.log('Login successful'); 74 | return scraper; 75 | } catch (authError) { 76 | console.error('Authentication failed:', authError); 77 | throw new UserError(`Authentication failed: ${authError.message}`); 78 | } 79 | } 80 | 81 | // Add getTweet tool 82 | server.addTool({ 83 | name: "getTweet", 84 | description: "Get a tweet by its ID", 85 | parameters: z.object({ 86 | tweetId: z.string().describe("The ID of the tweet to retrieve"), 87 | }), 88 | execute: async (args, { log }) => { 89 | try { 90 | log.info("Initializing Twitter scraper..."); 91 | const twitterScraper = await initScraper(); 92 | 93 | log.info("Fetching tweet...", { tweetId: args.tweetId }); 94 | const tweet = await twitterScraper.getTweet(args.tweetId); 95 | log.info("result:",tweet.text); 96 | log.info("Tweet fetched successfully"); 97 | return tweet.text; 98 | } catch (error) { 99 | log.error("Failed to get tweet", { error: error.message }); 100 | throw new UserError(`Failed to get tweet: ${error.message}`); 101 | } 102 | }, 103 | }); 104 | 105 | // Add sendTweet tool 106 | server.addTool({ 107 | name: "sendTweet", 108 | description: "Send a new tweet", 109 | parameters: z.object({ 110 | text: z.string().describe("The text content of the tweet to send"), 111 | }), 112 | execute: async (args, { log }) => { 113 | try { 114 | log.info("Initializing Twitter scraper..."); 115 | const twitterScraper = await initScraper(); 116 | 117 | log.info("Sending tweet..."); 118 | const result = await twitterScraper.sendTweet(args.text); 119 | log.info("result:",await result.json()); 120 | log.info("Tweet sent successfully"); 121 | const resultJson = await result.json(); 122 | return resultJson; 123 | } catch (error) { 124 | log.error("Failed to send tweet", { error: error.message }); 125 | throw new UserError(`Failed to send tweet: ${error.message}`); 126 | } 127 | }, 128 | }); 129 | 130 | // Start the server 131 | server.start({ 132 | transportType: "stdio", // Use stdio for direct process communication 133 | }); 134 | 135 | // log.info("Twitter MCP server started with stdio transport."); 136 | ``` -------------------------------------------------------------------------------- /stdio-docker-guide.md: -------------------------------------------------------------------------------- ```markdown 1 | # Twitter MCP Server with Stdio Transport in Docker 2 | 3 | This document explains how to run the Twitter MCP server with stdio transport in Docker and connect to it from Cline. 4 | 5 | ## Changes Made 6 | 7 | 1. Modified the server to use stdio transport instead of SSE: 8 | - Updated `index.ts` to use `transportType: "stdio"` instead of SSE 9 | - Removed port mapping from `docker-compose.yml` as it's no longer needed 10 | 11 | 2. Updated Cline MCP settings to connect to the Docker container: 12 | - Using `docker exec -i` to interact with the container's stdio 13 | 14 | ## Setup Instructions 15 | 16 | ### 1. Build and Start the Docker Container 17 | 18 | ```bash 19 | # Build the Docker image 20 | npm run docker:build 21 | 22 | # Start the container 23 | npm run docker:up 24 | ``` 25 | 26 | The container will start in the background with the name `twitter-scraper-mcp`. 27 | 28 | ### 2. Verify Cline MCP Settings 29 | 30 | The Cline MCP settings have been updated to use stdio transport with the Docker container: 31 | 32 | ```json 33 | { 34 | "mcpServers": { 35 | "twitter-server": { 36 | "command": "docker", 37 | "args": ["exec", "-i", "twitter-scraper-mcp", "node", "/app/dist/index.js"], 38 | "disabled": false, 39 | "autoApprove": [] 40 | } 41 | } 42 | } 43 | ``` 44 | 45 | This configuration: 46 | - Uses `docker exec -i` to execute the MCP server inside the container 47 | - The `-i` flag keeps stdin open, which is required for stdio transport 48 | - Runs the compiled JavaScript file at `/app/dist/index.js` in the container 49 | 50 | ### 3. Restart Cline 51 | 52 | After making these changes, restart Cline to apply the new MCP settings. 53 | 54 | ## Troubleshooting 55 | 56 | ### Container Not Running 57 | 58 | If you get an error about the container not being found, make sure the Docker container is running: 59 | 60 | ```bash 61 | docker ps | grep twitter-scraper-mcp 62 | ``` 63 | 64 | If it's not running, start it with: 65 | 66 | ```bash 67 | npm run docker:up 68 | ``` 69 | 70 | ### Checking Container Logs 71 | 72 | To check the logs from the container: 73 | 74 | ```bash 75 | npm run docker:logs 76 | ``` 77 | 78 | Or directly with Docker: 79 | 80 | ```bash 81 | docker logs twitter-scraper-mcp 82 | ``` 83 | 84 | ### Manual Testing 85 | 86 | You can manually test the stdio connection by running: 87 | 88 | ```bash 89 | docker exec -i twitter-scraper-mcp node /app/dist/index.js 90 | ``` 91 | 92 | Then type a valid MCP request and press Enter. You should get a response from the server. 93 | 94 | ## Environment Variables 95 | 96 | Remember that the Twitter credentials are passed to the container from the `.env` file. Make sure this file exists and contains the required credentials: 97 | 98 | ``` 99 | TWITTER_USERNAME=your_username 100 | TWITTER_PASSWORD=your_password 101 | [email protected] (optional) 102 | TWITTER_2FA_SECRET=your_2fa_secret (optional) 103 | 104 | # Optional v2 API credentials 105 | TWITTER_API_KEY=your_api_key 106 | TWITTER_API_SECRET_KEY=your_api_secret_key 107 | TWITTER_ACCESS_TOKEN=your_access_token 108 | TWITTER_ACCESS_TOKEN_SECRET=your_access_token_secret 109 | ``` 110 | # Twitter MCP Server with Stdio Transport in Docker 111 | 112 | This document explains how to run the Twitter MCP server with stdio transport in Docker and connect to it from Cline. 113 | 114 | ## Changes Made 115 | 116 | 1. Modified the server to use stdio transport instead of SSE: 117 | - Updated `index.ts` to use `transportType: "stdio"` instead of SSE 118 | - Removed port mapping from `docker-compose.yml` as it's no longer needed 119 | 120 | 2. Updated Cline MCP settings to connect to the Docker container: 121 | - Using `docker exec -i` to interact with the container's stdio 122 | 123 | ## Setup Instructions 124 | 125 | ### 1. Build and Start the Docker Container 126 | 127 | ```bash 128 | # Build the Docker image 129 | npm run docker:build 130 | 131 | # Start the container 132 | npm run docker:up 133 | ``` 134 | 135 | The container will start in the background with the name `twitter-scraper-mcp`. 136 | 137 | ### 2. Verify Cline MCP Settings 138 | 139 | The Cline MCP settings have been updated to use stdio transport with the Docker container: 140 | 141 | ```json 142 | { 143 | "mcpServers": { 144 | "twitter-server": { 145 | "command": "docker", 146 | "args": ["exec", "-i", "twitter-scraper-mcp", "node", "/app/dist/index.js"], 147 | "disabled": false, 148 | "autoApprove": [] 149 | } 150 | } 151 | } 152 | ``` 153 | 154 | This configuration: 155 | - Uses `docker exec -i` to execute the MCP server inside the container 156 | - The `-i` flag keeps stdin open, which is required for stdio transport 157 | - Runs the compiled JavaScript file at `/app/dist/index.js` in the container 158 | 159 | ### 3. Restart Cline 160 | 161 | After making these changes, restart Cline to apply the new MCP settings. 162 | 163 | ## Troubleshooting 164 | 165 | ### Container Not Running 166 | 167 | If you get an error about the container not being found, make sure the Docker container is running: 168 | 169 | ```bash 170 | docker ps | grep twitter-scraper-mcp 171 | ``` 172 | 173 | If it's not running, start it with: 174 | 175 | ```bash 176 | npm run docker:up 177 | ``` 178 | 179 | ### Checking Container Logs 180 | 181 | To check the logs from the container: 182 | 183 | ```bash 184 | npm run docker:logs 185 | ``` 186 | 187 | Or directly with Docker: 188 | 189 | ```bash 190 | docker logs twitter-scraper-mcp 191 | ``` 192 | 193 | ### Manual Testing 194 | 195 | You can manually test the stdio connection by running: 196 | 197 | ```bash 198 | docker exec -i twitter-scraper-mcp node /app/dist/index.js 199 | ``` 200 | 201 | Then type a valid MCP request and press Enter. You should get a response from the server. 202 | 203 | ## Environment Variables 204 | 205 | Remember that the Twitter credentials are passed to the container from the `.env` file. Make sure this file exists and contains the required credentials: 206 | 207 | ``` 208 | TWITTER_USERNAME=your_username 209 | TWITTER_PASSWORD=your_password 210 | [email protected] (optional) 211 | TWITTER_2FA_SECRET=your_2fa_secret (optional) 212 | 213 | # Optional v2 API credentials 214 | TWITTER_API_KEY=your_api_key 215 | TWITTER_API_SECRET_KEY=your_api_secret_key 216 | TWITTER_ACCESS_TOKEN=your_access_token 217 | # Twitter MCP Server with Stdio Transport in Docker 218 | 219 | This document explains how to run the Twitter MCP server with stdio transport in Docker and connect to it from Cline. 220 | 221 | ## Changes Made 222 | 223 | 1. Modified the server to use stdio transport instead of SSE: 224 | - Updated `index.ts` to use `transportType: "stdio"` instead of SSE 225 | - Removed port mapping from `docker-compose.yml` as it's no longer needed 226 | 227 | 2. Updated Cline MCP settings to connect to the Docker container: 228 | - Using `docker exec -i` to interact with the container's stdio 229 | 230 | ## Setup Instructions 231 | 232 | ### 1. Build and Start the Docker Container 233 | 234 | ```bash 235 | # Build the Docker image 236 | npm run docker:build 237 | 238 | # Start the container 239 | npm run docker:up 240 | ``` 241 | 242 | The container will start in the background with the name `twitter-scraper-mcp`. 243 | 244 | ### 2. Verify Cline MCP Settings 245 | 246 | The Cline MCP settings have been updated to use stdio transport with the Docker container: 247 | 248 | ```json 249 | { 250 | "mcpServers": { 251 | "twitter-server": { 252 | "command": "docker", 253 | "args": ["exec", "-i", "twitter-scraper-mcp", "node", "/app/dist/index.js"], 254 | "disabled": false, 255 | "autoApprove": [] 256 | } 257 | } 258 | } 259 | ``` 260 | 261 | This configuration: 262 | - Uses `docker exec -i` to execute the MCP server inside the container 263 | - The `-i` flag keeps stdin open, which is required for stdio transport 264 | - Runs the compiled JavaScript file at `/app/dist/index.js` in the container 265 | 266 | ### 3. Restart Cline 267 | 268 | After making these changes, restart Cline to apply the new MCP settings. 269 | 270 | ## Troubleshooting 271 | 272 | ### Container Not Running 273 | 274 | If you get an error about the container not being found, make sure the Docker container is running: 275 | 276 | ```bash 277 | docker ps | grep twitter-scraper-mcp 278 | ``` 279 | 280 | If it's not running, start it with: 281 | 282 | ```bash 283 | npm run docker:up 284 | ``` 285 | 286 | ### Checking Container Logs 287 | 288 | To check the logs from the container: 289 | 290 | ```bash 291 | npm run docker:logs 292 | ``` 293 | 294 | Or directly with Docker: 295 | 296 | ```bash 297 | docker logs twitter-scraper-mcp 298 | ``` 299 | 300 | ### Manual Testing 301 | 302 | You can manually test the stdio connection by running: 303 | 304 | ```bash 305 | docker exec -i twitter-scraper-mcp node /app/dist/index.js 306 | ``` 307 | 308 | Then type a valid MCP request and press Enter. You should get a response from the server. 309 | 310 | ## Environment Variables 311 | 312 | Remember that the Twitter credentials are passed to the container from the `.env` file. Make sure this file exists and contains the required credentials: 313 | 314 | ``` 315 | TWITTER_USERNAME=your_username 316 | TWITTER_PASSWORD=your_password 317 | [email protected] (optional) 318 | TWITTER_2FA_SECRET=your_2fa_secret (optional) 319 | 320 | # Optional v2 API credentials 321 | TWITTER_API_KEY=your_api_key 322 | TWITTER_API_SECRET_KEY=your_api_secret_key 323 | TWITTER_ACCESS_TOKEN=your_access_token 324 | TWITTER_ACCESS_TOKEN_SECRET=your_access_token_secret 325 | ``` -------------------------------------------------------------------------------- /stdio-docker-setup.md: -------------------------------------------------------------------------------- ```markdown 1 | # Twitter MCP Server with Stdio Transport in Docker 2 | 3 | This document explains how to run the Twitter MCP server with stdio transport in Docker and connect to it from Cline. 4 | 5 | ## Changes Made 6 | 7 | 1. Modified the server to use stdio transport instead of SSE: 8 | - Updated `index.ts` to use `transportType: "stdio"` instead of SSE 9 | - Removed port mapping from `docker-compose.yml` as it's no longer needed 10 | 11 | 2. Updated Cline MCP settings to connect to the Docker container: 12 | - Using `docker exec -i` to interact with the container's stdio 13 | 14 | ## Setup Instructions 15 | 16 | ### 1. Build and Start the Docker Container 17 | 18 | ```bash 19 | # Build the Docker image 20 | npm run docker:build 21 | 22 | # Start the container 23 | npm run docker:up 24 | ``` 25 | 26 | The container will start in the background with the name `twitter-scraper-mcp`. 27 | 28 | ### 2. Verify Cline MCP Settings 29 | 30 | The Cline MCP settings have been updated to use stdio transport with the Docker container: 31 | 32 | ```json 33 | { 34 | "mcpServers": { 35 | "twitter-server": { 36 | "command": "docker", 37 | "args": ["exec", "-i", "twitter-scraper-mcp", "node", "/app/dist/index.js"], 38 | "disabled": false, 39 | "autoApprove": [] 40 | } 41 | } 42 | } 43 | ``` 44 | 45 | This configuration: 46 | - Uses `docker exec -i` to execute the MCP server inside the container 47 | - The `-i` flag keeps stdin open, which is required for stdio transport 48 | - Runs the compiled JavaScript file at `/app/dist/index.js` in the container 49 | 50 | ### 3. Restart Cline 51 | 52 | After making these changes, restart Cline to apply the new MCP settings. 53 | 54 | ## Troubleshooting 55 | 56 | ### Container Not Running 57 | 58 | If you get an error about the container not being found, make sure the Docker container is running: 59 | 60 | ```bash 61 | docker ps | grep twitter-scraper-mcp 62 | ``` 63 | 64 | If it's not running, start it with: 65 | 66 | ```bash 67 | npm run docker:up 68 | ``` 69 | 70 | ### Checking Container Logs 71 | 72 | To check the logs from the container: 73 | 74 | ```bash 75 | npm run docker:logs 76 | ``` 77 | 78 | Or directly with Docker: 79 | 80 | ```bash 81 | docker logs twitter-scraper-mcp 82 | ``` 83 | 84 | ### Manual Testing 85 | 86 | You can manually test the stdio connection by running: 87 | 88 | ```bash 89 | docker exec -i twitter-scraper-mcp node /app/dist/index.js 90 | ``` 91 | 92 | Then type a valid MCP request and press Enter. You should get a response from the server. 93 | 94 | ## Environment Variables 95 | 96 | Remember that the Twitter credentials are passed to the container from the `.env` file. Make sure this file exists and contains the required credentials: 97 | 98 | ``` 99 | TWITTER_USERNAME=your_username 100 | TWITTER_PASSWORD=your_password 101 | [email protected] (optional) 102 | TWITTER_2FA_SECRET=your_2fa_secret (optional) 103 | 104 | # Optional v2 API credentials 105 | TWITTER_API_KEY=your_api_key 106 | TWITTER_API_SECRET_KEY=your_api_secret_key 107 | TWITTER_ACCESS_TOKEN=your_access_token 108 | TWITTER_ACCESS_TOKEN_SECRET=your_access_token_secret 109 | ``` 110 | # Twitter MCP Server with Stdio Transport in Docker 111 | 112 | This document explains how to run the Twitter MCP server with stdio transport in Docker and connect to it from Cline. 113 | 114 | ## Changes Made 115 | 116 | 1. Modified the server to use stdio transport instead of SSE: 117 | - Updated `index.ts` to use `transportType: "stdio"` instead of SSE 118 | - Removed port mapping from `docker-compose.yml` as it's no longer needed 119 | 120 | 2. Updated Cline MCP settings to connect to the Docker container: 121 | - Using `docker exec -i` to interact with the container's stdio 122 | 123 | ## Setup Instructions 124 | 125 | ### 1. Build and Start the Docker Container 126 | 127 | ```bash 128 | # Build the Docker image 129 | npm run docker:build 130 | 131 | # Start the container 132 | npm run docker:up 133 | ``` 134 | 135 | The container will start in the background with the name `twitter-scraper-mcp`. 136 | 137 | ### 2. Verify Cline MCP Settings 138 | 139 | The Cline MCP settings have been updated to use stdio transport with the Docker container: 140 | 141 | ```json 142 | { 143 | "mcpServers": { 144 | "twitter-server": { 145 | "command": "docker", 146 | "args": ["exec", "-i", "twitter-scraper-mcp", "node", "/app/dist/index.js"], 147 | "disabled": false, 148 | "autoApprove": [] 149 | } 150 | } 151 | } 152 | ``` 153 | 154 | This configuration: 155 | - Uses `docker exec -i` to execute the MCP server inside the container 156 | - The `-i` flag keeps stdin open, which is required for stdio transport 157 | - Runs the compiled JavaScript file at `/app/dist/index.js` in the container 158 | 159 | ### 3. Restart Cline 160 | 161 | After making these changes, restart Cline to apply the new MCP settings. 162 | 163 | ## Troubleshooting 164 | 165 | ### Container Not Running 166 | 167 | If you get an error about the container not being found, make sure the Docker container is running: 168 | 169 | ```bash 170 | docker ps | grep twitter-scraper-mcp 171 | ``` 172 | 173 | If it's not running, start it with: 174 | 175 | ```bash 176 | npm run docker:up 177 | ``` 178 | 179 | ### Checking Container Logs 180 | 181 | To check the logs from the container: 182 | 183 | ```bash 184 | npm run docker:logs 185 | ``` 186 | 187 | Or directly with Docker: 188 | 189 | ```bash 190 | docker logs twitter-scraper-mcp 191 | ``` 192 | 193 | ### Manual Testing 194 | 195 | You can manually test the stdio connection by running: 196 | 197 | ```bash 198 | docker exec -i twitter-scraper-mcp node /app/dist/index.js 199 | ``` 200 | 201 | Then type a valid MCP request and press Enter. You should get a response from the server. 202 | 203 | ## Environment Variables 204 | 205 | Remember that the Twitter credentials are passed to the container from the `.env` file. Make sure this file exists and contains the required credentials: 206 | 207 | ``` 208 | TWITTER_USERNAME=your_username 209 | TWITTER_PASSWORD=your_password 210 | [email protected] (optional) 211 | TWITTER_2FA_SECRET=your_2fa_secret (optional) 212 | 213 | # Optional v2 API credentials 214 | TWITTER_API_KEY=your_api_key 215 | TWITTER_API_SECRET_KEY=your_api_secret_key 216 | TWITTER_ACCESS_TOKEN=your_access_token 217 | # Twitter MCP Server with Stdio Transport in Docker 218 | 219 | This document explains how to run the Twitter MCP server with stdio transport in Docker and connect to it from Cline. 220 | 221 | ## Changes Made 222 | 223 | 1. Modified the server to use stdio transport instead of SSE: 224 | - Updated `index.ts` to use `transportType: "stdio"` instead of SSE 225 | - Removed port mapping from `docker-compose.yml` as it's no longer needed 226 | 227 | 2. Updated Cline MCP settings to connect to the Docker container: 228 | - Using `docker exec -i` to interact with the container's stdio 229 | 230 | ## Setup Instructions 231 | 232 | ### 1. Build and Start the Docker Container 233 | 234 | ```bash 235 | # Build the Docker image 236 | npm run docker:build 237 | 238 | # Start the container 239 | npm run docker:up 240 | ``` 241 | 242 | The container will start in the background with the name `twitter-scraper-mcp`. 243 | 244 | ### 2. Verify Cline MCP Settings 245 | 246 | The Cline MCP settings have been updated to use stdio transport with the Docker container: 247 | 248 | ```json 249 | { 250 | "mcpServers": { 251 | "twitter-server": { 252 | "command": "docker", 253 | "args": ["exec", "-i", "twitter-scraper-mcp", "node", "/app/dist/index.js"], 254 | "disabled": false, 255 | "autoApprove": [] 256 | } 257 | } 258 | } 259 | ``` 260 | 261 | This configuration: 262 | - Uses `docker exec -i` to execute the MCP server inside the container 263 | - The `-i` flag keeps stdin open, which is required for stdio transport 264 | - Runs the compiled JavaScript file at `/app/dist/index.js` in the container 265 | 266 | ### 3. Restart Cline 267 | 268 | After making these changes, restart Cline to apply the new MCP settings. 269 | 270 | ## Troubleshooting 271 | 272 | ### Container Not Running 273 | 274 | If you get an error about the container not being found, make sure the Docker container is running: 275 | 276 | ```bash 277 | docker ps | grep twitter-scraper-mcp 278 | ``` 279 | 280 | If it's not running, start it with: 281 | 282 | ```bash 283 | npm run docker:up 284 | ``` 285 | 286 | ### Checking Container Logs 287 | 288 | To check the logs from the container: 289 | 290 | ```bash 291 | npm run docker:logs 292 | ``` 293 | 294 | Or directly with Docker: 295 | 296 | ```bash 297 | docker logs twitter-scraper-mcp 298 | ``` 299 | 300 | ### Manual Testing 301 | 302 | You can manually test the stdio connection by running: 303 | 304 | ```bash 305 | docker exec -i twitter-scraper-mcp node /app/dist/index.js 306 | ``` 307 | 308 | Then type a valid MCP request and press Enter. You should get a response from the server. 309 | 310 | ## Environment Variables 311 | 312 | Remember that the Twitter credentials are passed to the container from the `.env` file. Make sure this file exists and contains the required credentials: 313 | 314 | ``` 315 | TWITTER_USERNAME=your_username 316 | TWITTER_PASSWORD=your_password 317 | [email protected] (optional) 318 | TWITTER_2FA_SECRET=your_2fa_secret (optional) 319 | 320 | # Optional v2 API credentials 321 | TWITTER_API_KEY=your_api_key 322 | TWITTER_API_SECRET_KEY=your_api_secret_key 323 | TWITTER_ACCESS_TOKEN=your_access_token 324 | TWITTER_ACCESS_TOKEN_SECRET=your_access_token_secret 325 | ``` -------------------------------------------------------------------------------- /fastMcp_readme.md: -------------------------------------------------------------------------------- ```markdown 1 | # FastMCP 2 | 3 | A TypeScript framework for building [MCP](https://modelcontextprotocol.io/) servers capable of handling client sessions. 4 | 5 | > [!NOTE] 6 | > 7 | > For a Python implementation, see [FastMCP](https://github.com/jlowin/fastmcp). 8 | 9 | ## Features 10 | 11 | - Simple Tool, Resource, Prompt definition 12 | - [Authentication](#authentication) 13 | - [Sessions](#sessions) 14 | - [Image content](#returning-an-image) 15 | - [Logging](#logging) 16 | - [Error handling](#errors) 17 | - [SSE](#sse) 18 | - CORS (enabled by default) 19 | - [Progress notifications](#progress) 20 | - [Typed server events](#typed-server-events) 21 | - [Prompt argument auto-completion](#prompt-argument-auto-completion) 22 | - [Sampling](#requestsampling) 23 | - Automated SSE pings 24 | - Roots 25 | - CLI for [testing](#test-with-mcp-cli) and [debugging](#inspect-with-mcp-inspector) 26 | 27 | ## Installation 28 | 29 | ```bash 30 | npm install fastmcp 31 | ``` 32 | 33 | ## Quickstart 34 | 35 | ```ts 36 | import { FastMCP } from "fastmcp"; 37 | import { z } from "zod"; 38 | 39 | const server = new FastMCP({ 40 | name: "My Server", 41 | version: "1.0.0", 42 | }); 43 | 44 | server.addTool({ 45 | name: "add", 46 | description: "Add two numbers", 47 | parameters: z.object({ 48 | a: z.number(), 49 | b: z.number(), 50 | }), 51 | execute: async (args) => { 52 | return String(args.a + args.b); 53 | }, 54 | }); 55 | 56 | server.start({ 57 | transportType: "stdio", 58 | }); 59 | ``` 60 | 61 | _That's it!_ You have a working MCP server. 62 | 63 | You can test the server in terminal with: 64 | 65 | ```bash 66 | git clone https://github.com/punkpeye/fastmcp.git 67 | cd fastmcp 68 | 69 | npm install 70 | 71 | # Test the addition server example using CLI: 72 | npx fastmcp dev src/examples/addition.ts 73 | # Test the addition server example using MCP Inspector: 74 | npx fastmcp inspect src/examples/addition.ts 75 | ``` 76 | 77 | ### SSE 78 | 79 | You can also run the server with SSE support: 80 | 81 | ```ts 82 | server.start({ 83 | transportType: "sse", 84 | sse: { 85 | endpoint: "/sse", 86 | port: 8080, 87 | }, 88 | }); 89 | ``` 90 | 91 | This will start the server and listen for SSE connections on `http://localhost:8080/sse`. 92 | 93 | You can then use `SSEClientTransport` to connect to the server: 94 | 95 | ```ts 96 | import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"; 97 | 98 | const client = new Client( 99 | { 100 | name: "example-client", 101 | version: "1.0.0", 102 | }, 103 | { 104 | capabilities: {}, 105 | }, 106 | ); 107 | 108 | const transport = new SSEClientTransport(new URL(`http://localhost:8080/sse`)); 109 | 110 | await client.connect(transport); 111 | ``` 112 | 113 | ## Core Concepts 114 | 115 | ### Tools 116 | 117 | [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. 118 | 119 | ```js 120 | server.addTool({ 121 | name: "fetch", 122 | description: "Fetch the content of a url", 123 | parameters: z.object({ 124 | url: z.string(), 125 | }), 126 | execute: async (args) => { 127 | return await fetchWebpageContent(args.url); 128 | }, 129 | }); 130 | ``` 131 | 132 | #### Returning a string 133 | 134 | `execute` can return a string: 135 | 136 | ```js 137 | server.addTool({ 138 | name: "download", 139 | description: "Download a file", 140 | parameters: z.object({ 141 | url: z.string(), 142 | }), 143 | execute: async (args) => { 144 | return "Hello, world!"; 145 | }, 146 | }); 147 | ``` 148 | 149 | The latter is equivalent to: 150 | 151 | ```js 152 | server.addTool({ 153 | name: "download", 154 | description: "Download a file", 155 | parameters: z.object({ 156 | url: z.string(), 157 | }), 158 | execute: async (args) => { 159 | return { 160 | content: [ 161 | { 162 | type: "text", 163 | text: "Hello, world!", 164 | }, 165 | ], 166 | }; 167 | }, 168 | }); 169 | ``` 170 | 171 | #### Returning a list 172 | 173 | If you want to return a list of messages, you can return an object with a `content` property: 174 | 175 | ```js 176 | server.addTool({ 177 | name: "download", 178 | description: "Download a file", 179 | parameters: z.object({ 180 | url: z.string(), 181 | }), 182 | execute: async (args) => { 183 | return { 184 | content: [ 185 | { type: "text", text: "First message" }, 186 | { type: "text", text: "Second message" }, 187 | ], 188 | }; 189 | }, 190 | }); 191 | ``` 192 | 193 | #### Returning an image 194 | 195 | Use the `imageContent` to create a content object for an image: 196 | 197 | ```js 198 | import { imageContent } from "fastmcp"; 199 | 200 | server.addTool({ 201 | name: "download", 202 | description: "Download a file", 203 | parameters: z.object({ 204 | url: z.string(), 205 | }), 206 | execute: async (args) => { 207 | return imageContent({ 208 | url: "https://example.com/image.png", 209 | }); 210 | 211 | // or... 212 | // return imageContent({ 213 | // path: "/path/to/image.png", 214 | // }); 215 | 216 | // or... 217 | // return imageContent({ 218 | // buffer: Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=", "base64"), 219 | // }); 220 | 221 | // or... 222 | // return { 223 | // content: [ 224 | // await imageContent(...) 225 | // ], 226 | // }; 227 | }, 228 | }); 229 | ``` 230 | 231 | The `imageContent` function takes the following options: 232 | 233 | - `url`: The URL of the image. 234 | - `path`: The path to the image file. 235 | - `buffer`: The image data as a buffer. 236 | 237 | Only one of `url`, `path`, or `buffer` must be specified. 238 | 239 | The above example is equivalent to: 240 | 241 | ```js 242 | server.addTool({ 243 | name: "download", 244 | description: "Download a file", 245 | parameters: z.object({ 246 | url: z.string(), 247 | }), 248 | execute: async (args) => { 249 | return { 250 | content: [ 251 | { 252 | type: "image", 253 | data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=", 254 | mimeType: "image/png", 255 | }, 256 | ], 257 | }; 258 | }, 259 | }); 260 | ``` 261 | 262 | #### Logging 263 | 264 | Tools can log messages to the client using the `log` object in the context object: 265 | 266 | ```js 267 | server.addTool({ 268 | name: "download", 269 | description: "Download a file", 270 | parameters: z.object({ 271 | url: z.string(), 272 | }), 273 | execute: async (args, { log }) => { 274 | log.info("Downloading file...", { 275 | url, 276 | }); 277 | 278 | // ... 279 | 280 | log.info("Downloaded file"); 281 | 282 | return "done"; 283 | }, 284 | }); 285 | ``` 286 | 287 | The `log` object has the following methods: 288 | 289 | - `debug(message: string, data?: SerializableValue)` 290 | - `error(message: string, data?: SerializableValue)` 291 | - `info(message: string, data?: SerializableValue)` 292 | - `warn(message: string, data?: SerializableValue)` 293 | 294 | #### Errors 295 | 296 | The errors that are meant to be shown to the user should be thrown as `UserError` instances: 297 | 298 | ```js 299 | import { UserError } from "fastmcp"; 300 | 301 | server.addTool({ 302 | name: "download", 303 | description: "Download a file", 304 | parameters: z.object({ 305 | url: z.string(), 306 | }), 307 | execute: async (args) => { 308 | if (args.url.startsWith("https://example.com")) { 309 | throw new UserError("This URL is not allowed"); 310 | } 311 | 312 | return "done"; 313 | }, 314 | }); 315 | ``` 316 | 317 | #### Progress 318 | 319 | Tools can report progress by calling `reportProgress` in the context object: 320 | 321 | ```js 322 | server.addTool({ 323 | name: "download", 324 | description: "Download a file", 325 | parameters: z.object({ 326 | url: z.string(), 327 | }), 328 | execute: async (args, { reportProgress }) => { 329 | reportProgress({ 330 | progress: 0, 331 | total: 100, 332 | }); 333 | 334 | // ... 335 | 336 | reportProgress({ 337 | progress: 100, 338 | total: 100, 339 | }); 340 | 341 | return "done"; 342 | }, 343 | }); 344 | ``` 345 | 346 | ### Resources 347 | 348 | [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: 349 | 350 | - File contents 351 | - Screenshots and images 352 | - Log files 353 | - And more 354 | 355 | Each resource is identified by a unique URI and can contain either text or binary data. 356 | 357 | ```ts 358 | server.addResource({ 359 | uri: "file:///logs/app.log", 360 | name: "Application Logs", 361 | mimeType: "text/plain", 362 | async load() { 363 | return { 364 | text: await readLogFile(), 365 | }; 366 | }, 367 | }); 368 | ``` 369 | 370 | > [!NOTE] 371 | > 372 | > `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. 373 | > 374 | > ```ts 375 | > async load() { 376 | > return [ 377 | > { 378 | > text: "First file content", 379 | > }, 380 | > { 381 | > text: "Second file content", 382 | > }, 383 | > ]; 384 | > } 385 | > ``` 386 | 387 | You can also return binary contents in `load`: 388 | 389 | ```ts 390 | async load() { 391 | return { 392 | blob: 'base64-encoded-data' 393 | }; 394 | } 395 | ``` 396 | 397 | ### Resource templates 398 | 399 | You can also define resource templates: 400 | 401 | ```ts 402 | server.addResourceTemplate({ 403 | uriTemplate: "file:///logs/{name}.log", 404 | name: "Application Logs", 405 | mimeType: "text/plain", 406 | arguments: [ 407 | { 408 | name: "name", 409 | description: "Name of the log", 410 | required: true, 411 | }, 412 | ], 413 | async load({ name }) { 414 | return { 415 | text: `Example log content for ${name}`, 416 | }; 417 | }, 418 | }); 419 | ``` 420 | 421 | #### Resource template argument auto-completion 422 | 423 | Provide `complete` functions for resource template arguments to enable automatic completion: 424 | 425 | ```ts 426 | server.addResourceTemplate({ 427 | uriTemplate: "file:///logs/{name}.log", 428 | name: "Application Logs", 429 | mimeType: "text/plain", 430 | arguments: [ 431 | { 432 | name: "name", 433 | description: "Name of the log", 434 | required: true, 435 | complete: async (value) => { 436 | if (value === "Example") { 437 | return { 438 | values: ["Example Log"], 439 | }; 440 | } 441 | 442 | return { 443 | values: [], 444 | }; 445 | }, 446 | }, 447 | ], 448 | async load({ name }) { 449 | return { 450 | text: `Example log content for ${name}`, 451 | }; 452 | }, 453 | }); 454 | ``` 455 | 456 | ### Prompts 457 | 458 | [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. 459 | 460 | ```ts 461 | server.addPrompt({ 462 | name: "git-commit", 463 | description: "Generate a Git commit message", 464 | arguments: [ 465 | { 466 | name: "changes", 467 | description: "Git diff or description of changes", 468 | required: true, 469 | }, 470 | ], 471 | load: async (args) => { 472 | return `Generate a concise but descriptive commit message for these changes:\n\n${args.changes}`; 473 | }, 474 | }); 475 | ``` 476 | 477 | #### Prompt argument auto-completion 478 | 479 | Prompts can provide auto-completion for their arguments: 480 | 481 | ```js 482 | server.addPrompt({ 483 | name: "countryPoem", 484 | description: "Writes a poem about a country", 485 | load: async ({ name }) => { 486 | return `Hello, ${name}!`; 487 | }, 488 | arguments: [ 489 | { 490 | name: "name", 491 | description: "Name of the country", 492 | required: true, 493 | complete: async (value) => { 494 | if (value === "Germ") { 495 | return { 496 | values: ["Germany"], 497 | }; 498 | } 499 | 500 | return { 501 | values: [], 502 | }; 503 | }, 504 | }, 505 | ], 506 | }); 507 | ``` 508 | 509 | #### Prompt argument auto-completion using `enum` 510 | 511 | If you provide an `enum` array for an argument, the server will automatically provide completions for the argument. 512 | 513 | ```js 514 | server.addPrompt({ 515 | name: "countryPoem", 516 | description: "Writes a poem about a country", 517 | load: async ({ name }) => { 518 | return `Hello, ${name}!`; 519 | }, 520 | arguments: [ 521 | { 522 | name: "name", 523 | description: "Name of the country", 524 | required: true, 525 | enum: ["Germany", "France", "Italy"], 526 | }, 527 | ], 528 | }); 529 | ``` 530 | 531 | ### Authentication 532 | 533 | FastMCP allows you to `authenticate` clients using a custom function: 534 | 535 | ```ts 536 | import { AuthError } from "fastmcp"; 537 | 538 | const server = new FastMCP({ 539 | name: "My Server", 540 | version: "1.0.0", 541 | authenticate: ({request}) => { 542 | const apiKey = request.headers["x-api-key"]; 543 | 544 | if (apiKey !== '123') { 545 | throw new Response(null, { 546 | status: 401, 547 | statusText: "Unauthorized", 548 | }); 549 | } 550 | 551 | // Whatever you return here will be accessible in the `context.session` object. 552 | return { 553 | id: 1, 554 | } 555 | }, 556 | }); 557 | ``` 558 | 559 | Now you can access the authenticated session data in your tools: 560 | 561 | ```ts 562 | server.addTool({ 563 | name: "sayHello", 564 | execute: async (args, { session }) => { 565 | return `Hello, ${session.id}!`; 566 | }, 567 | }); 568 | ``` 569 | 570 | ### Sessions 571 | 572 | The `session` object is an instance of `FastMCPSession` and it describes active client sessions. 573 | 574 | ```ts 575 | server.sessions; 576 | ``` 577 | 578 | We allocate a new server instance for each client connection to enable 1:1 communication between a client and the server. 579 | 580 | ### Typed server events 581 | 582 | You can listen to events emitted by the server using the `on` method: 583 | 584 | ```ts 585 | server.on("connect", (event) => { 586 | console.log("Client connected:", event.session); 587 | }); 588 | 589 | server.on("disconnect", (event) => { 590 | console.log("Client disconnected:", event.session); 591 | }); 592 | ``` 593 | 594 | ## `FastMCPSession` 595 | 596 | `FastMCPSession` represents a client session and provides methods to interact with the client. 597 | 598 | Refer to [Sessions](#sessions) for examples of how to obtain a `FastMCPSession` instance. 599 | 600 | ### `requestSampling` 601 | 602 | `requestSampling` creates a [sampling](https://modelcontextprotocol.io/docs/concepts/sampling) request and returns the response. 603 | 604 | ```ts 605 | await session.requestSampling({ 606 | messages: [ 607 | { 608 | role: "user", 609 | content: { 610 | type: "text", 611 | text: "What files are in the current directory?", 612 | }, 613 | }, 614 | ], 615 | systemPrompt: "You are a helpful file system assistant.", 616 | includeContext: "thisServer", 617 | maxTokens: 100, 618 | }); 619 | ``` 620 | 621 | ### `clientCapabilities` 622 | 623 | The `clientCapabilities` property contains the client capabilities. 624 | 625 | ```ts 626 | session.clientCapabilities; 627 | ``` 628 | 629 | ### `loggingLevel` 630 | 631 | The `loggingLevel` property describes the logging level as set by the client. 632 | 633 | ```ts 634 | session.loggingLevel; 635 | ``` 636 | 637 | ### `roots` 638 | 639 | The `roots` property contains the roots as set by the client. 640 | 641 | ```ts 642 | session.roots; 643 | ``` 644 | 645 | ### `server` 646 | 647 | The `server` property contains an instance of MCP server that is associated with the session. 648 | 649 | ```ts 650 | session.server; 651 | ``` 652 | 653 | ### Typed session events 654 | 655 | You can listen to events emitted by the session using the `on` method: 656 | 657 | ```ts 658 | session.on("rootsChanged", (event) => { 659 | console.log("Roots changed:", event.roots); 660 | }); 661 | 662 | session.on("error", (event) => { 663 | console.error("Error:", event.error); 664 | }); 665 | ``` 666 | 667 | ## Running Your Server 668 | 669 | ### Test with `mcp-cli` 670 | 671 | The fastest way to test and debug your server is with `fastmcp dev`: 672 | 673 | ```bash 674 | npx fastmcp dev server.js 675 | npx fastmcp dev server.ts 676 | ``` 677 | 678 | This will run your server with [`mcp-cli`](https://github.com/wong2/mcp-cli) for testing and debugging your MCP server in the terminal. 679 | 680 | ### Inspect with `MCP Inspector` 681 | 682 | Another way is to use the official [`MCP Inspector`](https://modelcontextprotocol.io/docs/tools/inspector) to inspect your server with a Web UI: 683 | 684 | ```bash 685 | npx fastmcp inspect server.ts 686 | ``` 687 | 688 | ## Showcase 689 | 690 | > [!NOTE] 691 | > 692 | > If you've developed a server using FastMCP, please [submit a PR](https://github.com/punkpeye/fastmcp) to showcase it here! 693 | 694 | - https://github.com/apinetwork/piapi-mcp-server 695 | 696 | ## Acknowledgements 697 | 698 | - FastMCP is inspired by the [Python implementation](https://github.com/jlowin/fastmcp) by [Jonathan Lowin](https://github.com/jlowin). 699 | - Parts of codebase were adopted from [LiteMCP](https://github.com/wong2/litemcp). 700 | - Parts of codebase were adopted from [Model Context protocolでSSEをやってみる](https://dev.classmethod.jp/articles/mcp-sse/). ```