@lark-apaas/nestjs-mcp 0.1.1-beta.0 → 0.2.0-alpha.0

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
@@ -1,9 +1,9 @@
1
1
  # @lark-apaas/nestjs-mcp
2
2
 
3
- 为妙搭全栈应用(NestJS)提供 MCP Server 能力:用装饰器把业务方法开放为 MCP 工具,SDK 负责协议、装配、入参校验、身份读取与清单导出,并将应用 Skills 暴露为标准 MCP Prompts,将交互界面暴露为 MCP Apps 资源。
3
+ 为妙搭全栈应用(NestJS)提供 MCP Server 能力:用装饰器把业务方法开放为 MCP 工具,SDK 负责协议、装配、入参校验、身份读取与清单导出,并通过标准 Skills 扩展发现应用工作流,将交互界面暴露为 MCP Apps 资源。
4
4
 
5
- - 端点:`POST /__innerapi__/mcp`(Streamable HTTP 无状态模式,不含 SSE;协议版本由 `@modelcontextprotocol/sdk` 协商,当前 2025-11-25)。应用设置 `CLIENT_BASE_PATH` 时完整路径为 `${CLIENT_BASE_PATH}/__innerapi__/mcp`
6
- - 底层:`@modelcontextprotocol/sdk` + `@modelcontextprotocol/ext-apps`(MCP Apps,`ui://` 单文件 HTML 资源)
5
+ - 端点:`POST /__innerapi__/mcp`(Streamable HTTP 无状态模式,不含 SSE;支持 2026-07-28 现代协议,兼容 2025-11-25 及既有旧版客户端;运行环境 Node.js 20+)。应用设置 `CLIENT_BASE_PATH` 时完整路径为 `${CLIENT_BASE_PATH}/__innerapi__/mcp`
6
+ - 底层:现代协议使用 `@modelcontextprotocol/server` / `@modelcontextprotocol/node` v2,旧协议保留 `@modelcontextprotocol/sdk` v1;`@modelcontextprotocol/ext-apps`(MCP Apps,`ui://` 单文件 HTML 资源)
7
7
  - 身份:网关注入 `x-larkgw-suda-webuser`,`UserContextMiddleware` 解析到 `req.userContext`,工具通过 `ctx.user` 读取
8
8
  - 装配:已被 `PlatformModule.forRoot()` 自动引入,业务工程 import `@lark-apaas/fullstack-nestjs-core` 即获得
9
9
 
@@ -12,7 +12,7 @@
12
12
  | 应用能力 | 定义方式 | 客户端发现与使用 |
13
13
  |---|---|---|
14
14
  | 工具(执行操作、查询数据) | `@McpTools()` + `@McpTool()` | `tools/list` → `tools/call` |
15
- | Skill(业务流程说明) | `server/mcp/skills/<name>/SKILL.md` | `prompts/list` → `prompts/get` |
15
+ | Skill(业务流程说明) | `server/mcp/skills/<name>/SKILL.md` | `skills/list` / `skills/get` → `resources/read` |
16
16
  | Apps UI(交互界面,可选) | `@McpUiResource()` + 独立 HTML 入口 | 工具的 `_meta.ui.resourceUri` → `resources/read` |
17
17
 
