Skip to main content
Glama
index.ts5.4 kB
import fs from "fs/promises"; import path from "path"; interface Resource { uri: string; name: string; description?: string; mimeType?: string; } interface ResourceTemplate { uriTemplate: string; name: string; description?: string; mimeType?: string; } interface ResourceContent { uri: string; mimeType?: string; text: string; } interface ListResourcesResponse { resources: Resource[]; } interface ListResourceTemplatesResponse { resourceTemplates: ResourceTemplate[]; } interface ReadResourceResponse { contents: ResourceContent[]; } /** * Provides access to notes as MCP resources, handling discovery and content retrieval */ export class NotesResourceProvider { private notesPath: string; constructor(notesPath: string) { this.notesPath = notesPath; } /** * Lists all available resources in the notes directory */ async listResources(): Promise<ListResourcesResponse> { try { const resources = await this.#getAllFiles(this.notesPath); return { resources }; } catch (error) { console.error("Error listing resources:", error); throw error; } } /** * Lists available resource templates */ async listResourceTemplates(): Promise<ListResourceTemplatesResponse> { try { const templates: ResourceTemplate[] = [ { uriTemplate: "file://{notes_path}/Log/{date}.md", name: "Daily Log", description: "Access a specific daily log by date (YYYY-MM-DD format)", mimeType: "text/markdown" }, { uriTemplate: "file://{notes_path}/Rollups/{date}-rollup.md", name: "Daily Rollup", description: "Access a specific daily rollup by date (YYYY-MM-DD format)", mimeType: "text/markdown" } ]; return { resourceTemplates: templates.map((template) => ({ ...template, uriTemplate: template.uriTemplate.replace( "{notes_path}", this.notesPath ) })) }; } catch (error) { console.error("Error listing resource templates:", error); throw error; } } /** * Reads the contents of a resource */ async readResource(uri: string): Promise<ReadResourceResponse> { try { if (!uri.startsWith("file://")) { throw new Error("Invalid URI scheme"); } const filePath = uri.slice(7); // Remove 'file://' prefix // Verify the file is within NOTES_PATH const relativePath = path.relative(this.notesPath, filePath); if ( relativePath.startsWith("..") || path.isAbsolute(relativePath) ) { throw new Error( "Access denied: File is outside of notes directory" ); } // Read the file contents const contents = await fs.readFile(filePath, "utf8"); return { contents: [ { uri, mimeType: filePath.endsWith(".md") ? "text/markdown" : "text/plain", text: contents } ] }; } catch (error) { console.error("Error reading resource:", error); throw error; } } /** * Recursively gets all files in a directory * @private */ async #getAllFiles(dir: string): Promise<Resource[]> { const entries = await fs.readdir(dir, { withFileTypes: true }); const files = await Promise.all( entries.map(async (entry) => { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { return this.#getAllFiles(fullPath); } else { // Only include markdown and text files if ( entry.name.endsWith(".md") || entry.name.endsWith(".txt") ) { const relativePath = path.relative( this.notesPath, fullPath ); return [ { uri: `file://${fullPath}`, name: relativePath, description: entry.name.includes("rollup") ? "Daily activity rollup" : "Daily log entry", mimeType: entry.name.endsWith(".md") ? "text/markdown" : "text/plain" } ]; } return []; } }) ); return files.flat(); } }

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