@lark-apaas/coding-steering 0.1.32-beta.0 → 0.1.32-dev.0f5e9dd

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.
Files changed (82) hide show
  1. package/package.json +6 -6
  2. package/steering/design-html/skills/charts/SKILL.md +15 -4
  3. package/steering/design-html/skills/data-viz/SKILL.md +1 -1
  4. package/steering/design-html/skills/hi-fi-design/SKILL.md +1 -1
  5. package/steering/design-html/skills/interactive-prototype/SKILL.md +1 -17
  6. package/steering/design-html/skills/pptx-style-extract/SKILL.md +71 -26
  7. package/steering/design-html/skills/pptx-style-extract/font-fallback.yaml +3 -3
  8. package/steering/design-html/skills/pptx-style-extract/scripts/census.py +26 -14
  9. package/steering/design-html/skills/pptx-style-extract/scripts/check_v2.py +153 -8
  10. package/steering/design-html/skills/pptx-style-extract/scripts/draft.py +2831 -293
  11. package/steering/design-html/skills/pptx-style-extract/scripts/extract.py +544 -22
  12. package/steering/design-html/skills/pptx-style-extract/scripts/ooxml.py +19 -2
  13. package/steering/design-html/skills/pptx-style-extract/scripts/package.py +991 -165
  14. package/steering/design-html/skills/pptx-style-extract/scripts/parts.py +22 -3
  15. package/steering/design-html/skills/pptx-style-extract/scripts/query.py +4 -9
  16. package/steering/design-html/skills/pptx-style-extract/scripts/render_pages.py +20 -12
  17. package/steering/design-html/skills/pptx-style-extract/scripts/test_asset_judgment_package.py +556 -0
  18. package/steering/design-html/skills/pptx-style-extract/scripts/test_background_composite.py +364 -0
  19. package/steering/design-html/skills/pptx-style-extract/scripts/test_color_contract.py +60 -0
  20. package/steering/design-html/skills/pptx-style-extract/scripts/test_design_consumer_contract.py +76 -0
  21. package/steering/design-html/skills/pptx-style-extract/scripts/test_flow_layout_contract.py +528 -0
  22. package/steering/design-html/skills/pptx-style-extract/scripts/test_layout_css.py +1814 -0
  23. package/steering/design-html/skills/pptx-style-extract/scripts/test_logo_scope.py +600 -0
  24. package/steering/design-html/skills/pptx-style-extract/scripts/test_rounded_contract.py +112 -0
  25. package/steering/design-html/skills/pptx-style-extract/scripts/test_text_role_contract.py +315 -0
  26. package/steering/design-html/skills/pptx-style-extract/scripts/verify_layout_assets.py +421 -0
  27. package/steering/design-html/skills/pptx-style-extract/scripts/verify_logo_scope.py +12 -0
  28. package/steering/design-html/skills/pptx-style-extract/v2-format-spec.md +27 -15
  29. package/steering/design-html/skills/slide-deck/SKILL.md +5 -3
  30. package/steering/design-html/skills/social-card/SKILL.md +263 -0
  31. package/steering/nestjs-react-fullstack/skills/app-init-feasibility-guide/SKILL.md +4 -0
  32. package/steering/nestjs-react-fullstack/skills/authn-guide/SKILL.md +6 -0
  33. package/steering/nestjs-react-fullstack/skills/authz-guide/SKILL.md +5 -5
  34. package/steering/nestjs-react-fullstack/skills/authz-guide/references/dynamic-permission-guide.md +1 -1
  35. package/steering/nestjs-react-fullstack/skills/client-builtins-file-storage-service/SKILL.md +38 -113
  36. package/steering/nestjs-react-fullstack/skills/client-builtins-user-service/SKILL.md +15 -2
  37. package/steering/nestjs-react-fullstack/skills/code-fix/SKILL.md +13 -7
  38. package/steering/nestjs-react-fullstack/skills/coding-guide/SKILL.md +205 -38
  39. package/steering/nestjs-react-fullstack/skills/connections-sdk/SKILL.md +202 -0
  40. package/steering/nestjs-react-fullstack/skills/contacts-service/SKILL.md +67 -4
  41. package/steering/nestjs-react-fullstack/skills/nestjs-cache/SKILL.md +254 -0
  42. package/steering/nestjs-react-fullstack/skills/openapi-guide/SKILL.md +75 -0
  43. package/steering/nestjs-react-fullstack/skills/plugin-guide/SKILL.md +164 -543
  44. package/steering/nestjs-react-fullstack/skills/plugin-guide/references/plugin-coding-guide.md +72 -15
  45. package/steering/nestjs-react-fullstack/skills/plugin-guide/references/table.md +30 -14
  46. package/steering/nestjs-react-fullstack/skills/raw-sql-boundary-audit/SKILL.md +63 -0
  47. package/steering/nestjs-react-fullstack/skills/server-builtins-file-storage-service/SKILL.md +1 -1
  48. package/steering/nestjs-react-fullstack/skills_common/mcp-guide/SKILL.md +78 -0
  49. package/steering/nestjs-react-fullstack/skills_common/mcp-guide/assets/eslint.mcp-ui.config.cjs +19 -0
  50. package/steering/nestjs-react-fullstack/skills_common/mcp-guide/assets/ui-tsconfig.json +19 -0
  51. package/steering/nestjs-react-fullstack/skills_common/mcp-guide/references/client-onboarding.md +90 -0
  52. package/steering/nestjs-react-fullstack/skills_common/mcp-guide/references/debugging.md +81 -0
  53. package/steering/nestjs-react-fullstack/skills_common/mcp-guide/references/mcp-apps.md +197 -0
  54. package/steering/nestjs-react-fullstack/skills_common/mcp-guide/references/tool-authoring.md +248 -0
  55. package/steering/nestjs-react-fullstack/skills_common/trigger-guide/SKILL.md +284 -12
  56. package/steering/nestjs-react-fullstack/skills_common/user-identity/SKILL.md +3 -1
  57. package/steering/nestjs-react-fullstack/skills_local/client-builtins-user-service/SKILL.md +2 -0
  58. package/steering/nestjs-react-fullstack/skills_local/code-fix/SKILL.md +6 -0
  59. package/steering/nestjs-react-fullstack/skills_local/coding-guide/SKILL.md +49 -22
  60. package/steering/nestjs-react-fullstack/skills_local/plugin-guide/SKILL.md +11 -3
  61. package/steering/nestjs-react-fullstack/skills_local/plugin-guide/references/plugin-coding-guide.md +57 -14
  62. package/steering/vite-react/skills/plugin-guide/SKILL.md +4 -1
  63. package/steering/vite-react/skills/react-three-fiber/SKILL.md +5 -0
  64. package/steering/vite-react/tech.md +3 -0
  65. package/steering/design-html/skills/preflight/SKILL.md +0 -51
  66. package/steering/design-html/skills/preflight/scripts/probe.sh +0 -108
  67. package/steering/nestjs-react-fullstack/skills/client-add-aily-web-chat/SKILL.md +0 -139
  68. package/steering/nestjs-react-fullstack/skills/feishu/SKILL.md +0 -269
  69. package/steering/nestjs-react-fullstack/skills/feishu/references/approval.md +0 -214
  70. package/steering/nestjs-react-fullstack/skills/feishu/references/attendance.md +0 -163
  71. package/steering/nestjs-react-fullstack/skills/feishu/references/bitable.md +0 -311
  72. package/steering/nestjs-react-fullstack/skills/feishu/references/calendar.md +0 -190
  73. package/steering/nestjs-react-fullstack/skills/feishu/references/contacts.md +0 -160
  74. package/steering/nestjs-react-fullstack/skills/feishu/references/doc.md +0 -257
  75. package/steering/nestjs-react-fullstack/skills/feishu/references/drive.md +0 -104
  76. package/steering/nestjs-react-fullstack/skills/feishu/references/events.md +0 -199
  77. package/steering/nestjs-react-fullstack/skills/feishu/references/id-convert.md +0 -128
  78. package/steering/nestjs-react-fullstack/skills/feishu/references/messaging.md +0 -207
  79. package/steering/nestjs-react-fullstack/skills/feishu/references/oauth.md +0 -165
  80. package/steering/nestjs-react-fullstack/skills/feishu/references/perm.md +0 -91
  81. package/steering/nestjs-react-fullstack/skills/feishu/references/wiki.md +0 -165
  82. package/steering/nestjs-react-fullstack/skills_common/trigger-guide/references/trigger-lifecycle.md +0 -301
