Meetup MCP Server
The Meetup MCP server is a persistent HTTP service that gives Claude direct access to a single Meetup group's organizer tools. It runs via Docker Compose and connects to Claude Code over Streamable HTTP.
When to Use This Skill
Use this skill when:
- Drafting, editing, or searching Meetup events
- Looking up venue information for the group
- Uploading and attaching event photos
- Troubleshooting auth or tool errors from the
meetupMCP server - Explaining the safety model or publish flow to users
Available Tools
Read (always available)
| Tool | Description |
|---|---|
authorize |
Start OAuth2 flow. Call this if any tool returns an auth error. Returns a URL to open in your browser. |
get_group |
Returns group name, URL name, and member count. |
search_events |
Search events by status: upcoming, past, or draft. Returns titles, dates, venues, and IDs. |
get_event |
Full event details for a specific event ID including description, venue, and photo. |
list_venues |
Lists known venues for the group with address details. |
Write (requires organizer mode)
| Tool | Description |
|---|---|
create_venue |
Create a new venue for the group. The API may return existing matches in didYouMean instead of creating a duplicate. |
create_event |
Creates an event as DRAFT by default. Set publishNow=true only if MEETUP_ALLOW_PUBLISH=true. |
edit_event |
Edit any combination of: title, description, startDateTime, duration, venueId, howToFindUs, featuredPhotoId. |
add_event_speaker |
Add structured speaker details to an event. Meetup currently exposes one speaker profile per event through this API. |
update_event_speaker |
Update the event's structured speaker details or replace/remove its speaker photo. |
remove_event_speaker |
Remove structured speaker details from the event. |
attach_event_speaker_photo |
Attach an uploaded photo to the event's structured speaker profile. |
publish_event |
Publishes a draft event. Requires MEETUP_ALLOW_PUBLISH=true AND confirm=true. |
create_event_photo_upload |
Gets a photo upload ticket with photoId and uploadUrl. The caller PUT's bytes to uploadUrl. |
attach_event_photo |
Attaches an uploaded photo to an event as its featured image. |
Safety Model
The server enforces a layered safety model via environment config:
- Group lock — each server instance is bound to one group via
MEETUP_GROUP_URLNAME. Cross-group operations are blocked. - Mode —
organizer(default) enables writes;read_onlyrestricts to read tools only. - Publish gate —
MEETUP_ALLOW_PUBLISH=false(default) blockspublish_event. Publishing requires explicit opt-in. - Draft default —
create_eventalways creates as DRAFT unlesspublishNow=trueAND publish is enabled.
Policy violations return errors prefixed with POLICY_DENIED.
The intended workflow is: draft → review → publish. Never go straight to publish without reviewing the draft first.
Auth Flow
The server uses OAuth2 authorization code flow. Tokens are stored in a Docker volume and persist across restarts — you only authorize once.
First-time authorization
- Call
authorize— it returns a Meetup authorization URL. - Open the URL in a browser. Log in and click Allow.
- The browser redirects to
http://127.0.0.1:8787/meetup/oauth/callback?code=...
If the server is on a remote machine: the redirect happens on your local browser, not the server. Copy the full callback URL and send it to the server manually:
curl "http://YOUR_SERVER:8787/meetup/oauth/callback?code=THE_CODE_FROM_URL"
- Call the original tool again — it will succeed now.
Token refresh
Access tokens expire after 1 hour. The server automatically refreshes using the stored refresh token. Meetup refresh tokens are single-use — the server always persists the new refresh token immediately after use.
Common Workflows
Search upcoming events
search_events(status="upcoming", limit=10)
Create a venue
create_venue(
name="Community Center",
address="500 Innovation Blvd",
city="Houston",
country="us",
state="TX"
)
Returns venue (the created venue) and didYouMean (existing similar venues if the API detected a potential duplicate). Note: the Meetup API does not support updating or deleting venues.
Draft a new event
list_venues() // find the right venueId first
create_event(
title="Your Event Title",
description="Event description in plain text or HTML",
startDateTime="2026-05-15T17:30:00-05:00",
duration="PT2H30M",
venueId="VENUE_ID_FROM_LIST_VENUES"
)
Returns the created event with its ID. Status will be DRAFT.
Edit a draft
edit_event(
eventId="EVENT_ID",
title="Updated Title", // optional
description="Updated copy", // optional
howToFindUs="Parking details" // optional
)
Only include fields you want to change — all fields are optional except eventId.
Publish a draft
Publishing requires MEETUP_ALLOW_PUBLISH=true in the server's environment. If it's not set, publish_event returns a POLICY_DENIED error.
publish_event(eventId="EVENT_ID", confirm=true)
The confirm=true parameter is required as an explicit acknowledgement.
Attach a photo to an event
// Step 1: get upload ticket
create_event_photo_upload(contentType="image/jpeg")
// Returns: { photoId, uploadUrl }
// Step 2: upload bytes (done externally via HTTP PUT to uploadUrl)
// Step 3: attach to event
attach_event_photo(eventId="EVENT_ID", photoId="PHOTO_ID")
Add or update the structured speaker profile
add_event_speaker(
eventId="EVENT_ID",
name="Jane Doe",
bio="Principal engineer and community organizer.",
photoId="PHOTO_ID" // optional
)
update_event_speaker(
eventId="EVENT_ID",
bio="Updated speaker bio",
clearPhoto=false
)
attach_event_speaker_photo(eventId="EVENT_ID", photoId="PHOTO_ID")
Meetup's GraphQL surface currently exposes a single speakerDetails object per event rather than an arbitrary array of speakers.
Datetime and Duration Formats
- startDateTime: ISO-8601 with timezone offset —
2026-05-15T17:30:00-05:00 - duration: ISO-8601 duration —
PT2H(2 hours),PT2H30M(2.5 hours),PT90M(90 minutes)
Deployment
The server runs as a persistent Docker Compose service:
# Build image
dotnet publish src/Meetup.McpServer/Meetup.McpServer.csproj /t:PublishContainer
# Start in background
docker compose up -d
# Check logs
docker compose logs -f
# Stop
docker compose down
Claude Code connects via .mcp.json:
{
"mcpServers": {
"meetup": {
"type": "http",
"url": "http://localhost:5180/mcp"
}
}
}
Troubleshooting
authorize keeps returning an auth URL even after completing the flow
- The background listener may not have received the callback. Verify you
curl'd the callback URL to the server, not just opened it in a browser on the same machine.
Tools return POLICY_DENIED
- Check
MEETUP_MODE=organizeris set in.env - For publish errors, set
MEETUP_ALLOW_PUBLISH=trueand restart the container
search_events returns empty results
- Check the
statusvalue: useupcoming,past, ordraft upcomingmaps to Meetup'sACTIVEstatus — published events with future dates
Container won't start
- Check
.envhasMEETUP_OAUTH_KEYandMEETUP_OAUTH_SECRETset - Run
docker compose logsto see the startup error