@mikoto_zero/minigame-open-mcp 1.4.13 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,531 +2,263 @@
2
2
 
3
3
  > 基于 Model Context Protocol (MCP) 的 **TapTap 小游戏和 H5 游戏**服务器 - 提供排行榜文档和管理 API,支持 **OAuth 2.0 零配置认证**。
4
4
 
5
- 🔐 **零配置 OAuth** | 📚 **完整文档** | 🎯 **模块化架构** | 🌍 **小游戏 & H5**
5
+ 🔐 **零配置 OAuth** | 📚 **完整文档** | 🎯 **17 Tools + 7 Resources** | 🌍 **小游戏 & H5**
6
6
 
7
- ## 功能特性
7
+ ## ✨ 核心特性
8
8
 
9
- ### 🔐 零配置 OAuth 认证
9
+ - **🔐 零配置认证** - OAuth 2.0 Device Code Flow,扫码即用
10
+ - **📖 完整 API 文档** - 6 个排行榜 API + 详细代码示例
11
+ - **⚙️ 服务端管理** - 创建/管理排行榜,自动处理 ID
12
+ - **🎮 H5 游戏支持** - 上传、发布、状态查询
13
+ - **🚀 三种传输模式** - stdio(本地)、SSE(远程/实时)、HTTP(兼容)
14
+ - **🔌 多客户端并发** - 独立会话管理,无限并发
10
15
 
