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! ๐ฏ