---
name: meegle-plugin-backend
version: 1.0.0
description: |
  Meegle 插件「自建后端」开发知识参考——接收并验签 webhook 回调、调 Meegle OpenAPI、把结果写回工作项 / AI 节点 / AI 字段、给前端组件做 `/api/proxy/*` 代理、用 `lpm perm` 开通 OpenAPI scope。
  这是**知识参考**（告诉你每类后端知识从哪取、哪些是带源可验的事实），**不是开发流程**——什么时候做什么由调起本 skill 的上层编排流程决定，本 skill 不规定顺序、不编排步骤。
  触发：用户问 / 做 Meegle 插件的服务端那一半（webhook 怎么收 / 怎么验签、OpenAPI 用哪些 / 怎么调、权限怎么开、写回怎么做、后端代理怎么写），或被上层编排流程在命中后端时调起取知识。
metadata:
  requires:
    bins: ["npx"]
  cliHelp: "lpm --help"
---

# Meegle 插件后端开发知识（参考）

「接飞书项目后端」的知识索引——**告诉你每类知识从哪取**。本 skill 不编排流程、不规定顺序、不带步骤：什么时候做什么由调起本 skill 的上层编排流程决定。本 skill **自足**，不依赖任何外部 skill 的 reference。

## 两条贯穿原则

- **平台事实带源、查不到即停**：碰平台的每一处（webhook 验签、token 获取、OpenAPI 出入参、scope、写回）都要能指回下面列出的源；**MCP / schema 查不到就停下问用户，绝不编造**（凭经验拼的 OpenAPI 签名能过编译但运行时跑废——这是 ai_node 评测里弱 AI 编造签名的教训）。这里的源是**真实可访问的外部出处**——写后端代码时**在 OpenAPI 调用邻近用 `// source: <MCP 查到的接口 doc>` 标出**（接口签名/出入参的依据来自 MCP，不是 `lpm perm list`——后者只给「能不能调」的 scope 目录），签名最易编造的手写 HTTP 尤其要标，方便联调和后续核对。
- **代码落地交接、不保证**：上面那些平台事实可验证；把它们拼成的后端代码跑在沙盒外、`lpm` 不碰它、没有 `lpm build` / `tsc` 反馈环——代码跑不跑得对，由你 / 你的 agent 在自己环境联调验证。

## 知识从哪取

- **后端代码在哪 / 边界**：后端代码跑在你自己的仓 / 服务里（**不在插件工程目录**）。本 skill 只指导「碰平台那几块」（webhook 接收+验签 / 调 OpenAPI / 写回 / `/api/proxy/*` 代理）怎么写，**不 scaffold 整个后端**——框架 / 语言 / 目录结构 / 业务逻辑 / 部署是你自己的技术方案。
- **webhook 验签 + 事件协议**：MCP 关键词 `兼容性说明与验签说明` / `webhook 签名校验`（验签细节在这两篇；`飞项插件 自建后端 鉴权` 召回的是 MCP Server 配置、不含验签算法）。验签是**裸 `sha256(plugin_id + request_time + token)`** 顺序拼接（`token` = 点位上配的回调 token），且 `signature` / `request_time` 是**请求体字段、不在 HTTP header**——**不是 HMAC、`pluginSecret` 不参与验签**（这条最易凭直觉编成 `HMAC(body, key=pluginSecret)` + 读 header，务必照源核）。
  - **AI 点位的事件协议**：`ai_node` / `ai_field` 的事件结构在 schema description 里：`lpm --cwd "$PLUGIN_DIR" schema` → `lpm --cwd "$PLUGIN_DIR" ai peek <schema 路径> AINodePoint`（或 `AIFieldPoint`）。
  - **webhook url/token 配在点位哪个字段**（按点位类型）：`ai_node` / `ai_field` 在某个 `listen_event` extension 里、不在点位顶层；`intercept` / `listen_event` 在点位顶层 `url`（+ `token`）；`control`（嵌新建页等场景）在 `control[].url`。
  - **已知现象**：`ai_node` / `ai_field` 的 url/token 走 `local-config set → update → get --remote` 往返后，`lpm local-config diff` 对这些点位**恒报 `[MODIFIED]`**（读端点 `GetPointInfoByPluginKey` 没把 `listen_event` extension 里的 url/token 回填）——**不阻断、不是改错了**（`listen_event` extension 里有值、`publish` 出来的插件正常），照常 `update` 推上去即可。
