@lessie/mcp-server 0.0.5 → 0.0.6

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/SKILL.md ADDED
@@ -0,0 +1,130 @@
1
+ ---
2
+ name: lessie-api
3
+ description: >-
4
+ 调用 Lessie API 的标准流程:API Key 换取 JWT、JWT 缓存刷新、通用请求封装。
5
+ 当需要对接 Lessie 后端、调用 Lessie API、或实现鉴权流程时使用。
6
+ ---
7
+
8
+ # Lessie API 调用流程
9
+
10
+ ## 鉴权:API Key → JWT
11
+
12
+ 环境变量:
13
+
14
+ - `LESSIE_BASE_URL` — 后端地址,默认 `https://www.lessie.ai/prod-api`
15
+ - `LESSIE_API_KEY` — 用户的 API Key(`sk-live-v1-xxx`)
16
+
17
+ ### 换取 JWT
18
+
19
+ ```typescript
20
+ const res = await fetch(`${LESSIE_BASE_URL}/auth/token`, {
21
+ method: "POST",
22
+ headers: { "Content-Type": "application/json" },
23
+ body: JSON.stringify({ apiKey: LESSIE_API_KEY }),
24
+ });
25
+ const body = await res.json();
26
+ // body 结构: { code: number, msg: string, data: { token: string, expiresIn: number } }
27
+ const { token, expiresIn } = body.data;
28
+ ```
29
+
30
+ - `expiresIn` 单位为秒(通常 3600 = 1 小时)
31
+ - 错误统一返回 401,不区分"不存在"和"已吊销"
32
+
33
+ ### JWT 缓存与刷新
34
+
35
+ 在内存中缓存 `{ token, expiry }`,每次请求前检查:
36
+
37
+ - 若缓存不存在,或距过期不足 **60 秒**,重新调用 `/auth/token` 换取
38
+ - 进程重启后需重新获取
39
+
40
+ ```typescript
41
+ interface JwtCache {
42
+ token: string;
43
+ expiry: number; // Unix timestamp (ms)
44
+ }
45
+
46
+ let jwtCache: JwtCache | null = null;
47
+
48
+ async function getJwt(): Promise<string> {
49
+ const now = Date.now();
50
+ if (jwtCache && jwtCache.expiry - now > 60_000) {
51
+ return jwtCache.token;
52
+ }
53
+
54
+ const res = await fetch(`${LESSIE_BASE_URL}/auth/token`, {
55
+ method: "POST",
56
+ headers: { "Content-Type": "application/json" },
57
+ body: JSON.stringify({ apiKey: LESSIE_API_KEY }),
58
+ });
59
+
60
+ if (!res.ok) {
61
+ throw new Error(`Failed to exchange API key for JWT: ${res.status}`);
62
+ }
63
+
64
+ const body = await res.json() as {
65
+ code: number; msg: string;
66
+ data: { token: string; expiresIn: number };
67
+ };
68
+
69
+ jwtCache = {
70
+ token: body.data.token,
71
+ expiry: now + body.data.expiresIn * 1000,
72
+ };
73
+ return jwtCache.token;
74
+ }
75
+ ```
76
+
77
+ ## 通用请求封装
78
+
79
+ ```typescript
80
+ async function api<T = unknown>(
81
+ method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE",
82
+ path: string,
83
+ body?: unknown,
84
+ ): Promise<T> {
85
+ const token = await getJwt();
86
+
87
+ const res = await fetch(`${LESSIE_BASE_URL}${path}`, {
88
+ method,
89
+ headers: {
90
+ Authorization: token,
91
+ "Content-Type": "application/json",
92
+ },
93
+ body: body !== undefined ? JSON.stringify(body) : undefined,
94
+ });
95
+
96
+ if (!res.ok) {
97
+ const text = await res.text().catch(() => "");
98
+ throw new Error(`API error ${res.status}${text ? `: ${text}` : ""}`);
99
+ }
100
+
101
+ if (res.status === 204) return undefined as T;
102
+
103
+ const wrapper = await res.json() as { code: number; msg: string; data: T };
104
+ return wrapper.data;
105
+ }
106
+ ```
107
+
108
+ 要点:
109
+
110
+ - Authorization header 直接传 token(不加 `Bearer ` 前缀)
111
+ - 后端统一返回 `{ code, msg, data }`,`api()` 自动解包返回 `data`
112
+ - 所有 Agent 端点路径以 `/agent/` 开头
113
+
114
+ ## 调用示例
115
+
116
+ ```typescript
117
+ // GET 无参数
118
+ const info = await api("GET", "/agent/account/info");
119
+
120
+ // GET 带查询参数
121
+ const params = new URLSearchParams({ page: "1", status: "active" });
122
+ const list = await api("GET", `/agent/projects?${params}`);
123
+
124
+ // POST 带请求体
125
+ const result = await api("POST", "/agent/projects", {
126
+ name: "新项目",
127
+ description: "项目描述",
128
+ });
129
+ ```
130
+
package/dist/index.js CHANGED
@@ -1,9 +1,19 @@
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 { readFileSync } from "node:fs";
4
5
  import { createRequire } from "node:module";
5
6
  const require = createRequire(import.meta.url);
6
7
  const pkg = require("../package.json");
8
+ function loadInstructions() {
9
+ try {
10
+ const raw = readFileSync(new URL("../SKILL.md", import.meta.url), "utf-8");
11
+ return raw.replace(/^---[\s\S]*?---\n*/, "").trim() || undefined;
12
+ }
13
+ catch {
14
+ return undefined;
15
+ }
16
+ }
7
17
  // ── 环境变量 ──────────────────────────────────────────────────────────────────
8
18
  const BASE_URL = process.env.LESSIE_BASE_URL || 'https://www.lessie.ai/prod-api';
9
19
  const API_KEY = process.env.LESSIE_API_KEY;
@@ -73,11 +83,20 @@ function textResult(data) {
73
83
  const server = new McpServer({
74
84
  name: "lessie-mcp",
75
85
  version: pkg.version,
86
+ }, {
87
+ instructions: loadInstructions(),
76
88
  });
77
89
  // ── 工具定义 ──────────────────────────────────────────────────────────────────
78
90
  // 命名规范:下划线分隔,动词开头(如 list_xxx、get_xxx、create_xxx)。
79
91
  // 写入类工具在 description 中注明"执行前请向用户确认"。
80
- // 账号信息
92
+ server.tool("get_jwt", "获取当前有效的 JWT token,可用于直接调用 Lessie API。返回 token 字符串和剩余有效时间。", {}, async () => {
93
+ const token = await getJwt();
94
+ const remainingMs = jwtCache ? jwtCache.expiry - Date.now() : 0;
95
+ return textResult({
96
+ token,
97
+ remainingSeconds: Math.max(0, Math.floor(remainingMs / 1000)),
98
+ });
99
+ });
81
100
  server.tool("get_account_info", "查看当前 Lessie 账号的详细信息,包括用户名、邮箱、账号状态、角色、邀请码等。", {}, async () => {
82
101
  const data = await api("GET", "/agent/account/info");
83
102
  return textResult(data);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lessie/mcp-server",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -1,132 +0,0 @@
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()` 封装