@lessie/mcp-server 0.0.3 → 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.
Files changed (3) hide show
  1. package/SKILL.md +130 -0
  2. package/dist/index.js +48 -12
  3. package/package.json +1 -1
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,15 +1,28 @@
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";
5
+ import { createRequire } from "node:module";
6
+ const require = createRequire(import.meta.url);
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
+ }
4
17
  // ── 环境变量 ──────────────────────────────────────────────────────────────────
5
- const BASE_URL = process.env.SAAS_BASE_URL;
6
- const API_KEY = process.env.SAAS_API_KEY;
18
+ const BASE_URL = process.env.LESSIE_BASE_URL || 'https://www.lessie.ai/prod-api';
19
+ const API_KEY = process.env.LESSIE_API_KEY;
7
20
  if (!BASE_URL) {
8
- console.error("Error: SAAS_BASE_URL is not set");
21
+ console.error("Error: LESSIE_BASE_URL is not set");
9
22
  process.exit(1);
10
23
  }
11
24
  if (!API_KEY) {
12
- console.error("Error: SAAS_API_KEY is not set");
25
+ console.error("Error: LESSIE_API_KEY is not set");
13
26
  process.exit(1);
14
27
  }
15
28
  let jwtCache = null;
@@ -31,20 +44,22 @@ async function getJwt() {
31
44
  if (!res.ok) {
32
45
  throw new Error(`Failed to exchange API key for JWT: ${res.status} ${res.statusText} ${BASE_URL}/auth/token`);
33
46
  }
34
- const data = (await res.json());
47
+ const body = (await res.json());
48
+ if (!body.data?.token) {
49
+ throw new Error(`/auth/token response missing data.token. Got: ${JSON.stringify(body)}`);
50
+ }
35
51
  jwtCache = {
36
- token: data.token,
37
- expiry: now + data.expiresIn * 1000,
52
+ token: body.data.token,
53
+ expiry: now + body.data.expiresIn * 1000,
38
54
  };
39
55
  return jwtCache.token;
40
56
  }
41
- // ── 通用请求封装 ───────────────────────────────────────────────────────────────
42
57
  async function api(method, path, body) {
43
58
  const token = await getJwt();
44
59
  const res = await fetch(`${BASE_URL}${path}`, {
45
60
  method,
46
61
  headers: {
47
- "Authorization": `Bearer ${token}`,
62
+ "Authorization": token,
48
63
  "Content-Type": "application/json",
49
64
  },
50
65
  body: body !== undefined ? JSON.stringify(body) : undefined,
@@ -55,7 +70,8 @@ async function api(method, path, body) {
55
70
  }
56
71
  if (res.status === 204)
57
72
  return undefined;
58
- return res.json();
73
+ const wrapper = (await res.json());
74
+ return wrapper.data;
59
75
  }
60
76
  // ── 工具结果辅助函数 ───────────────────────────────────────────────────────────
61
77
  function textResult(data) {
@@ -66,12 +82,21 @@ function textResult(data) {
66
82
  // ── MCP Server ────────────────────────────────────────────────────────────────
67
83
  const server = new McpServer({
68
84
  name: "lessie-mcp",
69
- version: "1.0.0",
85
+ version: pkg.version,
86
+ }, {
87
+ instructions: loadInstructions(),
70
88
  });
71
89
  // ── 工具定义 ──────────────────────────────────────────────────────────────────
72
90
  // 命名规范:下划线分隔,动词开头(如 list_xxx、get_xxx、create_xxx)。
73
91
  // 写入类工具在 description 中注明"执行前请向用户确认"。
74
- // 账号信息
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
+ });
75
100
  server.tool("get_account_info", "查看当前 Lessie 账号的详细信息,包括用户名、邮箱、账号状态、角色、邀请码等。", {}, async () => {
76
101
  const data = await api("GET", "/agent/account/info");
77
102
  return textResult(data);
@@ -79,3 +104,14 @@ server.tool("get_account_info", "查看当前 Lessie 账号的详细信息,包
79
104
  // ── 启动 ──────────────────────────────────────────────────────────────────────
80
105
  const transport = new StdioServerTransport();
81
106
  await server.connect(transport);
107
+ await getJwt();
108
+ server.sendLoggingMessage({
109
+ level: "info",
110
+ logger: "lessie",
111
+ data: "Token acquired successfully",
112
+ });
113
+ server.sendLoggingMessage({
114
+ level: "info",
115
+ logger: "lessie",
116
+ data: `Lessie MCP Server v${pkg.version} started`,
117
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lessie/mcp-server",
3
- "version": "0.0.3",
3
+ "version": "0.0.6",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {