note-mcp-server 2.4.1 → 2.5.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.
Files changed (2) hide show
  1. package/index.js +94 -33
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -12,73 +12,101 @@ import {
12
12
  import axios from "axios";
13
13
  import FormData from "form-data";
14
14
 
15
+ import fs from "node:fs";
16
+ import { parse as parseEnv } from "dotenv";
17
+
15
18
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
16
19
 
17
- // 優先載入專案目錄 (cwd) 的 .env,以支援不同專案自由切換
18
- loadEnv({ path: path.join(process.cwd(), ".env") });
19
- // 如果專案目錄沒有設定,則退回讀取全域的 .env
20
- loadEnv({ path: path.join(__dirname, ".env") });
21
20
  /**
22
21
  * MCP 須由 Cursor 依 mcp.json 啟動;勿在終端機手動跑本檔當成「掛載 MCP」。
23
22
  *
24
23
  * 設定優先序:
25
- * 1) NOTES_API_BASE 完整 API base(含 /api/notes/{socialite}/{key})
26
- * 2) notes_url + socialite + key(或 socialite_id,與 key 同義:加密路徑 token)
24
+ * 1) 專案目錄 (cwd) .env 優先(支援不同專案自由切換)
25
+ * 2) 全域的 note-mcp-server/.env 作為預設退路
26
+ *
27
+ * 變數組合:
28
+ * A) NOTES_API_BASE — 完整 API base(含 /api/notes/{socialite}/{key})
29
+ * B) notes_url + socialite + key(或 socialite_id,與 key 同義:加密路徑 token)
27
30
  *
28
31
  * 變數名支援常見大小寫(見 resolveNotesApiBase)。
29
32
  */
30
33
 
34
+ const cwdEnvPath = path.join(process.cwd(), ".env");
35
+ const cwdEnv = fs.existsSync(cwdEnvPath) ? parseEnv(fs.readFileSync(cwdEnvPath)) : {};
36
+
37
+ const globalEnvPath = path.join(__dirname, ".env");
38
+ const globalEnv = fs.existsSync(globalEnvPath) ? parseEnv(fs.readFileSync(globalEnvPath)) : {};
39
+
40
+ // 將載入的變數注入 process.env (cwd 優先蓋過 global)
41
+ for (const [k, v] of Object.entries(globalEnv)) {
42
+ if (process.env[k] === undefined) process.env[k] = v;
43
+ }
44
+ for (const [k, v] of Object.entries(cwdEnv)) {
45
+ process.env[k] = v; // cwd 總是能覆寫
46
+ }
47
+
31
48
  function stripTrailingSlashes(value) {
32
49
  return value.replace(/\/+$/, "");
33
50
  }
34
51
 
35
- function firstEnv(...keys) {
36
- for (const key of keys) {
37
- const raw = process.env[key];
38
- if (raw === undefined) {
39
- continue;
40
- }
41
- const trimmed = String(raw).trim();
42
- if (trimmed !== "") {
43
- return trimmed;
52
+ function extractBase(envObj) {
53
+ const get = (...keys) => {
54
+ for (const k of keys) {
55
+ if (envObj[k] !== undefined && String(envObj[k]).trim() !== "") {
56
+ return String(envObj[k]).trim();
57
+ }
44
58
  }
45
- }
46
- return "";
47
- }
59
+ return "";
60
+ };
48
61
 
49
- function resolveNotesApiBase() {
50
- const direct = firstEnv("NOTES_API_BASE");
62
+ const direct = get("NOTES_API_BASE");
51
63
  if (direct) {
52
64
  return stripTrailingSlashes(direct);
53
65
  }
54
66
 
55
- const notesUrl = stripTrailingSlashes(
56
- firstEnv("notes_url", "NOTES_URL")
57
- );
58
- const socialite = firstEnv("socialite", "SOCIALITE");
59
- const encryptedToken = firstEnv(
60
- "key",
61
- "KEY",
62
- "socialite_id",
63
- "SOCIALITE_ID"
64
- );
67
+ const notesUrl = stripTrailingSlashes(get("notes_url", "NOTES_URL"));
68
+ const socialite = get("socialite", "SOCIALITE");
69
+ const encryptedToken = get("key", "KEY", "socialite_id", "SOCIALITE_ID");
65
70
 
66
71
  if (notesUrl && socialite && encryptedToken) {
67
72
  return `${notesUrl}/api/notes/${socialite}/${encryptedToken}`;
68
73
  }
69
74
 
75
+ return null;
76
+ }
77
+
78
+ function resolveNotesApiBase() {
79
+ let base = extractBase(cwdEnv);
80
+ if (base) return base;
81
+
82
+ base = extractBase(globalEnv);
83
+ if (base) return base;
84
+
85
+ base = extractBase(process.env);
86
+ if (base) return base;
87
+
70
88
  throw new Error(
71
89
  "note-mcp-server:請在專案目錄建立 .env,設定 notes_url、socialite、key(或 socialite_id),或設定 NOTES_API_BASE。詳見 .env.example。"
72
90
  );
73
91
  }
74
92
 
93
+ function firstEnv(...keys) {
94
+ for (const key of keys) {
95
+ if (process.env[key] !== undefined) {
96
+ const trimmed = String(process.env[key]).trim();
97
+ if (trimmed !== "") return trimmed;
98
+ }
99
+ }
100
+ return "";
101
+ }
102
+
75
103
  const BASE_URL = resolveNotesApiBase();
76
104
  const APP_NAME = firstEnv("app_name", "APP_NAME", "notes_app_name", "NOTES_APP_NAME") || "Personal Note Assistant";
77
105
 
78
106
  const server = new Server(
79
107
  {
80
108
  name: `note-mcp-server (${APP_NAME})`,
81
- version: "2.4.1",
109
+ version: "2.4.2",
82
110
  },
83
111
  {
84
112
  capabilities: {
@@ -164,6 +192,16 @@ function buildNoteUrl(cId) {
164
192
  server.setRequestHandler(ListToolsRequestSchema, async () => {
165
193
  return {
166
194
  tools: [
195
+ {
196
+ name: "block_list",
197
+ description: withSecurityNotice(
198
+ `列出 ${APP_NAME} 中當前用戶有權限建立內容的所有區塊(看板/筆記本等)。回傳的資料會包含每個區塊的名稱與可用的狀態標籤 (tags)。如果該區塊有設定狀態標籤(例如用於 Kanban 或是 Defect Flow),請在安排工作流程或建立內容時參考這些可用的標籤;若無狀態標籤,則通常為單純的 CMS 或企業知識庫,直接新增內容即可。回傳的 b_id 可用於 create_note 建立內容到指定看板。`
199
+ ),
200
+ inputSchema: {
201
+ type: "object",
202
+ properties: {},
203
+ },
204
+ },
167
205
  {
168
206
  name: "list_notes",
169
207
  description: withSecurityNotice(
@@ -199,18 +237,19 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
199
237
  name: "create_note",
200
238
  description: withCardGuide(
201
239
  withSecurityNotice(
202
- `在 ${APP_NAME} 中新增筆記。內容請優先使用 AdminLTE 4 可摺疊 card(見下方結構)。\nAI 行為:除非用戶明確要求,否則不要傳 tags(勿自行推測、分類或補加標籤)。`
240
+ `在 ${APP_NAME} 中新增筆記。未指定任何 b_id 的情況下,代表就是新增至「我的筆記」預設值。內容請優先使用 AdminLTE 4 可摺疊 card(見下方結構)。\nAI 行為:\n1. 除非用戶明確要求,否則不要傳 tags(勿自行推測、分類或補加標籤)。\n2. 若用戶僅提供區塊名稱(例如「看板」)而未提供 b_id,請先呼叫 block_list 查詢。若只有一個相符的名稱,直接使用該 b_id 建立;若有多個相符名稱,請列出選項並詢問用戶要加到哪一個 b_id。`
203
241
  )
204
242
  ),
205
243
  inputSchema: {
206
244
  type: "object",
207
245
  properties: {
246
+ b_id: { type: "number", description: "指定要建立的區塊 ID(可選,預設為個人筆記本)。可先用 block_list 查詢可用的 b_id" },
208
247
  content: { type: "string", description: "HTML 或純文字內容" },
209
248
  title: { type: "string", description: "標題(可選)" },
210
249
  tags: {
211
250
  type: "string",
212
251
  description:
213
- "標籤,多個以逗號分隔(可選;僅當用戶明確要求設定標籤時才傳,勿主動填寫)",
252
+ "標籤,多個以逗號分隔(可選;若指定 b_id 且該區塊有必填標籤時,不傳則會自動補上預設狀態標籤)",
214
253
  },
215
254
  },
216
255
  required: ["content"],
@@ -226,6 +265,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
226
265
  inputSchema: {
227
266
  type: "object",
228
267
  properties: {
268
+ b_id: { type: "number", description: "移動到指定的區塊 ID(可選)" },
229
269
  c_id: { type: "number", description: "筆記 ID" },
230
270
  content: { type: "string", description: "新內容(可選,若未傳則使用現有內容作為基底)" },
231
271
  title: {
@@ -318,6 +358,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
318
358
 
319
359
  try {
320
360
  switch (name) {
361
+ case "block_list": {
362
+ const response = await axios.get(BASE_URL, {
363
+ params: { action: "list_blocks" },
364
+ });
365
+ const payload =
366
+ response.data && typeof response.data === "object"
367
+ ? { ...response.data }
368
+ : response.data;
369
+ return {
370
+ content: [
371
+ { type: "text", text: JSON.stringify(payload, null, 2) },
372
+ ],
373
+ };
374
+ }
375
+
321
376
  case "list_notes": {
322
377
  const params = {};
323
378
  if (args.search) params.search = args.search;
@@ -368,6 +423,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
368
423
 
369
424
  case "create_note": {
370
425
  const payload = { content: args.content };
426
+ if (args.b_id !== undefined && args.b_id !== null) {
427
+ payload.b_id = Number(args.b_id);
428
+ }
371
429
  if (args.title !== undefined && args.title !== "") {
372
430
  payload.title = args.title;
373
431
  }
@@ -388,6 +446,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
388
446
  const payload = {
389
447
  c_id: Number(args.c_id),
390
448
  };
449
+ if (args.b_id !== undefined && args.b_id !== null) {
450
+ payload.b_id = Number(args.b_id);
451
+ }
391
452
  if (args.content !== undefined) {
392
453
  payload.content = args.content;
393
454
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "note-mcp-server",
3
- "version": "2.4.1",
3
+ "version": "2.5.0",
4
4
  "description": "MCP (stdio) server for Notes API v2 — list/read/write notes, upload images, Web Push from Cursor and compatible clients.",
5
5
  "main": "index.js",
6
6
  "bin": {