18
18
  ```text
@@ -95,22 +95,21 @@ description: 查询订单当前状态并向用户说明处理进度
95
95
 
96
96
  ### 4. 发现与调用
97
97
 
98
- 在已连接的标准 MCP 客户端中:
98
+ 支持 Skills 扩展的客户端先通过 `server/discover` 确认 `capabilities.extensions["io.modelcontextprotocol/skills"]`,再调用:
99
99
 
100
- ```typescript
101
- const { prompts } = await client.listPrompts();
102
- // [{ name: 'order-status', description: '查询订单当前状态并向用户说明处理进度' }, ...]
103
- const prompt = await client.getPrompt({ name: 'order-status' });
104
- // prompt.messages[0].content.text 为去掉 YAML 前言的 Markdown 正文
105
- const result = await client.callTool({
106
- name: 'order_get',
107
- arguments: { orderId: 'order_001' },
108
- });
109
- ```
100
+ | 方法 | 参数 | 返回 |
101
+ |---|---|---|
102
+ | `skills/list` | `{}` | `skills[]`,每项含 `uri`、完整 `frontmatter`、文件清单 `resources[]` |
103
+ | `skills/get` | `{ uri: "skill://order-status/SKILL.md" }` | 同一结构的 `skill`;无需先列举 |
104
+ | `resources/read` | `{ uri: "skill://order-status/SKILL.md" }` | 含 YAML 前言的完整原文;附件也通过对应 URI 读取 |
105
+
106
+ 文件清单为每个文件提供 `uri`、`digest: "sha256:<hex>"`、原始字节数 `size`。客户端按需读取后核验摘要和 frontmatter,用户同意加载后再使用说明;读取不会执行工具或附件脚本。随后通过 `tools/call` 执行业务。
107
+
108
+ 以上仍是同一个 HTTP 端点的 JSON-RPC 方法,不增加 `/mcp/skills/get` 子路由。2026-07-28 请求的 `_meta` 和 HTTP 协议头应由支持该版本的 MCP 客户端生成;官方 TypeScript v2 客户端需启用 `versionNegotiation: { mode: 'auto' }`。SDK 尚无专用 Skills 方法时,使用其通用 `client.request()` 并提供结果 schema。`skills/list/get` 的线协议结果带 `resultType: "complete"`、`ttlMs: 0`、`cacheScope: "private"`;客户端 SDK 可能在解码时移除 `resultType`。
110
109
 
111
- `prompts/get` 只返回使用说明,不会自动执行工具;后续由客户端或 Agent 按说明调用 `tools/call`。当前 Prompt 不声明参数,返回 `description` 和 `messages: [{ role: "user", content: { type: "text", text: "Markdown 正文" } }]`。Skill 不注册为 Resource,不使用 `skill://` URI,也没有自定义 `skills/list` 方法。
110
+ 旧版客户端仍可通过 `initialize`、`prompts/list`、`prompts/get` 使用既有能力。Prompts 映射进入弃用过渡,新接入使用 Skills 扩展;旧 Prompt 仍返回去掉 YAML 前言的 Markdown 消息。平台 manifest v3 保持原协议,`prompts` / `sources.prompts` 字段保留,不把标准 Skills 响应结构混入面板接口。
112
111
 
