UNPKG

mcp-routine

Version:

Stitch MCP actions together into reusable routines

177 lines (150 loc) 5.3 kB
import { promises as fs } from 'fs' type ToolStep = { description: string tool: string params: unknown } export type Routine = { name: string description: string steps: ToolStep[] } function validateRoutine(routine: Routine, context: 'write' | 'read' = 'write') { const prefix = context === 'write' ? 'Routine' : 'Each routine' // Validate routine structure if (!routine.name || typeof routine.name !== 'string') { throw new Error(`${prefix} must have a valid name`) } if (!routine.description || typeof routine.description !== 'string') { throw new Error(`${prefix} must have a valid description`) } if (!Array.isArray(routine.steps) || routine.steps.length === 0) { throw new Error(`${prefix} must have at least one step`) } // Validate each step for (const step of routine.steps) { if (!step.description || typeof step.description !== 'string') { throw new Error('Each step must have a valid description') } if (!step.tool || typeof step.tool !== 'string') { throw new Error('Each step must have a valid tool name') } if (!step.params || typeof step.params !== 'object') { throw new Error('Each step must have valid parameters') } } } async function findRoutineIndex(name: string, routines: Routine[]): Promise<number> { const index = routines.findIndex(r => r.name === name) if (index === -1) { throw new Error(`No routine found with name "${name}"`) } return index } export async function createRoutine(args: { routine: Routine, filename: string }) { const { routine, filename } = args // Validate routine validateRoutine(routine, 'write') try { // Try to read existing routines let routines: Routine[] = [] try { const content = await fs.readFile(filename, 'utf-8') routines = JSON.parse(content) // Validate that the loaded content is an array if (!Array.isArray(routines)) { throw new Error('Existing file content must be an array of routines') } } catch (error) { // If file doesn't exist or is empty, start with empty array if ((error as NodeJS.ErrnoException).code === 'ENOENT') { routines = [] } else { throw error } } // Check for duplicate routine name if (routines.some(r => r.name === routine.name)) { throw new Error(`A routine with name "${routine.name}" already exists`) } // Append the new routine and write back to file routines.push(routine) await fs.writeFile(filename, JSON.stringify(routines, null, 2)) } catch (error) { if (error instanceof Error) { throw new Error(`Failed to create routine: ${error.message}`) } throw new Error('Failed to create routine: Unknown error') } } export async function loadRoutines(filename: string): Promise<Routine[]> { try { try { // Read and parse the JSON file const fileContent = await fs.readFile(filename, 'utf-8') const data = JSON.parse(fileContent) // Ensure the data is an array if (!Array.isArray(data)) { throw new Error('File content must be an array of routines') } // Type check each routine before validation for (const item of data) { if (typeof item !== 'object' || item === null) { throw new Error('Each item in the array must be an object') } } // Now validate each routine's structure for (const routine of data) { validateRoutine(routine, 'read') } return data as Routine[] } catch (error) { // If file doesn't exist, return empty array if ((error as NodeJS.ErrnoException).code === 'ENOENT') { return [] } throw error } } catch (error) { if (error instanceof Error) { throw new Error(`Failed to load routines: ${error.message}`) } throw new Error('Failed to load routines: Unknown error') } } export async function deleteRoutine(args: { name: string, filename: string }): Promise<void> { const { name, filename } = args try { // Load existing routines let routines = await loadRoutines(filename) // Find and remove the routine const routineIndex = await findRoutineIndex(name, routines) routines.splice(routineIndex, 1) // Write back to file await fs.writeFile(filename, JSON.stringify(routines, null, 2)) } catch (error) { if (error instanceof Error) { throw new Error(`Failed to delete routine: ${error.message}`) } throw new Error('Failed to delete routine: Unknown error') } } export async function updateRoutine(args: { routine: Routine, filename: string }): Promise<void> { const { routine, filename } = args // Validate routine validateRoutine(routine, 'write') try { // Load existing routines let routines = await loadRoutines(filename) // Find and update the routine const routineIndex = await findRoutineIndex(routine.name, routines) routines[routineIndex] = routine // Write back to file await fs.writeFile(filename, JSON.stringify(routines, null, 2)) } catch (error) { if (error instanceof Error) { throw new Error(`Failed to update routine: ${error.message}`) } throw new Error('Failed to update routine: Unknown error') } }