Skip to content

Meetup MCP Server

This document provides comprehensive documentation for the Meetup Model Context Protocol (MCP) server integration in Inttrest, enabling community-driven event discovery from Meetup.com.

๐ŸŽฏ Overview

The Meetup MCP server integrates with Meetup.com's API and web scraping capabilities to discover community events, local gatherings, and interest-based meetups. It specializes in grassroots community events and recurring group activities.

graph TB
    subgraph "Meetup MCP Architecture"
        A[MCP Client] --> B[Meetup MCP Server]
        B --> C[Meetup API Client]
        B --> D[Community Scraper]
        B --> E[Group Analyzer]
        B --> F[Event Recommender]

        C --> G[Meetup REST API]
        C --> H[GraphQL API]
        C --> I[OAuth Authentication]

        D --> J[Group Discovery]
        D --> K[Event Extraction]
        D --> L[Member Analysis]

        E --> M[Community Insights]
        E --> N[Group Health Score]
        E --> O[Activity Patterns]

        F --> P[Interest Matching]
        F --> Q[Location Filtering]
        F --> R[Time Preferences]

        G --> S[Events API]
        G --> T[Groups API]
        G --> U[Members API]
        G --> V[Categories API]
    end

    subgraph "Inttrest Integration"
        W[Community Events] --> A
        X[Local Meetups] --> A
        Y[Interest Groups] --> A

        M --> Z[Community Database]
        P --> AA[Recommendation Engine]
        Q --> BB[Geographic Search]
    end

    style B fill:#e3f2fd
    style G fill:#e8f5e8
    style E fill:#fff3e0
    style F fill:#fce4ec

๐Ÿ“ฆ Installation & Setup

Prerequisites

  • Node.js 18+ with npm/pnpm
  • Meetup Pro Account or API access
  • Chrome/Chromium for web scraping fallback
  • Redis for caching (optional)

Installation

# Navigate to Meetup MCP server directory
cd mcp_servers/mcp-meetup

# Install dependencies
npm install
# or
pnpm install

# Install browser for web scraping
npx puppeteer browsers install chrome

# Build the server
npm run build

Configuration

Create a .env file in the mcp-meetup directory:

# Meetup API Configuration
MEETUP_API_KEY=your_meetup_api_key
MEETUP_CLIENT_ID=your_oauth_client_id
MEETUP_CLIENT_SECRET=your_oauth_client_secret
MEETUP_REDIRECT_URI=http://localhost:3000/auth/meetup/callback
MEETUP_ACCESS_TOKEN=your_access_token

# Meetup Web Scraping (Fallback)
MEETUP_EMAIL=your_meetup_email
MEETUP_PASSWORD=your_meetup_password

# Geographic Configuration
DEFAULT_RADIUS=25  # miles
MAX_RADIUS=100
DEFAULT_LOCATION=37.7749,-122.4194  # San Francisco coordinates

# Event Filtering
MAX_EVENTS_PER_REQUEST=50
DEFAULT_DAYS_AHEAD=30
MAX_DAYS_AHEAD=365

# Categories of Interest
FEATURED_CATEGORIES=["tech", "business", "health", "arts", "sports", "food", "photography"]
EXCLUDE_CATEGORIES=["dating", "singles"]

# Cache Configuration
REDIS_URL=redis://localhost:6379
CACHE_TTL=3600000  # 1 hour
CACHE_MAX_SIZE=2000

# Rate Limiting
API_RATE_LIMIT=200
API_RATE_WINDOW=3600000  # 1 hour
SCRAPING_RATE_LIMIT=20
SCRAPING_RATE_WINDOW=300000  # 5 minutes

# Group Analysis
MIN_GROUP_MEMBERS=10
MIN_GROUP_ACTIVITY_SCORE=3
FEATURED_GROUP_MIN_MEMBERS=100

# Browser Configuration
BROWSER_HEADLESS=true
BROWSER_TIMEOUT=30000

# Logging
LOG_LEVEL=info
LOG_FILE=meetup-mcp.log

