triliumnext-mcp 0.3.8-beta.4 → 0.3.8-beta.5

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/build/index.js CHANGED
@@ -8,7 +8,7 @@ import { generateTools } from "./modules/toolDefinitions.js";
8
8
  import { handleCreateNoteRequest, handleUpdateNoteRequest, handleAppendNoteRequest, handleDeleteNoteRequest, handleGetNoteRequest } from "./modules/noteHandler.js";
9
9
  import { handleSearchNotesRequest } from "./modules/searchHandler.js";
10
10
  import { handleResolveNoteRequest } from "./modules/resolveHandler.js";
11
- import { handleManageAttributes } from "./modules/attributeHandler.js";
11
+ import { handleManageAttributes, handleReadAttributes } from "./modules/attributeHandler.js";
12
12
  const TRILIUM_API_URL = process.env.TRILIUM_API_URL;
13
13
  const TRILIUM_API_TOKEN = process.env.TRILIUM_API_TOKEN;
14
14
  const PERMISSIONS = process.env.PERMISSIONS || "READ;WRITE";
@@ -73,6 +73,8 @@ class TriliumServer {
73
73
  return await handleSearchNotesRequest(request.params.arguments, this.axiosInstance, this);
74
74
  case "resolve_note_id":
75
75
  return await handleResolveNoteRequest(request.params.arguments, this, this.axiosInstance);
76
+ case "read_attributes":
77
+ return await handleReadAttributes(request.params.arguments, this.axiosInstance, this);
76
78
  case "manage_attributes":
77
79
  return await handleManageAttributes(request.params.arguments, this.axiosInstance, this);
78
80
  default:
@@ -3,7 +3,7 @@
3
3
  * Processes MCP requests for attribute management operations
4
4
  */
5
5
  import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
6
- import { manage_attributes, get_note_attributes } from './attributeManager.js';
6
+ import { manage_attributes, read_attributes } from './attributeManager.js';
7
7
  /**
8
8
  * Handle manage_attributes MCP request
9
9
  */
@@ -32,35 +32,23 @@ export async function handleManageAttributes(args, axiosInstance, permissionChec
32
32
  isError: true
33
33
  };
34
34
  }
