create_note
Create new notes in TriliumNext with duplicate title detection, handling text, code, files, and specialized note types while managing conflicts.
Instructions
Create a new note in TriliumNext with duplicate title detection. When a note with the same title already exists in the same directory, you'll be presented with choices: skip creation, create anyway (with forceCreate: true), or update the existing note. ONLY use this tool when the user explicitly requests note creation (e.g., 'create a note', 'make a new note'). DO NOT use this tool proactively or when the user is only asking questions about their notes. TIP: For code notes, content is plain text (no HTML processing).
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| parentNoteId | Yes | ID of the parent note | root |
| title | Yes | Title of the note | |
| content | No | Content of the note (optional). Content requirements by note type: TEXT notes require HTML content (plain text auto-wrapped in <p> tags, e.g., '<p>Hello world</p>', '<strong>bold</strong>'); CODE/MERMAID notes require plain text ONLY (HTML tags rejected, e.g., 'def fibonacci(n):'); ⚠️ OMIT CONTENT for: 1) FILE notes (binary content uploaded separately via fileUri parameter), 2) WEBVIEW notes (use #webViewSrc label instead), 3) Container templates (Board, Calendar, Grid View, List View, Table, Geo Map), 4) System notes: RENDER (create child HTML note with type='code' and mime='application/x-html', then link with ~renderNote relation), SEARCH (queries in search properties), RELATION_MAP (visual maps), NOTE_MAP (visual hierarchies), BOOK (container notes) - these must be EMPTY to work properly. When omitted, note will be created with empty content. | |
| type | Yes | Type of note (aligned with TriliumNext ETAPI specification). For file uploads: Use 'image' for Images (JPG/JPEG/PNG/WebP), 'file' for Documents & Audio (PDF/DOCX/MP3/WAV/M4A). Other types: 'text', 'code', 'render', 'search', 'relationMap', 'book', 'noteMap', 'mermaid', 'webView'. | |
| mime | No | MIME type for code/file/image notes. For file uploads, auto-detected from file extension when not specified. Supported: application/pdf, application/vnd.openxmlformats-officedocument.wordprocessingml.document, audio/mpeg, audio/wav, audio/mp4, image/jpg, image/png, image/webp | |
| fileUri | No | File data source (required when type='file' or type='image'). Supports: 1) Local file path: '/path/to/document.pdf', 2) Base64 data URI: 'data:application/pdf;base64,JVBERi0xLjcK...', 3) Raw base64 string. Supports PDF, DOCX, PPTX, XLSX, CSV, MP3, WAV, M4A, JPG, JPEG, PNG, WebP formats. File will be uploaded via Trilium's two-step process: create note metadata, then upload binary content. | |
| attributes | No | Optional attributes to create with the note (labels and relations). Enables one-step note creation with metadata. Labels use #tag format (e.g., 'important', 'project'), relations connect to other notes (e.g., template relations use 'Board', 'Calendar', 'Text Snippet'). ⚠️ TEMPLATE RESTRICTIONS: Container templates (Board, Calendar, Grid View, List View, Table, Geo Map) MUST be empty notes - add content as child notes. | |
| forceCreate | No | Bypass duplicate title check and create note even if a note with the same title already exists in the same directory. Use this when you want to intentionally create duplicate notes. |
Implementation Reference
- src/modules/noteHandler.ts:20-90 (handler)Primary MCP tool handler for 'create_note'. Validates permissions and parameters, constructs NoteOperation object, calls core handleCreateNote function, and formats MCP response.export async function handleCreateNoteRequest( args: any, axiosInstance: any, permissionChecker: PermissionChecker ): Promise<{ content: Array<{ type: string; text: string }> }> { if (!permissionChecker.hasPermission("WRITE")) { throw new McpError(ErrorCode.InvalidRequest, "Permission denied: Not authorized to create notes."); } // Validate file upload requirements if (args.type === 'file' || args.type === 'image') { if (!args.fileUri) { throw new McpError( ErrorCode.InvalidParams, `Parameter 'fileUri' is required when type='${args.type}'.` ); } } // For non-file/image notes, fileUri should not be provided if (args.fileUri && !['file', 'image'].includes(args.type)) { throw new McpError( ErrorCode.InvalidParams, "Parameter 'fileUri' can only be used when type='file' or type='image'." ); } // Validate file upload requirements if (args.type === 'file' || args.type === 'image') { if (!args.fileUri) { throw new McpError( ErrorCode.InvalidParams, `Parameter 'fileUri' is required when type='${args.type}'.` ); } } // For non-file/image notes, fileUri should not be provided if (args.fileUri && !['file', 'image'].includes(args.type)) { throw new McpError( ErrorCode.InvalidParams, "Parameter 'fileUri' can only be used when type='file' or type='image'." ); } try { const noteOperation: NoteOperation = { parentNoteId: args.parentNoteId || "root", // Use default value if not provided title: args.title, type: args.type, content: args.content, mime: args.mime, fileUri: args.fileUri, attributes: args.attributes }; const result = await handleCreateNote(noteOperation, axiosInstance); return { content: [{ type: "text", text: result.message }] }; } catch (error) { if (error instanceof McpError) { throw error; } throw new McpError(ErrorCode.InvalidParams, error instanceof Error ? error.message : String(error)); } }
- src/modules/noteManager.ts:262-392 (helper)Core implementation logic for creating notes. Handles duplicate title detection, content validation and processing based on note type, file uploads via FileManager, Trilium ETAPI calls (POST /create-note), and attribute creation.export async function handleCreateNote( args: NoteOperation, axiosInstance: any ): Promise<NoteCreateResponse> { const { parentNoteId, title, type, content: rawContent, mime, fileUri, attributes, forceCreate = false } = args; // Validate required parameters if (!parentNoteId || !title || !type) { throw new Error("parentNoteId, title, and type are required for create operation."); } // Handle file uploads (both 'file' and 'image' types) if (type === 'file' || type === 'image') { // Import FileManager and utils only when needed const { FileManager } = await import('./fileManager.js'); // Validate file if provided if (!fileUri) { throw new Error(`fileUri is required when type='${type}'.`); } // Use FileManager to handle the upload const fileManager = new FileManager(axiosInstance); try { const fileResult = await fileManager.createFileNote({ parentNoteId, filePath: fileUri, title: title, mimeType: mime, attributes, noteType: type as 'file' | 'image' }); return { noteId: fileResult.note.noteId, message: `Created file note: ${fileResult.note.noteId} (${fileResult.note.title})`, duplicateFound: false }; } catch (error) { throw new Error(`File upload failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } // Check for duplicate title in the same directory (unless forceCreate is true) if (!forceCreate) { const duplicateCheck = await checkDuplicateTitleInDirectory(parentNoteId, title, axiosInstance); if (duplicateCheck.found) { return { message: `Found existing note with title "${title}" in this directory. Please choose how to proceed:`, duplicateFound: true, duplicateNoteId: duplicateCheck.duplicateNoteId, choices: { skip: "Skip creation - do nothing", createAnyway: "Create anyway - create duplicate note with same title (set forceCreate: true)", updateExisting: "Update existing - replace content of existing note with new content" }, nextSteps: `Please specify your choice by calling create_note again with your preferred action. To update the existing note, use update_note with noteId: ${duplicateCheck.duplicateNoteId}` }; } } // Process content to ETAPI format // Content is optional - if not provided, default to empty string const content = rawContent || ""; // Extract template relation for content validation const templateRelation = extractTemplateRelation(attributes); // Validate content with template-aware rules const contentValidation = await validateContentForNoteType( content, type as NoteType, undefined, templateRelation ); if (!contentValidation.valid) { return { message: `CONTENT_VALIDATION_ERROR: ${contentValidation.error}`, duplicateFound: false, nextSteps: "Please adjust your content according to the requirements and try again." }; } // Use validated content (may have been auto-corrected) const validatedContent = contentValidation.content; // Process content to ETAPI format const processed = await processContentArray(validatedContent, type); if (processed.error) { throw new Error(`Content processing error: ${processed.error}`); } const processedContent = processed.content; // Create note with processed content (empty for file/image-only notes) const noteData: any = { parentNoteId, title, type, content: processedContent }; // Add MIME type if specified if (mime) { noteData.mime = mime; } const response = await axiosInstance.post("/create-note", noteData); const noteId = response.data.note.noteId; // Handle attributes if provided if (attributes && attributes.length > 0) { try { logVerbose("handleCreateNote", `Creating ${attributes.length} attributes for note ${noteId}`, attributes); await createNoteAttributes(noteId, attributes, axiosInstance); logVerbose("handleCreateNote", `Successfully created all attributes for note ${noteId}`); } catch (attributeError) { const errorMsg = `Note created but attributes failed to apply: ${attributeError instanceof Error ? attributeError.message : attributeError}`; logVerboseError("handleCreateNote", attributeError); console.warn(errorMsg); } } return { noteId: noteId, message: `Created note: ${noteId}`, duplicateFound: false }; }
- src/modules/toolDefinitions.ts:14-86 (schema)Input schema and description definition for the 'create_note' tool, including all parameters, validation rules, and usage instructions.name: "create_note", description: "Create a new note in TriliumNext with duplicate title detection. When a note with the same title already exists in the same directory, you'll be presented with choices: skip creation, create anyway (with forceCreate: true), or update the existing note. ONLY use this tool when the user explicitly requests note creation (e.g., 'create a note', 'make a new note'). DO NOT use this tool proactively or when the user is only asking questions about their notes. TIP: For code notes, content is plain text (no HTML processing).", inputSchema: { type: "object", properties: { parentNoteId: { type: "string", description: "ID of the parent note", default: "root" }, title: { type: "string", description: "Title of the note", }, content: { type: "string", description: "Content of the note (optional). Content requirements by note type: TEXT notes require HTML content (plain text auto-wrapped in <p> tags, e.g., '<p>Hello world</p>', '<strong>bold</strong>'); CODE/MERMAID notes require plain text ONLY (HTML tags rejected, e.g., 'def fibonacci(n):'); ⚠️ OMIT CONTENT for: 1) FILE notes (binary content uploaded separately via fileUri parameter), 2) WEBVIEW notes (use #webViewSrc label instead), 3) Container templates (Board, Calendar, Grid View, List View, Table, Geo Map), 4) System notes: RENDER (create child HTML note with type='code' and mime='application/x-html', then link with ~renderNote relation), SEARCH (queries in search properties), RELATION_MAP (visual maps), NOTE_MAP (visual hierarchies), BOOK (container notes) - these must be EMPTY to work properly. When omitted, note will be created with empty content." }, type: { type: "string", enum: ["text", "code", "render", "search", "relationMap", "book", "noteMap", "mermaid", "webView", "file", "image"], description: "Type of note (aligned with TriliumNext ETAPI specification). For file uploads: Use 'image' for Images (JPG/JPEG/PNG/WebP), 'file' for Documents & Audio (PDF/DOCX/MP3/WAV/M4A). Other types: 'text', 'code', 'render', 'search', 'relationMap', 'book', 'noteMap', 'mermaid', 'webView'.", }, mime: { type: "string", description: "MIME type for code/file/image notes. For file uploads, auto-detected from file extension when not specified. Supported: application/pdf, application/vnd.openxmlformats-officedocument.wordprocessingml.document, audio/mpeg, audio/wav, audio/mp4, image/jpg, image/png, image/webp" }, fileUri: { type: "string", description: "File data source (required when type='file' or type='image'). Supports: 1) Local file path: '/path/to/document.pdf', 2) Base64 data URI: 'data:application/pdf;base64,JVBERi0xLjcK...', 3) Raw base64 string. Supports PDF, DOCX, PPTX, XLSX, CSV, MP3, WAV, M4A, JPG, JPEG, PNG, WebP formats. File will be uploaded via Trilium's two-step process: create note metadata, then upload binary content." }, attributes: { type: "array", description: "Optional attributes to create with the note (labels and relations). Enables one-step note creation with metadata. Labels use #tag format (e.g., 'important', 'project'), relations connect to other notes (e.g., template relations use 'Board', 'Calendar', 'Text Snippet'). ⚠️ TEMPLATE RESTRICTIONS: Container templates (Board, Calendar, Grid View, List View, Table, Geo Map) MUST be empty notes - add content as child notes.", items: { type: "object", properties: { type: { type: "string", enum: ["label", "relation"], description: "Attribute type: 'label' for #tags, 'relation' for ~connections" }, name: { type: "string", description: "Attribute name: for labels use descriptive tags, for relations use connection types (e.g., 'template', 'author')" }, value: { type: "string", description: "Attribute value: optional for labels (e.g., 'In Progress'), required for relations (e.g., 'Board', 'Calendar', target note IDs/titles)" }, position: { type: "number", description: "Display position (lower numbers appear first, default: 10)", default: 10 }, isInheritable: { type: "boolean", description: "Whether attribute is inherited by child notes (default: false)", default: false } }, required: ["type", "name"] } }, forceCreate: { type: "boolean", description: "Bypass duplicate title check and create note even if a note with the same title already exists in the same directory. Use this when you want to intentionally create duplicate notes.", default: false }, }, required: ["parentNoteId", "title", "type"], }, },
- src/index.ts:93-94 (registration)MCP server switch statement registration: routes 'create_note' tool calls to the handleCreateNoteRequest function.case "create_note": return await handleCreateNoteRequest(request.params.arguments, this.axiosInstance, this);
- src/index.ts:77-82 (registration)MCP ListTools handler that dynamically generates tool list including 'create_note' schema via generateTools (permission-based).this.server.setRequestHandler(ListToolsRequestSchema, async () => { // Generate standard tools based on permissions const tools = generateTools(this); return { tools };