Package Dependencies

{
  "name": "meetup-mcp-server",
  "version": "1.0.0",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.17.4",
    "axios": "^1.6.0",
    "puppeteer": "^22.0.0",
    "node-cache": "^5.1.2",
    "redis": "^4.6.0",
    "winston": "^3.11.0",
    "zod": "^4.1.5",
    "dotenv": "^16.3.1",
    "cheerio": "^1.0.0-rc.12",
    "geolib": "^3.3.4",
    "moment": "^2.29.4",
    "uuid": "^9.0.1",
    "lodash": "^4.17.21"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "@types/lodash": "^4.14.0",
    "typescript": "^5.0.0",
    "tsx": "^4.0.0",
    "vitest": "^1.0.0"
  }
}

๐Ÿ—๏ธ Server Implementation

Core Server Structure

// src/meetup-mcp-server.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js'
import { MeetupAPIClient } from './services/meetup-api-client.js'
import { CommunityAnalyzer } from './services/community-analyzer.js'
import { EventRecommender } from './services/event-recommender.js'
import { MeetupScraper } from './services/meetup-scraper.js'
import { Logger } from './utils/logger.js'
import { z } from 'zod'

const logger = new Logger('MeetupMCP')

class MeetupMCPServer {
  private server: Server
  private apiClient: MeetupAPIClient
  private communityAnalyzer: CommunityAnalyzer
  private eventRecommender: EventRecommender
  private scraper: MeetupScraper

  constructor() {
    this.server = new Server(
      {
        name: 'meetup-mcp-server',
        version: '1.0.0',
      },
      {
        capabilities: {
          tools: {},
        },
      }
    )

    this.apiClient = new MeetupAPIClient()
    this.communityAnalyzer = new CommunityAnalyzer()
    this.eventRecommender = new EventRecommender()
    this.scraper = new MeetupScraper()

    this.setupToolHandlers()
  }

