joplin-mcp-server 1.0.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 (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +384 -0
  3. package/dist/bin/cli.d.ts +2 -0
  4. package/dist/bin/cli.js +7 -0
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.js +204 -0
  7. package/dist/lib/joplin-api-client.d.ts +23 -0
  8. package/dist/lib/joplin-api-client.js +110 -0
  9. package/dist/lib/logger.d.ts +21 -0
  10. package/dist/lib/logger.js +68 -0
  11. package/dist/lib/parse-args.d.ts +2 -0
  12. package/dist/lib/parse-args.js +81 -0
  13. package/dist/lib/tools/base-tool.d.ts +27 -0
  14. package/dist/lib/tools/base-tool.js +24 -0
  15. package/dist/lib/tools/create-folder.d.ts +9 -0
  16. package/dist/lib/tools/create-folder.js +79 -0
  17. package/dist/lib/tools/create-note.d.ts +13 -0
  18. package/dist/lib/tools/create-note.js +88 -0
  19. package/dist/lib/tools/delete-folder.d.ts +10 -0
  20. package/dist/lib/tools/delete-folder.js +138 -0
  21. package/dist/lib/tools/delete-note.d.ts +9 -0
  22. package/dist/lib/tools/delete-note.js +92 -0
  23. package/dist/lib/tools/edit-folder.d.ts +10 -0
  24. package/dist/lib/tools/edit-folder.js +136 -0
  25. package/dist/lib/tools/edit-note.d.ts +15 -0
  26. package/dist/lib/tools/edit-note.js +153 -0
  27. package/dist/lib/tools/index.d.ts +12 -0
  28. package/dist/lib/tools/index.js +12 -0
  29. package/dist/lib/tools/list-notebooks.d.ts +7 -0
  30. package/dist/lib/tools/list-notebooks.js +59 -0
  31. package/dist/lib/tools/read-multi-note.d.ts +5 -0
  32. package/dist/lib/tools/read-multi-note.js +108 -0
  33. package/dist/lib/tools/read-note.d.ts +5 -0
  34. package/dist/lib/tools/read-note.js +80 -0
  35. package/dist/lib/tools/read-notebook.d.ts +5 -0
  36. package/dist/lib/tools/read-notebook.js +66 -0
  37. package/dist/lib/tools/search-notes.d.ts +5 -0
  38. package/dist/lib/tools/search-notes.js +68 -0
  39. package/dist/tests/integration/joplin-integration.test.d.ts +1 -0
  40. package/dist/tests/integration/joplin-integration.test.js +117 -0
  41. package/dist/tests/manual/create-folder.test.d.ts +1 -0
  42. package/dist/tests/manual/create-folder.test.js +81 -0
  43. package/dist/tests/manual/create-note.test.d.ts +1 -0
  44. package/dist/tests/manual/create-note.test.js +84 -0
  45. package/dist/tests/manual/delete-folder.test.d.ts +1 -0
  46. package/dist/tests/manual/delete-folder.test.js +118 -0
  47. package/dist/tests/manual/delete-note.test.d.ts +1 -0
  48. package/dist/tests/manual/delete-note.test.js +101 -0
  49. package/dist/tests/manual/edit-folder.test.d.ts +1 -0
  50. package/dist/tests/manual/edit-folder.test.js +104 -0
  51. package/dist/tests/manual/edit-note.test.d.ts +1 -0
  52. package/dist/tests/manual/edit-note.test.js +118 -0
  53. package/dist/tests/manual/list-notebooks.test.d.ts +1 -0
  54. package/dist/tests/manual/list-notebooks.test.js +42 -0
  55. package/dist/tests/manual/read-note.test.d.ts +1 -0
  56. package/dist/tests/manual/read-note.test.js +54 -0
  57. package/dist/tests/manual/search-notes.test.d.ts +1 -0
  58. package/dist/tests/manual/search-notes.test.js +43 -0
  59. package/dist/tests/unit/create-tools.test.d.ts +1 -0
  60. package/dist/tests/unit/create-tools.test.js +223 -0
  61. package/dist/tests/unit/delete-tools.test.d.ts +1 -0
  62. package/dist/tests/unit/delete-tools.test.js +225 -0
  63. package/dist/tests/unit/edit-tools.test.d.ts +1 -0
  64. package/dist/tests/unit/edit-tools.test.js +261 -0
  65. package/dist/tests/unit/joplin-api-client.test.d.ts +1 -0
  66. package/dist/tests/unit/joplin-api-client.test.js +154 -0
  67. package/dist/vitest.config.d.ts +2 -0
  68. package/dist/vitest.config.js +22 -0
  69. package/dist/vitest.setup.d.ts +1 -0
  70. package/dist/vitest.setup.js +24 -0
  71. package/package.json +58 -0
@@ -0,0 +1,110 @@
1
+ import axios from "axios";
2
+ class JoplinAPIClient {
3
+ baseURL;
4
+ token;
5
+ constructor({ port = 41184, token }) {
6
+ this.baseURL = `http://127.0.0.1:${port}`;
7
+ this.token = token;
8
+ }
9
+ async serviceAvailable() {
10
+ try {
11
+ const response = await axios.get(`${this.baseURL}/ping`);
12
+ return response.status === 200 && response.data === "JoplinClipperServer";
13
+ }
14
+ catch (error) {
15
+ process.stderr.write(`Error checking Joplin service availability: ${error}\n`);
16
+ return false;
17
+ }
18
+ }
19
+ async getAllItems(path, options = {}) {
20
+ let page = 1;
21
+ const items = [];
22
+ try {
23
+ while (true) {
24
+ const response = await this.get(path, this.mergeRequestOptions(options, { query: { page } }));
25
+ // Validate response format
26
+ if (!response || typeof response !== "object" || !Array.isArray(response.items)) {
27
+ throw new Error(`Unexpected response format from Joplin API for path: ${path}`);
28
+ }
29
+ items.push(...response.items);
30
+ page += 1;
31
+ if (!response.has_more)
32
+ break;
33
+ }
34
+ return items;
35
+ }
36
+ catch (error) {
37
+ process.stderr.write(`Error in getAllItems for path ${path}: ${error}\n`);
38
+ throw error;
39
+ }
40
+ }
41
+ async get(path, options = {}) {
42
+ try {
43
+ const { data } = await axios.get(`${this.baseURL}${path}`, {
44
+ params: this.requestOptions(options).query,
45
+ });
46
+ return data;
47
+ }
48
+ catch (error) {
49
+ process.stderr.write(`Error in GET request for path ${path}: ${error}\n`);
50
+ throw error;
51
+ }
52
+ }
53
+ async post(path, body, options = {}) {
54
+ try {
55
+ const { data } = await axios.post(`${this.baseURL}${path}`, body, {
56
+ params: this.requestOptions(options).query,
57
+ });
58
+ return data;
59
+ }
60
+ catch (error) {
61
+ process.stderr.write(`Error in POST request for path ${path}: ${error}\n`);
62
+ throw error;
63
+ }
64
+ }
65
+ async delete(path, options = {}) {
66
+ try {
67
+ const { data } = await axios.delete(`${this.baseURL}${path}`, {
68
+ params: this.requestOptions(options).query,
69
+ });
70
+ return data;
71
+ }
72
+ catch (error) {
73
+ process.stderr.write(`Error in DELETE request for path ${path}: ${error}\n`);
74
+ throw error;
75
+ }
76
+ }
77
+ async put(path, body, options = {}) {
78
+ try {
79
+ const { data } = await axios.put(`${this.baseURL}${path}`, body, {
80
+ params: this.requestOptions(options).query,
81
+ });
82
+ return data;
83
+ }
84
+ catch (error) {
85
+ process.stderr.write(`Error in PUT request for path ${path}: ${error}\n`);
86
+ throw error;
87
+ }
88
+ }
89
+ requestOptions(options = {}) {
90
+ return this.mergeRequestOptions({
91
+ query: { token: this.token },
92
+ }, options);
93
+ }
94
+ mergeRequestOptions(options1, options2) {
95
+ return {
96
+ query: {
97
+ ...(options1.query || {}),
98
+ ...(options2.query || {}),
99
+ },
100
+ ...this.except(options1, "query"),
101
+ ...this.except(options2, "query"),
102
+ };
103
+ }
104
+ except(obj, key) {
105
+ const result = { ...obj };
106
+ delete result[key];
107
+ return result;
108
+ }
109
+ }
110
+ export default JoplinAPIClient;
@@ -0,0 +1,21 @@
1
+ type LogLevel = "error" | "warn" | "info" | "debug";
2
+ interface LoggerOptions {
3
+ logLevel?: LogLevel;
4
+ enableTimestamp?: boolean;
5
+ }
6
+ declare class Logger {
7
+ private readonly logLevel;
8
+ private readonly logLevels;
9
+ private readonly enableTimestamp;
10
+ constructor(options?: LoggerOptions);
11
+ getTimestamp(): string;
12
+ formatMessage(level: LogLevel, message: string): string;
13
+ shouldLog(level: LogLevel): boolean;
14
+ error(message: string): void;
15
+ warn(message: string): void;
16
+ info(message: string): void;
17
+ debug(message: string): void;
18
+ logObject(level: LogLevel, label: string, obj: any): void;
19
+ }
20
+ declare const logger: Logger;
21
+ export default logger;
@@ -0,0 +1,68 @@
1
+ class Logger {
2
+ logLevel;
3
+ logLevels;
4
+ enableTimestamp;
5
+ constructor(options = {}) {
6
+ this.logLevel = options.logLevel || "info";
7
+ this.logLevels = {
8
+ error: 0,
9
+ warn: 1,
10
+ info: 2,
11
+ debug: 3,
12
+ };
13
+ this.enableTimestamp = options.enableTimestamp !== false;
14
+ }
15
+ getTimestamp() {
16
+ return new Date().toISOString();
17
+ }
18
+ formatMessage(level, message) {
19
+ const timestamp = this.enableTimestamp ? `[${this.getTimestamp()}] ` : "";
20
+ return `${timestamp}[${level.toUpperCase()}] ${message}`;
21
+ }
22
+ shouldLog(level) {
23
+ return this.logLevels[level] <= this.logLevels[this.logLevel];
24
+ }
25
+ error(message) {
26
+ if (this.shouldLog("error")) {
27
+ console.error(this.formatMessage("error", message));
28
+ }
29
+ }
30
+ warn(message) {
31
+ if (this.shouldLog("warn")) {
32
+ console.warn(this.formatMessage("warn", message));
33
+ }
34
+ }
35
+ info(message) {
36
+ if (this.shouldLog("info")) {
37
+ console.info(this.formatMessage("info", message));
38
+ }
39
+ }
40
+ debug(message) {
41
+ if (this.shouldLog("debug")) {
42
+ console.debug(this.formatMessage("debug", message));
43
+ }
44
+ }
45
+ logObject(level, label, obj) {
46
+ if (this.shouldLog(level)) {
47
+ const message = `${label}: ${JSON.stringify(obj, null, 2)}`;
48
+ switch (level) {
49
+ case "error":
50
+ this.error(message);
51
+ break;
52
+ case "warn":
53
+ this.warn(message);
54
+ break;
55
+ case "info":
56
+ this.info(message);
57
+ break;
58
+ case "debug":
59
+ this.debug(message);
60
+ break;
61
+ default:
62
+ this.info(message);
63
+ }
64
+ }
65
+ }
66
+ }
67
+ const logger = new Logger({ logLevel: process.env.LOG_LEVEL || "info" });
68
+ export default logger;
@@ -0,0 +1,2 @@
1
+ declare function parseArgs(): string[];
2
+ export default parseArgs;
@@ -0,0 +1,81 @@
1
+ import dotenv from "dotenv";
2
+ import { fileURLToPath } from "url";
3
+ import { dirname, resolve } from "path";
4
+ const __filename = fileURLToPath(import.meta.url);
5
+ const __dirname = dirname(__filename);
6
+ function parseArgs() {
7
+ const args = process.argv.slice(2);
8
+ // Handle --env-file
9
+ if (args.includes("--env-file")) {
10
+ const envFileIndex = args.indexOf("--env-file");
11
+ const envFile = args[envFileIndex + 1];
12
+ if (!envFile || envFile.startsWith("--")) {
13
+ process.stderr.write("Error: --env-file requires a file path\n");
14
+ process.exit(1);
15
+ }
16
+ // Remove the --env-file and its value from args
17
+ args.splice(envFileIndex, 2);
18
+ // Load the environment variables from the specified file
19
+ dotenv.config({ path: resolve(process.cwd(), envFile) });
20
+ }
21
+ else {
22
+ // Load from default .env file
23
+ dotenv.config();
24
+ }
25
+ // Handle --port
26
+ if (args.includes("--port")) {
27
+ const portIndex = args.indexOf("--port");
28
+ const port = args[portIndex + 1];
29
+ if (!port || port.startsWith("--")) {
30
+ process.stderr.write("Error: --port requires a port number\n");
31
+ process.exit(1);
32
+ }
33
+ // Remove the --port and its value from args
34
+ args.splice(portIndex, 2);
35
+ // Set environment variable
36
+ process.env.JOPLIN_PORT = port;
37
+ }
38
+ // Handle --token
39
+ if (args.includes("--token")) {
40
+ const tokenIndex = args.indexOf("--token");
41
+ const token = args[tokenIndex + 1];
42
+ if (!token || token.startsWith("--")) {
43
+ process.stderr.write("Error: --token requires a token value\n");
44
+ process.exit(1);
45
+ }
46
+ // Remove the --token and its value from args
47
+ args.splice(tokenIndex, 2);
48
+ // Set environment variable
49
+ process.env.JOPLIN_TOKEN = token;
50
+ }
51
+ // Handle --help
52
+ if (args.includes("--help") || args.includes("-h")) {
53
+ process.stderr.write(`
54
+ Joplin MCP Server
55
+
56
+ USAGE:
57
+ joplin-mcp-server [OPTIONS]
58
+
59
+ OPTIONS:
60
+ --env-file <file> Load environment variables from file
61
+ --port <port> Joplin port (default: 41184)
62
+ --token <token> Joplin API token
63
+ --help, -h Show this help message
64
+
65
+ ENVIRONMENT VARIABLES:
66
+ JOPLIN_PORT Joplin port (default: 41184)
67
+ JOPLIN_TOKEN Joplin API token (required)
68
+ LOG_LEVEL Log level: debug, info, warn, error (default: info)
69
+
70
+ EXAMPLES:
71
+ joplin-mcp-server --port 41184 --token your_token
72
+ joplin-mcp-server --env-file /path/to/.env
73
+ joplin-mcp-server --env-file .env.local --port 41185
74
+
75
+ Find your Joplin token in: Tools > Options > Web Clipper
76
+ `);
77
+ process.exit(0);
78
+ }
79
+ return args;
80
+ }
81
+ export default parseArgs;
@@ -0,0 +1,27 @@
1
+ import JoplinAPIClient from "../joplin-api-client.js";
2
+ interface JoplinFolder {
3
+ id: string;
4
+ title: string;
5
+ parent_id?: string;
6
+ }
7
+ interface JoplinNote {
8
+ id: string;
9
+ title: string;
10
+ body?: string;
11
+ parent_id?: string;
12
+ created_time: number;
13
+ updated_time: number;
14
+ is_todo: boolean;
15
+ todo_completed?: boolean;
16
+ todo_due?: number;
17
+ }
18
+ declare abstract class BaseTool {
19
+ protected apiClient: JoplinAPIClient;
20
+ constructor(apiClient: JoplinAPIClient);
21
+ abstract call(...args: any[]): Promise<string>;
22
+ protected formatError(error: any, context: string): string;
23
+ protected validateId(id: string, type: "note" | "notebook"): string | null;
24
+ protected formatDate(timestamp: number): string;
25
+ }
26
+ export default BaseTool;
27
+ export type { JoplinFolder, JoplinNote };
@@ -0,0 +1,24 @@
1
+ class BaseTool {
2
+ apiClient;
3
+ constructor(apiClient) {
4
+ this.apiClient = apiClient;
5
+ }
6
+ formatError(error, context) {
7
+ process.stderr.write(`${context} error: ${error}\n`);
8
+ return `Error ${context.toLowerCase()}: ${error.message || "Unknown error"}`;
9
+ }
10
+ validateId(id, type) {
11
+ if (!id) {
12
+ return `Please provide a ${type} ID. Example: ${type === "note" ? "read_note" : "read_notebook"} ${type}_id="your-${type}-id"`;
13
+ }
14
+ if (id.length < 10 || !id.match(/[a-f0-9]/i)) {
15
+ const searchHint = type === "note" ? "search_notes" : "list_notebooks";
16
+ return `Error: "${id}" does not appear to be a valid ${type} ID. \n\n${type.charAt(0).toUpperCase() + type.slice(1)} IDs are long alphanumeric strings like "58a0a29f68bc4141b49c99f5d367638a".\n\nUse ${searchHint} to ${type === "note" ? "find notes" : "see all available notebooks"} and their IDs.`;
17
+ }
18
+ return null;
19
+ }
20
+ formatDate(timestamp) {
21
+ return new Date(timestamp).toLocaleString();
22
+ }
23
+ }
24
+ export default BaseTool;
@@ -0,0 +1,9 @@
1
+ import BaseTool from "./base-tool.js";
2
+ interface CreateFolderOptions {
3
+ title: string;
4
+ parent_id?: string | undefined;
5
+ }
6
+ declare class CreateFolder extends BaseTool {
7
+ call(options: CreateFolderOptions): Promise<string>;
8
+ }
9
+ export default CreateFolder;
@@ -0,0 +1,79 @@
1
+ import BaseTool from "./base-tool.js";
2
+ class CreateFolder extends BaseTool {
3
+ async call(options) {
4
+ if (!options || typeof options !== "object") {
5
+ return 'Please provide folder creation options. Example: create_folder {"title": "My Notebook"}';
6
+ }
7
+ // Validate required title
8
+ if (!options.title || typeof options.title !== "string" || options.title.trim() === "") {
9
+ return 'Please provide a title for the folder/notebook. Example: create_folder {"title": "My Notebook"}';
10
+ }
11
+ // Validate parent_id if provided
12
+ if (options.parent_id && (options.parent_id.length < 10 || !options.parent_id.match(/[a-f0-9]/i))) {
13
+ return `Error: "${options.parent_id}" does not appear to be a valid parent notebook ID.\n\nNotebook IDs are long alphanumeric strings like "58a0a29f68bc4141b49c99f5d367638a".\n\nUse list_notebooks to see available notebooks and their IDs, or omit parent_id to create a top-level notebook.`;
14
+ }
15
+ try {
16
+ // Prepare the request body
17
+ const requestBody = {
18
+ title: options.title.trim(),
19
+ };
20
+ if (options.parent_id) {
21
+ requestBody.parent_id = options.parent_id;
22
+ }
23
+ // Create the folder
24
+ const createdFolder = await this.apiClient.post("/folders", requestBody);
25
+ // Validate response
26
+ if (!createdFolder || typeof createdFolder !== "object" || !createdFolder.id) {
27
+ return "Error: Unexpected response format from Joplin API when creating folder";
28
+ }
29
+ // Get parent notebook info if available
30
+ let parentInfo = "Top level";
31
+ if (createdFolder.parent_id) {
32
+ try {
33
+ const parentNotebook = await this.apiClient.get(`/folders/${createdFolder.parent_id}`, {
34
+ query: { fields: "id,title" },
35
+ });
36
+ if (parentNotebook && parentNotebook.title) {
37
+ parentInfo = `Inside "${parentNotebook.title}" (notebook_id: "${createdFolder.parent_id}")`;
38
+ }
39
+ }
40
+ catch (error) {
41
+ // Continue even if we can't get parent info
42
+ parentInfo = `Parent notebook ID: ${createdFolder.parent_id}`;
43
+ }
44
+ }
45
+ // Format success response
46
+ const resultLines = [];
47
+ resultLines.push(`✅ Successfully created notebook!`);
48
+ resultLines.push("");
49
+ resultLines.push(`📁 Notebook Details:`);
50
+ resultLines.push(` Title: "${createdFolder.title}"`);
51
+ resultLines.push(` Notebook ID: ${createdFolder.id}`);
52
+ resultLines.push(` Location: ${parentInfo}`);
53
+ const createdDate = this.formatDate(createdFolder.created_time);
54
+ resultLines.push(` Created: ${createdDate}`);
55
+ resultLines.push("");
56
+ resultLines.push(`🔗 Next steps:`);
57
+ resultLines.push(` - View notebook: read_notebook notebook_id="${createdFolder.id}"`);
58
+ resultLines.push(` - Create a note in it: create_note {"title": "My Note", "parent_id": "${createdFolder.id}"}`);
59
+ resultLines.push(` - View all notebooks: list_notebooks`);
60
+ return resultLines.join("\n");
61
+ }
62
+ catch (error) {
63
+ if (error.response) {
64
+ // Handle specific API errors
65
+ if (error.response.status === 400) {
66
+ return `Error creating notebook: Invalid request data.\n\nPlease check your input parameters. ${error.response.data?.error || ""}`;
67
+ }
68
+ if (error.response.status === 404 && options.parent_id) {
69
+ return `Error: Parent notebook with ID "${options.parent_id}" not found.\n\nUse list_notebooks to see available notebooks and their IDs, or omit parent_id to create a top-level notebook.`;
70
+ }
71
+ if (error.response.status === 409) {
72
+ return `Error: A notebook with the title "${options.title}" might already exist in this location.\n\nTry a different title or check existing notebooks with list_notebooks.`;
73
+ }
74
+ }
75
+ return this.formatError(error, "creating notebook");
76
+ }
77
+ }
78
+ }
79
+ export default CreateFolder;
@@ -0,0 +1,13 @@
1
+ import BaseTool from "./base-tool.js";
2
+ interface CreateNoteOptions {
3
+ title?: string | undefined;
4
+ body?: string | undefined;
5
+ body_html?: string | undefined;
6
+ parent_id?: string | undefined;
7
+ is_todo?: boolean | undefined;
8
+ image_data_url?: string | undefined;
9
+ }
10
+ declare class CreateNote extends BaseTool {
11
+ call(options: CreateNoteOptions): Promise<string>;
12
+ }
13
+ export default CreateNote;
@@ -0,0 +1,88 @@
1
+ import BaseTool from "./base-tool.js";
2
+ class CreateNote extends BaseTool {
3
+ async call(options) {
4
+ if (!options || typeof options !== "object") {
5
+ return 'Please provide note creation options. Example: create_note {"title": "My Note", "body": "Note content"}';
6
+ }
7
+ // Validate that we have at least a title or body
8
+ if (!options.title && !options.body && !options.body_html) {
9
+ return "Please provide at least a title, body, or body_html for the note.";
10
+ }
11
+ // Validate parent_id if provided
12
+ if (options.parent_id && (options.parent_id.length < 10 || !options.parent_id.match(/[a-f0-9]/i))) {
13
+ return `Error: "${options.parent_id}" does not appear to be a valid notebook ID.\n\nNotebook IDs are long alphanumeric strings like "58a0a29f68bc4141b49c99f5d367638a".\n\nUse list_notebooks to see available notebooks and their IDs.`;
14
+ }
15
+ try {
16
+ // Prepare the request body
17
+ const requestBody = {};
18
+ if (options.title)
19
+ requestBody.title = options.title;
20
+ if (options.body)
21
+ requestBody.body = options.body;
22
+ if (options.body_html)
23
+ requestBody.body_html = options.body_html;
24
+ if (options.parent_id)
25
+ requestBody.parent_id = options.parent_id;
26
+ if (options.is_todo !== undefined)
27
+ requestBody.is_todo = options.is_todo;
28
+ if (options.image_data_url)
29
+ requestBody.image_data_url = options.image_data_url;
30
+ // Create the note
31
+ const createdNote = await this.apiClient.post("/notes", requestBody);
32
+ // Validate response
33
+ if (!createdNote || typeof createdNote !== "object" || !createdNote.id) {
34
+ return "Error: Unexpected response format from Joplin API when creating note";
35
+ }
36
+ // Get notebook info if available
37
+ let notebookInfo = "Root level";
38
+ if (createdNote.parent_id) {
39
+ try {
40
+ const notebook = await this.apiClient.get(`/folders/${createdNote.parent_id}`, {
41
+ query: { fields: "id,title" },
42
+ });
43
+ if (notebook && notebook.title) {
44
+ notebookInfo = `"${notebook.title}" (notebook_id: "${createdNote.parent_id}")`;
45
+ }
46
+ }
47
+ catch (error) {
48
+ // Continue even if we can't get notebook info
49
+ notebookInfo = `Notebook ID: ${createdNote.parent_id}`;
50
+ }
51
+ }
52
+ // Format success response
53
+ const resultLines = [];
54
+ resultLines.push(`✅ Successfully created note!`);
55
+ resultLines.push("");
56
+ resultLines.push(`📝 Note Details:`);
57
+ resultLines.push(` Title: "${createdNote.title || "Untitled"}"`);
58
+ resultLines.push(` Note ID: ${createdNote.id}`);
59
+ resultLines.push(` Location: ${notebookInfo}`);
60
+ if (createdNote.is_todo) {
61
+ resultLines.push(` Type: Todo item`);
62
+ }
63
+ const createdDate = this.formatDate(createdNote.created_time);
64
+ resultLines.push(` Created: ${createdDate}`);
65
+ resultLines.push("");
66
+ resultLines.push(`🔗 Next steps:`);
67
+ resultLines.push(` - Read the note: read_note note_id="${createdNote.id}"`);
68
+ if (createdNote.parent_id) {
69
+ resultLines.push(` - View notebook: read_notebook notebook_id="${createdNote.parent_id}"`);
70
+ }
71
+ resultLines.push(` - Search for it: search_notes query="${createdNote.title}"`);
72
+ return resultLines.join("\n");
73
+ }
74
+ catch (error) {
75
+ if (error.response) {
76
+ // Handle specific API errors
77
+ if (error.response.status === 400) {
78
+ return `Error creating note: Invalid request data.\n\nPlease check your input parameters. ${error.response.data?.error || ""}`;
79
+ }
80
+ if (error.response.status === 404 && options.parent_id) {
81
+ return `Error: Notebook with ID "${options.parent_id}" not found.\n\nUse list_notebooks to see available notebooks and their IDs.`;
82
+ }
83
+ }
84
+ return this.formatError(error, "creating note");
85
+ }
86
+ }
87
+ }
88
+ export default CreateNote;
@@ -0,0 +1,10 @@
1
+ import BaseTool from "./base-tool.js";
2
+ interface DeleteFolderOptions {
3
+ folder_id: string;
4
+ confirm?: boolean | undefined;
5
+ force?: boolean | undefined;
6
+ }
7
+ declare class DeleteFolder extends BaseTool {
8
+ call(options: DeleteFolderOptions): Promise<string>;
9
+ }
10
+ export default DeleteFolder;