Pulumi活动MCP
MCP服务器,用于通过Claude等人工智能助手管理Meetup.com和Luma上的事件。
建于 FastMCP 3.x,它公开了Meetup的GraphQL API和Luma的REST API作为MCP工具和资源,因此LLM可以搜索事件、管理组、创建和发布事件等等。
特性
- 22工具 跨两个平台(Meetup+Luma),按平台和域标记
- 6资源 用于只读查找(用户配置文件、组/事件/网络详细信息)
- 封面图片上传 --在创建/更新事件时传递本地文件路径;服务器自动处理CDN上传(Luma主持URL,Meetup两步照片上传)
- 所有列表工具自动分页——单个工具调用返回所有结果
- Meetup:用于无头服务器到服务器访问的JWT身份验证(无需浏览器),支持OAuth2回退
- 使用成员元数据(角色、参加的活动、组数)进行Meetup Pro网络搜索
- Luma:API密钥认证
- 无状态HTTP传输——服务器重启时没有过时的会话问题
- 提供商架构,便于添加新平台
中间件
服务器使用FastMCP的中间件堆栈来提高可靠性和可观察性:
| 中间件 | 用途 |
|---|---|
ErrorHandlingMiddleware | 将原始异常转换为正确的MCP错误代码,一致地记录错误 |
RetryMiddleware | 在瞬态网络故障时,采用指数回退自动重试(ConnectionError, TimeoutError) |
ResponseCachingMiddleware | 只读工具(列表、搜索、获取)上的5分钟TTL缓存——突变永远不会被缓存 |
工具元数据
所有工具都包含FastMCP元数据,以实现更好的LLM集成:
- 标签 --每个工具都按平台标记(
meetup,luma)和域(events,groups,members等等)用于发现和过滤 - 超时 --支持上传的工具和所有自动分页列表工具超时120秒,以防止在慢速网络上挂起
- 输出模式 --关键工具声明其响应结构,以便LLM客户端知道预期的字段
- 注释 --只读工具标记为
readOnlyHint,幂等工具idempotentHint
快速启动
请参阅 入门指南 有关完整的设置说明。
云(已部署)
服务器在CloudFront后面的AWS ECS Fargate上运行。直接连接克劳德桌面或克劳德代码:
{
"mcpServers": {
"pulumi-events": {
"command": "npx",
"args": ["mcp-remote", "https:///mcp"]
}
}
}Google OAuth处理MCP身份验证。Meetup在服务器启动时通过JWT自动进行身份验证。CloudFront域由输出 pulumi stack output cloudfront_url 部署后。
本地开发
# Prerequisites: Python 3.12+, uv, Pulumi CLI (logged in)
git clone https://github.com/dirien/pulumi-events.git
cd pulumi-events
uv sync
# Start the server (credentials injected via Pulumi ESC)
pulumi env run ediri/pulumi-idp/auth -- uv run pulumi-events然后将Claude Code指向 http://127.0.0.1:8080/mcp.
工具
聚会
| 工具 | 标签 | 描述 |
|---|---|---|
list_platforms | platform | 列出所有具有身份验证状态的已配置平台 |
meetup_login | meetup, auth | 启动Meetup OAuth2登录流程 |
meetup_get_event | meetup, events | 按ID获取Meetup活动的完整详细信息 |
meetup_list_group_events | meetup, events | 使用状态筛选器列出组的事件(包括草稿) |
meetup_search_events | meetup, events, search | 使用过滤器搜索事件(需要纬度/经度、日期、类型) |
meetup_search_groups | meetup, groups, search | 按关键字和位置搜索组(需要纬度/经度) |
meetup_list_my_groups | meetup, groups | 列出您所属的所有组 |
meetup_create_event | meetup, events | 创建事件(默认为DRAFT)。支持 featured_image_path 用于上传封面照片 |
meetup_edit_event | meetup, events | 编辑现有事件。支持 featured_image_path 用于上传封面照片 |
meetup_event_action | meetup, events | 删除、发布、宣布或管理RSVP |
meetup_network_search | meetup, network, search | 搜索Pro网络中的事件、组或成员 |
meetup_list_group_members | meetup, members | 列出具有角色和加入日期的组成员 |
meetup_get_member | meetup, members | 获取组中特定成员的详细信息 |
meetup_find_member | meetup, members | 在所有组中查找成员(跨组查找) |
meetup_create_venue | meetup, venues | 为活动创建场地 |
老的
| 工具 | 标签 | 描述 |
|---|---|---|
luma_list_events | luma, events | 列出Luma日历中的事件 |
luma_get_event | luma, events | 通过API ID获取Luma事件的全部详细信息 |
luma_create_event | luma, events | 创建Luma事件。支持 cover_image_path 用于上传封面图片 |
luma_update_event | luma, events | 更新Luma事件。支持 cover_image_path 用于上传封面图片 |
luma_cancel_event | luma, events | 取消Luma活动 |
luma_list_people | luma, people | 列出Luma日历中的所有人 |
luma_list_guests | luma, guests | 列出Luma活动的客人 |
图片上传
这两个平台都通过其创建/更新工具支持事件封面图像:
- 老的:通行证
cover_image_path(本地文件路径)到luma_create_event或luma_update_event服务器通过预签名的URL上传到Luma的CDN,并设置cover_url自动。 - 聚会:通行证
featured_image_path(本地文件路径)到meetup_create_event或meetup_edit_event服务器通过Meetup上传createGroupEventPhoto突变并设置featuredPhotoId。对于创建,首先创建活动,然后上传照片并通过编辑附加(因为Meetup的CreateEventInput不支持featuredPhotoId).
支持的图像格式:JPEG、PNG、GIF、WebP、SVG、AVIF。
跨平台:遇见Luma
当将活动从Meetup复制到Luma时,LLM应查找场地的 谷歌地图地点ID 并将其作为 geo_address_json:
// Recommended — Google Maps place ID (most reliable)
{"type": "google", "place_id": "ChIJJzpTdyB0nkcRblzKCp3kxeQ"}Luma会自动从位置ID解析完整的地址、坐标和地图引脚。不要传递未经处理的Meetup会场对象——它们使用不兼容的格式。服务器也会删除无效 type 将服务器端字段作为安全网。
资源
| URI | 描述 |
|---|---|
meetup://self | 经过身份验证的Meetup用户资料 |
meetup://group/{urlname} | 按URL名称列出的聚会组详细信息 |
meetup://event/{event_id} | 按ID列出的聚会活动详细信息 |
meetup://network/{urlname} | Meetup Pro网络信息 |
luma://self | 经过身份验证的Luma用户资料 |
luma://event/{event_id} | API ID的Luma事件详细信息 |
配置
所有设置都是通过环境变量加载的 PULUMI_EVENTS_ 前缀:
| 变量 | 默认值 | 描述 |
|---|---|---|
PULUMI_EVENTS_MEETUP_CLIENT_ID | -- | 会见OAuth客户端ID |
PULUMI_EVENTS_LUMA_API_KEY | - | Luma API密钥(需要Luma Plus) |
PULUMI_EVENTS_SERVER_HOST | 127.0.0.1 | 服务器绑定地址 |
PULUMI_EVENTS_SERVER_PORT | 8080 | 服务器端口 |
PULUMI_EVENTS_TOKEN_CACHE_DIR | ~/.config/pulumi-events | 令牌缓存目录 |
PULUMI_EVENTS_AUTH_TOKEN | -- | MCP端点身份验证的承载令牌(可选) |
PULUMI_EVENTS_AUTO_OPEN_BROWSER | false | 自动打开OAuth登录浏览器 |
PULUMI_EVENTS_MEETUP_PRO_NETWORK_URLNAME | pugs | 默认Meetup Pro网络URL名称 |
PULUMI_EVENTS_BASE_URL | -- | 公共URL覆盖(例如CloudFront域) |
PULUMI_EVENTS_MEETUP_JWT_SIGNING_KEY | -- | 用于Meetup JWT身份验证的RSA私钥PEM |
PULUMI_EVENTS_MEETUP_JWT_KEY_ID | -- | 聚会签名密钥ID(儿童) |
PULUMI_EVENTS_MEETUP_MEMBER_ID | -- | JWT身份验证的Meetup成员ID(子声明) |
PULUMI_EVENTS_MEETUP_TOKEN_BACKEND | file | 令牌后端: file 或 env |
PULUMI_EVENTS_GOOGLE_CLIENT_ID | -- | 谷歌OAuth客户端ID(用于MCP身份验证) |
PULUMI_EVENTS_GOOGLE_CLIENT_SECRET | -- | 谷歌OAuth客户端机密 |
认证
MCP端点支持可选的承载令牌身份验证。当 PULUMI_EVENTS_AUTH_TOKEN 如果已设置,所有MCP请求都必须包含 Authorization: Bearer 头球未设置时,服务器将在没有身份验证的情况下运行(本地开发的默认设置)。
要启用:
export PULUMI_EVENTS_AUTH_TOKEN="your-secret-token"然后,MCP客户端必须在 Authorization 头球Claude代码配置示例:
{
"mcpServers": {
"pulumi-events": {
"type": "streamable-http",
"url": "http://127.0.0.1:8080/mcp",
"headers": {
"Authorization": "Bearer your-secret-token"
}
}
}
}健康(/health)OAuth回调(/auth/meetup/callback)路由不受MCP身份验证的影响。
项目结构
src/pulumi_events/
├── server.py # FastMCP instance, lifespan, middleware, custom routes
├── settings.py # Pydantic Settings configuration
├── exceptions.py # Exception hierarchy
├── utils.py # Shared utilities (image MIME type detection)
├── auth/
│ ├── backends.py # Pluggable token backends (File, Env)
│ ├── jwt_auth.py # Meetup JWT auth (headless, server-to-server)
│ ├── oauth.py # OAuth2 flow helpers (Meetup)
│ └── token_store.py # Token persistence + auto-refresh
├── providers/
│ ├── base.py # EventProvider protocol + capabilities
│ ├── registry.py # Provider registry
│ ├── meetup/
│ │ ├── client.py # GraphQL client with auto token refresh + binary upload
│ │ ├── provider.py # MeetupProvider implementation + photo upload
│ │ ├── queries.py # GraphQL query/mutation strings
│ │ └── models.py # Pydantic response models
│ └── luma/
│ ├── client.py # REST client for Luma public API + image upload
│ └── provider.py # LumaProvider implementation
├── tools/
│ ├── _deps.py # Shared dependency factories
│ ├── platform_tools.py # list_platforms, meetup_login
│ ├── event_tools.py # Meetup event mutations (with image upload)
│ ├── group_tools.py # Meetup group tools
│ ├── member_tools.py # Meetup member tools
│ ├── search_tools.py # Meetup search tools
│ ├── venue_tools.py # Meetup venue tools
│ └── luma_tools.py # Luma event + guest tools (with image upload)
└── resources/
├── meetup_resources.py
└── luma_resources.py部署
服务器通过Pulumi部署到AWS ECS Fargate。基础设施代码存在于 deploy/.
deploy/
├── __main__.py # Pulumi program (VPC, ECR, ECS, ALB, CloudFront, Secrets Manager)
├── Pulumi.yaml # Project config (org: pulumi, toolchain: uv)
├── Pulumi.dev.yaml # Stack config (ESC environments: marketing/aws-auth, marketing/pulumi-events)
├── pyproject.toml # Pulumi SDK dependencies
└── uv.lock架构:CloudFront(HTTPS)→ ALB(HTTP)→ ECS Fargate(端口8080)
Meetup在每次启动容器时都会通过JWT进行无头身份验证。机密存储在AWS Secrets Manager中,并通过ECS任务定义注入。所有配置均来自Pulumi ESC。
cd deploy
pulumi up # deploy or update
pulumi logs # tail container logs发展
make help # Show all targets
make check # Lint + format check
make format # Auto-format
make test # Run tests
make run # Start the server许可证
麻省理工学院