  private setupToolHandlers() {
    // List available tools
    this.server.setRequestHandler(ListToolsRequestSchema, async () => {
      return {
        tools: [
          {
            name: 'search_meetup_events',
            description: 'Search for Meetup events by location and interests',
            inputSchema: {
              type: 'object',
              properties: {
                location: {
                  type: 'string',
                  description: 'Location (city, zip code, or coordinates)'
                },
                radius: {
                  type: 'number',
                  minimum: 1,
                  maximum: 100,
                  description: 'Search radius in miles'
                },
                categories: {
                  type: 'array',
                  items: { type: 'string' },
                  description: 'Event categories of interest'
                },
                keywords: {
                  type: 'array',
                  items: { type: 'string' },
                  description: 'Keywords to search for'
                },
                date_range: {
                  type: 'object',
                  properties: {
                    start: { type: 'string' },
                    end: { type: 'string' }
                  },
                  description: 'Date range for events'
                },
                group_size_min: {
                  type: 'number',
                  description: 'Minimum group size'
                },
                event_status: {
                  type: 'string',
                  enum: ['upcoming', 'past', 'proposed', 'suggested'],
                  description: 'Event status filter'
                }
              },
              required: []
            }
          },
          {
            name: 'discover_local_groups',
            description: 'Discover active Meetup groups in an area',
            inputSchema: {
              type: 'object',
              properties: {
                location: {
                  type: 'string',
                  description: 'Location to search for groups'
                },
                categories: {
                  type: 'array',
                  items: { type: 'string' },
                  description: 'Group categories of interest'
                },
                min_members: {
                  type: 'number',
                  description: 'Minimum number of group members'
                },
                activity_level: {
                  type: 'string',
                  enum: ['low', 'medium', 'high'],
                  description: 'Required group activity level'
                },
                include_new_groups: {
                  type: 'boolean',
                  description: 'Include recently created groups'
                }
              },
              required: []
            }
          },
          {
            name: 'analyze_group_health',
            description: 'Analyze the health and activity of a Meetup group',
            inputSchema: {
              type: 'object',
              properties: {
                group_id: {
                  type: 'string',
                  description: 'Meetup group ID'
                },
                group_url: {
                  type: 'string',
                  description: 'Meetup group URL'
                },
                analyze_members: {
                  type: 'boolean',
                  description: 'Include member activity analysis'
                },
                analyze_events: {
                  type: 'boolean',
                  description: 'Include event history analysis'
                },
                time_period: {
                  type: 'string',
                  enum: ['month', 'quarter', 'year'],
                  description: 'Time period for analysis'
                }
              },
              required: []
            }
          },
          {
            name: 'get_personalized_recommendations',
            description: 'Get personalized event recommendations based on interests',
            inputSchema: {
              type: 'object',
              properties: {
                interests: {
                  type: 'array',
                  items: { type: 'string' },
                  description: 'User interests and hobbies'
                },
                location: {
                  type: 'string',
                  description: 'User location'
                },
                availability: {
                  type: 'object',
                  properties: {
                    weekdays: { type: 'boolean' },
                    weekends: { type: 'boolean' },
                    evenings: { type: 'boolean' },
                    mornings: { type: 'boolean' }
                  },
                  description: 'Time availability preferences'
                },
                group_size_preference: {
                  type: 'string',
                  enum: ['small', 'medium', 'large', 'any'],
                  description: 'Preferred group size'
                },
                experience_level: {
                  type: 'string',
                  enum: ['beginner', 'intermediate', 'advanced', 'any'],
                  description: 'Experience level in interests'
                }
              },
              required: ['interests', 'location']
            }
          },
          {
            name: 'monitor_group_activities',
            description: 'Monitor specific groups for new events and updates',
            inputSchema: {
              type: 'object',
              properties: {
                groups: {
                  type: 'array',
                  items: { type: 'string' },
                  description: 'Group IDs or URLs to monitor'
                },
                notification_types: {
                  type: 'array',
                  items: { 
                    type: 'string',
                    enum: ['new_events', 'event_updates', 'group_updates', 'new_members']
                  },
                  description: 'Types of notifications to monitor'
                },
                check_interval: {
                  type: 'number',
                  description: 'Check interval in hours'
                }
              },
              required: ['groups']
            }
          },
          {
            name: 'analyze_event_trends',
            description: 'Analyze trends in local Meetup events',
            inputSchema: {
              type: 'object',
              properties: {
                location: {
                  type: 'string',
                  description: 'Location for trend analysis'
                },
                categories: {
                  type: 'array',
                  items: { type: 'string' },
                  description: 'Categories to analyze'
                },
                time_period: {
                  type: 'string',
                  enum: ['month', 'quarter', 'year'],
                  description: 'Time period for trend analysis'
                },
                metrics: {
                  type: 'array',
                  items: {
                    type: 'string',
                    enum: ['attendance', 'frequency', 'new_groups', 'popular_topics']
                  },
                  description: 'Metrics to analyze'
                }
              },
              required: ['location']
            }
          }
        ]
      }
    })

    // Handle tool calls
    this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
      try {
        const { name, arguments: args } = request.params

        switch (name) {
          case 'search_meetup_events':
            return await this.handleSearchEvents(args)

          case 'discover_local_groups':
            return await this.handleDiscoverGroups(args)

          case 'analyze_group_health':
            return await this.handleAnalyzeGroupHealth(args)

          case 'get_personalized_recommendations':
            return await this.handleGetRecommendations(args)

          case 'monitor_group_activities':
            return await this.handleMonitorGroups(args)

          case 'analyze_event_trends':
            return await this.handleAnalyzeTrends(args)

          default:
            throw new Error(`Unknown tool: ${name}`)
        }
      } catch (error) {
        logger.error('Tool execution failed:', error)
        throw error
      }
    })
  }

  async start() {
    try {
      await this.apiClient.initialize()
      await this.scraper.initialize()

      const transport = new StdioServerTransport()
      await this.server.connect(transport)

      logger.info('Meetup MCP Server started successfully')
    } catch (error) {
      logger.error('Failed to start Meetup MCP Server:', error)
      throw error
    }
  }

  async stop() {
    await this.scraper.close()
    logger.info('Meetup MCP Server stopped')
  }
}