- **这个 app 能调哪些 OpenAPI（+ scope + doc URL）**：把要调的接口丢 `lpm perm check --apis <a,b,c>` 拿判决（CLI 已替你把「接口 → 归属 scope → 在不在已开通」反查好）——`satisfied` 可直接调；`needApply` 是 `normal` 工程缺、由插件会话用 `lpm perm apply` 申请后才可调；`ai_node` / `ai_field` 权限创建时全开、恒 `satisfied`，没有申请这一步（`perm apply` 在 AI 应用上不可用），`lpm perm list` 看 `granted` 即可；`unknown` 是此 app 目录里没这接口。要总览能力天花板用 `lpm perm list`（`granted` 已开通可调 / `applicable` 还能申请，各含覆盖的 OpenAPI；`--raw` 出原始 `perm_meta[].resource[]`）。坐在自己后端仓里、读不到 `plugin.config.json` 时，给 `perm check` / `perm list` 加 `--plugin <pluginId> --site-domain <开发者站点 origin>` 可 token-only 在任意目录查（首次带 flag 后该插件记进 `~/.lpm/` 全局注册表，之后可省略 flag）。
- **OpenAPI 出入参签名 + 鉴权 / token 获取流程**：出入参用 MCP 关键词 `<perm list/check 给的接口中文名> OpenAPI`（如 `获取工作项详情 OpenAPI` / `完成 AI 节点 OpenAPI`）；鉴权 + token 获取用 `Open API 概述`（鉴权 Header 表在此篇）/ `获取访问凭证`。**鉴权 header 是 `X-PLUGIN-TOKEN`（用插件身份须同时配 `X-USER-KEY`），不是 `Authorization: Bearer`**；这个 token 是**单独取的 `plugin_access_token`，与 webhook 回调 token 是两套独立凭证**——别拿 webhook 事件里的 token 当 OpenAPI 鉴权（最常见的双重编造）。命中 JSSDK / 总览而非接口规格时，换属性词重试（`OpenAPI` ↔ `接口` ↔ `权限`）；别用英文 API 名（命中前端 JSSDK 错文档）。
- **用 SDK 还是手搓 HTTP**：Go / Java **优先用官方服务端 SDK**（`larksuite/project-oapi-sdk-golang` / `project-oapi-sdk-java`，封装 OpenAPI 出入参 + 自动管 token 与鉴权 header，用法走 MCP 关键词 `飞项 服务端 SDK` / `project-oapi-sdk 使用`）——用 SDK 则上面 `X-PLUGIN-TOKEN` / token 获取由 SDK 代办、免手填出错；**别在有 SDK 的语言里手搓 HTTP**。TS / Node **无**官方 SDK → 只能逐接口查文档手写 HTTP，签名最易编造，尤其严守"无源即停"。
- **写回**：`ai_node` 调「完成 AI 节点」（用 webhook 解出的 `work_item_id` + `node_uuid` 定位）；`ai_field` 用 webhook 的 `field_ai_entity.task_id` 调「更新 AI 字段」回传 `field_value`；`work-item` 调「更新字段」。接口出入参 MCP 查。
- **前端要平台数据的 `/api/proxy/*` 代理**：动线 = 前端 `fetch('/api/proxy/X')` → 后端持凭据调 OpenAPI → 返回前端期望形态；token 拼装走 MCP（关键词同上「鉴权」），不把 token 放进前端 bundle。
- **凭据**：放后端环境变量，代码只写 `process.env.<变量名>` 引用；真值由用户设进 env，AI 不读、不回显，**不从 `plugin.config.json` grep / decrypt `pluginSecret` 粘进代码**（那是把凭据泄漏进产物）。
- **后端运行时不依赖** `lark-project` / `lpm`：它们是开发期本地工具、无运行时鉴权与 SLA；运行时拿数据只走上面的 OpenAPI 三段动线。
- **交接包格式**（把上面带源事实落成一份 md，交给当前会话写代码 / 留给后续接手的人或 agent）：见 [`backend-handoff.md`](references/backend-handoff.md)。