11
- - **无需手动配置 token!**
12
- - Device Code Flow - 扫码即可授权
13
- - 自动保存 token 到 `~/.config/taptap-minigame/token.json`
14
- - 支持 Cursor、Claude Code、VSCode
15
- - 懒加载 - 服务器秒级启动,需要时才触发认证
16
+ **当前版本**: v1.4.13 | **NPM**: [@mikoto_zero/minigame-open-mcp](https://www.npmjs.com/package/@mikoto_zero/minigame-open-mcp)
16
17
 
17
- ### 📖 完整的 API 文档
18
+ ## 🚀 快速开始
18
19
 
19
- 6 个 LeaderboardManager API 及详细文档:
20
- - `tap.getLeaderboardManager()` - 初始化排行榜
21
- - `submitScores()` - 提交玩家分数
22
- - `openLeaderboard()` - 显示排行榜 UI
23
- - `loadLeaderboardScores()` - 获取排行榜数据
24
- - `loadCurrentPlayerLeaderboardScore()` - 获取玩家排名
25
- - `loadPlayerCenteredScores()` - 加载周围玩家
26
-
27
- **⚠️ 关键提示:无需安装任何 SDK!**
28
- - `tap` 是全局对象(类似 `window`)
29
- - 不需要 `npm install`
30
- - 不需要 import
31
-
32
- ### ⚙️ 服务端管理
33
-
34
- - **创建排行榜** - 服务端创建排行榜
35
- - **列出排行榜** - 查询现有排行榜
36
- - **自动 ID 管理** - 自动获取 developer_id、app_id、miniapp_id
37
- - **完整集成指南** - 分步骤工作流
38
-
39
- ### 🎯 模块化架构
40
-
41
- - **17 个 Tools** - 完整的工具集(应用管理 + 排行榜 + H5 游戏)
42
- - **7 个 Resources** - 详细 API 文档
43
- - **模块化设计** - 易于添加新功能(云存档、分享等)
44
- - **完全兼容** - Claude Code ✅、VSCode ✅、Cursor ✅、OpenHands ✅
45
-
46
- ### 🚀 v1.4.0 新特性 - Context Resolver & 多租户隔离
47
-
48
- - **ContextResolver 系统** - 集中式上下文解析([详细文档](docs/MCP_PROXY_GUIDE.md))
49
- - 统一的 `contextResolver.resolve()` 替代分散的 `ensureAppInfo()` 调用
50
- - 优先级机制:私有参数 > 上下文 > 缓存
51
- - 纯内存/缓存查询,避免重复 API 调用
52
- - 支持多租户隔离部署
53
-
54
- - **多租户隔离** - 通过 `_project_path` 实现租户隔离([v1.4.1+ 架构优化](docs/ARCHITECTURE.md))
55
- - 每个租户独立的工作空间路径
56
- - **缓存目录独立**:`/tmp/taptap-mcp/cache/{userId}/{projectId}/`
57
- - **临时目录独立**:`/tmp/taptap-mcp/temp/{userId}/{projectId}/`
58
- - **支持只读 workspace**:缓存和临时文件不写入用户代码目录
59
- - MCP Proxy 注入租户上下文
60
- - 支持 RuntimeContainer 架构
61
-
62
- - **扩展私有参数** - 新增应用上下文和追踪字段
63
- - `_developer_id`, `_app_id`: 应用上下文注入
64
- - `_project_path`: 租户隔离的关键
65
- - `_tenant_id`, `_trace_id`, `_request_id`: 追踪支持
66
-
67
- - **架构优化**
68
- - 消除模块间循环依赖
69
- - 统一所有 `HandlerContext` 接口定义
70
- - 更清晰的错误提示(不暴露私有参数给 AI)
71
- - API 层使用统一的 ContextResolver
72
-
73
- ### 🚀 v1.3.0 特性 - 私有参数协议
74
-
75
- - **私有参数协议** - 支持 MCP Proxy 模式多账号认证([详细文档](docs/PRIVATE_PROTOCOL.md))
76
- - 对 AI Agent 完全透明(Tool Definition 不声明私有参数)
77
- - 双模式注入:arguments 或 HTTP Header
78
- - 四层认证优先级:自动选择最合适的 Token
79
- - 业务层完全隔离:不感知私有参数
80
- - [MCP Proxy 开发指引](docs/MCP_PROXY_GUIDE.md) - 完整的 Proxy 实现指南
81
-
82
- - **多客户端并发支持** - 无限客户端同时连接,独立会话管理
83
- - **智能自动授权(SSE 模式)** - 一步完成授权,无需手动调用 `complete_oauth_authorization`
84
- - **三种传输模式** - stdio(本地)、SSE(远程/实时)、HTTP JSON(兼容)
85
- - **客户端连接日志** - verbose 模式下完整记录连接事件
86
- - **实时进度推送** - SSE 模式下支持授权、上传、压缩等操作的实时进度
87
-
88
- ## 快速开始
89
-
90
- ### 本地安装
20
+ ### 安装
91
21
 
92
22
  ```bash
23
+ # 全局安装
93
24
  npm install -g @mikoto_zero/minigame-open-mcp
94
- ```
95
-
96
- 或使用 npx 直接运行(无需安装):
97
25
 
98
- ```bash
26
+ # 或使用 npx 直接运行(无需安装)
99
27
  npx @mikoto_zero/minigame-open-mcp
100
28
  ```
101
29
 
102
- ### Docker 部署
30
+ ### 配置(MCP 客户端)
103
31
 
104
- **快速启动(docker-compose)**:
105
- ```bash
106
- # 1. 配置环境变量
107
- cp .env.docker .env
108
- # 编辑 .env,填入 CLIENT_ID 和 CLIENT_TOKEN
109
-
110
- # 2. 启动服务
111
- docker-compose up -d
32
+ #### Claude Code / VSCode / Cursor
112
33
 
113
- # 3. 验证服务
114
- curl http://localhost:5003/health
115
- ```
116
-
117
- **自动化部署脚本**:
118
- ```bash
119
- # 使用部署脚本(支持参数化配置)
120
- ./scripts/deploy-docker.sh [version] [env] [port]
121
-
122
- # 示例
123
- ./scripts/deploy-docker.sh latest production 3000
124
- ```
125
-
126
- 详见 [Docker 部署文档](docs/DOCKER_DEPLOYMENT.md)
127
-
128
- ### 配置
129
-
130
- #### 在 Claude Code / VSCode / Cursor 中使用(零配置 OAuth)
131
-
132
- **推荐**:使用 OAuth Device Code Flow - 无需手动配置 token!
133
-
134
- 在项目中创建 `.mcp.json`:
34
+ 在项目中创建 `.mcp.json`:
135
35
 
136
36
  ```json
137
37
  {
138
38
  "mcpServers": {
139
39
  "taptap-minigame": {
140
40
  "command": "npx",
141
- "args": ["-y", "@mikoto_zero/minigame-open-mcp"],
142
- "env": {
143
- "TDS_MCP_ENV": "production"
144
- }
41
+ "args": ["-y", "@mikoto_zero/minigame-open-mcp"]
145
42
  }
146
43
  }
147
44
  }
148
45
  ```
149
46
 
150
- **首次使用流程(stdio 模式 - 默认)**:
151
- 1. 服务器秒级启动
152
- 2. 使用需要认证的功能时,会显示授权二维码链接
153
- 3. 用 TapTap App 扫码授权
154
- 4. 调用 `complete_oauth_authorization` 工具完成授权
155
- 5. Token 自动保存,后续使用自动加载!
156
-
157
- **SSE 模式(OpenHands 等)**:
158
- - 步骤 2-4 自动完成,无需手动调用 `complete_oauth_authorization`
159
- - 实时显示授权进度(每 10 秒更新)
47
+ **零配置 OAuth**:首次使用会提示扫码授权,token 自动保存!
160
48
 
161
- **手动配置 Token**(可选):
49
+ #### OpenHands(推荐 SSE 模式)
162
50
 
163
- 如果你想手动配置:
164
-
165
- ```json
166
- {
167
- "mcpServers": {
168
- "taptap-minigame": {
169
- "command": "npx",
170
- "args": ["@mikoto_zero/minigame-open-mcp"],
171
- "env": {
172
- "TDS_MCP_MAC_TOKEN": "{\"kid\":\"your_kid\",\"token_type\":\"mac\",\"mac_key\":\"your_key\",\"mac_algorithm\":\"hmac-sha-1\"}",
173
- "TDS_MCP_ENV": "production",
174
- "TDS_MCP_VERBOSE": "false"
175
- }
176
- }
177
- }
178
- }
179
- ```
180
-
181
- #### 在 OpenHands 中使用(推荐 SSE 模式)
182
-
183
- **方式 1:SSE 远程模式(推荐 - 支持自动授权和实时进度)**
51
+ **远程部署**:
184
52
 
185
53
  ```bash
186
- # 1. 在服务器上启动 MCP 服务
187
- TDS_MCP_TRANSPORT=sse TDS_MCP_PORT=3000 TDS_MCP_VERBOSE=true \
54
+ # 启动 SSE 服务器
55
+ TDS_MCP_TRANSPORT=sse TDS_MCP_PORT=3000 \
188
56
  npx @mikoto_zero/minigame-open-mcp
189
-
190
- # 2. OpenHands 配置
191
- {
192
- "mcpServers": {
193
- "taptap-minigame": {
194
- "url": "http://your-server:3000",
195
- "transport": "sse"
196
- }
197
- }
198
- }
199
57
  ```
200
58
 
201
- **特性**:
202
- - ✅ 一步式自动授权(无需手动调用 `complete_oauth_authorization`)
203
- - ✅ 实时进度推送(授权、上传、压缩等)
204
- - ✅ 多客户端并发支持
205
- - ✅ 连接日志和监控
206
-
207
- **方式 2:stdio 本地模式(兼容模式)**
59
+ **OpenHands 配置**:
208
60
 
209
61
  ```json
210
62
  {
211
63
  "mcpServers": {
212
64
  "taptap-minigame": {
213
- "command": "npx",
214
- "args": ["@mikoto_zero/minigame-open-mcp@beta"],
215
- "env": {
216
- "TDS_MCP_MAC_TOKEN": "${CURRENT_USER_MAC_TOKEN}",
217
- "TDS_MCP_ENV": "production"
218
- }
65
+ "url": "http://your-server:3000",
66
+ "transport": "sse"
219
67
  }
220
68
  }
221
69
  }
222
70
  ```
223
71
 
224
- ### 传输模式
72
+ SSE 模式支持自动授权和实时进度推送!
225
73
 
226
- 服务器支持三种传输模式:
227
-
228
- | 模式 | 配置 | 授权方式 | 进度反馈 | 适用场景 |
229
- |------|------|---------|---------|---------|
230
- | **stdio** | 默认 | 两步式 | ❌ | Claude Desktop、Cursor、本地单客户端 |
231
- | **sse** | `TDS_MCP_TRANSPORT=sse` | **一步式自动** | ✅ 实时 | **OpenHands**、Claude Code、远程/多客户端 |
232
- | **http** | `TDS_MCP_TRANSPORT=http` | 两步式 | ❌ | 普通 HTTP 客户端 |
233
-
234
- ### 部署和运行
235
-
236
- #### npm scripts 方式(推荐)
237
-
238
- ```bash
239
- # stdio 模式(默认,本地开发)
240
- npm start # 或 npm run dev
241
-
242
- # SSE 模式(远程部署,推荐用于 OpenHands)
243
- npm run serve:sse # 基础模式(端口 3000)
244
- npm run serve:sse:dev # 开发模式(详细日志)
245
-
246
- # HTTP JSON 模式(兼容普通 HTTP 客户端)
247
- npm run serve:http # 端口 3000
248
- ```
249
-
250
- #### 直接启动方式
251
-
252
- ```bash
253
- # SSE 模式(推荐用于 OpenHands)
254
- TDS_MCP_TRANSPORT=sse TDS_MCP_PORT=3000 TDS_MCP_VERBOSE=true \
255
- npx @mikoto_zero/minigame-open-mcp
256
-
257
- # HTTP JSON 模式
258
- TDS_MCP_TRANSPORT=http TDS_MCP_PORT=3000 \
259
- npx @mikoto_zero/minigame-open-mcp
260
-
261
- # stdio 模式(默认)
262
- npx @mikoto_zero/minigame-open-mcp
263
- ```
264
-
265
- #### 自定义端口和环境
266
-
267
- ```bash
268
- # 通过环境变量覆盖端口
269
- TDS_MCP_PORT=8080 npm run serve:sse
270
-
271
- # 启用详细日志
272
- TDS_MCP_VERBOSE=true npm start
273
-
274
- # 切换环境
275
- TDS_MCP_ENV=rnd npm run serve:sse
276
- ```
277
-
278
- #### 生产部署
279
-
280
- **使用 PM2 进程管理器**:
281
-
282
- ```bash
283
- # 安装 PM2
284
- npm install -g pm2
285
-
286
- # 启动服务
287
- pm2 start npm --name "taptap-mcp" -- run serve:sse
288
-
289
- # 查看日志
290
- pm2 logs taptap-mcp
291
-
292
- # 重启/停止
293
- pm2 restart taptap-mcp
294
- pm2 stop taptap-mcp
295
-
296
- # 开机自启
297
- pm2 startup
298
- pm2 save
299
- ```
300
-
301
- **使用 Docker 部署脚本**:
74
+ ### Docker 部署
302
75
 
303
76
  ```bash
304
- # 1. 配置环境变量
305
- export TDS_MCP_CLIENT_ID=your_client_id
306
- export TDS_MCP_CLIENT_TOKEN=your_client_token
307
-
308
- # 2. 执行部署脚本
309
- ./scripts/deploy-docker.sh [version] [env] [port]
310
-
311
- # 示例
312
- ./scripts/deploy-docker.sh latest production 3000
77
+ # 快速启动
78
+ docker-compose up -d
313
79
 
314
- # 3. 验证服务
315
- curl http://localhost:3000/health
80
+ # 健康检查
81
+ curl http://localhost:5003/health
316
82
  ```
317
83
 
318
- 详见 [Docker 部署文档](docs/DOCKER_DEPLOYMENT.md)
319
-
320
- #### 健康检查
84
+ 详见: [Docker 部署文档](docs/DOCKER_DEPLOYMENT.md)
321
85
 
322
- ```bash
323
- # 检查服务器状态
324
- curl http://localhost:3000/health
86
+ ## 📖 功能列表
325
87
 
326
- # 预期响应
327
- {
328
- "status": "ok",
329
- "version": "1.4.8",
330
- "transport": "streamable-http",
331
- "tools": 17,
332
- "resources": 7,
333
- "activeSessions": 0
334
- }
335
- ```
88
+ ### 17 个 Tools
336
89
 
337
- ### 环境变量
90
+ #### 流程指引 (1)
91
+ - `get_integration_guide` - 完整接入工作流指引
338
92
 
339
- **OAuth 认证(推荐 - 零配置):**
340
- - 无需配置环境变量!
341
- - Token 自动保存到 `~/.config/taptap-minigame/token.json`
342
- - **SSE 模式**:自动授权,实时进度推送
343
- - **stdio/http 模式**:两步式授权
93
+ #### 信息查询 (2)
94
+ - `get_current_app_info` - 获取当前应用信息
95
+ - `check_environment` - 检查环境配置
344
96
 
345
- **手动配置(可选):**
346
- - `TDS_MCP_MAC_TOKEN` - MAC Token JSON 格式(可选,不设置则使用 OAuth)
347
- - `TDS_MCP_CLIENT_ID` - 客户端 ID(已内置默认值,可覆盖)
348
- - `TDS_MCP_CLIENT_TOKEN` - 签名密钥(已内置默认值,可覆盖)
97
+ #### 认证 (1)
98
+ - `complete_oauth_authorization` - 完成 OAuth 授权
349
99
 
350
- **其他配置:**
351
- - `TDS_MCP_ENV` - 环境选择:`production`(默认)或 `rnd`
352
- - `WORKSPACE_ROOT` - 工作空间根路径(默认:`process.cwd()`),用于路径解析
353
- - `TDS_MCP_VERBOSE` - 详细日志:`true` 或 `false`(默认)
100
+ #### 应用管理 (2)
101
+ - `list_developers_and_apps` - 列出所有应用
102
+ - `select_app` - 选择当前应用
354
103
 
355
- **调试模式:**
104
+ #### 排行榜管理 (4)
105
+ - `create_leaderboard` - 创建排行榜
106
+ - `list_leaderboards` - 列出排行榜
107
+ - `publish_leaderboard` - 发布排行榜
108
+ - `get_user_leaderboard_scores` - 获取用户分数
356
109
 
357
- 启用详细日志查看所有工具调用、HTTP 请求/响应:
110
+ #### H5 游戏管理 (7)
111
+ - `list_h5_games` - 列出 H5 游戏
112
+ - `create_h5_game` - 创建 H5 游戏
113
+ - `update_h5_game` - 更新游戏信息
114
+ - `upload_h5_game` - 上传游戏包
115
+ - `publish_h5_game` - 发布游戏
116
+ - `get_h5_game_status` - 查询发布状态
117
+ - `get_h5_game_share_url` - 获取分享链接
358
118
 
359
- ```bash
360
- export TDS_MCP_VERBOSE=true
361
- npm start
362
- ```
119
+ ### 7 个 Resources
363
120
 
364
- 详细日志包含:
365
- - 📥 工具调用的输入和输出
366
- - 📤 HTTP 请求头和请求体
367
- - 📥 HTTP 响应状态和数据
368
- - 🔒 敏感数据自动脱敏
121
+ 完整的排行榜 API 文档:
122
+ - `docs://leaderboard/overview` - 完整概览
123
+ - `docs://leaderboard/api/get-manager` - 初始化
124
+ - `docs://leaderboard/api/submit-scores` - 提交分数
125
+ - `docs://leaderboard/api/open` - 显示 UI
126
+ - `docs://leaderboard/api/load-scores` - 加载数据
127
+ - `docs://leaderboard/api/load-player-score` - 玩家排名
128
+ - `docs://leaderboard/api/load-centered-scores` - 周围玩家
369
129
 
370
- ## 使用示例
130
+ ## 🎯 使用示例
371
131
 
372
- ### 场景 1: 接入排行榜功能
132
+ ### 接入排行榜
373
133
 
374
134
  ```
375
- 用户: "我想在游戏中接入排行榜功能"
135
+ 用户: "我想在游戏中接入排行榜"
376
136
 
377
137
  AI 调用: get_integration_guide
138
+ → 返回完整工作流(创建排行榜 → 客户端代码 → 测试)
378
139
 
379
- 返回: 完整的分步骤工作流
380
- 强调无需安装 SDK
381
- ✅ 步骤 1: 检查/创建服务端排行榜
382
- ✅ 步骤 2: 客户端代码(使用全局 tap 对象)
383
- ✅ 步骤 3: 测试检查清单
384
- ✅ 列出所有 Resources 供详细阅读
385
- ```
386
-
387
- ### 场景 2: 获取 API 文档
388
-
389
- **在 Claude Code 中**(AI 自动读取):
390
- ```
391
- 用户: "如何提交分数?"
140
+ AI 调用: create_leaderboard
141
+ 创建服务端排行榜
392
142
 
393
143
  AI 读取: docs://leaderboard/api/submit-scores
394
-
395
- 返回: submitScores() 完整文档(参数、示例、错误码)
396
- ```
397
-
398
- **在 VSCode 中**(调用工具):
144
+ → 获取客户端代码示例
399
145
  ```
400
- 用户: "如何提交分数?"
401
146
 
402
- AI 调用: get_integration_guide
147
+ ### OAuth 授权(首次)
403
148
 
404
- 返回: 完整工作流(包含代码示例)
405
149
  ```
150
+ AI 调用: create_leaderboard
151
+ → 🔐 需要授权,显示二维码链接
406
152
 
407
- ### 场景 3: OAuth 授权流程
153
+ 用户: 扫码后告知 "已授权"
408
154
 
409
- ```
410
- 用户: "创建排行榜"
155
+ AI 调用: complete_oauth_authorization
156
+ ✅ 授权完成,token 已保存
411
157
 
412
158
  AI 调用: create_leaderboard
159
+ → ✅ 排行榜创建成功
160
+ ```
413
161
 
414
- 返回: 🔐 需要授权
415
- 🔗 https://www.taptap.cn/tap-qrcode?...
162
+ ## 🛠️ 开发
416
163
 
417
- 步骤:
418
- 1. 点击链接在浏览器打开
419
- 2. 用 TapTap App 扫码
420
- 3. 调用 complete_oauth_authorization
421
- 4. 重新执行
164
+ ### 环境要求
422
165
 
423
- 用户: "我已经授权了"
166
+ - Node.js 16+
167
+ - npm 或 pnpm
424
168
 
425
- AI 调用: complete_oauth_authorization
169
+ ### 本地开发
426
170
 
427
- 返回: ✅ 授权完成!Token 已保存
171
+ ```bash
172
+ # 安装依赖
173
+ npm install
428
174
 
429
- 用户: "创建排行榜"
175
+ # 启动开发服务器
176
+ npm run dev
430
177
 
431
- AI 调用: create_leaderboard
178
+ # 构建
179
+ npm run build
432
180
 
433
- 返回: ✅ 排行榜创建成功!
434
- 🎮 客户端 ID: xxx
181
+ # 运行测试
182
+ npm test
435
183
  ```
436
184
 
437
- ## 🏗️ 架构
185
+ ### 环境变量
438
186
 
439
- ### 模块化设计
187
+ **OAuth 认证(推荐)**:
188
+ - 无需配置!自动保存 token 到 `~/.config/taptap-minigame/`
440
189
 
441
- v1.2.0-beta 引入了**完全模块化的架构**,每个功能都是自包含的:
190
+ **手动配置(可选)**:
191
+ - `TDS_MCP_MAC_TOKEN` - MAC Token(JSON 格式)
192
+ - `TDS_MCP_CLIENT_ID` - 客户端 ID(已内置)
193
+ - `TDS_MCP_CLIENT_TOKEN` - 签名密钥(已内置)
442
194
 
443
- ```
444
- src/
445
- ├── features/ # 功能模块(自包含)
446
- │ ├── app/ # 应用管理模块(基础功能)
447
- │ │ ├── index.ts # 模块定义和注册
448
- │ │ ├── tools.ts # 5 个工具(应用选择、环境检查等)
449
- │ │ ├── handlers.ts # 业务逻辑
450
- │ │ └── api.ts # API 调用
451
- │ │
452
- │ └── leaderboard/ # 排行榜模块
453
- │ ├── index.ts # 模块定义和注册
454
- │ ├── tools.ts # 5 个工具 + 处理器
455
- │ ├── resources.ts # 7 个 Resources + 处理器
456
- │ ├── docs.ts # 文档内容
457
- │ ├── docTools.ts # 文档工具
458
- │ ├── handlers.ts # 业务逻辑
459
- │ └── api.ts # API 调用
460
-
461
- ├── core/ # 共享核心功能
462
- │ ├── auth/ # OAuth Device Code Flow
463
- │ ├── network/ # HTTP 客户端
464
- │ ├── handlers/ # 通用处理器(environment)
465
- │ ├── utils/ # 缓存、日志、文档助手
466
- │ └── types/ # 类型定义
467
-
468
- └── server.ts # 主服务器(自动注册)
469
- ```
195
+ **其他**:
196
+ - `TDS_MCP_ENV` - 环境:`production`(默认)或 `rnd`
197
+ - `TDS_MCP_TRANSPORT` - 传输模式:`stdio`(默认)、`sse`、`http`
198
+ - `TDS_MCP_PORT` - 端口(默认 3000)
199
+ - `TDS_MCP_VERBOSE` - 详细日志:`true` 或 `false`
200
+ - `TDS_MCP_CACHE_DIR` - 缓存目录(默认 `/tmp/taptap-mcp/cache`)
201
+ - `TDS_MCP_TEMP_DIR` - 临时文件目录(默认 `/tmp/taptap-mcp/temp`)
470
202
 
471
- **模块说明**:
472
- - **app**: 基础应用管理(开发者/应用选择、OAuth 授权、环境检查)
473
- - **leaderboard**: 排行榜功能(依赖 app 模块)
474
- - 未来: cloudSave, share 等(都可以复用 app 模块)
203
+ ### 添加新功能
475
204
 
476
- **添加新功能**:
477
- ```typescript
478
- // 1. 使用脚手架创建新模块
205
+ ```bash
206
+ # 使用脚手架
479
207
  ./scripts/create-feature.sh
480
208
 
481
- // 2. 按照提示输入功能信息
482
- // Feature Key: cloud-save
483
- // Feature Name: 云存档
484
- // Resources: yes
485
- // Prompts: no
486
-
487
- // 3. 在 server.ts 导入
488
- import { cloudSaveModule } from './features/cloudSave/index.js';
489
- const allModules = [appModule, leaderboardModule, cloudSaveModule];
490
-
491
- // 完成!自动注册 ✅
209
+ # 按提示输入功能信息
210
+ # 自动生成模块结构到 src/features/yourFeature/
492
211
  ```
493
212
 
494
213
  ## 📚 文档
495
214
 
496
- - **README.md** - 本文件(用户指南)
497
- - **CLAUDE.md** - Claude Code 集成指南
498
- - **CONTRIBUTING.md** - 开发者贡献指南(添加新功能)
499
- - **CHANGELOG.md** - 版本变更历史
215
+ - **[CLAUDE.md](CLAUDE.md)** - AI Agent 开发指引
216
+ - **[CHANGELOG.md](CHANGELOG.md)** - 版本变更历史
217
+ - **[docs/DOCKER_DEPLOYMENT.md](docs/DOCKER_DEPLOYMENT.md)** - Docker 部署指南
218
+ - **[docs/MCP_PROXY_GUIDE.md](docs/MCP_PROXY_GUIDE.md)** - MCP Proxy 开发指南
219
+ - **[docs/PRIVATE_PROTOCOL.md](docs/PRIVATE_PROTOCOL.md)** - 私有参数协议
500
220
 
501
- ## 🤝 贡献
221
+ ## 🔄 CI/CD
222
+
223
+ 基于 Conventional Commits 的完全自动化发布:
502
224
 
503
- 想要添加新功能(云存档、分享等)?
225
+ ```bash
226
+ # 创建功能分支
227
+ git checkout -b feat/awesome-feature
228
+
229
+ # 提交代码
230
+ git commit -m "feat: add awesome feature"
504
231
 
505
- 查看 **CONTRIBUTING.md** 了解:
506
- - 分步骤指南
507
- - 代码结构
508
- - 设计原则
509
- - 快速脚手架:`./scripts/create-feature.sh`
232
+ # 创建 PR 并合并
233
+ gh pr create && gh pr merge
510
234
 
511
- ## 📖 相关资源
235
+ # 自动发布到 npm(版本:1.4.13 → 1.5.0)
236
+ ```
512
237
 
513
- - **官方 API 文档**: https://developer.taptap.cn/minigameapidoc/dev/api/open-api/leaderboard/
514
- - **MCP 规范**: https://modelcontextprotocol.io/
515
- - **npm 包**: [@mikoto_zero/minigame-open-mcp](https://www.npmjs.com/package/@mikoto_zero/minigame-open-mcp)
238
+ **发布流程**:
239
+ 1. PR 合并 → 触发 Actions
240
+ 2. 分析 commits 确定版本号
241
+ 3. 发布到 npm
242
+ 4. 自动创建版本 PR 并合并
243
+ 5. 创建 GitHub Release
516
244
 
517
- ## 📊 版本
245
+ 详见: [CLAUDE.md - CI/CD 章节](CLAUDE.md#cicd-和自动化发布)
518
246
 
519
- - **latest (v1.1.4)**: 纯 Tools 稳定版(17 tools)
520
- - **beta (v1.2.0-beta.12)**: 模块化架构(app + leaderboard)+ OAuth(10 tools + 7 resources)
247
+ ## 🤝 贡献
521
248
 
522
- 推荐使用 **beta 版本**获得最佳体验!
249
+ 欢迎贡献!请遵循:
250
+ 1. Fork 仓库并创建 feature 分支
251
+ 2. 使用 Conventional Commits 规范
252
+ 3. 创建 PR,等待 CI 检查
253
+ 4. Review 通过后合并
523
254
 
524
255
  ## 📄 许可证
525
256
 
526
257
  MIT
527
258
 
528
- ## 🔗 链接
259
+ ## 🔗 相关链接
529
260
 
530
261
  - [TapTap 开发者中心](https://developer.taptap.cn/)
531
- - [MCP 协议](https://modelcontextprotocol.io/)
532
- - [Issues](https://github.com/taptap/taptap-minigame-mcp-server/issues)
262
+ - [官方 API 文档](https://developer.taptap.cn/minigameapidoc/dev/api/open-api/leaderboard/)
263
+ - [MCP 协议规范](https://modelcontextprotocol.io/)
264
+ - [Issues](https://github.com/taptap/taptap_minigame_open_mcp/issues)
@@ -0,0 +1,4 @@
1
+ /**
2
+ * 基础测试 - 确保测试框架正常工作
3
+ */
4
+ //# sourceMappingURL=version.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/version.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ /**
3
+ * 基础测试 - 确保测试框架正常工作
4
+ */
5
+ describe('Basic Tests', () => {
6
+ test('simple math should work', () => {
7
+ expect(1 + 1).toBe(2);
8
+ });
9
+ test('string concatenation should work', () => {
10
+ expect('hello' + ' ' + 'world').toBe('hello world');
11
+ });
12
+ test('array operations should work', () => {
13
+ const arr = [1, 2, 3];
14
+ expect(arr.length).toBe(3);
15
+ expect(arr[0]).toBe(1);
16
+ });
17
+ });
18
+ //# sourceMappingURL=version.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.test.js","sourceRoot":"","sources":["../../src/__tests__/version.test.ts"],"names":[],"mappings":";AAAA;;GAEG;AAEH,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,IAAI,CAAC,yBAAyB,EAAE,GAAG,EAAE;QACnC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC5C,MAAM,CAAC,OAAO,GAAG,GAAG,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACxC,MAAM,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QACtB,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC3B,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACzB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/mcp-proxy/config.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAY,MAAM,YAAY,CAAC;AA0BxD;;;;;GAKG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,WAAW,CAAC,CAgDvD"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/mcp-proxy/config.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AA0B9C;;;;;GAKG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,WAAW,CAAC,CAgDvD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikoto_zero/minigame-open-mcp",
3
- "version": "1.4.13",
3
+ "version": "1.5.1",
4
4
  "type": "module",
5
5
  "description": "TapTap Open API MCP Server - Documentation and Management APIs for TapTap Minigame and H5 Games (Leaderboard, and more features coming)",
6
6
  "main": "dist/server.js",
@@ -51,14 +51,26 @@
51
51
  "dotenv": "17.2.3"
52
52
  },
53
53
  "devDependencies": {
54
+ "@commitlint/cli": "^18.4.3",
55
+ "@commitlint/config-conventional": "^18.4.3",
56
+ "@semantic-release/changelog": "^6.0.3",
57
+ "@semantic-release/commit-analyzer": "^11.1.0",
58
+ "@semantic-release/git": "^10.0.1",
59
+ "@semantic-release/github": "^9.2.6",
60
+ "@semantic-release/npm": "^11.0.2",
61
+ "@semantic-release/release-notes-generator": "^12.1.0",
54
62
  "@types/crypto-js": "^4.2.0",
55
63
  "@types/jest": "^29.0.0",
56
64
  "@types/node": "^20.0.0",
57
65
  "@typescript-eslint/eslint-plugin": "^6.0.0",
58
66
  "@typescript-eslint/parser": "^6.0.0",
67
+ "conventional-changelog-cli": "5.0.0",
68
+ "conventional-changelog-conventionalcommits": "^7.0.2",
59
69
  "eslint": "^8.0.0",
60
70
  "jest": "^29.0.0",
61
71
  "prettier": "^3.0.0",
72
+ "semantic-release": "^22.0.12",
73
+ "ts-jest": "^29.1.0",
62
74
  "ts-node": "^10.9.0",
63
75
  "tsx": "4.20.6",
64
76
  "typescript": "^5.0.0"
@@ -70,10 +82,10 @@
70
82
  ],
71
83
  "repository": {
72
84
  "type": "git",
73
- "url": "git+https://github.com/taptap/minigame-open-mcp.git"
85
+ "url": "git+https://github.com/taptap/taptap_minigame_open_mcp.git"
74
86
  },
75
- "homepage": "https://github.com/taptap/minigame-open-mcp#readme",
87
+ "homepage": "https://github.com/taptap/taptap_minigame_open_mcp#readme",
76
88
  "bugs": {
77
- "url": "https://github.com/taptap/minigame-open-mcp/issues"
89
+ "url": "https://github.com/taptap/taptap_minigame_open_mcp/issues"
78
90
  }
79
91
  }
@@ -1,9 +0,0 @@
1
- /**
2
- * Application Management Handlers (DEPRECATED - moved to features/app)
3
- * @deprecated Use handlers from features/app/handlers.ts instead
4
- *
5
- * This file is kept for backward compatibility only.
6
- * All app management logic has been moved to the app feature module.
7
- */
8
- export { listDevelopersAndApps, selectApp } from '../../features/app/handlers.js';
9
- //# sourceMappingURL=appHandlers.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"appHandlers.d.ts","sourceRoot":"","sources":["../../../src/core/handlers/appHandlers.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,qBAAqB,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC"}
@@ -1,9 +0,0 @@
1
- /**
2
- * Application Management Handlers (DEPRECATED - moved to features/app)
3
- * @deprecated Use handlers from features/app/handlers.ts instead
4
- *
5
- * This file is kept for backward compatibility only.
6
- * All app management logic has been moved to the app feature module.
7
- */
8
- export { listDevelopersAndApps, selectApp } from '../../features/app/handlers.js';
9
- //# sourceMappingURL=appHandlers.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"appHandlers.js","sourceRoot":"","sources":["../../../src/core/handlers/appHandlers.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,qBAAqB,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC"}
@@ -1,113 +0,0 @@
1
- /**
2
- * Tap 开发者身份信息
3
- */
4
- export interface TapDeveloperInfo {
5
- /** 游戏列表 */
6
- levels: TapAppItem[];
7
- /** 开发者 ID */
8
- developer_id: number;
9
- /** 开发者名称 */
10
- developer_name: string;
11
- /** 是否已认证 */
12
- is_certified: boolean;
13
- }
14
- /**
15
- * H5 游戏信息
16
- */
17
- export interface TapAppItem {
18
- /** 游戏 ID */
19
- app_id: number;
20
- /** 分类 */
21
- category: string;
22
- /** 游戏标题 */
23
- app_title: string;
24
- /** 是否已发布 */
25
- is_published: boolean;
26
- }
27
- /**
28
- * 上传参数
29
- */
30
- export interface UploadParams {
31
- /** 上传包 ID */
32
- h5_package_id: number;
33
- /** 上传 URL */
34
- url: string;
35
- /** 上传方法 */
36
- method: string;
37
- /** 上传头 */
38
- headers: Record<string, string>;
39
- }
40
- /**
41
- * 创建开发者身份响应
42
- */
43
- export interface CreateDeveloperResponse {
44
- /** 开发者名称 */
45
- developer_name: string;
46
- /** 开发者 ID */
47
- developer_id: number;
48
- }
49
- /**
50
- * 创建游戏响应
51
- */
52
- export interface CreateAppResponse {
53
- /** 应用 ID */
54
- app_id: number;
55
- /** 应用标题 */
56
- app_title: string;
57
- /** 显示应用标题 */
58
- display_app_title: string;
59
- }
60
- /**
61
- * 编辑游戏响应
62
- */
63
- export interface EditAppResponse {
64
- /** 游戏标题 */
65
- app_title?: string;
66
- /** 显示应用标题 */
67
- display_app_title?: string;
68
- }
69
- /**
70
- * 游戏详情响应
71
- */
72
- export interface AppDetailAPIResponse {
73
- /** 游戏详情 */
74
- level?: {
75
- /** 显示应用标题 */
76
- display_app_title?: string;
77
- /** 开发者 ID */
78
- developer_id?: number;
79
- /** 开发者名称 */
80
- developer_name?: string;
81
- };
82
- /** 上传游戏详情 */
83
- upload_level?: {
84
- /** 游戏 ID */
85
- app_id: number;
86
- /** 表单数据 */
87
- form_data: {
88
- /** 游戏信息 */
89
- info: {
90
- /** 游戏标题 */
91
- title: string;
92
- };
93
- };
94
- };
95
- }
96
- /**
97
- * 游戏状态响应
98
- */
99
- export interface AppStatusResponse {
100
- /** 审核状态:0-未发布,1-审核中,2-审核失败,3-已上线 */
101
- review_status: number;
102
- }
103
- /**
104
- * App 详情
105
- */
106
- export interface AppDetail {
107
- appId: number;
108
- appTitle: string;
109
- displayAppTitle: string;
110
- developerId: number;
111
- developerName: string;
112
- }
113
- //# sourceMappingURL=types.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/features/h5Game/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,WAAW;IACX,MAAM,EAAE,UAAU,EAAE,CAAC;IAErB,aAAa;IACb,YAAY,EAAE,MAAM,CAAC;IAErB,YAAY;IACZ,cAAc,EAAE,MAAM,CAAC;IAEvB,YAAY;IACZ,YAAY,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,YAAY;IACZ,MAAM,EAAE,MAAM,CAAC;IAEf,SAAS;IACT,QAAQ,EAAE,MAAM,CAAC;IAEjB,WAAW;IACX,SAAS,EAAE,MAAM,CAAC;IAElB,YAAY;IACZ,YAAY,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,aAAa;IACb,aAAa,EAAE,MAAM,CAAC;IAEtB,aAAa;IACb,GAAG,EAAE,MAAM,CAAC;IAEZ,WAAW;IACX,MAAM,EAAE,MAAM,CAAC;IAEf,UAAU;IACV,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,YAAY;IACZ,cAAc,EAAE,MAAM,CAAC;IAEvB,aAAa;IACb,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,YAAY;IACZ,MAAM,EAAE,MAAM,CAAC;IAEf,WAAW;IACX,SAAS,EAAE,MAAM,CAAC;IAElB,aAAa;IACb,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,WAAW;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,aAAa;IACb,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,WAAW;IACX,KAAK,CAAC,EAAE;QACN,aAAa;QACb,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAE3B,aAAa;QACb,YAAY,CAAC,EAAE,MAAM,CAAC;QAEtB,YAAY;QACZ,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;IAEF,aAAa;IACb,YAAY,CAAC,EAAE;QACb,YAAY;QACZ,MAAM,EAAE,MAAM,CAAC;QAEf,WAAW;QACX,SAAS,EAAE;YACT,WAAW;YACX,IAAI,EAAE;gBACJ,WAAW;gBACX,KAAK,EAAE,MAAM,CAAC;aACf,CAAC;SACH,CAAC;KACH,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,oCAAoC;IACpC,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;CACvB"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=types.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/features/h5Game/types.ts"],"names":[],"mappings":""}
@@ -1,17 +0,0 @@
1
- import type { MacToken } from './types.js';
2
- /**
3
- * 基于文件的 Token 存储
4
- */
5
- export declare class FileTokenStore {
6
- private tokenFile;
7
- constructor(tokenFile: string);
8
- /**
9
- * 获取 MAC Token
10
- */
11
- get(): Promise<MacToken | null>;
12
- /**
13
- * 检查 Token 文件是否存在
14
- */
15
- has(): boolean;
16
- }
17
- //# sourceMappingURL=tokenStore.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"tokenStore.d.ts","sourceRoot":"","sources":["../../src/mcp-proxy/tokenStore.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C;;GAEG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,SAAS,CAAS;gBAEd,SAAS,EAAE,MAAM;IAI7B;;OAEG;IACG,GAAG,IAAI,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IA2BrC;;OAEG;IACH,GAAG,IAAI,OAAO;CAGf"}
@@ -1,42 +0,0 @@
1
- import { readFile } from 'node:fs/promises';
2
- import { existsSync } from 'node:fs';
3
- /**
4
- * 基于文件的 Token 存储
5
- */
6
- export class FileTokenStore {
7
- constructor(tokenFile) {
8
- this.tokenFile = tokenFile;
9
- }
10
- /**
11
- * 获取 MAC Token
12
- */
13
- async get() {
14
- try {
15
- if (!this.has()) {
16
- return null;
17
- }
18
- const content = await readFile(this.tokenFile, 'utf-8');
19
- const token = JSON.parse(content);
20
- // 验证 Token 格式
21
- if (!token.kid ||
22
- !token.mac_key ||
23
- token.token_type !== 'mac' ||
24
- token.mac_algorithm !== 'hmac-sha-1') {
25
- console.error('[TokenStore] Invalid token format');
26
- return null;
27
- }
28
- return token;
29
- }
30
- catch (error) {
31
- console.error('[TokenStore] Failed to read token:', error);
32
- return null;
33
- }
34
- }
35
- /**
36
- * 检查 Token 文件是否存在
37
- */
38
- has() {
39
- return existsSync(this.tokenFile);
40
- }
41
- }
42
- //# sourceMappingURL=tokenStore.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"tokenStore.js","sourceRoot":"","sources":["../../src/mcp-proxy/tokenStore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAGrC;;GAEG;AACH,MAAM,OAAO,cAAc;IAGzB,YAAY,SAAiB;QAC3B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,GAAG;QACP,IAAI,CAAC;YACH,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;gBAChB,OAAO,IAAI,CAAC;YACd,CAAC;YAED,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;YACxD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAa,CAAC;YAE9C,cAAc;YACd,IACE,CAAC,KAAK,CAAC,GAAG;gBACV,CAAC,KAAK,CAAC,OAAO;gBACd,KAAK,CAAC,UAAU,KAAK,KAAK;gBAC1B,KAAK,CAAC,aAAa,KAAK,YAAY,EACpC,CAAC;gBACD,OAAO,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;gBACnD,OAAO,IAAI,CAAC;YACd,CAAC;YAED,OAAO,KAAK,CAAC;QACf,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAE,KAAK,CAAC,CAAC;YAC3D,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED;;OAEG;IACH,GAAG;QACD,OAAO,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACpC,CAAC;CACF"}