@@ -2,15 +2,19 @@
2
2
 
3
3
  ### 核心原则:根据场景选择调用侧
4
4
 
5
- **默认优先在 Client 侧调用 capabilityClient;但当插件结果需要持久化到数据库时,应考虑在 Server 侧调用或确保前端调用后及时通过后端接口保存。**
5
+ **默认优先在 Client 侧调用 capabilityClient;流式或高耗时 AI 生成优先 `callStream` 渐进展示。结果需要持久化时,优先在流式结束后通过已有后端接口保存;只有 Client 侧无法满足时才切到 Server 侧。**
6
+
7
+ **高耗时 AI capability 先判调用形态**:只要输出规模大、输出字段多、需要多份结果、多语言/长文本、文件或多模态输入后继续生成、多个 capability 串联、或结果需后续查看/落库,就不得把完整 AI 结果塞进一个同步 HTTP 请求等待。能由前端承接时,优先用前端 `callStream` 渐进展示,并在流式结束后按需复用已有 CRUD 接口保存结果;只有 Client 侧无法满足(触发器/敏感凭证/强事务/必须由后端保证落库一致性等)时,才采用后端任务记录 + 状态/结果查询,避免把后台任务作为默认方案。
8
+
6
9
  **严禁** import { capabilityClient } from '@lark-apaas/client-capability'。
7
10
  **唯一指定**的导入方式是 import { capabilityClient } from '@lark-apaas/client-toolkit';
8
11
 
9
12
  | 优先级 | 场景 | 调用方式 |
10
13
  |-------|------|---------|
11
- | **首选** | 绝大多数场景 | `capabilityClient.load(id).call()` |
12
- | **首选** | 流式输出场景 | `capabilityClient.load(id).callStream()` |
13
- | **兜底** | Client 侧无法满足时 | `CapabilityService.load(id).call()` |
14
+ | **首选** | 绝大多数即时展示场景 | `capabilityClient.load(id).call()` |
15
+ | **首选** | 流式输出,或高耗时但可由前端承接的 AI 生成 | `capabilityClient.load(id).callStream()`(流式结束后按需持久化) |
16
+ | **必要时** | 高耗时且 Client 侧无法满足:触发器、敏感凭证、强事务、必须由后端保证落库一致性 | 后端任务记录 + 后台调用 `CapabilityService.load(id).call()` + 前端短轮询状态/结果 |
17
+ | **兜底** | Client 侧无法满足且单次调用可在交互边界内完成 | `CapabilityService.load(id).call()` |
14
18
 
15
19
  #### 什么情况下应使用 Server 侧?
16
20
 
@@ -19,7 +23,7 @@
19
23
  1. **涉及敏感凭证**:调用需要服务端私密 token/secret,不适合暴露给前端
20
24
  2. **必须后端编排**:多个插件调用之间有强事务依赖,需要后端统一编排
21
25
  3. **触发器/定时任务场景**:没有前端上下文,只能由后端发起
22
- 4. **插件结果需要持久化**:调用结果需要保存到数据库,在 Server 侧可以在同一方法中完成调用+落库,保证数据一致性
26
+ 4. **插件结果需要持久化**:调用结果需要保存到数据库;能由前端承接时,优先前端 `callStream` 渐进展示并在结束后复用已有 CRUD 接口保存;只有 Client 侧无法满足或必须由后端保证落库一致性时,才在 Server 侧调用并落库。若此时输出规模大、字段多、结果多份或需多步能力串联,必须改为任务记录 + 后台执行 + 状态/结果查询,避免一个 HTTP 请求等待完整生成
23
27
 
24
28
  > **提示**:如果插件结果不需要存储、仅用于即时展示(如流式生成文本、发送消息),优先在前端调用。但当结果需要保存到数据库时,不要回避使用 Server 侧。
25
29
 
@@ -42,9 +46,13 @@
42
46
 
43
47
  ```
44
48
  插件结果是否需要持久化到数据库?
45
- ├── 否(一次性即时展示)→ Client 侧调用(默认)
46
- └── 是 → 推荐方案 A:Server 侧调用,在 Service 中调用插件并在同一方法中落库
47
- 备选方案 B:Client 侧调用插件 → 成功后通过已有 CRUD 接口保存结果
49
+ ├── 否(一次性即时展示)
50
+ │ ├── `outputMode=stream` 或内容生成较慢 → Client 侧 `callStream()` 渐进展示
51
+ │ └── 单次短输出 → Client 侧 `call()`(默认)
52
+ └── 是
53
+ ├── `outputMode=stream` 且前端可承接 → 推荐方案 A:Client 侧 `callStream()` 渐进展示,成功后通过已有 CRUD 接口保存结果
54
+ ├── 输出较小且可在交互边界内完成、且必须由后端保证一致性 → Server 侧调用并在同一方法中落库
55
+ └── Client 侧无法满足且输出规模大/多字段/多份/多语言/多步骤 → 方案 B:Server 侧创建任务记录,快速返回任务状态;后台调用插件并落库;前端短轮询状态/结果
48
56
  ```