35
- // Check permissions based on operation type
36
- const readOperations = ["read"];
37
- const writeOperations = ["create", "update", "delete", "batch_create"];
38
- if (readOperations.includes(args.operation)) {
39
- if (!permissionChecker.hasPermission("READ")) {
40
- throw new McpError(ErrorCode.InvalidRequest, "Permission denied: Not authorized to read attributes.");
41
- }
35
+ // Check WRITE permission for all manage_attributes operations
36
+ if (!permissionChecker.hasPermission("WRITE")) {
37
+ throw new McpError(ErrorCode.InvalidRequest, "Permission denied: Not authorized to manage attributes.");
42
38
  }
43
- else if (writeOperations.includes(args.operation)) {
44
- if (!permissionChecker.hasPermission("WRITE")) {
45
- throw new McpError(ErrorCode.InvalidRequest, "Permission denied: Not authorized to manage attributes.");
46
- }
47
- }
48
- else {
39
+ // Validate operation
40
+ const validOperations = ["create", "update", "delete", "batch_create"];
41
+ if (!validOperations.includes(args.operation)) {
49
42
  return {
50
43
  content: [
51
44
  {
52
45
  type: "text",
53
- text: `❌ Invalid operation: ${args.operation}. Valid operations are: ${[...readOperations, ...writeOperations].join(", ")}`
46
+ text: `❌ Invalid operation: ${args.operation}. Valid operations are: ${validOperations.join(", ")}`
54
47
  }
55
48
  ],
56
49
  isError: true
57
50
  };
58
51
  }
59
- // Handle read operation
60
- if (args.operation === "read") {
61
- const result = await get_note_attributes(args.noteId, axiosInstance);
62
- return format_attribute_response(result, args.noteId, "read");
63
- }
64
52
  // Validate attributes for write operations
65
53
  if (!args.attributes || !Array.isArray(args.attributes) || args.attributes.length === 0) {
66
54
  return {
@@ -106,6 +94,109 @@ export async function handleManageAttributes(args, axiosInstance, permissionChec
106
94
  };
107
95
  }
108
96
  }
97
+ /**
98
+ * Handle read_attributes MCP request
99
+ */
100
+ export async function handleReadAttributes(args, axiosInstance, permissionChecker) {
101
+ try {
102
+ // Validate required parameters
103
+ if (!args.noteId) {
104
+ return {
105
+ content: [
106
+ {
107
+ type: "text",
108
+ text: "❌ Missing required parameter: noteId"
109
+ }
110
+ ],
111
+ isError: true
112
+ };
113
+ }
114
+ // Check READ permission
115
+ if (!permissionChecker.hasPermission("READ")) {
116
+ throw new McpError(ErrorCode.InvalidRequest, "Permission denied: Not authorized to read attributes.");
117
+ }
118
+ // Execute the read operation
119
+ const result = await read_attributes(args, axiosInstance);
120
+ return format_read_attribute_response(result, args.noteId);
121
+ }
122
+ catch (error) {
123
+ return {
124
+ content: [
125
+ {
126
+ type: "text",
127
+ text: `❌ Attribute read operation failed: ${error instanceof Error ? error.message : 'Unknown error'}`
128
+ }
129
+ ],
130
+ isError: true
131
+ };
132
+ }
133
+ }
134
+ /**
135
+ * Format read attribute operation result for MCP response
136
+ */
137
+ function format_read_attribute_response(result, noteId) {
138
+ const content = [];
139
+ // Add status message
140
+ if (result.success) {
141
+ content.push({
142
+ type: "text",
143
+ text: `✅ ${result.message}`
144
+ });
145
+ }
146
+ else {
147
+ content.push({
148
+ type: "text",
149
+ text: `❌ ${result.message}`
150
+ });
151
+ // Add error details if available
152
+ if (result.errors && result.errors.length > 0) {
153
+ content.push({
154
+ type: "text",
155
+ text: `📋 Error details:\n${result.errors.map((err, i) => `${i + 1}. ${err}`).join('\n')}`
156
+ });
157
+ }
158
+ }
159
+ // Add attribute data for successful operations
160
+ if (result.success && result.attributes && result.attributes.length > 0) {
161
+ // Separate labels and relations for better organization
162
+ const labels = result.attributes.filter(attr => attr.type === 'label');
163
+ const relations = result.attributes.filter(attr => attr.type === 'relation');
164
+ content.push({
165
+ type: "text",
166
+ text: format_attributes_for_display(result.attributes)
167
+ });
168
+ // Add structured summary if available
169
+ if (result.summary) {
170
+ content.push({
171
+ type: "text",
172
+ text: `📊 Summary: ${result.summary.total} total attributes (${result.summary.labels} labels, ${result.summary.relations} relations)`
173
+ });
174
+ }
175
+ // Add detailed breakdown
176
+ if (labels.length > 0) {
177
+ content.push({
178
+ type: "text",
179
+ text: `🏷️ Labels (${labels.length}):\n${labels.map(attr => {
180
+ const value = attr.value ? ` = "${attr.value}"` : "";
181
+ return ` #${attr.name}${value}`;
182
+ }).join('\n')}`
183
+ });
184
+ }
185
+ if (relations.length > 0) {
186
+ content.push({
187
+ type: "text",
188
+ text: `🔗 Relations (${relations.length}):\n${relations.map(attr => ` ~${attr.name} = "${attr.value}"`).join('\n')}`
189
+ });
190
+ }
191
+ }
192
+ else if (result.success) {
193
+ content.push({
194
+ type: "text",
195
+ text: "📋 No attributes found for this note"
196
+ });
197
+ }
198
+ return { content };
199
+ }
109
200
  /**
110
201
  * Format attribute operation result for MCP response
111
202
  */
@@ -178,23 +269,27 @@ function format_attributes_for_display(attributes) {
178
269
  */
179
270
  export function get_attributes_help() {
180
271
  return `
181
- 🔧 Attribute Management Tool (manage_attributes)
272
+ 🔧 Attribute Management Tools
182
273
 
183
- This tool manages note attributes (labels and relations) in TriliumNext.
274
+ 📖 read_attributes: Read all attributes (labels and relations) for a note
275
+ 🔧 manage_attributes: Create, update, delete attributes (write operations)
184
276
 
185
277
  📝 Usage Examples:
186
278
 
187
- 1. Create a single label:
279
+ 📖 Read Attributes:
280
+ - noteId: "abc123"
281
+
282
+ 🔧 Create a single label:
188
283
  - noteId: "abc123"
189
284
  - operation: "create"
190
285
  - attributes: [{type: "label", name: "important", position: 10}]
191
286
 
192
- 2. Create a template relation:
287
+ 🔧 Create a template relation:
193
288
  - noteId: "abc123"
194
289
  - operation: "create"
195
290
  - attributes: [{type: "relation", name: "template", value: "Board", position: 10}]
196
291
 
197
- 3. Create multiple attributes (batch):
292
+ 🔧 Create multiple attributes (batch):
198
293
  - noteId: "abc123"
199
294
  - operation: "batch_create"
200
295
  - attributes: [
@@ -203,16 +298,12 @@ This tool manages note attributes (labels and relations) in TriliumNext.
203
298
  {type: "relation", name: "template", value: "Grid View", position: 30}
204
299
  ]
205
300
 
206
- 4. Read all attributes:
207
- - noteId: "abc123"
208
- - operation: "read"
209
-
210
- 5. Update an attribute:
301
+ 🔧 Update an attribute:
211
302
  - noteId: "abc123"
212
303
  - operation: "update"
213
304
  - attributes: [{type: "label", name: "important", position: 15}]
214
305
 
215
- 6. Delete an attribute:
306
+ 🔧 Delete an attribute:
216
307
  - noteId: "abc123"
217
308
  - operation: "delete"
218
309
  - attributes: [{type: "label", name: "important"}]
@@ -224,5 +315,6 @@ This tool manages note attributes (labels and relations) in TriliumNext.
224
315
  - Use "batch_create" for multiple attributes (faster than individual calls)
225
316
  - Template relations require the target note to exist in your Trilium instance
226
317
  - Position values control display order (lower numbers appear first)
318
+ - Use read_attributes to view existing attributes before making changes
227
319
  `;
228
320
  }
@@ -5,7 +5,8 @@
5
5
  import axios from 'axios';
6
6
  import { logVerbose, logVerboseApi, logVerboseAxiosError } from "../utils/verboseUtils.js";
7
7
  /**
8
- * Main attribute management function that orchestrates different operations
8
+ * Manage note attributes with write operations (create, update, delete)
9
+ * This function provides write-only access to note attributes
9
10
  */
10
11
  export async function manage_attributes(params, axiosInstance) {
11
12
  try {
@@ -262,11 +263,12 @@ function validate_attribute(attribute) {
262
263
  };
263
264
  }
264
265
  /**
265
- * Get all attributes for a note
266
+ * Read all attributes for a note (labels and relations)
267
+ * This function provides read-only access to note attributes
266
268
  */
267
- export async function get_note_attributes(noteId, axiosInstance) {
269
+ export async function read_attributes(params, axiosInstance) {
268
270
  try {
269
- const response = await axiosInstance.get(`/notes/${noteId}`);
271
+ const response = await axiosInstance.get(`/notes/${params.noteId}`);
270
272
  const attributes = response.data.attributes.map((attr) => ({
271
273
  type: attr.type,
272
274
  name: attr.name,
@@ -274,10 +276,20 @@ export async function get_note_attributes(noteId, axiosInstance) {
274
276
  position: attr.position,
275
277
  isInheritable: attr.isInheritable
276
278
  }));
279
+ // Separate labels and relations for better organization
280
+ const labels = attributes.filter(attr => attr.type === 'label');
281
+ const relations = attributes.filter(attr => attr.type === 'relation');
277
282
  return {
278
283
  success: true,
279
- message: `Retrieved ${attributes.length} attributes for note ${noteId}`,
280
- attributes
284
+ message: `Retrieved ${attributes.length} attributes for note ${params.noteId} (${labels.length} labels, ${relations.length} relations)`,
285
+ attributes,
286
+ // Add structured summary for easier parsing
287
+ summary: {
288
+ total: attributes.length,
289
+ labels: labels.length,
290
+ relations: relations.length,
291
+ noteId: params.noteId
292
+ }
281
293
  };
282
294
  }
283
295
  catch (error) {
@@ -321,22 +321,17 @@ function createSearchProperties() {
321
321
  export function createReadAttributeTools() {
322
322
  return [
323
323
  {
324
- name: "manage_attributes",
325
- description: "Read note attributes (labels and relations). View existing labels (#tags), template relations (~template), and note metadata. This tool allows you to inspect the current attributes assigned to any note.",
324
+ name: "read_attributes",
325
+ description: "Read all attributes (labels and relations) for a note. View existing labels (#tags), template relations (~template), and note metadata. This tool provides read-only access to inspect current attributes assigned to any note. Returns structured data with labels, relations, and summary information.",
326
326
  inputSchema: {
327
327
  type: "object",
328
328
  properties: {
329
329
  noteId: {
330
330
  type: "string",
331
331
  description: "ID of the note to read attributes from"
332
- },
333
- operation: {
334
- type: "string",
335
- enum: ["read"],
336
- description: "Operation type: 'read' (list all attributes)"
337
332
  }
338
333
  },
339
- required: ["noteId", "operation"]
334
+ required: ["noteId"]
340
335
  }
341
336
  }
342
337
  ];
@@ -348,7 +343,7 @@ export function createWriteAttributeTools() {
348
343
  return [
349
344
  {
350
345
  name: "manage_attributes",
351
- description: "Manage note attributes (labels and relations) with write operations. ONLY use this tool when the user explicitly requests attribute management (e.g., 'add a tag', 'create a relation', 'manage attributes'). TRY NOT to use this tool proactively unless for automated metadata tagging as part of a clear workflow. Create labels (#tags), template relations (~template), update existing attributes, and organize notes with metadata. IMPORTANT: Relations require values pointing to existing notes (e.g., template relations use 'Board', 'Calendar'; author relations use target note titles or IDs). UPDATE LIMITATIONS: For labels, only value and position can be updated. For relations, only position can be updated. The isInheritable property cannot be changed via update - delete and recreate to modify inheritability. Supports single operations and efficient batch creation for better performance. Template relations like ~template.title = 'Board' enable specialized note layouts and functionality.",
346
+ description: "Manage note attributes with write operations (create, update, delete). Create labels (#tags), template relations (~template), update existing attributes, and organize notes with metadata. IMPORTANT: This tool only provides write access - use read_attributes to view existing attributes. Relations require values pointing to existing notes (e.g., template relations use 'Board', 'Calendar'; author relations use target note titles or IDs). UPDATE LIMITATIONS: For labels, only value and position can be updated. For relations, only position can be updated. The isInheritable property cannot be changed via update - delete and recreate to modify inheritability. Supports single operations and efficient batch creation for better performance.",
352
347
  inputSchema: {
353
348
  type: "object",
354
349
  properties: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "triliumnext-mcp",
3
- "version": "0.3.8-beta.4",
3
+ "version": "0.3.8-beta.5",
4
4
  "description": "A model context protocol server for TriliumNext Notes",
5
5
  "type": "module",
6
6
  "bin": {