# TwitterAPI.io Documentation MCP Server This MCP (Model Context Protocol) server provides offline access to TwitterAPI.io documentation for Claude and other AI assistants. Created as an independent community project, it bundles comprehensive documentation for 52 API endpoints, site pages (pricing, authentication, QPS limits, privacy, contact, changelog, etc.), and blog articles, enabling instant access without network calls or tab-switching. The server implements advanced search with fuzzy matching, camelCase support, and hybrid caching for optimal performance. The server runs as a stdio-based MCP service with production-ready features including structured logging, SLO tracking, input validation, and comprehensive error handling. It uses ES modules (no build step required) and supports Node.js 18.18.0+, making it simple to integrate into Claude Desktop, Claude Code, or any MCP-compatible AI assistant. Documentation is stored locally in JSON format with metadata tracking freshness, enabling fast queries even when offline. Version 1.0.5 adds support for fetching specific URLs from both the offline snapshot and optional live fetching. ## APIs and Key Functions ### MCP Server Initialization The server initializes with documentation validation, cache setup, and SLO configuration. It loads all documentation from `data/docs.json` and starts the hybrid cache cleanup scheduler. ```javascript import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; const server = new Server( { name: "twitterapi-docs", version: "1.0.5", }, { capabilities: { tools: {}, resources: {}, }, } ); // Start the server async function main() { const docs = loadDocs(); // Loads from data/docs.json startCacheCleanup(); // Starts hourly cleanup const transport = new StdioServerTransport(); await server.connect(transport); // Server ready on stdio } main().catch((error) => { console.error('[FATAL] Unexpected error:', error); process.exit(1); }); ``` ### search_twitterapi_docs - Full-Text Search Searches across all documentation (endpoints, guides, blogs) with advanced tokenization supporting camelCase, underscores, and fuzzy matching. Returns ranked results with relevance scores. ```bash # Via MCP Tool Call (JSON-RPC) { "method": "tools/call", "params": { "name": "search_twitterapi_docs", "arguments": { "query": "getUserInfo", "max_results": 10 } } } # Response { "content": [{ "type": "text", "text": "## \"getUserInfo\" - 3 results (showing up to 10):\n\n### API Endpoints (2)\n1. **get_user_by_username** - GET /twitter/user/info\n Get user info by screen name\n\n2. **batch_get_user_by_userids** - GET /twitter/user/batch\n Get multiple users by IDs\n\n### Guides (1)\n1. **authentication** - Authentication Guide\n https://docs.twitterapi.io/guides/authentication" }] } ``` ### get_twitterapi_endpoint - Endpoint Details Retrieves complete documentation for a specific endpoint including parameters, cURL examples, and code snippets. ```bash # Via MCP Tool Call { "method": "tools/call", "params": { "name": "get_twitterapi_endpoint", "arguments": { "endpoint_name": "get_user_by_username" } } } # Response includes full markdown documentation # Title: Get User Info # Method: GET # Path: /twitter/user/info # Full URL: https://api.twitterapi.io/twitter/user/info # # Parameters: # - userName (required): The screen name of the user # # cURL Example: curl --request GET \ --url https://api.twitterapi.io/twitter/user/info?userName=elonmusk \ --header 'x-api-key: YOUR_API_KEY' # Expected Response: { "data": { "type": "user", "userName": "elonmusk", "id": "44196397", "name": "Elon Musk", "followers": 123456789, "following": 456, "description": "CEO of Tesla and SpaceX" }, "status": "success" } ``` ### list_twitterapi_endpoints - Browse Endpoints Lists all API endpoints organized by category (user, tweet, community, webhook, stream, action, dm, list, trend). ```bash # List all endpoints { "method": "tools/call", "params": { "name": "list_twitterapi_endpoints", "arguments": {} } } # Filter by category { "method": "tools/call", "params": { "name": "list_twitterapi_endpoints", "arguments": { "category": "user" } } } # Response (filtered by user category): ## USER Endpoints (9) - **get_user_by_username**: GET /twitter/user/info - **batch_get_user_by_userids**: GET /twitter/user/batch - **get_user_last_tweets**: GET /twitter/user/tweets - **get_user_followers**: GET /twitter/user/followers - **get_user_followings**: GET /twitter/user/following - **get_user_mentions**: GET /twitter/user/mentions - **check_follow_relationship**: GET /twitter/user/relationship - **search_user_by_keyword**: GET /twitter/user/search - **get_user_verified_followers**: GET /twitter/user/verified-followers ``` ### get_twitterapi_guide - Conceptual Documentation Retrieves guide pages for topics like pricing, rate limits, authentication, and tweet filter rules. ```bash # Get authentication guide { "method": "tools/call", "params": { "name": "get_twitterapi_guide", "arguments": { "guide_name": "authentication" } } } # Response includes structured markdown with: # - API Key Header: x-api-key # - Base URL: https://api.twitterapi.io # - Setup instructions # - Code examples in cURL, Python, JavaScript # Python example from guide: import requests response = requests.get( "https://api.twitterapi.io/twitter/user/info", params={"userName": "elonmusk"}, headers={"x-api-key": "YOUR_API_KEY"} ) print(response.json()) # JavaScript example from guide: const response = await fetch( "https://api.twitterapi.io/twitter/user/info?userName=elonmusk", { headers: { "x-api-key": "YOUR_API_KEY" } } ); const data = await response.json(); ``` ### get_twitterapi_url - Fetch Specific URLs Retrieves content for specific TwitterAPI.io or docs.twitterapi.io URLs from the offline snapshot, with optional live fetching capability. ```bash # Fetch from offline snapshot { "method": "tools/call", "params": { "name": "get_twitterapi_url", "arguments": { "url": "https://twitterapi.io/pricing" } } } # Fetch with live fallback if not in snapshot { "method": "tools/call", "params": { "name": "get_twitterapi_url", "arguments": { "url": "https://docs.twitterapi.io/introduction", "fetch_live": true } } } # Response includes: # - url: Canonical URL # - source: "snapshot" or "live" # - kind: "endpoint", "page", or "blog" # - name: Internal key name # - title: Page title # - description: Page description # - markdown: Full formatted content # Supported URL formats: # - Full URLs: https://twitterapi.io/pricing # - Partial URLs: /pricing, twitterapi.io/pricing # - Docs URLs: https://docs.twitterapi.io/introduction ``` ### get_twitterapi_pricing - Pricing Information Returns detailed pricing structure including credit costs, QPS limits by balance level, and cost comparisons. ```bash # Get pricing info { "method": "tools/call", "params": { "name": "get_twitterapi_pricing", "arguments": {} } } # Response: # Credit System: # - 1 USD = 100,000 Credits # # Endpoint Costs: # - tweets: 15 credits per tweet # - profiles: 18 credits per user # - followers: 15 credits per follower # - list_calls: 150 credits per call # # Minimum Charge: 15 credits ($0.00015) per request # # QPS Limits by Balance: # - 1,000 credits: 3 QPS # - 5,000 credits: 6 QPS # - 10,000 credits: 10 QPS # - 50,000 credits: 20 QPS # # Free Users: 1 request per 5 seconds ``` ### get_twitterapi_auth - Quick Authentication Reference Provides quick access to authentication setup with header format and code examples. ```bash # Get auth reference { "method": "tools/call", "params": { "name": "get_twitterapi_auth", "arguments": {} } } # Response includes: # API Key Header: x-api-key # Base URL: https://api.twitterapi.io # Dashboard: https://twitterapi.io/dashboard # # Quick examples in cURL, Python, JavaScript ``` ### MCP Resources - Static Guide Access Resources provide direct access to documentation without tool calls, useful for context loading. ```bash # List available resources { "method": "resources/list", "params": {} } # Read specific resource { "method": "resources/read", "params": { "uri": "twitterapi://guides/pricing" } } # Available resource URIs: # - twitterapi://guides/pricing # - twitterapi://guides/authentication # - twitterapi://guides/qps_limits # - twitterapi://guides/tweet_filter_rules # - twitterapi://guides/changelog # - twitterapi://guides/introduction # - twitterapi://guides/readme # - twitterapi://docs/all (full JSON dump) # - twitterapi://docs/endpoints (endpoint summary) # - twitterapi://metrics (performance metrics) # - twitterapi://health (server health) # - twitterapi://status/freshness (data age check) ``` ### HybridCache - Memory and Disk Caching The server uses hybrid caching with memory-first access and disk persistence for cross-session caching. ```javascript // Cache implementation class HybridCache { constructor(name, options = {}) { this.memory = new Map(); this.MAX_MEMORY = options.maxEntries || 500; this.DEFAULT_TTL = options.ttl || 24 * 60 * 60 * 1000; // 24 hours this.diskDir = path.join(CACHE_DIR, name); } get(key) { // Check memory first const memEntry = this.memory.get(key); if (memEntry && !this.isExpired(memEntry)) { return memEntry.value; } // Check disk if memory miss const diskPath = path.join(this.diskDir, `${key}.json`); if (fs.existsSync(diskPath)) { const diskEntry = JSON.parse(fs.readFileSync(diskPath, 'utf-8')); if (!this.isExpired(diskEntry)) { this.memory.set(key, diskEntry); // Restore to memory return diskEntry.value; } } return null; } set(key, value, ttl = this.DEFAULT_TTL) { const entry = { key, value, timestamp: Date.now(), ttl }; this.memory.set(key, entry); // Write to disk for persistence if (Math.random() < this.DISK_WRITE_PROBABILITY) { this.writeToDisk(key, entry); } } } // Initialize caches with different TTLs const searchCache = new HybridCache('search', { maxEntries: 200, ttl: 6 * 60 * 60 * 1000, // 6 hours diskWriteProbability: 1.0 // Always persist }); const endpointCache = new HybridCache('endpoints', { maxEntries: 100, ttl: 24 * 60 * 60 * 1000, // 24 hours diskWriteProbability: 1.0 }); const urlCache = new HybridCache('urls', { maxEntries: 200, ttl: 24 * 60 * 60 * 1000, // 24 hours diskWriteProbability: 1.0 }); ``` ### Advanced Search Tokenization The search engine uses advanced tokenization with camelCase support, n-gram fuzzy matching, and weighted scoring. ```javascript // Tokenization supports multiple formats function tokenize(text) { // "getUserInfo" → ["get", "user", "info"] // "get_user_info" → ["get", "user", "info"] // "OAuth2Token" → ["oauth", "2", "token"] let processed = text.replace(/([a-z])([A-Z])/g, '$1 $2'); // Split camelCase processed = processed.replace(/([a-zA-Z])(\d)/g, '$1 $2'); // Split numbers processed = processed.toLowerCase().replace(/[_\-\/\.]/g, ' '); return processed.split(/\s+/).filter(t => t.length > 1); } // Scoring algorithm with weighted matching function calculateScore(searchText, queryTokens) { // Exact token match: 10 points // Prefix match: 8 points // Substring match: 6 points // Direct text inclusion: 5 points // N-gram fuzzy match: 0.5-3 points // Multi-token bonus: +5 points per additional match // Position bonus: +3 points if match in first word let score = 0; for (const token of queryTokens) { if (textTokens.includes(token)) score += 10; else if (textTokens.some(t => t.startsWith(token))) score += 8; // ... more scoring logic } return score; } // Usage example const results = searchInDocs("getUserInfo", 10); // Returns ranked results sorted by relevance score ``` ### Structured Logging and SLO Tracking The server implements comprehensive logging with Service Level Objective (SLO) tracking for latency monitoring. ```javascript // SLO Configuration const SLO = { search_twitterapi_docs: { target: 50, acceptable: 100, alert: 200 }, get_twitterapi_endpoint: { target: 10, acceptable: 50, alert: 100 }, list_twitterapi_endpoints: { target: 5, acceptable: 20, alert: 50 }, get_twitterapi_url: { target: 20, acceptable: 200, alert: 1000 } }; // Logger tracks metrics class Logger { recordToolCall(toolName, duration, success) { this.metrics.tools[toolName].calls++; this.metrics.tools[toolName].totalDuration += duration; // Track SLO violations const slo = SLO[toolName]; if (duration > slo.alert) { this.warn('slo', `ALERT: ${toolName} exceeded alert threshold`, { duration, threshold: slo.alert }); } } getMetrics() { return { requests: { total, successful, failed, averageLatency }, cache: { hits, misses, hitRate }, sloViolations: { target, acceptable, alert }, tools: { /* per-tool stats */ } }; } } // Access metrics via resource // URI: twitterapi://metrics // Returns: Full performance metrics with SLO violation counts ``` ### Input Validation and Error Handling All tool inputs are validated with helpful error messages and suggestions for recovery. ```javascript // Query validation function validateQuery(query) { if (!query || typeof query !== 'string') { return { valid: false, error: { type: ErrorType.INPUT_VALIDATION, message: 'Query cannot be empty', suggestion: 'Try: "user info", "advanced search", "rate limits"', retryable: false } }; } const trimmed = query.trim(); if (trimmed.length > 500) { return { valid: false, error: { message: `Query too long (${trimmed.length} chars, max 500)`, suggestion: 'Use fewer, more specific keywords' } }; } return { valid: true, value: trimmed }; } // URL validation with canonicalization function validateTwitterApiUrl(url) { try { const canonical = canonicalizeUrl(url); // Normalizes URLs: /pricing → https://twitterapi.io/pricing // Validates hosts: only twitterapi.io and docs.twitterapi.io return { valid: true, value: canonical }; } catch (err) { return { valid: false, error: { message: `Invalid URL: ${err.message}`, suggestion: 'Only https://twitterapi.io/* and https://docs.twitterapi.io/* URLs are supported' } }; } } // Error formatting with suggestions function formatToolError(error) { return { content: [{ type: 'text', text: `Error: ${error.message}\n\nSuggestion: ${error.suggestion}` }], isError: true }; } // Example error response: // Error: Endpoint "get_user" not found // // Suggestion: Similar endpoints: get_user_by_username, get_user_followers, batch_get_user_by_userids ``` ### Data Freshness Monitoring The server monitors documentation age and warns when data becomes stale. ```javascript function getDataFreshness() { const stat = fs.statSync(DOCS_PATH); const ageMs = Date.now() - stat.mtimeMs; const ageHours = ageMs / (60 * 60 * 1000); let status = 'fresh'; if (ageMs > 72 * 60 * 60 * 1000) status = 'stale'; // 72 hours else if (ageMs > 24 * 60 * 60 * 1000) status = 'warning'; // 24 hours return { lastModified: new Date(stat.mtimeMs).toISOString(), ageHuman: `${ageHours.toFixed(1)} hours`, status }; } // Check freshness via resource // URI: twitterapi://health // Returns: { status: "healthy|degraded", dataFreshness: {...}, cache: {...} } // URI: twitterapi://status/freshness // Returns: { lastModified, ageMs, ageHuman, status, thresholds } ``` ## Integration and Use Cases This MCP server is designed for seamless integration with Claude Desktop and Claude Code, providing instant documentation access during development workflows. Developers can query endpoint details, check rate limits, explore authentication requirements, and search for specific functionality without leaving their coding environment. The offline-first architecture ensures consistent performance regardless of network conditions, making it ideal for travel or restricted network environments. Common use cases include rapid prototyping with TwitterAPI.io, learning the API surface area through interactive exploration, debugging integration issues with immediate access to parameter documentation, and cost estimation using the built-in pricing calculator. The server's advanced search capabilities support natural language queries like "how to get user followers" or "webhook setup", automatically mapping them to relevant endpoints and guides. With hybrid caching and SLO tracking, the server maintains sub-100ms response times for most queries while monitoring its own performance metrics accessible through the health and metrics resources. Version 1.0.5 adds the ability to fetch any TwitterAPI.io or docs.twitterapi.io URL directly, with optional live fetching when content isn't in the offline snapshot.