๐Ÿ”ง Configuration & Usage

MCP Server Integration

// Example integration with Inttrest
import { MCPClient } from '@modelcontextprotocol/sdk/client/index.js'
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'

async function setupMeetupMCP() {
  const transport = new StdioClientTransport({
    command: 'node',
    args: ['./mcp_servers/mcp-meetup/dist/meetup-mcp-server.js']
  })

  const client = new MCPClient(
    {
      name: 'inttrest-client',
      version: '1.0.0',
    },
    {
      capabilities: {}
    }
  )

  await client.connect(transport)
  return client
}

// Search for local tech meetups
async function findTechMeetups(location: string) {
  const client = await setupMeetupMCP()

  const result = await client.callTool({
    name: 'search_meetup_events',
    arguments: {
      location: location,
      categories: ['tech', 'software-development'],
      radius: 25,
      date_range: {
        start: new Date().toISOString(),
        end: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString()
      }
    }
  })

  return result._meta?.events || []
}

๐Ÿ“Š API Tools & Examples

Search Meetup Events

// Find Python programming meetups
const pythonEvents = await client.callTool({
  name: 'search_meetup_events',
  arguments: {
    location: 'San Francisco, CA',
    keywords: ['python', 'programming'],
    radius: 15,
    group_size_min: 50
  }
})

// Find weekend events for families
const familyEvents = await client.callTool({
  name: 'search_meetup_events',
  arguments: {
    location: 'New York, NY',
    categories: ['family', 'kids'],
    date_range: {
      start: '2024-02-03T00:00:00Z', // Saturday
      end: '2024-02-04T23:59:59Z'    // Sunday
    }
  }
})

Discover Local Groups

// Find active photography groups
const photoGroups = await client.callTool({
  name: 'discover_local_groups',
  arguments: {
    location: 'Austin, TX',
    categories: ['photography', 'arts'],
    min_members: 100,
    activity_level: 'high'
  }
})

// Find new startup groups
const startupGroups = await client.callTool({
  name: 'discover_local_groups',
  arguments: {
    location: 'Boston, MA',
    categories: ['business', 'entrepreneurship'],
    include_new_groups: true,
    min_members: 25
  }
})

Analyze Group Health

// Analyze a specific tech group
const groupHealth = await client.callTool({
  name: 'analyze_group_health',
  arguments: {
    group_url: 'https://www.meetup.com/san-francisco-react-js',
    analyze_members: true,
    analyze_events: true,
    time_period: 'quarter'
  }
})

console.log('Group Health Score:', groupHealth._meta.health_analysis.overall_score)
console.log('Recommendations:', groupHealth._meta.health_analysis.insights.recommendations)

Get Personalized Recommendations

// Get recommendations for a software developer
const recommendations = await client.callTool({
  name: 'get_personalized_recommendations',
  arguments: {
    interests: ['javascript', 'react', 'node.js', 'networking'],
    location: 'Seattle, WA',
    availability: {
      weekdays: false,
      weekends: true,
      evenings: true,
      mornings: false
    },
    group_size_preference: 'medium',
    experience_level: 'intermediate'
  }
})

Monitor Group Activities

// Monitor favorite groups for updates
const monitoring = await client.callTool({
  name: 'monitor_group_activities',
  arguments: {
    groups: [
      'https://www.meetup.com/reactjs-san-francisco',
      'https://www.meetup.com/node-js-sf',
      'https://www.meetup.com/javascript-sf'
    ],
    notification_types: ['new_events', 'event_updates'],
    check_interval: 24 // Check daily
  }
})

๐Ÿงช Testing & Development

Unit Tests

// tests/meetup-api.test.ts
import { MeetupAPIClient } from '../src/services/meetup-api-client.js'
import { describe, it, expect, beforeEach } from 'vitest'

