Skip to main content
Glama
filesystem.ts11.2 kB
import path from 'path'; import fs from 'fs/promises'; import { searchFiles } from '../../search-utils.js'; import { loadSystemPrompt } from '../../prompts/template-loader.js'; // Types interface ToolDefinition { name: string; description: string; inputSchema: { type: string; properties: Record<string, any>; required?: string[]; }; } interface ToolCallResult { content: Array<{ type: string; text: string }>; isError?: boolean; } interface SearchFilesArgs { pattern: string; excludePatterns?: string[]; } interface ReadNoteArgs { path: string; } interface ReadMultipleNotesArgs { paths: string[]; } interface ListDirectoryArgs { path?: string; } interface CreateDirectoryArgs { path: string; } // Tool definitions export function getFilesystemToolDefinitions(): ToolDefinition[] { return [ { name: "search_files", description: "Recursively search for files and directories matching a pattern in your notes directory. " + "The search is case-insensitive and matches partial names. Returns full paths to all " + "matching items. Great for finding notes when you don't know their exact location.", inputSchema: { type: "object", properties: { pattern: { type: "string", description: "The pattern to search for in file and directory names" }, excludePatterns: { type: "array", items: { type: "string" }, description: "Glob patterns to exclude from search results", default: [] } }, required: ["pattern"] }, }, { name: "read_note", description: "Read the complete contents of a note file from your notes directory. " + "Specify the path relative to your notes directory (e.g., 'Log/2023-01-01.md'). " + "Returns the full text content of the note file.", inputSchema: { type: "object", properties: { path: { type: "string", description: "The path to the note file, relative to your notes directory" } }, required: ["path"] }, }, { name: "read_multiple_notes", description: "Read the contents of multiple note files simultaneously. " + "Specify paths relative to your notes directory (e.g., ['Log/2023-01-01.md', 'Rollups/2023-01-01-rollup.md']). " + "Returns each file's content with its path as a reference.", inputSchema: { type: "object", properties: { paths: { type: "array", items: { type: "string" }, description: "Array of paths to note files, relative to your notes directory" } }, required: ["paths"] }, }, { name: "list_directory", description: "List the contents of a directory in your notes. " + "Shows all files and directories with clear labels. " + "Specify path relative to your notes directory (e.g., 'Log' or 'Rollups').", inputSchema: { type: "object", properties: { path: { type: "string", description: "Directory path relative to notes directory (defaults to root notes directory if not provided)", default: "" } } }, }, { name: "create_directory", description: "Create a new directory in your notes. " + "Can create nested directories in one operation. " + "Path should be relative to your notes directory.", inputSchema: { type: "object", properties: { path: { type: "string", description: "Directory path to create, relative to notes directory" } }, required: ["path"] }, } ]; } // Helper functions export async function ensureDirectory(dirPath: string): Promise<boolean> { try { await fs.mkdir(dirPath, { recursive: true }); return true; } catch (err) { if (err instanceof Error && 'code' in err && err.code === 'EEXIST') { return true; } console.error(`Error creating directory ${dirPath}:`, err); return false; } } export async function initializeNotesDirectory(notesPath: string): Promise<void> { // Create base directories await ensureDirectory(notesPath); await ensureDirectory(path.join(notesPath, 'Log')); await ensureDirectory(path.join(notesPath, 'Rollups')); // // Create a system prompt file if it doesn't exist // const promptPath = path.join(notesPath, 'claude-system-prompt.md'); // try { // await fs.access(promptPath); // } catch (err) { // // File doesn't exist, create it // try { // const systemPrompt = await loadSystemPrompt(); // await fs.writeFile(promptPath, systemPrompt, 'utf8'); // } catch (error) { // console.error("Error loading system prompt template:", error); // } // } } // Tool handlers export async function handleSearchFiles(notesPath: string, args: SearchFilesArgs): Promise<ToolCallResult> { try { // Validate pattern is provided if (!args.pattern) { throw new Error("'pattern' parameter is required"); } // Always search in the notes directory const results = await searchFiles( notesPath, args.pattern, args.excludePatterns || [] ); if (results.length === 0) { return { content: [{ type: "text", text: "No files found matching the pattern." }] }; } // Format results as relative paths const formattedResults = results.map((filePath: string) => path.relative(notesPath, filePath) ); return { content: [{ type: "text", text: `Found ${results.length} files matching "${args.pattern}":\n\n${formattedResults.join('\n')}` }] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [{ type: "text", text: `Error searching files: ${errorMessage}` }], isError: true }; } } export async function handleReadNote(notesPath: string, args: ReadNoteArgs): Promise<ToolCallResult> { try { // Validate path is provided if (!args.path) { throw new Error("'path' parameter is required"); } const filePath = path.join(notesPath, args.path); // Ensure the path is within allowed directory if (!filePath.startsWith(notesPath)) { throw new Error("Access denied - path outside notes directory"); } try { const content = await fs.readFile(filePath, 'utf-8'); return { content: [{ type: "text", text: content }] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); throw new Error(`Error reading file: ${errorMessage}`); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [{ type: "text", text: `Error reading note: ${errorMessage}` }], isError: true }; } } export async function handleReadMultipleNotes(notesPath: string, args: ReadMultipleNotesArgs): Promise<ToolCallResult> { try { // Validate paths is provided and is an array if (!args.paths || !Array.isArray(args.paths)) { throw new Error("'paths' parameter is required and must be an array"); } // Process each file path const results = await Promise.all( args.paths.map(async (notePath) => { try { const filePath = path.join(notesPath, notePath); // Ensure the path is within allowed directory if (!filePath.startsWith(notesPath)) { return `${notePath}: Error - Access denied - path outside notes directory`; } try { const content = await fs.readFile(filePath, 'utf-8'); return `${notePath}:\n${content}\n`; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return `${notePath}: Error - ${errorMessage}`; } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return `${notePath}: Error - ${errorMessage}`; } }) ); return { content: [{ type: "text", text: results.join("\n---\n") }] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [{ type: "text", text: `Error reading notes: ${errorMessage}` }], isError: true }; } } export async function handleListDirectory(notesPath: string, args: ListDirectoryArgs): Promise<ToolCallResult> { try { // Use provided path or default to NOTES_PATH root const dirPath = args.path ? path.join(notesPath, args.path) : notesPath; // Ensure the path is within allowed directory if (!dirPath.startsWith(notesPath)) { throw new Error("Access denied - path outside notes directory"); } try { const entries = await fs.readdir(dirPath, { withFileTypes: true }); const formatted = entries .map((entry) => `${entry.isDirectory() ? "[DIR]" : "[FILE]"} ${entry.name}`) .join("\n"); const relativePath = path.relative(notesPath, dirPath) || '.'; return { content: [{ type: "text", text: `Contents of ${relativePath}:\n\n${formatted || "No files or directories found."}` }] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); throw new Error(`Error reading directory: ${errorMessage}`); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [{ type: "text", text: `Error listing directory: ${errorMessage}` }], isError: true }; } } export async function handleCreateDirectory(notesPath: string, args: CreateDirectoryArgs): Promise<ToolCallResult> { try { // Validate path parameter if (!args.path) { throw new Error("'path' parameter is required"); } const dirPath = path.join(notesPath, args.path); // Ensure the path is within allowed directory if (!dirPath.startsWith(notesPath)) { throw new Error("Access denied - path outside notes directory"); } try { await fs.mkdir(dirPath, { recursive: true }); return { content: [{ type: "text", text: `Successfully created directory: ${args.path}` }] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); throw new Error(`Error creating directory: ${errorMessage}`); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [{ type: "text", text: `Error creating directory: ${errorMessage}` }], isError: true }; } }

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/mikeysrecipes/mcp-notes'

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