@lessie/mcp-server 0.0.3 → 0.0.5
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/.cursor/skills/add-mcp-tool/SKILL.md +132 -0
- package/dist/index.js +28 -11
- package/package.json +1 -1
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: add-mcp-tool
|
|
3
|
+
description: >-
|
|
4
|
+
为 Lessie MCP Server 添加新的 MCP 工具。当用户要求新增 MCP tool、暴露新的 API
|
|
5
|
+
端点、或为 AI Agent 添加新功能时使用。
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# 为 Lessie MCP Server 添加工具
|
|
9
|
+
|
|
10
|
+
## 项目基础设施
|
|
11
|
+
|
|
12
|
+
`src/index.ts` 已包含以下基础设施,添加工具时直接复用,**不要重复实现**:
|
|
13
|
+
|
|
14
|
+
- **`getJwt()`** — API Key → JWT 换取 + 内存缓存 + 到期前 60s 自动刷新
|
|
15
|
+
- **`api<T>(method, path, body?)`** — 通用请求封装,自动注入 Authorization header,非 2xx 抛错
|
|
16
|
+
- **`textResult(data)`** — 将返回数据包装为 MCP content 格式
|
|
17
|
+
|
|
18
|
+
## 添加工具流程
|
|
19
|
+
|
|
20
|
+
### 1. 确认 API 端点
|
|
21
|
+
|
|
22
|
+
从 `swagger.json` 中找到目标端点,记录:
|
|
23
|
+
|
|
24
|
+
- HTTP 方法和路径(如 `GET /agent/account/info`)
|
|
25
|
+
- 请求参数 / 请求体 schema
|
|
26
|
+
- 响应 `data` 字段的结构
|
|
27
|
+
|
|
28
|
+
### 2. 在工具定义区域添加代码
|
|
29
|
+
|
|
30
|
+
在 `src/index.ts` 的 `// ── 工具定义` 区域、`// ── 启动` 区域之前添加:
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
server.tool(
|
|
34
|
+
"动词_名词", // 工具名:下划线分隔,动词开头
|
|
35
|
+
"一句话描述功能和用途", // Agent 靠 description 决定是否调用
|
|
36
|
+
{ // zod schema 定义参数,无参数传 {}
|
|
37
|
+
paramName: z.string().describe("参数说明"),
|
|
38
|
+
},
|
|
39
|
+
async ({ paramName }) => {
|
|
40
|
+
const data = await api("GET", `/agent/your/path?param=${paramName}`);
|
|
41
|
+
return textResult(data);
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 3. 命名规范
|
|
47
|
+
|
|
48
|
+
| 类型 | 前缀 | 示例 |
|
|
49
|
+
|------|------|------|
|
|
50
|
+
| 查询单个 | `get_` | `get_account_info` |
|
|
51
|
+
| 查询列表 | `list_` | `list_projects` |
|
|
52
|
+
| 创建 | `create_` | `create_task` |
|
|
53
|
+
| 更新 | `update_` | `update_task_status` |
|
|
54
|
+
| 删除 | `delete_` | `delete_project` |
|
|
55
|
+
|
|
56
|
+
### 4. Description 编写要求
|
|
57
|
+
|
|
58
|
+
- 用中文,一句话说清**做什么**
|
|
59
|
+
- Agent 完全依赖 description 决定是否调用,必须精确
|
|
60
|
+
- **写入类工具**(create / update / delete)在末尾加:`执行前请向用户确认`
|
|
61
|
+
|
|
62
|
+
好的 description:
|
|
63
|
+
```
|
|
64
|
+
查看当前 Lessie 账号的详细信息,包括用户名、邮箱、账号状态、角色、邀请码等。
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
差的 description:
|
|
68
|
+
```
|
|
69
|
+
获取账号信息
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 5. 参数定义
|
|
73
|
+
|
|
74
|
+
- 用 `zod` 定义,每个参数加 `.describe()` 说明用途
|
|
75
|
+
- 可选参数用 `.optional()`
|
|
76
|
+
- 无参数传空对象 `{}`
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
{
|
|
80
|
+
projectId: z.string().describe("项目 ID"),
|
|
81
|
+
status: z.enum(["active", "archived"]).optional().describe("筛选状态"),
|
|
82
|
+
page: z.number().default(1).describe("页码"),
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 6. 构建验证
|
|
87
|
+
|
|
88
|
+
添加完工具后运行 `npm run build` 确认 TypeScript 编译通过。
|
|
89
|
+
|
|
90
|
+
## 完整示例
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
// 列出项目
|
|
94
|
+
server.tool(
|
|
95
|
+
"list_projects",
|
|
96
|
+
"列出当前用户的所有项目,支持按状态筛选。返回项目名称、状态、创建时间等信息。",
|
|
97
|
+
{
|
|
98
|
+
status: z.enum(["active", "archived"]).optional().describe("按状态筛选"),
|
|
99
|
+
page: z.number().default(1).describe("页码"),
|
|
100
|
+
},
|
|
101
|
+
async ({ status, page }) => {
|
|
102
|
+
const params = new URLSearchParams({ page: String(page) });
|
|
103
|
+
if (status) params.set("status", status);
|
|
104
|
+
const data = await api("GET", `/agent/projects?${params}`);
|
|
105
|
+
return textResult(data);
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// 创建项目(写入类,description 要求确认)
|
|
110
|
+
server.tool(
|
|
111
|
+
"create_project",
|
|
112
|
+
"创建一个新项目。执行前请向用户确认。",
|
|
113
|
+
{
|
|
114
|
+
name: z.string().describe("项目名称"),
|
|
115
|
+
description: z.string().optional().describe("项目描述"),
|
|
116
|
+
},
|
|
117
|
+
async ({ name, description }) => {
|
|
118
|
+
const data = await api("POST", "/agent/projects", { name, description });
|
|
119
|
+
return textResult(data);
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## API 响应格式
|
|
125
|
+
|
|
126
|
+
后端统一返回格式为 `{ code: number, msg: string, data: T }`。`api()` 函数已自动解包,工具 handler 中拿到的就是 `data` 部分。
|
|
127
|
+
|
|
128
|
+
## 注意事项
|
|
129
|
+
|
|
130
|
+
- 所有路径以 `/agent/` 开头(Agent 专用端点)
|
|
131
|
+
- `api()` 已处理 token 注入和错误抛出,handler 中只需关注业务逻辑
|
|
132
|
+
- 不要在工具 handler 中直接使用 `fetch`,统一走 `api()` 封装
|
package/dist/index.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
const pkg = require("../package.json");
|
|
4
7
|
// ── 环境变量 ──────────────────────────────────────────────────────────────────
|
|
5
|
-
const BASE_URL = process.env.
|
|
6
|
-
const API_KEY = process.env.
|
|
8
|
+
const BASE_URL = process.env.LESSIE_BASE_URL || 'https://www.lessie.ai/prod-api';
|
|
9
|
+
const API_KEY = process.env.LESSIE_API_KEY;
|
|
7
10
|
if (!BASE_URL) {
|
|
8
|
-
console.error("Error:
|
|
11
|
+
console.error("Error: LESSIE_BASE_URL is not set");
|
|
9
12
|
process.exit(1);
|
|
10
13
|
}
|
|
11
14
|
if (!API_KEY) {
|
|
12
|
-
console.error("Error:
|
|
15
|
+
console.error("Error: LESSIE_API_KEY is not set");
|
|
13
16
|
process.exit(1);
|
|
14
17
|
}
|
|
15
18
|
let jwtCache = null;
|
|
@@ -31,20 +34,22 @@ async function getJwt() {
|
|
|
31
34
|
if (!res.ok) {
|
|
32
35
|
throw new Error(`Failed to exchange API key for JWT: ${res.status} ${res.statusText} ${BASE_URL}/auth/token`);
|
|
33
36
|
}
|
|
34
|
-
const
|
|
37
|
+
const body = (await res.json());
|
|
38
|
+
if (!body.data?.token) {
|
|
39
|
+
throw new Error(`/auth/token response missing data.token. Got: ${JSON.stringify(body)}`);
|
|
40
|
+
}
|
|
35
41
|
jwtCache = {
|
|
36
|
-
token: data.token,
|
|
37
|
-
expiry: now + data.expiresIn * 1000,
|
|
42
|
+
token: body.data.token,
|
|
43
|
+
expiry: now + body.data.expiresIn * 1000,
|
|
38
44
|
};
|
|
39
45
|
return jwtCache.token;
|
|
40
46
|
}
|
|
41
|
-
// ── 通用请求封装 ───────────────────────────────────────────────────────────────
|
|
42
47
|
async function api(method, path, body) {
|
|
43
48
|
const token = await getJwt();
|
|
44
49
|
const res = await fetch(`${BASE_URL}${path}`, {
|
|
45
50
|
method,
|
|
46
51
|
headers: {
|
|
47
|
-
"Authorization":
|
|
52
|
+
"Authorization": token,
|
|
48
53
|
"Content-Type": "application/json",
|
|
49
54
|
},
|
|
50
55
|
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
@@ -55,7 +60,8 @@ async function api(method, path, body) {
|
|
|
55
60
|
}
|
|
56
61
|
if (res.status === 204)
|
|
57
62
|
return undefined;
|
|
58
|
-
|
|
63
|
+
const wrapper = (await res.json());
|
|
64
|
+
return wrapper.data;
|
|
59
65
|
}
|
|
60
66
|
// ── 工具结果辅助函数 ───────────────────────────────────────────────────────────
|
|
61
67
|
function textResult(data) {
|
|
@@ -66,7 +72,7 @@ function textResult(data) {
|
|
|
66
72
|
// ── MCP Server ────────────────────────────────────────────────────────────────
|
|
67
73
|
const server = new McpServer({
|
|
68
74
|
name: "lessie-mcp",
|
|
69
|
-
version:
|
|
75
|
+
version: pkg.version,
|
|
70
76
|
});
|
|
71
77
|
// ── 工具定义 ──────────────────────────────────────────────────────────────────
|
|
72
78
|
// 命名规范:下划线分隔,动词开头(如 list_xxx、get_xxx、create_xxx)。
|
|
@@ -79,3 +85,14 @@ server.tool("get_account_info", "查看当前 Lessie 账号的详细信息,包
|
|
|
79
85
|
// ── 启动 ──────────────────────────────────────────────────────────────────────
|
|
80
86
|
const transport = new StdioServerTransport();
|
|
81
87
|
await server.connect(transport);
|
|
88
|
+
await getJwt();
|
|
89
|
+
server.sendLoggingMessage({
|
|
90
|
+
level: "info",
|
|
91
|
+
logger: "lessie",
|
|
92
|
+
data: "Token acquired successfully",
|
|
93
|
+
});
|
|
94
|
+
server.sendLoggingMessage({
|
|
95
|
+
level: "info",
|
|
96
|
+
logger: "lessie",
|
|
97
|
+
data: `Lessie MCP Server v${pkg.version} started`,
|
|
98
|
+
});
|