Skip to main content
Glama

TriliumNext Notes' MCP Server

resolveManager.ts5.79 kB
/** * Note Resolution Module * Handles simple note ID resolution by name/title * Built on top of search functionality with title-based search only */ import { buildSearchQuery } from "./searchQueryBuilder.js"; import { SearchOperation } from "./searchManager.js"; import { logVerboseInput } from "../utils/verboseUtils.js"; export interface ResolveNoteOperation { noteName: string; exactMatch?: boolean; maxResults?: number; autoSelect?: boolean; } export interface ResolveNoteResponse { noteId: string | null; title: string | null; found: boolean; matches: number; requiresUserChoice?: boolean; topMatches?: Array<{ noteId: string; title: string; type: string; dateModified: string; }>; nextSteps?: string; } /** * Handle resolve note ID operation - find note ID by name/title * Simple title-based search only */ export async function handleResolveNoteId( args: ResolveNoteOperation, axiosInstance: any ): Promise<ResolveNoteResponse> { const { noteName, exactMatch = false, maxResults = 3, autoSelect = false } = args; // Verbose logging logVerboseInput("resolve_note_id", args); if (!noteName?.trim()) { throw new Error("Note name must be provided"); } // Simple title-based search criteria const searchCriteria: any[] = [{ property: "title", type: "noteProperty", op: exactMatch ? "=" : "contains", value: noteName.trim() }]; // Build search query from simple criteria const searchParams: SearchOperation = { searchCriteria }; logVerboseInput("resolve_note_id", searchParams); // Search for notes matching the criteria const query = buildSearchQuery(searchParams); const params = new URLSearchParams(); params.append("search", query); params.append("fastSearch", "false"); params.append("includeArchivedNotes", "true"); const response = await axiosInstance.get(`/notes?${params.toString()}`); let searchResults = response.data.results || []; const totalMatches = searchResults.length; if (searchResults.length === 0) { // Enhanced fallback suggestions when no matches found let nextSteps = "No notes found matching the search criteria."; // Primary fallback: suggest broader search using search_notes nextSteps += ` Consider using search_notes for broader results: search_notes(text: "${noteName.trim()}") to find notes containing "${noteName.trim()}" in title or content.`; return { noteId: null, title: null, found: false, matches: 0, nextSteps }; } // Store original results for top matches const originalResults = [...searchResults]; // Check if user choice is required (multiple matches and autoSelect is false) if (totalMatches > 1 && !autoSelect) { // Simple prioritization: exact matches → folders → most recent const topMatches = originalResults .sort((a: any, b: any) => { // First priority: exact title matches const aExact = a.title && a.title.toLowerCase() === noteName.toLowerCase(); const bExact = b.title && b.title.toLowerCase() === noteName.toLowerCase(); if (aExact && !bExact) return -1; if (!aExact && bExact) return 1; // Second priority: book type (folders) if (a.type === 'book' && b.type !== 'book') return -1; if (a.type !== 'book' && b.type === 'book') return 1; // Final priority: most recent return new Date(b.dateModified).getTime() - new Date(a.dateModified).getTime(); }) .slice(0, maxResults) .map((note: any) => ({ noteId: note.noteId, title: note.title, type: note.type, dateModified: note.dateModified })); return { noteId: null, title: null, found: true, matches: totalMatches, requiresUserChoice: true, topMatches }; } // Apply simple prioritization for selecting the best match if (searchResults.length > 1) { searchResults.sort((a: any, b: any) => { // First priority: exact title matches (if not exactMatch mode, prefer exact matches) if (!exactMatch) { const aExact = a.title && a.title.toLowerCase() === noteName.toLowerCase(); const bExact = b.title && b.title.toLowerCase() === noteName.toLowerCase(); if (aExact && !bExact) return -1; if (!aExact && bExact) return 1; } // Second priority: book type (folders) if (a.type === 'book' && b.type !== 'book') return -1; if (a.type !== 'book' && b.type === 'book') return 1; // Final priority: most recent return new Date(b.dateModified).getTime() - new Date(a.dateModified).getTime(); }); } // Return the first match (or best match if multiple) const selectedNote = searchResults[0]; // Prepare top N matches for display (from original results, with simple prioritization) const topMatches = originalResults .sort((a: any, b: any) => { // Simple prioritization: exact matches → folders → most recent const aExact = a.title && a.title.toLowerCase() === noteName.toLowerCase(); const bExact = b.title && b.title.toLowerCase() === noteName.toLowerCase(); if (aExact && !bExact) return -1; if (!aExact && bExact) return 1; if (a.type === 'book' && b.type !== 'book') return -1; if (a.type !== 'book' && b.type === 'book') return 1; return new Date(b.dateModified).getTime() - new Date(a.dateModified).getTime(); }) .slice(0, maxResults) .map((note: any) => ({ noteId: note.noteId, title: note.title, type: note.type, dateModified: note.dateModified })); return { noteId: selectedNote.noteId, title: selectedNote.title, found: true, matches: totalMatches, topMatches }; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/tan-yong-sheng/triliumnext-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server