@lark-apaas/nestjs-mcp 0.1.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/LICENSE +13 -0
- package/README.md +165 -0
- package/bin/miaoda-mcp.cjs +3 -0
- package/dist/cli.cjs +231618 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/index.cjs +1753 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +360 -0
- package/dist/index.d.ts +360 -0
- package/dist/index.js +1704 -0
- package/dist/index.js.map +1 -0
- package/package.json +73 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Lark Technologies Pte. Ltd. and/or its affiliates
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted,provided that the above copyright notice and this permission notice appear in all copies.
|
|
6
|
+
|
|
7
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
|
8
|
+
IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE
|
|
9
|
+
INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO
|
|
10
|
+
EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
|
11
|
+
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
|
|
12
|
+
DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
|
|
13
|
+
ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# @lark-apaas/nestjs-mcp
|
|
2
|
+
|
|
3
|
+
为妙搭全栈应用(NestJS)提供 MCP Server 能力:用装饰器把业务方法开放为 MCP 工具,SDK 负责协议、装配、入参校验、身份读取与清单导出。
|
|
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 资源)
|
|
7
|
+
- 身份:网关注入 `x-larkgw-suda-webuser`,`UserContextMiddleware` 解析到 `req.userContext`,工具通过 `ctx.user` 读取
|
|
8
|
+
- 装配:已被 `PlatformModule.forRoot()` 自动引入,业务工程 import `@lark-apaas/fullstack-nestjs-core` 即获得
|
|
9
|
+
|
|
10
|
+
## 快速开始
|
|
11
|
+
|
|
12
|
+
### 1. 定义工具类
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
// server/mcp/tools/order.tools.ts
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import {
|
|
18
|
+
McpTools, McpTool, McpToolError,
|
|
19
|
+
type Infer, type McpContext, type McpToolResult,
|
|
20
|
+
} from '@lark-apaas/fullstack-nestjs-core';
|
|
21
|
+
import { OrderService } from '@server/modules/order/order.service';
|
|
22
|
+
|
|
23
|
+
export const GetOrderInput = { orderId: z.string().describe('订单 ID') };
|
|
24
|
+
export const GetOrderOutput = { orderId: z.string(), amount: z.number(), status: z.string() };
|
|
25
|
+
|
|
26
|
+
@McpTools({ prefix: 'order_' })
|
|
27
|
+
export class OrderMcpTools {
|
|
28
|
+
constructor(private readonly orders: OrderService) {}
|
|
29
|
+
|
|
30
|
+
@McpTool({
|
|
31
|
+
title: '查询订单',
|
|
32
|
+
description: '按订单 ID 查询订单金额与状态。用户询问某个订单的情况时调用。',
|
|
33
|
+
inputSchema: GetOrderInput,
|
|
34
|
+
outputSchema: GetOrderOutput,
|
|
35
|
+
annotations: { readOnlyHint: true },
|
|
36
|
+
})
|
|
37
|
+
async get(input: Infer<typeof GetOrderInput>, ctx: McpContext): Promise<McpToolResult<typeof GetOrderOutput>> {
|
|
38
|
+
const order = await this.orders.findVisibleTo(input.orderId, ctx.user.userId);
|
|
39
|
+
if (!order) throw new McpToolError('订单不存在', { code: 'ORDER_NOT_FOUND' });
|
|
40
|
+
return { structuredContent: order };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 2. 注册为所属业务模块的 provider
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
@Module({
|
|
49
|
+
providers: [OrderService, OrderMcpTools],
|
|
50
|
+
})
|
|
51
|
+
export class OrderModule {}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
应用启动时 `McpModule` 自动扫描所有 `@McpTools()` 类,不需要改 `app.module.ts`。启动日志会打印已注册的工具名。
|
|
55
|
+
|
|
56
|
+
### 3. 校验并导出清单
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npx miaoda-mcp validate # 生成 .spark/mcp/manifest.json
|
|
60
|
+
npx miaoda-mcp validate --check # CI:清单与源码不一致则失败
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
开发态启动应用时 SDK 也会自动写入清单(生产环境不写)。
|
|
64
|
+
|
|
65
|
+
## 约定
|
|
66
|
+
|
|
67
|
+
| 项目 | 约定 |
|
|
68
|
+
|---|---|
|
|
69
|
+
| 工具类位置 | `server/mcp/tools/*.tools.ts` |
|
|
70
|
+
| 工具名 | `prefix + 方法名`(或 `name` 覆盖),需匹配 `^[A-Za-z0-9_.-]{1,128}$`,全局唯一 |
|
|
71
|
+
| 方法签名 | `(input: Infer<typeof InputSchema>, ctx: McpContext) => McpToolResult<typeof OutputSchema>` |
|
|
72
|
+
| schema | zod(与 `@modelcontextprotocol/sdk` 一致),原始形状 `{ a: z.string() }` 或 `z.object({...})` 均可 |
|
|
73
|
+
| 返回值 | 声明 `outputSchema` 时返回 `{ structuredContent }`(SDK 自动补 `content` 文本);否则返回 `{ content: [...] }` |
|
|
74
|
+
| 业务错误 | 抛 `McpToolError(message, { code?, data? })`,原样返回给 Agent(`data` 会随结果返回,勿放敏感字段);其他异常记日志并返回通用失败提示 |
|
|
75
|
+
| 身份 | 默认 `requireUser: true`:请求缺少用户身份时返回 `MCP_USER_REQUIRED` 错误,不执行业务代码 |
|
|
76
|
+
| 权限 | 复用业务 Service 与数据库行级权限,工具层只负责把 `ctx.user` 传下去 |
|
|
77
|
+
|
|
78
|
+
## MCP Apps(可选)
|
|
79
|
+
|
|
80
|
+
当工具结果需要界面渲染(如订单详情卡片)时,声明一个 `ui://` 资源并在工具上关联:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
@McpTools()
|
|
84
|
+
export class OrderMcpTools {
|
|
85
|
+
@McpTool({ description: '…', inputSchema, outputSchema, ui: { resourceUri: 'ui://order/detail' } })
|
|
86
|
+
async get(/* … */) { /* … */ }
|
|
87
|
+
|
|
88
|
+
@McpUiResource({ uri: 'ui://order/detail', title: '订单详情' })
|
|
89
|
+
detailView() {
|
|
90
|
+
return readMcpUiTemplate('order-detail'); // 读取 dist/mcp-ui/order-detail.html
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
界面源码放在 `client/mcp-ui/order-detail/index.html`(浏览器代码,归 `tsconfig.app.json` 管;可用 React + `@modelcontextprotocol/ext-apps` 的 App / hooks),`@lark-apaas/fullstack-vite-preset` 会把每个入口打包为单文件 `dist/mcp-ui/<entry>.html`,开发态监听变更自动重建。界面子构建只带 React 插件与 `@shared` 别名,不支持 styled-jsx 与 `@server/*`。目前仅 Vite 预设提供该构建,Rspack 预设不支持 MCP Apps。
|
|
96
|
+
|
|
97
|
+
## 模块配置
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
PlatformModule.forRoot({
|
|
101
|
+
mcp: {
|
|
102
|
+
serverName: 'my-app', // 默认 SUDA_APP_ID
|
|
103
|
+
serverVersion: '1.0.0',
|
|
104
|
+
instructions: '…', // initialize 响应中的说明
|
|
105
|
+
manifest: { writeOnBoot: false }, // 默认 NODE_ENV !== 'production'
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
// 关闭端点:PlatformModule.forRoot({ mcp: false })
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## 端点行为
|
|
112
|
+
|
|
113
|
+
| 请求 | 响应 |
|
|
114
|
+
|---|---|
|
|
115
|
+
| `POST /__innerapi__/mcp`(JSON-RPC) | 标准 MCP 响应(JSON) |
|
|
116
|
+
| 未注册任何工具、UI 资源且不存在 Skill | 404 |
|
|
117
|
+
| `GET` / `DELETE` | 405,`Allow: POST` |
|
|
118
|
+
| `Accept` 缺少 `application/json` 与 `text/event-stream` | 406(SDK 行为,标准客户端默认会带) |
|
|
119
|
+
|
|
120
|
+
## 导出
|
|
121
|
+
|
|
122
|
+
`McpModule`、`McpTools`、`McpTool`、`McpUiResource`、`McpToolError`、`readMcpUiTemplate`、
|
|
123
|
+
类型 `McpContext` / `McpUser` / `McpToolOptions` / `McpToolResult` / `Infer` / `McpModuleOptions` / `McpCatalog` / `McpSkill` / `McpSources` / `McpSourceLocation`,
|
|
124
|
+
以及端点、清单和 Skill 常量。内部注册表与构建函数不作为包入口 API 导出。
|
|
125
|
+
|
|
126
|
+
## 平台定义查询
|
|
127
|
+
|
|
128
|
+
`GET ${CLIENT_BASE_PATH}/__innerapi__/mcp/manifest` 返回完整平台目录:
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
interface Catalog {
|
|
132
|
+
version: number; // 当前为 1,平台响应结构版本
|
|
133
|
+
generatedAt: string; // 本次响应生成时间,UTC
|
|
134
|
+
endpoint: string; // /__innerapi__/mcp,相对应用根
|
|
135
|
+
tools: Tool[];
|
|
136
|
+
resources: Resource[];
|
|
137
|
+
skill: { uri: string; path: string; content: string } | null;
|
|
138
|
+
sources: {
|
|
139
|
+
tools: Record<string, { path: string; line?: number }>;
|
|
140
|
+
resources: Record<string, { path: string; line?: number }>;
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Tool / Resource 完整复用当前实例的标准列表字段,按 name / uri 排序,不内嵌旧 source 扩展。sources 的键为工具 name / 资源 uri,路径相对工程根、行号从 1 开始。启动时可选使用工程 TypeScript 提取原始声明位置,UI 静态入口定位到 `client/mcp-ui/<entry>/index.html`;歧义、继承、动态入口或生产无源码时省略对应映射。平台负责工程版本匹配;定位不是文件读取授权,也不代表未编译修改的位置。
|
|
146
|
+
|
|
147
|
+
面板无需 MCP 握手;接口不读写 `.spark/mcp/manifest.json`,每次读取 Skill 正文,返回 `Cache-Control: no-store`。空应用仍返回完整结构和空值;模块关闭为404,未就绪503,定义生成或文件读取失败500(RFC9457)。入口访问权限由平台负责。
|
|
148
|
+
|
|
149
|
+
## 应用 Skill
|
|
150
|
+
|
|
151
|
+
`server/mcp/SKILL.md` 是给应用 MCP 客户端的使用说明,与指导编码的 `mcp-guide` 不同。Agent 生成和维护 Markdown(含 name / description frontmatter),描述工具选择、调用顺序、约束与失败处理。GUI 查看、跳转源码、添加到对话,修改统一交给 AI。
|
|
152
|
+
|
|
153
|
+
文件自动注册为 `skill://app/SKILL.md`,mimeType 为 `text/markdown`,标准客户端通过 `resources/list` / `resources/read` 获取。manifest.skill.uri 关联同一资源,content 与 Resource 从同一源文件读取;没有文件返回 null 并从资源列表移除。每次请求重新读取,无需重启;最大1 MiB,读取失败、越界链接或非法 UTF-8 报错,不当作“未配置”。资源读取沿用 requireUser 身份要求,列表查询不要求用户身份。仅 Skill 的应用也可初始化和读取资源。
|
|
154
|
+
|
|
155
|
+
这是普通 MCP Resource,不声明 Skills 扩展或 Prompts。客户端是否把 Markdown 当作 Skill 加载需消费侧适配。Apps 面板仅展示 `text/html;profile=mcp-app` 资源,不能把 Markdown 当作界面。
|
|
156
|
+
|
|
157
|
+
生产由 Vite 预设复制到 `dist/server/server/mcp/SKILL.md`,适配发布进程 cwd=`dist/server`;没有 UI 同样复制,源文件删除会清理旧产物。修改开发工程不影响已发布版本。自定义构建/Rspack 项目需自行保证同样的文件交付位置。
|
|
158
|
+
|
|
159
|
+
离线清单由开发启动和 `miaoda-mcp validate` 原子更新,保留旧 server 与声明来源字段用于离线兼容,新增 skill 引用(不复制正文)和 sources。无任何能力及 Skill 时清理旧清单,`--check` 只检查、不写入;不会生成、覆盖或清理 SKILL.md。
|
|
160
|
+
|
|
161
|
+
## MCP Apps 交付
|
|
162
|
+
|
|
163
|
+
Vite 构建将单文件 HTML 放入 `dist/mcp-ui`,并同步到发布目录 `dist/server/dist/mcp-ui`;生产进程以 `dist/server` 为工作目录时可直接读取。开发态支持新增/删除入口及 client/shared 依赖变更后的重建。Rspack 本次仅提供身份代理,MCP Apps 验收使用 nrf 默认 Vite。
|
|
164
|
+
|
|
165
|
+
工具类使用单例 provider,通过 `ctx.user` 读取每次请求的用户;不承诺工具方法上的 Nest Guard/Pipe/Interceptor 自动执行。需要访问控制的业务逻辑应在共享 Service 中执行,不能依赖 HTTP Controller 上的装饰器。
|