113
- 以上方法均通过同一个 HTTP 端点发送 JSON-RPC 请求,例如 `{"jsonrpc":"2.0","id":2,"method":"prompts/get","params":{"name":"order-status"}}`,没有 `/mcp/prompts/get` 等子路由。先完成 MCP 握手;业务调用的身份要求见下表。
112
+ 规范来源:[Skills 扩展](https://modelcontextprotocol.io/extensions/skills/overview)、[正式规范](https://github.com/modelcontextprotocol/ext-skills/blob/main/specification/stable/skills.mdx)。本实现不声明可选的 `directoryRead`。
114
113
 
115
114
  ## 约定
116
115
 
@@ -135,9 +134,9 @@ const result = await client.callTool({
135
134
  | 数量与大小 | 最多 100 份,单文件最多 1 MiB,含前言的正文总量最多 8 MiB |
136
135
  | 错误处理 | 新目录必须有合法完整的 YAML 前言;重复键、别名引用等报错;不会静默返回部分列表 |
137
136
 
138
- 实际 Prompt 名称取 frontmatter 的 `name`,源码定位保留真实文件路径;仅修改 `name` 无需同步重命名目录或重启服务,下次请求即生效。
137
+ 标准 Skill URI 中的目录名和旧 Prompt 名称都取 frontmatter 的 `name`,源码定位保留真实文件路径;仅修改 `name` 无需同步重命名目录或重启服务,下次请求即生效。
139
138
 
140
- 只发现 `skills/` 下一级目录内的 `SKILL.md`,不加载附件;没有该文件的普通目录会被忽略。对外使用 `skills` 数组,每份 Skill 对应一个标准 Prompt。
139
+ 发现 `skills/` 下一级目录内的 `SKILL.md`,并将该目录下所有普通文件作为附件纳入清单;没有 SKILL.md 的目录不发布。每份 Skill 最多 512 个文件、总计 16 MiB;符号链接和非普通文件拒绝发布。不要将凭证或不希望公开的文件放入 Skill 目录。嵌套 SKILL.md 作为附件,不自动注册为独立 Skill。非 UTF-8 或二进制附件通过 base64 `blob` 无损返回。
141
140
 
142
141
  每次 MCP 请求或平台目录查询都重新读取 Skill 文件,单次请求使用同一份快照;增改删在下一请求生效。工具定义来自本次应用启动的注册表,修改工具代码需要开发服务重载;生产修改需重新构建并发布。当前无主动变更通知,能力声明为 `listChanged: false`,客户端需主动重新获取列表或正文。
143
142
 
@@ -183,16 +182,16 @@ PlatformModule.forRoot({
183
182
 
184
183
  | 请求 | 响应 / 默认身份要求 |
185
184
  |---|---|
186
- | `POST /__innerapi__/mcp`:`initialize`、`tools/list`、`resources/list`、`prompts/list` | HTTP 200,标准 JSON-RPC 响应;SDK 不要求用户身份,列表需按已声明能力调用 |
185
+ | `POST /__innerapi__/mcp`:`server/discover`、`skills/list`、`skills/get`、旧版 `initialize`、`tools/list`、`resources/list`、`prompts/list` | HTTP 200,标准 JSON-RPC 响应;SDK 不要求用户身份,列表需按已声明能力调用 |
187
186
  | 同端点 `tools/call` | 要求 `ctx.user.userId`;缺失返回 `isError: true`,错误码 `MCP_USER_REQUIRED` 在结果元数据中 |
188
- | 同端点 `resources/read`、`prompts/get` | 要求用户身份;缺失返回 JSON-RPC error |
187
+ | 同端点 `resources/read`、旧版 `prompts/get` | 要求用户身份;缺失返回 JSON-RPC error |
189
188
  | 同端点 `notifications/initialized` | HTTP 202,空响应体 |
190
189
  | `GET` / `DELETE` MCP 端点 | HTTP 405,`Allow: POST`;不提供 SSE 流 |
191
190
  | POST 时工具、UI 资源和 Skill 均为空 | HTTP 404 |
192
191
  | POST 的 `Accept` 未同时包含 `application/json` 和 `text/event-stream` | HTTP 406;标准客户端会设置这两个值,即使服务端使用 JSON 响应 |
193
192
  | `GET /__innerapi__/mcp/manifest` | 平台目录接口,见下文;不需要 MCP 握手 |
194
193
 
195
- `tools/list`、`resources/list` 仅在注册对应能力时可用,否则返回 JSON-RPC `-32601 Method not found`。客户端应根据 `initialize` 的 `capabilities` 调用;`prompts/list` 在服务可用时支持返回空数组。
194
+ `tools/list`、`resources/list` 仅在注册对应能力时可用,否则返回 JSON-RPC `-32601 Method not found`。客户端应根据 `server/discover` 或旧版 `initialize` 的 `capabilities` 调用;`prompts/list` 在服务可用时支持返回空数组。
196
195
 
197
196
  平台负责凭证鉴权和可信身份注入;SDK 检查用户身份是否存在,并交给业务 Service 执行权限校验。工具类使用单例 provider,不承诺工具方法上的 Nest Guard / Pipe / Interceptor 自动执行,也不能依赖 HTTP Controller 上的权限装饰器。`requireUser: false` 可关闭 SDK 的身份存在性检查,不会关闭网关鉴权。
198
197
 
@@ -210,10 +209,10 @@ PlatformModule.forRoot({
210
209
 
211
210
  | 位置 | 含义 |
212
211
  |---|---|
213
- | 成功响应 `result._meta["com.feishu.miaoda/appRevision"]` | 当前请求使用的修订;包括 initialize、列表、工具、资源和 Prompt 响应 |
214
- | `tools/call`、`resources/read`、`prompts/get` 的 `params._meta` 同名键 | 调用方期望使用的修订,可选;提供时必须为 1–256 字符的字符串 |
212
+ | 成功响应 `result._meta["com.feishu.miaoda/appRevision"]` | 当前请求使用的修订;包括发现、列表、工具、资源、Skill 元数据和旧 Prompt 响应 |
213
+ | `tools/call`、`resources/read`、`skills/get`、旧版 `prompts/get` 的 `params._meta` 同名键 | 调用方期望使用的修订,可选;提供时必须为 1–256 字符的字符串 |
215
214
 
216
- 宿主通过 `tools/list`(纯 Skill 应用可用 `prompts/list`)刷新修订,将应用/环境/用户、修订和资源 URI 纳入缓存键。旧 UI 发起请求时由 Host 附加原绑定值,不允许用查询到的新值替换它。示例:
215
+ 宿主通过 `tools/list`(纯 Skill 应用可用 `skills/list`,旧客户端用 `prompts/list`)刷新修订,将应用/环境/用户、协议版本、修订和资源 URI 纳入缓存键。现代协议的修订额外覆盖附件,旧协议保持已发布的正文修订语义;切换协议后重新获取修订,不跨协议复用。旧 UI 发起请求时由 Host 附加原绑定值,不允许用查询到的新值替换它。示例:
217
216
 
218
217
  ```ts
219
218
  const key = 'com.feishu.miaoda/appRevision';
@@ -248,7 +247,7 @@ if (typeof revision === 'string') {
248
247
  Host 按 `error.data.code` 处理,保留旧结果、禁用旧 UI 并提示重新调试;不得自动重放可能产生副作用的调用。非法修订参数返回 `-32602`。此检查不是鉴权,原有身份校验仍然生效。
249
248
 
250
249
  - **开发态**:发现已注册的工具、资源或 Skill 后,在 Nest 启动完成前,对实际 Node 入口所在的后端编译产物与依赖配置生成固定指纹,再结合已构建 UI 内容与 Skill 内容计算 SHA-256。相同产物和依赖重启不变;后端更新在新进程生效后变化,不因尚未生效的源码编辑提前变化。本地与沙箱规则一致。默认识别 `dist/server` + `dist/shared` 或扁平 `dist` 布局,排除客户端、MCP UI、source map 和临时文件;UI/Skill 仍按运行快照独立更新。此规则适用于编译完成后启动、依赖按锁文件安装的标准工程;不跟踪手工替换 `node_modules` 或运行期间自行热替换后端模块。非标准入口(如直接运行 TypeScript)请显式提供与运行代码对应的 `appBuildId`;无法识别时不返回修订,不伪造固定版本。
251
- - **发布态**:SDK 在初始化时读取 veFaaS 注入的 `_FAAS_FUNC_ID` 与正整数 `_FAAS_REVISION_NUMBER`,结合 UI/Skill 内容形成修订。同一发布 revision 跨实例、重启保持一致,新后端发布(含依赖变更)更新;仅重新构建但未部署不会影响运行版本。无需模板写入构建 ID 文件。其他部署平台通过 `PlatformModule.forRoot({ mcp: { appBuildId: 'immutable-release-id' } })` 显式提供不可变版本。
250
+ - **发布态**:SDK 在初始化时读取 veFaaS 注入的 `_FAAS_FUNC_ID` 与正整数 `_FAAS_REVISION_NUMBER`,结合 UI/Skill 正文及附件内容形成修订。同一发布 revision 跨实例、重启保持一致,新后端发布(含依赖变更)更新;仅重新构建但未部署不会影响运行版本。无需模板写入构建 ID 文件。其他部署平台通过 `PlatformModule.forRoot({ mcp: { appBuildId: 'immutable-release-id' } })` 显式提供不可变版本。
252
251
  - **版本缺失**:缺少有效平台版本且未配置 `appBuildId` 时不返回修订,普通 MCP 仍可用;绑定调用返回 `-32000`、`data.code=MCP_APP_REVISION_UNAVAILABLE`,不允许伪造固定版本。
253
252
  - **按需初始化**:无 MCP 能力时不扫描后端编译产物。纯 Skill 应用也在启动时固定后端指纹。开发进程启动后才新增首份 Skill,标准 Prompt 可直接使用,但须重启开发服务才能获得自动修订;未固定后端快照前不返回绑定版本。
254
253
  - **一致性边界**:MCP 请求中的 `readMcpUiTemplate()` 复用该请求的 HTML 快照,避免修订与读取内容错配。仅涵盖固定 UI 产物及 Skill;动态业务数据不是代码修订。已经开始的请求可完成,保留开始时的修订;不撤销已发生的副作用,也不承诺后端/UI/Skill 在开发态整体原子切换或滚动发布期间全局最新版本校验。
@@ -295,7 +294,7 @@ SDK 不提供校验 CLI,也不生成离线 manifest 文件;能力目录以
295
294
  |---|---|---|---|
296
295
  | `dist` | `node server/main.js` | `dist/server/mcp/skills` | `dist/dist/mcp-ui` |
297
296
 
298
- 只复制 `skills/<name>/SKILL.md`,不复制附件。清理删除项时保留相邻工具编译产物。无需 UI 也交付 Skills;自定义构建须满足相同的运行目录约定。
297
+ 复制 `skills/<name>/SKILL.md` 及同目录的全部附件;未包含 SKILL.md 的目录不发布。新预设会清理已删除附件,发布态仅读取实际交付文件。使用旧预设时只有 SKILL.md,带附件的 Skill 需要同步升级构建预设。清理删除项时保留相邻工具编译产物。无需 UI 也交付 Skills;自定义构建须满足相同的运行目录约定。
299
298
 
300
299
  | 构建预设 | Skill 交付 | MCP UI 单文件构建与交付 |
301
300
  |---|---|---|