Skip to main content
Glama
schedule-store.ts4.59 kB
/** * Schedule Store - Local persistence for scheduled tasks * Implements the "Thick Server" pattern with file-based storage * Storage location: ~/.jules-mcp/schedules.json */ import { readFile, writeFile, mkdir } from 'fs/promises'; import { existsSync } from 'fs'; import { homedir } from 'os'; import { join } from 'path'; import type { ScheduledTask, ScheduleStore } from '../types/schedule.js'; /** * Handles persistence of scheduled tasks to the local file system. */ export class ScheduleStorage { private readonly storagePath: string; private readonly storageDir: string; private cache: ScheduleStore | null = null; /** * Creates an instance of ScheduleStorage. * Initializes paths for storage directory and file. */ constructor() { this.storageDir = join(homedir(), '.jules-mcp'); this.storagePath = join(this.storageDir, 'schedules.json'); } /** * Ensures storage directory exists. * Creates the directory if it doesn't exist. */ private async ensureStorageDir(): Promise<void> { if (!existsSync(this.storageDir)) { await mkdir(this.storageDir, { recursive: true }); } } /** * Loads schedules from disk. * If storage file doesn't exist, initializes an empty store. * @returns The loaded schedule store. * @throws Error if loading fails. */ async load(): Promise<ScheduleStore> { if (this.cache) { return this.cache; } await this.ensureStorageDir(); if (!existsSync(this.storagePath)) { // Initialize empty store const emptyStore: ScheduleStore = { schedules: {}, version: '1.0.0', }; await this.save(emptyStore); return emptyStore; } try { const data = await readFile(this.storagePath, 'utf-8'); this.cache = JSON.parse(data) as ScheduleStore; return this.cache; } catch (error) { throw new Error( `Failed to load schedules from ${this.storagePath}: ${error}` ); } } /** * Saves schedules to disk. * @param store - The schedule store to save. * @throws Error if saving fails. */ async save(store: ScheduleStore): Promise<void> { await this.ensureStorageDir(); try { const data = JSON.stringify(store, null, 2); await writeFile(this.storagePath, data, 'utf-8'); this.cache = store; } catch (error) { throw new Error( `Failed to save schedules to ${this.storagePath}: ${error}` ); } } /** * Adds or updates a scheduled task. * @param task - The task to upsert. */ async upsertTask(task: ScheduledTask): Promise<void> { const store = await this.load(); store.schedules[task.id] = task; await this.save(store); } /** * Retrieves a specific task by ID. * @param id - The ID of the task. * @returns The task if found, otherwise undefined. */ async getTask(id: string): Promise<ScheduledTask | undefined> { const store = await this.load(); return store.schedules[id]; } /** * Retrieves a task by name. * @param name - The name of the task. * @returns The task if found, otherwise undefined. */ async getTaskByName(name: string): Promise<ScheduledTask | undefined> { const store = await this.load(); return Object.values(store.schedules).find((task) => task.name === name); } /** * Lists all tasks. * @returns An array of all scheduled tasks. */ async listTasks(): Promise<ScheduledTask[]> { const store = await this.load(); return Object.values(store.schedules); } /** * Deletes a task by ID. * @param id - The ID of the task to delete. * @returns True if the task was deleted, false if it wasn't found. */ async deleteTask(id: string): Promise<boolean> { const store = await this.load(); if (!store.schedules[id]) { return false; } delete store.schedules[id]; await this.save(store); return true; } /** * Updates the last run information for a task. * @param id - The ID of the task. * @param timestamp - The timestamp of the run. * @param sessionId - The session ID of the run (optional). */ async updateLastRun( id: string, timestamp: string, sessionId?: string ): Promise<void> { const store = await this.load(); const task = store.schedules[id]; if (task) { task.lastRun = timestamp; task.lastSessionId = sessionId; await this.save(store); } } /** * Clears the cache, forcing a reload on next access. */ invalidateCache(): void { this.cache = null; } }

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/savethepolarbears/jules-mcp-server'

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