49
57
 
50
58
  | 应避免的做法 | 推荐做法 |
@@ -68,7 +76,9 @@
68
76
  ```markdown
69
77
  [Schema 摘录卡]
70
78
  - pluginInstanceId / actionKey / outputMode
71
- - input.required / output.fields / readme.constraints
79
+ - input.required: [字段名: 类型, ...]
80
+ - output.fields: [字段名: 类型, ...](每个字段必须在代码中被消费,未消费需注释说明原因)
81
+ - readme.constraints
72
82
  - 调用侧决策: Client | Server
73
83
  ```
74
84
 
@@ -77,12 +87,18 @@
77
87
  #### call / callStream 函数签名
78
88
 
79
89
  ```typescript
80
- .call(actionKey: string, input: object) // 非流式,返回 Promise<output>
81
- .callStream(actionKey: string, input: object) // 流式,返回 AsyncIterable<chunk>
90
+ // 前端 capabilityClient(@lark-apaas/client-toolkit,实际类型来自 @lark-apaas/client-capability)
91
+ .call<T = unknown>(actionKey: string, params?: Record<string, unknown>): Promise<T> // 非流式
92
+ .callStream<T = unknown>(actionKey: string, params?: Record<string, unknown>): AsyncIterable<T> // 流式
82
93
  ```
83
94
 
84
95
  - **第一个参数 `actionKey`**:必须是字符串,值来自 `get_plugin_ai_json` 返回的 `actions[].key`(如 `'sendFeishuMessage'`、`'textGenerate'`)
85
- - **第二个参数 `input`**:必须是对象,结构符合 `actions[].inputSchema`
96
+ - **第二个参数 `params`**:类型是 `Record<string, unknown>`,结构符合 `actions[].inputSchema`
97
+ - 上面是**前端**签名,带泛型。**服务端 `CapabilityService` 的 `call` / `callStream` / `callStreamWithEvents` 都没有泛型**,两者不通用,详见「Server 侧调用方式」
98
+
99
+ > **`Record<string, unknown>` 不接 `interface` 声明的对象**(interface 没有隐式索引签名),直接传会撞
100
+ > `Argument of type 'XxxInput' is not assignable to parameter of type 'Record<string, unknown>'. Index signature for type 'string' is missing in type 'XxxInput'.`
101
+ > 三种正确写法:① 直接传内联对象字面量;② 入参类型用 `type XxxInput = { ... }` 而非 `interface`(type 别名有隐式索引签名);③ 已有 interface 时在调用点显式 `input as unknown as Record<string, unknown>`。**不要为此改用 `as any`。**
86
102
 
87
103
  ```typescript
88
104
  // ❌ 错误:把参数 JSON.stringify 后当作 actionKey
@@ -269,10 +285,11 @@ function MultiPluginStreamExample({ recordId }: { recordId: string }) {
269
285
  2. 一个 `stream` action 的真实调用结果(chunk 按 `outputSchema` 字段读取)
270
286
  3. 调用失败时的最小日志字段齐全
271
287
  4. 若无法执行真实调用,必须明确写明阻塞原因,禁止直接标记“开发完成”
288
+ 5. 若用户触发 AI 生成的请求超时、连接断开或工具返回超时,必须判定为验收失败;只有触发请求快速返回、随后能通过页面或接口读到明确完成或失败终态和结果,才允许标记通过
272
289
 
273
290
  ### Server 侧调用方式(仅兜底场景)
274
291
 
275
- > 以下场景适合使用 Server 侧调用,特别是涉及数据持久化时不要回避后端。
292
+ > 以下场景适合使用 Server 侧调用;若前端 `callStream` + 既有 CRUD 保存即可满足展示和持久化,不要优先引入后端后台任务。
276
293
 
277
294
  #### 1. 何时适合用 Server 侧?
278
295
 
@@ -283,6 +300,7 @@ function MultiPluginStreamExample({ recordId }: { recordId: string }) {
283
300
  | 敏感凭证调用 | 凭证不能暴露给前端 | 调用需要 admin token 的 API |
284
301
  | 强事务编排 | 多步骤需要原子性 | 创建记录 → 发通知 → 更新状态必须全成功或全回滚 |
285
302
  | 插件结果需持久化 | 调用结果需保存到数据库 | AI 分类/摘要结果需落库、文档解析的结构化数据需入库、图片识别结果需关联业务记录、语音转文字结果需存档等 |
303
+ | 高耗时 AI 生成需后续查看 | 请求不能长期占用用户交互链路 | 批量、多版本、多语言、长文本、多字段结构化生成,或文件/多模态分析后再生成内容 |
286
304
 
287
305
  #### 2. NestJS 注入方式
288
306
 
@@ -294,23 +312,49 @@ import { CapabilityService } from '@lark-apaas/fullstack-nestjs-core';
294
312
  export class XxxService {
295
313
  private readonly logger = new Logger(XxxService.name);
296
314
 
315
+ // 注入 token 必须显式写成 @Inject(CapabilityService)
297
316
  constructor(
298
- @Inject() private readonly capabilityService: CapabilityService,
317
+ @Inject(CapabilityService) private readonly capabilityService: CapabilityService,
299
318
  ) {}
300
319
  }
301
320
  ```
302
321
 
322
+ **禁止空参 `@Inject()`**。本 stack 的 `nest-cli.json` 用 `builder: swc`,空参 `@Inject()` 会把注入 token 置为 undefined,Nest 回退读 `design:type` 拿到 `Function`,服务端启动即崩溃、所有 `/api` 请求返回 502:
323
+
324
+ ```
325
+ UnknownDependenciesException: Nest can't resolve dependencies of the XxxService (?).
326
+ Please make sure that the argument Function at index [0] is available in the XxxModule context.
327
+ ```
328
+
329
+ 见到这段报错先查构造函数有没有空参 `@Inject()`,不要去改 Module 的 `imports` / `providers`。
330
+
331
+ **业务 Module 不需要注册 CapabilityModule**。`PlatformModule` 是 `@Global()` 且已把 `CapabilityModule` 放进 `exports`,`CapabilityService` 全局可注入。不要在业务 Module 里写 `imports: [CapabilityModule.forRoot(...)]` 或 `imports: [PlatformModule.forRoot()]`。
332
+
333
+ 同一规则适用于从 `@lark-apaas/fullstack-nestjs-core` 注入的其他平台服务(`AuthNPaasService`、`FileService` 等)。
334
+
303
335
  #### 3. 调用示例
