@fietiger/joplin-mcp 1.0.0 → 1.0.1
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/dist/index.js +166 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +194 -14
package/dist/index.js
CHANGED
|
@@ -4,8 +4,14 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
4
4
|
import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import axios from "axios";
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
7
9
|
const JOPLIN_BASE_URL = "http://localhost:41184";
|
|
8
10
|
let JOPLIN_TOKEN = process.env.JOPLIN_TOKEN || "";
|
|
11
|
+
/**
|
|
12
|
+
* 笔记本缓存
|
|
13
|
+
*/
|
|
14
|
+
let notebooksCache = null;
|
|
9
15
|
/**
|
|
10
16
|
* 通用 Joplin API 调用函数
|
|
11
17
|
*/
|
|
@@ -43,10 +49,10 @@ async function callJoplin(method, path, queryParams = {}, jsonData = null) {
|
|
|
43
49
|
// 创建 McpServer 实例 (新 API)
|
|
44
50
|
const server = new McpServer({
|
|
45
51
|
name: "@fietiger/joplin-mcp",
|
|
46
|
-
version: "1.0.
|
|
52
|
+
version: "1.0.1",
|
|
47
53
|
});
|
|
48
54
|
// 注册 get_version 工具
|
|
49
|
-
server.
|
|
55
|
+
server.registerTool("get_version", { description: "获取 MCP 服务和 Joplin API 的版本信息" }, async () => {
|
|
50
56
|
let apiStatus = "Unknown";
|
|
51
57
|
let joplinResponse = "Unknown";
|
|
52
58
|
try {
|
|
@@ -67,7 +73,7 @@ server.tool("get_version", {}, async () => {
|
|
|
67
73
|
{
|
|
68
74
|
type: "text",
|
|
69
75
|
text: JSON.stringify({
|
|
70
|
-
mcp_version: "1.0.
|
|
76
|
+
mcp_version: "1.0.1",
|
|
71
77
|
joplin_api_url: JOPLIN_BASE_URL,
|
|
72
78
|
joplin_api_status: apiStatus,
|
|
73
79
|
joplin_response: joplinResponse,
|
|
@@ -77,23 +83,38 @@ server.tool("get_version", {}, async () => {
|
|
|
77
83
|
};
|
|
78
84
|
});
|
|
79
85
|
// 注册 list_notebooks 工具
|
|
80
|
-
server.
|
|
86
|
+
server.registerTool("list_notebooks", { description: "列出所有笔记本" }, async () => {
|
|
87
|
+
if (notebooksCache) {
|
|
88
|
+
console.error("使用本地缓存的笔记本列表");
|
|
89
|
+
return {
|
|
90
|
+
content: [
|
|
91
|
+
{
|
|
92
|
+
type: "text",
|
|
93
|
+
text: JSON.stringify(notebooksCache, null, 2),
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
};
|
|
97
|
+
}
|
|
81
98
|
const results = await callJoplin("GET", "folders");
|
|
99
|
+
notebooksCache = results.items || [];
|
|
82
100
|
return {
|
|
83
101
|
content: [
|
|
84
102
|
{
|
|
85
103
|
type: "text",
|
|
86
|
-
text: JSON.stringify(
|
|
104
|
+
text: JSON.stringify(notebooksCache, null, 2),
|
|
87
105
|
},
|
|
88
106
|
],
|
|
89
107
|
};
|
|
90
108
|
});
|
|
91
109
|
// 注册 create_note 工具
|
|
92
|
-
server.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
110
|
+
server.registerTool("create_note", {
|
|
111
|
+
description: "在指定笔记本中创建新笔记",
|
|
112
|
+
inputSchema: {
|
|
113
|
+
title: z.string().describe("笔记标题"),
|
|
114
|
+
body: z.string().describe("笔记内容 (Markdown 格式)"),
|
|
115
|
+
parent_id: z.string().optional().describe("笔记本 ID (可选)"),
|
|
116
|
+
tags: z.array(z.string()).optional().describe("标签列表 (可选)"),
|
|
117
|
+
}
|
|
97
118
|
}, async ({ title, body, parent_id, tags }) => {
|
|
98
119
|
const data = { title, body };
|
|
99
120
|
if (parent_id) {
|
|
@@ -126,8 +147,11 @@ server.tool("create_note", {
|
|
|
126
147
|
};
|
|
127
148
|
});
|
|
128
149
|
// 注册 search_notes 工具
|
|
129
|
-
server.
|
|
130
|
-
|
|
150
|
+
server.registerTool("search_notes", {
|
|
151
|
+
description: "搜索笔记",
|
|
152
|
+
inputSchema: {
|
|
153
|
+
query: z.string().describe("搜索关键词"),
|
|
154
|
+
}
|
|
131
155
|
}, async ({ query }) => {
|
|
132
156
|
const results = await callJoplin("GET", "search", { query });
|
|
133
157
|
return {
|
|
@@ -140,8 +164,11 @@ server.tool("search_notes", {
|
|
|
140
164
|
};
|
|
141
165
|
});
|
|
142
166
|
// 注册 get_note 工具
|
|
143
|
-
server.
|
|
144
|
-
|
|
167
|
+
server.registerTool("get_note", {
|
|
168
|
+
description: "获取指定 ID 的笔记详情",
|
|
169
|
+
inputSchema: {
|
|
170
|
+
note_id: z.string().describe("笔记 ID"),
|
|
171
|
+
}
|
|
145
172
|
}, async ({ note_id }) => {
|
|
146
173
|
const result = await callJoplin("GET", `notes/${note_id}`, {
|
|
147
174
|
fields: "id,title,body,parent_id,created_time,updated_time",
|
|
@@ -155,6 +182,131 @@ server.tool("get_note", {
|
|
|
155
182
|
],
|
|
156
183
|
};
|
|
157
184
|
});
|
|
185
|
+
// 注册 update_note 工具
|
|
186
|
+
server.registerTool("update_note", {
|
|
187
|
+
description: "修改已有的笔记",
|
|
188
|
+
inputSchema: {
|
|
189
|
+
note_id: z.string().describe("要修改的笔记 ID"),
|
|
190
|
+
title: z.string().optional().describe("新标题"),
|
|
191
|
+
body: z.string().optional().describe("新内容 (Markdown 格式)"),
|
|
192
|
+
parent_id: z.string().optional().describe("新笔记本 ID"),
|
|
193
|
+
tags: z.array(z.string()).optional().describe("新标签列表 (将覆盖旧标签)"),
|
|
194
|
+
}
|
|
195
|
+
}, async ({ note_id, title, body, parent_id, tags }) => {
|
|
196
|
+
const data = {};
|
|
197
|
+
if (title !== undefined)
|
|
198
|
+
data.title = title;
|
|
199
|
+
if (body !== undefined)
|
|
200
|
+
data.body = body;
|
|
201
|
+
if (parent_id !== undefined)
|
|
202
|
+
data.parent_id = parent_id;
|
|
203
|
+
const note = await callJoplin("PUT", `notes/${note_id}`, {}, data);
|
|
204
|
+
if (tags !== undefined && Array.isArray(tags)) {
|
|
205
|
+
// 在 Joplin API 中,设置标签通常是覆盖式的,但需要通过特定接口
|
|
206
|
+
const tagTitles = tags.join(",");
|
|
207
|
+
await callJoplin("PUT", `notes/${note_id}`, {}, { tags: tagTitles });
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
content: [
|
|
211
|
+
{
|
|
212
|
+
type: "text",
|
|
213
|
+
text: JSON.stringify(note, null, 2),
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
};
|
|
217
|
+
});
|
|
218
|
+
// 注册 delete_note 工具
|
|
219
|
+
server.registerTool("delete_note", {
|
|
220
|
+
description: "删除指定 ID 的笔记",
|
|
221
|
+
inputSchema: {
|
|
222
|
+
note_id: z.string().describe("要删除的笔记 ID"),
|
|
223
|
+
}
|
|
224
|
+
}, async ({ note_id }) => {
|
|
225
|
+
await callJoplin("DELETE", `notes/${note_id}`);
|
|
226
|
+
return {
|
|
227
|
+
content: [
|
|
228
|
+
{
|
|
229
|
+
type: "text",
|
|
230
|
+
text: `笔记 ${note_id} 已成功删除`,
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
};
|
|
234
|
+
});
|
|
235
|
+
// 注册 list_tags 工具
|
|
236
|
+
server.registerTool("list_tags", { description: "列出所有标签" }, async () => {
|
|
237
|
+
const results = await callJoplin("GET", "tags");
|
|
238
|
+
return {
|
|
239
|
+
content: [
|
|
240
|
+
{
|
|
241
|
+
type: "text",
|
|
242
|
+
text: JSON.stringify(results.items || [], null, 2),
|
|
243
|
+
},
|
|
244
|
+
],
|
|
245
|
+
};
|
|
246
|
+
});
|
|
247
|
+
// 注册 create_notebook 工具
|
|
248
|
+
server.registerTool("create_notebook", {
|
|
249
|
+
description: "创建新笔记本",
|
|
250
|
+
inputSchema: {
|
|
251
|
+
title: z.string().describe("笔记本名称"),
|
|
252
|
+
parent_id: z.string().optional().describe("父笔记本 ID"),
|
|
253
|
+
}
|
|
254
|
+
}, async ({ title, parent_id }) => {
|
|
255
|
+
const data = { title };
|
|
256
|
+
if (parent_id)
|
|
257
|
+
data.parent_id = parent_id;
|
|
258
|
+
const folder = await callJoplin("POST", "folders", {}, data);
|
|
259
|
+
notebooksCache = null; // 清除缓存
|
|
260
|
+
return {
|
|
261
|
+
content: [
|
|
262
|
+
{
|
|
263
|
+
type: "text",
|
|
264
|
+
text: JSON.stringify(folder, null, 2),
|
|
265
|
+
},
|
|
266
|
+
],
|
|
267
|
+
};
|
|
268
|
+
});
|
|
269
|
+
// 注册 attach_file 工具
|
|
270
|
+
server.registerTool("attach_file", {
|
|
271
|
+
description: "上传文件并关联到笔记",
|
|
272
|
+
inputSchema: {
|
|
273
|
+
note_id: z.string().describe("笔记 ID"),
|
|
274
|
+
file_path: z.string().describe("本地文件路径"),
|
|
275
|
+
title: z.string().optional().describe("附件标题 (可选)"),
|
|
276
|
+
}
|
|
277
|
+
}, async ({ note_id, file_path, title }) => {
|
|
278
|
+
if (!fs.existsSync(file_path)) {
|
|
279
|
+
throw new McpError(ErrorCode.InvalidParams, `文件不存在: ${file_path}`);
|
|
280
|
+
}
|
|
281
|
+
const fileName = title || path.basename(file_path);
|
|
282
|
+
const fileStats = fs.statSync(file_path);
|
|
283
|
+
// 使用 FormData 发送文件
|
|
284
|
+
const formData = new FormData();
|
|
285
|
+
const fileBuffer = fs.readFileSync(file_path);
|
|
286
|
+
const blob = new Blob([fileBuffer]);
|
|
287
|
+
formData.append("data", blob, fileName);
|
|
288
|
+
// 如果有其他属性,可以作为 props 传入 (Joplin API 要求)
|
|
289
|
+
const props = JSON.stringify({ title: fileName, parent_id: note_id });
|
|
290
|
+
formData.append("props", props);
|
|
291
|
+
// 调用 resources 接口上传
|
|
292
|
+
const resource = await callJoplin("POST", "resources", {}, formData);
|
|
293
|
+
// 将资源链接添加到笔记末尾
|
|
294
|
+
const note = await callJoplin("GET", `notes/${note_id}`, { fields: "body" });
|
|
295
|
+
const updatedBody = `${note.body}\n\n`;
|
|
296
|
+
await callJoplin("PUT", `notes/${note_id}`, {}, { body: updatedBody });
|
|
297
|
+
return {
|
|
298
|
+
content: [
|
|
299
|
+
{
|
|
300
|
+
type: "text",
|
|
301
|
+
text: JSON.stringify({
|
|
302
|
+
message: "文件已成功上传并关联到笔记",
|
|
303
|
+
resource_id: resource.id,
|
|
304
|
+
file_name: fileName
|
|
305
|
+
}, null, 2),
|
|
306
|
+
},
|
|
307
|
+
],
|
|
308
|
+
};
|
|
309
|
+
});
|
|
158
310
|
async function main() {
|
|
159
311
|
const transport = new StdioServerTransport();
|
|
160
312
|
await server.connect(transport);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -4,10 +4,17 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
4
4
|
import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import axios from "axios";
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
7
9
|
|
|
8
10
|
const JOPLIN_BASE_URL = "http://localhost:41184";
|
|
9
11
|
let JOPLIN_TOKEN = process.env.JOPLIN_TOKEN || "";
|
|
10
12
|
|
|
13
|
+
/**
|
|
14
|
+
* 笔记本缓存
|
|
15
|
+
*/
|
|
16
|
+
let notebooksCache: any[] | null = null;
|
|
17
|
+
|
|
11
18
|
/**
|
|
12
19
|
* 通用 Joplin API 调用函数
|
|
13
20
|
*/
|
|
@@ -52,11 +59,11 @@ async function callJoplin(method: string, path: string, queryParams: any = {}, j
|
|
|
52
59
|
// 创建 McpServer 实例 (新 API)
|
|
53
60
|
const server = new McpServer({
|
|
54
61
|
name: "@fietiger/joplin-mcp",
|
|
55
|
-
version: "1.0.
|
|
62
|
+
version: "1.0.1",
|
|
56
63
|
});
|
|
57
64
|
|
|
58
65
|
// 注册 get_version 工具
|
|
59
|
-
server.
|
|
66
|
+
server.registerTool("get_version", { description: "获取 MCP 服务和 Joplin API 的版本信息" }, async () => {
|
|
60
67
|
let apiStatus = "Unknown";
|
|
61
68
|
let joplinResponse = "Unknown";
|
|
62
69
|
try {
|
|
@@ -76,7 +83,7 @@ server.tool("get_version", {}, async () => {
|
|
|
76
83
|
{
|
|
77
84
|
type: "text",
|
|
78
85
|
text: JSON.stringify({
|
|
79
|
-
mcp_version: "1.0.
|
|
86
|
+
mcp_version: "1.0.1",
|
|
80
87
|
joplin_api_url: JOPLIN_BASE_URL,
|
|
81
88
|
joplin_api_status: apiStatus,
|
|
82
89
|
joplin_response: joplinResponse,
|
|
@@ -87,26 +94,42 @@ server.tool("get_version", {}, async () => {
|
|
|
87
94
|
});
|
|
88
95
|
|
|
89
96
|
// 注册 list_notebooks 工具
|
|
90
|
-
server.
|
|
97
|
+
server.registerTool("list_notebooks", { description: "列出所有笔记本" }, async () => {
|
|
98
|
+
if (notebooksCache) {
|
|
99
|
+
console.error("使用本地缓存的笔记本列表");
|
|
100
|
+
return {
|
|
101
|
+
content: [
|
|
102
|
+
{
|
|
103
|
+
type: "text",
|
|
104
|
+
text: JSON.stringify(notebooksCache, null, 2),
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
91
110
|
const results = await callJoplin("GET", "folders");
|
|
111
|
+
notebooksCache = results.items || [];
|
|
92
112
|
return {
|
|
93
113
|
content: [
|
|
94
114
|
{
|
|
95
115
|
type: "text",
|
|
96
|
-
text: JSON.stringify(
|
|
116
|
+
text: JSON.stringify(notebooksCache, null, 2),
|
|
97
117
|
},
|
|
98
118
|
],
|
|
99
119
|
};
|
|
100
120
|
});
|
|
101
121
|
|
|
102
122
|
// 注册 create_note 工具
|
|
103
|
-
server.
|
|
123
|
+
server.registerTool(
|
|
104
124
|
"create_note",
|
|
105
125
|
{
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
126
|
+
description: "在指定笔记本中创建新笔记",
|
|
127
|
+
inputSchema: {
|
|
128
|
+
title: z.string().describe("笔记标题"),
|
|
129
|
+
body: z.string().describe("笔记内容 (Markdown 格式)"),
|
|
130
|
+
parent_id: z.string().optional().describe("笔记本 ID (可选)"),
|
|
131
|
+
tags: z.array(z.string()).optional().describe("标签列表 (可选)"),
|
|
132
|
+
}
|
|
110
133
|
},
|
|
111
134
|
async ({ title, body, parent_id, tags }) => {
|
|
112
135
|
const data: any = { title, body };
|
|
@@ -145,10 +168,13 @@ server.tool(
|
|
|
145
168
|
);
|
|
146
169
|
|
|
147
170
|
// 注册 search_notes 工具
|
|
148
|
-
server.
|
|
171
|
+
server.registerTool(
|
|
149
172
|
"search_notes",
|
|
150
173
|
{
|
|
151
|
-
|
|
174
|
+
description: "搜索笔记",
|
|
175
|
+
inputSchema: {
|
|
176
|
+
query: z.string().describe("搜索关键词"),
|
|
177
|
+
}
|
|
152
178
|
},
|
|
153
179
|
async ({ query }) => {
|
|
154
180
|
const results = await callJoplin("GET", "search", { query });
|
|
@@ -164,10 +190,13 @@ server.tool(
|
|
|
164
190
|
);
|
|
165
191
|
|
|
166
192
|
// 注册 get_note 工具
|
|
167
|
-
server.
|
|
193
|
+
server.registerTool(
|
|
168
194
|
"get_note",
|
|
169
195
|
{
|
|
170
|
-
|
|
196
|
+
description: "获取指定 ID 的笔记详情",
|
|
197
|
+
inputSchema: {
|
|
198
|
+
note_id: z.string().describe("笔记 ID"),
|
|
199
|
+
}
|
|
171
200
|
},
|
|
172
201
|
async ({ note_id }) => {
|
|
173
202
|
const result = await callJoplin("GET", `notes/${note_id}`, {
|
|
@@ -184,6 +213,157 @@ server.tool(
|
|
|
184
213
|
}
|
|
185
214
|
);
|
|
186
215
|
|
|
216
|
+
// 注册 update_note 工具
|
|
217
|
+
server.registerTool(
|
|
218
|
+
"update_note",
|
|
219
|
+
{
|
|
220
|
+
description: "修改已有的笔记",
|
|
221
|
+
inputSchema: {
|
|
222
|
+
note_id: z.string().describe("要修改的笔记 ID"),
|
|
223
|
+
title: z.string().optional().describe("新标题"),
|
|
224
|
+
body: z.string().optional().describe("新内容 (Markdown 格式)"),
|
|
225
|
+
parent_id: z.string().optional().describe("新笔记本 ID"),
|
|
226
|
+
tags: z.array(z.string()).optional().describe("新标签列表 (将覆盖旧标签)"),
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
async ({ note_id, title, body, parent_id, tags }) => {
|
|
230
|
+
const data: any = {};
|
|
231
|
+
if (title !== undefined) data.title = title;
|
|
232
|
+
if (body !== undefined) data.body = body;
|
|
233
|
+
if (parent_id !== undefined) data.parent_id = parent_id;
|
|
234
|
+
|
|
235
|
+
const note = await callJoplin("PUT", `notes/${note_id}`, {}, data);
|
|
236
|
+
|
|
237
|
+
if (tags !== undefined && Array.isArray(tags)) {
|
|
238
|
+
// 在 Joplin API 中,设置标签通常是覆盖式的,但需要通过特定接口
|
|
239
|
+
const tagTitles = tags.join(",");
|
|
240
|
+
await callJoplin("PUT", `notes/${note_id}`, {}, { tags: tagTitles });
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
content: [
|
|
245
|
+
{
|
|
246
|
+
type: "text",
|
|
247
|
+
text: JSON.stringify(note, null, 2),
|
|
248
|
+
},
|
|
249
|
+
],
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
// 注册 delete_note 工具
|
|
255
|
+
server.registerTool(
|
|
256
|
+
"delete_note",
|
|
257
|
+
{
|
|
258
|
+
description: "删除指定 ID 的笔记",
|
|
259
|
+
inputSchema: {
|
|
260
|
+
note_id: z.string().describe("要删除的笔记 ID"),
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
async ({ note_id }) => {
|
|
264
|
+
await callJoplin("DELETE", `notes/${note_id}`);
|
|
265
|
+
return {
|
|
266
|
+
content: [
|
|
267
|
+
{
|
|
268
|
+
type: "text",
|
|
269
|
+
text: `笔记 ${note_id} 已成功删除`,
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
// 注册 list_tags 工具
|
|
277
|
+
server.registerTool("list_tags", { description: "列出所有标签" }, async () => {
|
|
278
|
+
const results = await callJoplin("GET", "tags");
|
|
279
|
+
return {
|
|
280
|
+
content: [
|
|
281
|
+
{
|
|
282
|
+
type: "text",
|
|
283
|
+
text: JSON.stringify(results.items || [], null, 2),
|
|
284
|
+
},
|
|
285
|
+
],
|
|
286
|
+
};
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// 注册 create_notebook 工具
|
|
290
|
+
server.registerTool(
|
|
291
|
+
"create_notebook",
|
|
292
|
+
{
|
|
293
|
+
description: "创建新笔记本",
|
|
294
|
+
inputSchema: {
|
|
295
|
+
title: z.string().describe("笔记本名称"),
|
|
296
|
+
parent_id: z.string().optional().describe("父笔记本 ID"),
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
async ({ title, parent_id }) => {
|
|
300
|
+
const data: any = { title };
|
|
301
|
+
if (parent_id) data.parent_id = parent_id;
|
|
302
|
+
const folder = await callJoplin("POST", "folders", {}, data);
|
|
303
|
+
notebooksCache = null; // 清除缓存
|
|
304
|
+
return {
|
|
305
|
+
content: [
|
|
306
|
+
{
|
|
307
|
+
type: "text",
|
|
308
|
+
text: JSON.stringify(folder, null, 2),
|
|
309
|
+
},
|
|
310
|
+
],
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
// 注册 attach_file 工具
|
|
316
|
+
server.registerTool(
|
|
317
|
+
"attach_file",
|
|
318
|
+
{
|
|
319
|
+
description: "上传文件并关联到笔记",
|
|
320
|
+
inputSchema: {
|
|
321
|
+
note_id: z.string().describe("笔记 ID"),
|
|
322
|
+
file_path: z.string().describe("本地文件路径"),
|
|
323
|
+
title: z.string().optional().describe("附件标题 (可选)"),
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
async ({ note_id, file_path, title }) => {
|
|
327
|
+
if (!fs.existsSync(file_path)) {
|
|
328
|
+
throw new McpError(ErrorCode.InvalidParams, `文件不存在: ${file_path}`);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const fileName = title || path.basename(file_path);
|
|
332
|
+
const fileStats = fs.statSync(file_path);
|
|
333
|
+
|
|
334
|
+
// 使用 FormData 发送文件
|
|
335
|
+
const formData = new FormData();
|
|
336
|
+
const fileBuffer = fs.readFileSync(file_path);
|
|
337
|
+
const blob = new Blob([fileBuffer]);
|
|
338
|
+
formData.append("data", blob, fileName);
|
|
339
|
+
|
|
340
|
+
// 如果有其他属性,可以作为 props 传入 (Joplin API 要求)
|
|
341
|
+
const props = JSON.stringify({ title: fileName, parent_id: note_id });
|
|
342
|
+
formData.append("props", props);
|
|
343
|
+
|
|
344
|
+
// 调用 resources 接口上传
|
|
345
|
+
const resource = await callJoplin("POST", "resources", {}, formData);
|
|
346
|
+
|
|
347
|
+
// 将资源链接添加到笔记末尾
|
|
348
|
+
const note = await callJoplin("GET", `notes/${note_id}`, { fields: "body" });
|
|
349
|
+
const updatedBody = `${note.body}\n\n`;
|
|
350
|
+
await callJoplin("PUT", `notes/${note_id}`, {}, { body: updatedBody });
|
|
351
|
+
|
|
352
|
+
return {
|
|
353
|
+
content: [
|
|
354
|
+
{
|
|
355
|
+
type: "text",
|
|
356
|
+
text: JSON.stringify({
|
|
357
|
+
message: "文件已成功上传并关联到笔记",
|
|
358
|
+
resource_id: resource.id,
|
|
359
|
+
file_name: fileName
|
|
360
|
+
}, null, 2),
|
|
361
|
+
},
|
|
362
|
+
],
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
);
|
|
366
|
+
|
|
187
367
|
async function main() {
|
|
188
368
|
const transport = new StdioServerTransport();
|
|
189
369
|
await server.connect(transport);
|