describe('MeetupAPIClient', () => {
  let api: MeetupAPIClient

  beforeEach(() => {
    api = new MeetupAPIClient()
  })

  it('should search events successfully', async () => {
    const events = await api.searchEvents({
      location: 'San Francisco',
      categories: ['tech']
    })

    expect(Array.isArray(events)).toBe(true)
    expect(events.length).toBeGreaterThan(0)
  })

  it('should discover groups by location', async () => {
    const groups = await api.discoverGroups({
      location: 'New York',
      min_members: 50
    })

    expect(Array.isArray(groups)).toBe(true)
    groups.forEach(group => {
      expect(group.members).toBeGreaterThanOrEqual(50)
    })
  })
})

Integration Tests

// tests/mcp-integration.test.ts
import { MeetupMCPServer } from '../src/meetup-mcp-server.js'

describe('Meetup MCP Integration', () => {
  let server: MeetupMCPServer

  beforeEach(() => {
    server = new MeetupMCPServer()
  })

  it('should handle search_meetup_events tool call', async () => {
    const result = await server.handleSearchEvents({
      location: 'Los Angeles',
      categories: ['tech', 'business']
    })

    expect(result.content).toBeDefined()
    expect(result._meta?.events).toBeDefined()
    expect(result._meta.events.length).toBeGreaterThan(0)
  })
})

Development Scripts

{
  "scripts": {
    "dev": "tsx src/meetup-mcp-server.ts",
    "build": "tsc",
    "test": "vitest",
    "test:watch": "vitest --watch",
    "lint": "eslint src/**/*.ts",
    "start": "node dist/meetup-mcp-server.js"
  }
}

๐Ÿ“ˆ Performance & Monitoring

Caching Strategy

// Cache configuration for optimal performance
const cacheConfig = {
  // Event searches - cache for 1 hour
  events: { ttl: 3600, maxSize: 1000 },

  // Group data - cache for 4 hours
  groups: { ttl: 14400, maxSize: 500 },

  // Group health analysis - cache for 24 hours
  health_analysis: { ttl: 86400, maxSize: 100 },

  // Categories - cache for 7 days
  categories: { ttl: 604800, maxSize: 50 }
}

Rate Limiting

// Rate limiting configuration
const rateLimits = {
  api: {
    requests: 200,
    window: 3600000, // 1 hour
    burst: 10
  },
  scraping: {
    requests: 20,
    window: 300000, // 5 minutes
    delay: 2000 // 2 second delay between requests
  }
}

Health Monitoring

// Health check endpoint
async function checkMeetupHealth(): Promise<{
  status: 'healthy' | 'degraded' | 'unhealthy'
  details: Record<string, any>
}> {
  try {
    const api = new MeetupAPIClient()
    await api.initialize()

    // Test API connectivity
    const categories = await api.getCategories()

    return {
      status: 'healthy',
      details: {
        api_connection: 'ok',
        categories_count: categories.length,
        timestamp: new Date().toISOString()
      }
    }
  } catch (error) {
    return {
      status: 'unhealthy',
      details: {
        api_connection: 'failed',
        error: error.message,
        timestamp: new Date().toISOString()
      }
    }
  }
}

๐Ÿ”’ Security & Privacy

Data Protection

// Anonymize user data
function anonymizeUserData(userData: any): any {
  return {
    ...userData,
    email: undefined,
    phone: undefined,
    full_name: userData.name ? userData.name.split(' ')[0] : undefined
  }
}

// Rate limiting per user
const userRateLimits = new Map<string, {
  requests: number
  resetTime: number
}>()

API Security

# Environment variables for security
MEETUP_API_KEY_ENCRYPTED=encrypted_api_key
MEETUP_WEBHOOK_SECRET=webhook_secret_for_verification
ALLOWED_ORIGINS=["https://inttrest.com", "https://app.inttrest.com"]

This comprehensive Meetup MCP server documentation provides everything needed for community-driven event discovery and group analysis in the Inttrest platform! ๐ŸŽฏ