304
336
 
337
+ 服务端 `CapabilityExecutor` 的**三个方法全都没有泛型参数**(`@lark-apaas/nestjs-capability`):
338
+
339
+ ```typescript
340
+ call(actionName: string, input: unknown, context?: Partial<PluginActionContext>): Promise<unknown>;
341
+ callStream(actionName: string, input: unknown, context?): AsyncIterable<unknown>;
342
+ callStreamWithEvents(actionName: string, input: unknown, context?): AsyncIterable<StreamEvent<unknown>>;
343
+ ```
344
+
345
+ 写 `.call<T>(...)` / `.callStream<T>(...)` / `.callStreamWithEvents<T>(...)` 都会报「应有 0 个类型参数,但获得 1 个」。泛型只存在于前端 `capabilityClient`。注意服务端 `input` 是**必传**(没有 `?`),且类型是 `unknown` 而非前端的 `Record<string, unknown>`。服务端接 `unknown` 后在运行时收窄,禁止 `as any`:
346
+
305
347
  ```typescript
306
348
  const inputParams = {
307
349
  // 严格按 get_plugin_ai_json.actions[].inputSchema 构造
308
350
  };
309
351
 
310
352
  try {
311
- const output = await this.capabilityService
353
+ const output: unknown = await this.capabilityService
312
354
  .load('')
313
355
  .call('', inputParams);
356
+ // 运行时收窄后再取字段,例如:
357
+ // if (typeof output === 'object' && output !== null && 'content' in output) { ... }
314
358
  return output;
315
359
  } catch (error) {
316
360
  this.logger.error('pluginInstance call failed', {
@@ -326,6 +370,7 @@ try {
326
370
 
327
371
  - PluginInstance 调用在 Server 侧通常属于 **外部依赖 / side-effect**
328
372
  - 除非业务明确要求强一致性,**默认不应阻塞主业务流程**
373
+ - 已选择 Server 侧承接的高耗时 AI capability 必须有可观测状态:创建任务时记录处理进度、完成终态、失败终态、输入摘要、错误信息和结果引用;触发接口只返回任务标识与当前状态,前端通过短轮询读取进度和最终结果
329
374
 
330
375
  推荐写法:异步触发 + catch 兜底:
331
376
 
@@ -339,6 +384,18 @@ this.somePluginInstanceSideEffect(input).catch(error => {
339
384
 
340
385
  ---
341
386
 
387
+ ### 调用错误分类与应对
388
+
389
+ | 错误类型 | 含义 | 应对策略 |
390
+ |----------|------|---------|
391
+ | `InputValidationError` | 入参不符合 schema | 修复参数后重试,不应出现在生产环境 |
392
+ | `RateLimitError` | 触发限流 | 指数退避重试(1s/2s/4s),最多 3 次 |
393
+ | `ExecutionError` | 插件执行失败 | 记录日志 + 降级方案(如规则计算)+ 通知用户 |
394
+ | `OutputValidationError` | 返回值不符合 schema | 记录异常返回 + 使用默认值或降级 |
395
+ | 网络超时 | 请求超时 | 重试 + 超时后降级 |
396
+
397
+ ---
398
+
342
399
  ### outputMode 与调用侧选择
343
400
 
344
401
  先通过 `get_plugin_ai_json(pluginInstanceId)` 获取 `actions[].outputMode`:
@@ -146,6 +146,20 @@
146
146
  ❌ searchRecords 拉全量 → find 找单条 → 必须用 getRecord
147
147
  ```
148
148
 
149
+ ## 数据架构选择(应用读取多维表格数据时)
150
+
151
+ 按**查询模式**选择架构:
152
+
153
+ | 查询模式 | 架构选择 | 实现方式 |
154
+ |---------|---------|---------|
155
+ | 展示/编辑单条记录 | 纯插件 | `getRecord` / `batchUpdateRecords` |
156
+ | 列表分页浏览 | 纯插件 | `searchRecords` + `pageToken` 游标分页 |
157
+ | 统计聚合(计数/求和/平均) | **纯插件 + aggregateQuery** | 禁止 searchRecords 全量拉取后内存计算 |
158
+ | 统计 + 明细下钻 | 纯插件 | 聚合用 `aggregateQuery`,下钻用 `searchRecords` + filter |
159
+ | 排行榜 / TOP N | 纯插件 | `searchRecords` + sort + pageSize=N |
160
+ | 复杂排序/多表关联/全文搜索 | 插件同步 + 本地数据库 | 定时/Webhook 同步到 postgres,复杂查询走数据库 |
161
+ | 高频写入 + 读取 | 本地数据库为主 | 多维表格仅作展示/备份 |
162
+
149
163
  ## 数据分析类Action补充说明
150
164
 
151
165
  `aggregateQuery` 用于分组聚合统计,支持 dimensions(分组维度)和 measures(聚合计算)。
@@ -301,23 +315,25 @@ const chartData =
301
315
 
302
316
  ## 推荐 UI 组件
303
317
 
304
- | BizType | 展示 | 录入 |
305
- | ---------------------------- | --------------------------------------------------------------------- | ------------------------------------------- |
306
- | `User` | `<UserDisplay users={ids.map(id => ({ user_id: id.toString() }))} />` | `<UserSelect multiple />` |
307
- | `Url` | `<Hyperlink value={val} readOnly />` | `<Hyperlink readOnly={false} />` |
308
- | `SingleSelect`/`MultiSelect` | 文本/标签 | `<Select>` 选项来自 enumValues |
309
- | `DateTime` | `new Date(ts).toLocaleDateString()` | `<Input type="date" />` |
310
- | `Checkbox` | `value ? '是' : '否'` | `<Checkbox />` |
311
- | `Currency` | `¥${value.toFixed(2)}` | `<Input type="number" />` |
312
- | `Progress` | `${value}%` 或进度条 | `<Input type="number" min={0} max={100} />` |
313
- | `Rating` | `'★'.repeat(value) + '☆'.repeat(5-value)` | `<Input type="number" min={0} max={5} />` |
318
+ | BizType | 展示 | 录入 |
319
+ | ---------------------------- | -------------------------------------------------------------------- | ------------------------------------------- |
320
+ | `User` | `<UserDisplay value={ids.map(id => id.toString())} />` | `<UserSelect multiple />` |
321
+ | `Url` | `<Hyperlink value={val} readOnly />` | `<Hyperlink readOnly={false} />` |
322
+ | `SingleSelect`/`MultiSelect` | 文本/标签 | `<Select>` 选项来自 enumValues |
323
+ | `DateTime` | `new Date(ts).toLocaleDateString()` | `<Input type="date" />` |
324
+ | `Checkbox` | `value ? '是' : '否'` | `<Checkbox />` |
325
+ | `Currency` | `¥${value.toFixed(2)}` | `<Input type="number" />` |
326
+ | `Progress` | `${value}%` 或进度条 | `<Input type="number" min={0} max={100} />` |
327
+ | `Rating` | `'★'.repeat(value) + '☆'.repeat(5-value)` | `<Input type="number" min={0} max={5} />` |
328
+
329
+ BizType 为 `User` 的字段展示必须使用 business-ui `UserDisplay`;不要直接渲染、拼接或 `join` 用户 ID 文本。
314
330
 
315
331
  ### 组件代码示例
316
332
 
317
333
  ```tsx
318
- // 人员展示
334
+ // 人员展示(business-ui UserDisplay 使用 value;不要传 users)
319
335
  <UserDisplay
320
- users={record['人员字段']?.map(userId => ({ user_id: userId.toString() })) || []}
336
+ value={record['人员字段']?.map(userId => userId.toString()) || []}
321
337
  size="small"
322
338
  showLabel={true}
323
339
  />
@@ -365,13 +381,13 @@ const chartData =
365
381
  const columns = [
366
382
  // 文本字段 - 注意读取格式是 { text: string }
367
383
  { title: '文本', dataIndex: ['record', '文本字段', 'text'], key: 'text' },
368
- // 人员字段 - 使用 UserDisplay 组件
384
+ // 人员字段 - 使用 business-ui UserDisplay 组件,传 value(用户 ID 字符串数组),不要传 users
369
385
  {
370
386
  title: '负责人',
371
387
  key: 'user',
372
388
  render: (_, record) => (
373
389
  <UserDisplay
374
- users={record.record['人员字段']?.map((id) => ({ user_id: id.toString() })) || []}
390
+ value={record.record['人员字段']?.map((id) => id.toString()) || []}
375
391
  size="small"
376
392
  />
377
393
  ),
@@ -0,0 +1,63 @@
1
+ ---
2
+ name: raw-sql-boundary-audit
3
+ description: >-
4
+ Use when editing or reviewing NestJS backend database code that contains Drizzle raw `sql` tagged templates,
5
+ db.execute(sql`...`), report/statistics/aggregation endpoints, filter (...), CASE WHEN, window functions,
6
+ or custom type / Date range parameters.
7
+ steering: true
8
+ steering-inclusion: always
9
+ steering-topic: raw_sql_boundary_audit
10
+ match-template-name: nestjs-react-fullstack
11
+ unavailable-agents:
12
+ - AppInit
13
+ - SpecDoc
14
+ ---
15
+
16
+ # Drizzle raw SQL 参数边界审计
17
+
18
+ 本 skill 只管一件事:改 NestJS 后端数据库代码时,raw `sql` 模板里的 `${...}` 参数必须是数据库 driver 可直接接收的标量。Drizzle helper 绑定 schema 列会按列 encoder 处理;raw `sql` 模板参数不会自动调用列的 `toDriver`。
19
+
20
+ ## 何时必须执行审计
21
+
22
+ 满足任一条件就执行:
23
+
24
+ - 正在修改 `server/**/*.service.ts` 或 repository 中的数据库查询;
25
+ - 文件里有 `sql\``、`db.execute(sql`、`filter (...)`、`CASE WHEN`、窗口函数或复杂 where;
26
+ - 需求涉及统计、报表、趋势、排行、周期范围、截止时间、创建/完成时间等后端汇总接口。
27
+
28
+ ## 审计步骤(写代码前和提交前各做一次)
29
+
30
+ 1. 先读 `server/database/schema.ts`,确认相关列类型和 custom type。
31
+ 2. 枚举当前 service/repository 里的 raw SQL 模板;不要只看刚新增的查询。可用搜索辅助,但结论以读代码为准:
32
+
33
+ ```bash
34
+ rg -n 'sql`|db\.execute\(sql|filter \(|CASE WHEN|OVER \(' server
35
+ ```
36
+
37
+ 3. 对每个 raw SQL 模板逐个检查 `${...}` 参数:
38
+ - 已是 string/number/boolean、ISO string、普通 ID、`sql.join(...)` 中逐个参数化的标量:可以保留;
39
+ - `user_profile` 是特殊 custom type,不适用“普通 ID 标量即可”的泛化判断;raw SQL 遇到 `user_profile` 列必须按 coding-guide 的三侧写法处理:读 `(col).user_id`,写 `ROW(${userId})::user_profile`,过滤 `(col).user_id = ${userId}`;禁止裸列 SELECT、`col = ${userId}`、`col::text = ${userId}` 或 `col = ROW(...)::user_profile` 比较;
40
+ - 是 `Date`、date/timestamptz/custom type 的运行时对象,或变量来源不明但表示时间/自定义结构:先转 driver-safe 标量再传入 raw SQL;日期时间用 `.toISOString()`;
41
+ - 如果能用 `gte(col, Date)` / `lt(col, Date)` / `eq(col, value)` / `insert().values()` / `update().set()` 等绑定 schema 列的 helper 表达,就优先改回 helper。
42
+ 4. 保留 helper 安全路径:不要把 `gte(col, Date)`、`lt(col, Date)` 这类绑定列的 Drizzle helper 当成坏边界。
43
+ 5. 改完后调用受影响的真实 API,至少覆盖一个会触发该 raw SQL 的范围/筛选参数;HTTP 200 且后端无参数序列化错误才算完成。
44
+
45
+ ## 写法示例
46
+
47
+ ```typescript
48
+ const rangeStart: Date = getRangeStart(range);
49
+ const rangeStartIso: string = rangeStart.toISOString();
50
+
51
+ // helper 绑定 schema 列,可以直接传 Date
52
+ where: gte(tasks.createdAt, rangeStart)
53
+
54
+ // raw SQL 参数先转 driver-safe 标量
55
+ total: sql<number>`
56
+ count(*) filter (where ${tasks.createdAt} >= ${rangeStartIso})
57
+ `
58
+
59
+ // 不要把 Date/custom type 运行时对象直接放进 raw SQL 模板参数
60
+ total: sql<number>`
61
+ count(*) filter (where ${tasks.createdAt} >= ${rangeStart})
62
+ `
63
+ ```
@@ -11,7 +11,7 @@ match-template-name: nestjs-react-fullstack
11
11
  在 NestJS 服务端代码中使用 `FileService` 进行文件上传、下载、删除和管理。
12
12
 
13
13
  > **使用注意**: 仅用于服务端文件处理场景,非必要文件上传下载场景请使用前端 `client-builtins-file-storage-service` skill。
14
- > CLI 调试场景请参考 `file-storage` skill。
14
+ > **入口边界**:本 SDK 是**服务端代码**读写应用存储的入口;Agent 在对话 / 开发中自己上传或调试文件,先加载应用文件存储操作 skill(按「应用文件存储 / 文件上传 / 文件下载」召回),用其 CLI 命令操作,具体命令一律以该 skill 为准。二者与本 SDK 操作同一个应用存储桶,但**该 CLI 命令只供 Agent 在沙箱终端使用,禁止写进服务端代码**。
15
15
 
16
16
  ## 使用场景
17
17
 
@@ -0,0 +1,78 @@
1
+ ---
2
+ name: mcp-guide
3
+ description: "Use when 用户要把应用能力开放给外部 Agent / 工作伙伴 / MCP 客户端调用,新增或修改 MCP 工具,给工具结果加界面(MCP Apps / ui:// 资源),维护应用 Skill(server/mcp/skills/<name>/SKILL.md,SDK 注册为 MCP Prompt),要在开发态核对、调用、调试 MCP 工具,或要把本应用的 MCP 接到自己的 Agent 客户端上使用。触发词:MCP, MCP Server, MCP 工具, MCP Apps, ui://, @McpTools, @McpTool, @McpUiResource, McpToolError, readMcpUiTemplate, tools/list, tools/call, resources/read, prompts/list, prompts/get, mcp_call, 开放给 Agent, 工作伙伴调用, Agent 调用应用, 应用 Skill, server/mcp, server/mcp/ui, server/mcp/skills, 接入 MCP, 连接 MCP, 怎么用这个 MCP, MCP 客户端, Claude Code, Codex, mcp add, mcp login, config.toml, X-Mcp-Token, MCP 凭证, 可用范围, 连不上。不负责 HTTP 对外开放接口(走 openapi-guide)、自动化触发器(走 trigger-guide)。"
4
+ steering: true
5
+ steering-topic: mcp_guide
6
+ match-template-name: nestjs-react-fullstack
7
+ ---
8
+
9
+ # MCP 开发与调试
10
+
11
+ 应用的 MCP Server 是全栈工程里多出来的一层出口:同仓、同域名、同版本。SDK(`@lark-apaas/fullstack-nestjs-core` 重导出 `@lark-apaas/nestjs-mcp`)已经内置端点 `POST /__innerapi__/mcp`、Streamable HTTP 协议、zod 校验、身份读取、`ui://` 资源与应用 Skill(MCP Prompt)下发。你只写三种文件:工具类、界面入口、应用 Skill。
12
+
13
+ **真值顺序**:工程内 `node_modules/@lark-apaas/nestjs-mcp/dist/index.d.ts` > 本文。装饰器选项、类型名、返回值形状以它为准;本文与它冲突时按包改,不按本文改。
14
+
15
+ ## 固定路径(不探测)
16
+
17
+ | 路径 | 内容 |
18
+ |---|---|
19
+ | `server/mcp/tools/<域>.tools.ts` | 工具类:一个业务域一个文件,`@McpTools()` 类 + 多个 `@McpTool()` 方法 |
20
+ | `server/mcp/ui/<entry>/index.html`(+ `main.tsx`)、`server/mcp/ui/tsconfig.json` | MCP Apps 界面源码:独立的浏览器代码,Vite 预设自动打成 `dist/mcp-ui/<entry>.html` |
21
+ | `server/mcp/skills/<name>/SKILL.md` | 应用 Skill:一个业务流程一份,给消费方 Agent 看的用法说明,SDK 注册为同名 MCP Prompt |
22
+
23
+ `server/mcp/` 不存在即为首次开通;开通只需建上述文件并把工具类注册为所属业务模块的 provider。`McpModule` 已由 `PlatformModule.forRoot()` 引入,SDK 启动时扫已加载模块的 provider 自动发现工具类——挂在已有业务模块上就不用动 `app.module.ts`,新建的模块仍要接进去,否则整个模块不会实例化、工具静默缺席;不写注册表,不写 `tools/index.ts`。
24
+
25
+ 两份同名的 SKILL.md 别混:本文教你写代码,`server/mcp/skills/*/SKILL.md` 是产物、教下游 Agent 用工具。不把本文复制成应用 Skill,应用 Skill 不加 `steering: true`。
26
+
27
+ ## 前置检查(一条命令)
28
+
29
+ ```bash
30
+ ls -d node_modules/@lark-apaas/nestjs-mcp node_modules/@lark-apaas/coding-preset-vite-react/lib/plugins/mcp-ui.js
31
+ ```
32
+
33
+ - 缺 `@lark-apaas/nestjs-mcp` → 当前 `@lark-apaas/fullstack-nestjs-core` 不含 MCP 层(`1.1.62` 起含)。告知用户升级 core,停止;不装 `@modelcontextprotocol/sdk`、不手写端点。
34
+ - 缺 `mcp-ui.js` → 只能做工具、不能做界面(`@lark-apaas/coding-preset-vite-react` `1.0.23` 起含)。要做界面先让用户升级该预设。Rspack 预设不支持 MCP Apps。
35
+
36
+ ## 任务路由
37
+
38
+ `$SKILL_DIR` 取技能清单里本 skill 的 location 所在目录。
39
+
40
+ | 任务 | 读 |
41
+ |---|---|
42
+ | 新增 / 修改工具、开通 MCP、维护应用 Skill | `$SKILL_DIR/references/tool-authoring.md` |
43
+ | 给工具结果加界面、改界面、界面与工具联动 | `$SKILL_DIR/references/mcp-apps.md` |
44
+ | 用 `mcp_call` 核对与调用、按错误定位、本地模式调试 | `$SKILL_DIR/references/debugging.md` |
45
+ | 用户要在自己的 Agent(Claude Code / Codex / 其它客户端)里连本应用的 MCP | `$SKILL_DIR/references/client-onboarding.md` |
46
+
47
+ 前三类是改代码,改完等 dev server 重启(定义有问题会在启动日志里报),再按 `$SKILL_DIR/references/debugging.md` 的回路用 `mcp_call` 验证。`mcp_call` 报「当前运行环境不支持」是本地 CLI 宿主,走该文件的本地模式。向用户汇报用业务表述(「找到 4 个演示航班」),不贴 JSON。
48
+
49
+ ## 不变量
50
+
51
+ 1. 一个工具对应一件用户说得出口的事(找订单、取消订单),不按库表铺增删改查;读和写分在不同工具里。
52
+ 2. schema 用 zod,提成常量放类上方;方法签名固定 `(input: Infer<typeof In>, ctx: McpContext)`,返回 `McpToolResult<typeof Out>`。签名写错在 `npm run type:check:server` 暴露。
53
+ 3. `description` 面向模型:用途、何时调用、参数来源、返回什么、失败怎样。每个 zod 字段 `.describe()`。消费方 Agent 只靠这些选工具、填参数。
54
+ 4. 四项 `annotations` 显式写出。只读工具必须 `readOnlyHint: true`:缺省会被当成可能写数据,调试时每次都要 `safe: true`,消费方也会额外确认。
55
+ 5. 声明了 `outputSchema` 就返回匹配的 `structuredContent`,对不上时 `mcp_call` 报「输出与其声明的 outputSchema 不符」;列表工具入参带 `limit`(默认不超过 20)与 `cursor`;写操作的幂等由 Service 保证,不引入幂等键字段。
56
+ 6. 返回值按消费方够用来裁:`structuredContent` 只放它推理或界面渲染要用的字段。SDK 不截断,返回多少对方就吃多少,详见 `$SKILL_DIR/references/tool-authoring.md` 的「返回体积」。
57
+ 7. 业务失败抛 `McpToolError(message, { code })`,Agent 原样收到;`message` 连下一步一起写(「订单不存在,用 order_search 按客户名重查一个有效 ID」),只陈述现象会让对方原样重试或直接放弃。其他异常只落日志、返回通用提示,Agent 看不到原因。
58
+ 8. 身份只从 `ctx.user` 读;权限与行级过滤留在 Service。工具类作为业务模块 provider 注册,单例——请求态(当前用户、本次入参、游标)只放方法内的局部变量,写进类字段会在并发请求间串号。
59
+ 9. 工具名 = `prefix + 方法名`,匹配 `^[A-Za-z0-9_.-]{1,128}$`,全应用唯一;用 `域_动作`。
60
+ 10. 界面源码只放 `server/mcp/ui/<entry>/`,TS 与 ESLint 配置从 `$SKILL_DIR/assets/` 拷、不手写(见 `$SKILL_DIR/references/mcp-apps.md`)。用 `@McpUiResource` + `readMcpUiTemplate('<entry>')` 下发,工具用 `ui: { resourceUri }` 关联。
61
+ 11. 新增、改名、删除工具后同步改对应的 `server/mcp/skills/<name>/SKILL.md`;frontmatter 的 `name` 必须等于目录名。
62
+
63
+ ## 禁止
64
+
65
+ - 从入参接收用户 ID、token、连接串——身份来自网关,入参里的身份可伪造。
66
+ - 自己拼一个 `x-larkgw-suda-webuser` 头来让调用通过——SDK 会把它当真实用户,写工具会以这个身份落库、发通知。
67
+ - 把 `requireUser` 设为 `false` 绕开身份检查——SDK 只是跳过检查,方法照样执行,`ctx.user.userId` 是 undefined,跟着 `ctx.user.userId!` 一路进 Service 的权限过滤。
68
+ - 服务端 import `server/mcp/ui/` 里的源码,或界面 import `client/`、`server/` 里的业务代码、工具类、Node 模块——子构建的边界检查直接报错,服务端只经 `readMcpUiTemplate` 读构建产物。
69
+ - 界面里存凭证、直连业务系统——沙箱 iframe 是独立 origin,用户登录态带不进来;数据走宿主转发的工具调用。
70
+ - `McpToolError.data` 与 `structuredContent` 里放密钥、内部连接信息——两者原样进 Agent 上下文。
71
+ - 自己实现 MCP 协议、JSON-RPC、`/mcp` 路由、凭证校验——SDK 与网关已做,重复实现会与网关映射冲突。
72
+
73
+ ## 交付前核对
74
+
75
+ 1. `list_tools` 与源码里的工具、资源一一对应。
76
+ 2. 每个工具有面向模型的 `description`、四项 `annotations`;写工具有 `outputSchema`。
77
+ 3. 每个 `ui.resourceUri` 能 `read_resource` 读到,界面拿到 `structuredContent` 后能渲染;`npx tsc --noEmit -p server/mcp/ui/tsconfig.json` 与 `npx eslint -c eslint.mcp-ui.config.cjs server/mcp/ui` 通过。
78
+ 4. `server/mcp/skills/` 下每个业务流程一份 SKILL.md,合起来覆盖全部工具:何时调用、如何选工具、调用顺序、约束、失败处理、界面 URI;目录接口的 `prompts[]` 与目录一一对应。
@@ -0,0 +1,19 @@
1
+ const tseslint = require('typescript-eslint');
2
+ const globals = require('globals');
3
+ const hooks = require('eslint-plugin-react-hooks');
4
+ const { builtinModules } = require('node:module');
5
+
6
+ module.exports = tseslint.config({
7
+ files: ['server/mcp/ui/**/*.{ts,tsx,js,jsx,mts,cts,mjs,cjs}'],
8
+ extends: [...tseslint.configs.recommended],
9
+ languageOptions: { globals: globals.browser },
10
+ plugins: { 'react-hooks': hooks },
11
+ rules: {
12
+ ...hooks.configs.recommended.rules,
13
+ 'no-restricted-globals': ['error', 'process', 'Buffer', '__dirname', '__filename'],
14
+ 'no-restricted-imports': ['error', {
15
+ paths: [...new Set(builtinModules.flatMap(name => [name, `node:${name.replace(/^node:/, '')}`]))],
16
+ patterns: ['@client/*', '@server/*', '@/*', '**/client/**'],
17
+ }],
18
+ },
19
+ });
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
5
+ "module": "ESNext",
6
+ "moduleResolution": "Bundler",
7
+ "jsx": "react-jsx",
8
+ "strict": true,
9
+ "allowJs": true,
10
+ "checkJs": true,
11
+ "types": ["react", "react-dom"],
12
+ "skipLibCheck": true,
13
+ "noEmit": true,
14
+ "esModuleInterop": true,
15
+ "baseUrl": "../../..",
16
+ "paths": { "@shared/*": ["shared/*"] }
17
+ },
18
+ "include": ["./**/*.ts", "./**/*.tsx", "./**/*.js", "./**/*.jsx", "./**/*.mts", "./**/*.cts", "./**/*.mjs", "./**/*.cjs"]
19
+ }
@@ -0,0 +1,90 @@
1
+ # 接入:把本应用的 MCP 连到外部客户端
2
+
3
+ 适用于「应用已经有 MCP 工具,用户要在自己的 Agent(Claude Code / Codex / 其它 MCP 客户端)里调用它」。开发态自己验工具走 `debugging.md` 的 `mcp_call`,与本文无关。
4
+
5
+ **产出是一条用户复制即可执行的命令,不是操作说明。**连接地址和凭证都由你查出来填好,不要让用户自己去界面上找。
6
+
7
+ ## 取连接配置
8
+
9
+ ```bash
10
+ lark-cli apps +mcp-get --app-id <app_id> --include-secret
11
+ ```
12
+
13
+ `data.config` 里是完整的 `mcpServers` 对象,含连接地址与 `X-Mcp-Token`。不加 `--include-secret` 拿到的是脱敏展示(`data.redacted=true`),不能用于连接。
14
+
15
+ 命令语义、风险等级与权限要求以 `lark-apps-ops` 的 `references/lark-apps-mcp.md` 与各命令 `--help` 为准,本文不复述。两条只在这里强调:
16
+
17
+ - **该命令会创建缺失的运行态凭证,风险是 `write`。**不要仅为「看看这应用开没开 MCP」而调用它。重复调用返回已有凭证,不轮换、不重置。
18
+ - 拿不到命令(沙箱 CLI 版本不带该子命令)时如实告知用户,**不要自己拼连接地址兜底**——地址形如 `https://<租户域名>.<环境后缀>/mcp/app`,两段都不可推测。
19
+
20
+ ## 服务名你来起
21
+
22
+ `<名字>` 会成为客户端里的工具前缀(`mcp__<名字>__<工具名>`),用户此后每次看到这些工具都要读它。**直接给一个贴合本 MCP 用途的名字,不要问用户要。**
23
+
24
+ - 取材于这个 MCP 做什么:差旅审批叫 `travel-approval`,库存查询叫 `inventory`
25
+ - 小写字母 + 数字 + `-` 或 `_`,两三个词以内;不要空格、点号、中文
26
+ - 同一台机器内唯一。用户可能已经接过别的妙搭应用,泛指类的名字会撞上
27
+ - server 声明的 `serverName` 一眼能看出这个 MCP 做什么时,直接用它,保持开发态与客户端一致
28
+ - 给完顺带说一句名字可改,但不要停下来等用户确认
29
+
30
+ ## 落到客户端
31
+
32
+ 从 `data.config` 的 `mcpServers` 条目里取出连接地址与请求头,连同上面定好的名字填进下面对应模板,整条给用户。
33
+
34
+ **Claude Code**
35
+
36
+ ```bash
37
+ claude mcp add --transport http <名字> <连接地址> --header "X-Mcp-Token: <凭证>" --header "x-tt-env: boe_miaoda_mcp"
38
+ claude mcp login <名字>
39
+ ```
40
+
41
+ **Codex** — 编辑 `~/.codex/config.toml`
42
+
43
+ ```toml
44
+ [mcp_servers.<名字>]
45
+ url = "<连接地址>"
46
+ http_headers = { "X-Mcp-Token" = "<凭证>", "x-tt-env" = "boe_miaoda_mcp" }
47
+ ```
48
+
49
+ ```bash
50
+ codex mcp login <名字>
51
+ ```
52
+
53
+ Codex 侧同名 table 在 TOML 里出现两次是解析错误,已有同名配置要整块替换而不是追加;不要填 `bearer_token_env_var`,它会走静态 token 分支把 OAuth 跳过去。
54
+
55
+ 两份模板里的 `x-tt-env` 照原样带上,不要改它的值,也不要就它询问用户。
56
+
57
+ ## 为什么是两条命令
58
+
59
+ `X-Mcp-Token` 认「连的是哪个应用」,第二条命令办的是「谁在调用」——浏览器里完成一次飞书授权,客户端拿到用户令牌并自行续期。两者缺一都连不上,也互相替代不了。应用凭证不是用户身份,查出凭证不等于用户已获授权。
60
+
61
+ `login` 只服务 HTTP 传输。用户若报「only supported for streamable HTTP servers」,是配置成了本地 stdio 形态,按上面的模板改成 `url` 形式。
62
+
63
+ ## 两件命令解决不了的事
64
+
65
+ 凭证能提前建,但要真正调通还需要:
66
+
67
+ | 条件 | 不满足时的表现 |
68
+ |---|---|
69
+ | 应用已发布且线上 MCP 已启用 | 连接失败;创建凭证不要求发布,所以拿到凭证不代表能连 |
70
+ | 调用人在应用可用范围内 | 403,与凭证是否有效无关 |
71
+
72
+ 这两条你查不到也改不了,连不上时按下表对号,属于这两类就直接告诉用户去处理,不要反复重试。
73
+
74
+ ## 排错
75
+
76
+ | 现象 | 原因 | 动作 |
77
+ |---|---|---|
78
+ | 401,reason `lark_oauth_client_mismatch` | 授权用的 client_id 与服务端元数据不一致 | 清掉客户端授权缓存重新 `login` |
79
+ | 401,reason `lark_oauth_conf_unavailable` | 平台侧配置问题 | 告知用户找平台同学,不要让他反复重试 |
80
+ | 401,无 reason | 凭证复制不全,或配的是预览态凭证 | 重新 `+mcp-get --include-secret` 取一次,整段替换 |
81
+ | 403 `user is not in the visible scope of this app` | 不在可用范围,或授权账号不属于应用租户 | 前者找负责人放开范围;后者换应用租户内的账号授权 |
82
+ | 404 | 应用没启用 MCP;或零工具零资源无应用 Skill(SDK 此时也返回 404) | 后者是正常状态不是故障,先确认本应用确实注册了工具 |
83
+ | 连接超时 / 长时间挂起 | 多为泳道头指向了不存在或未部署的泳道 | 去掉 `x-tt-env` 再试一次可快速定位 |
84
+ | POST 被拒,CSRF 相关 | 旧模板应用对 POST 有 CSRF 双提交校验 | 平台侧已知未收口项,告知用户,不在客户端侧拼 cookie 绕 |
85
+
86
+ ## 凭证纪律
87
+
88
+ - `--include-secret` 的输出只用于拼那条给用户的命令。**不写进项目任何文件、不进日志、不复述进汇报与摘要、不提交仓库。**
89
+ - 保持客户端默认的 local scope;不要建议 `--scope project`——那会把凭证写进随代码提交的配置文件。
90
+ - 用户自己贴出过凭证(聊天、issue、文档)时提醒他重置,旧值立即失效。