Skip to main content
Glama

mcp-chain-of-thought

taskModel.ts31.9 kB
import { Task, TaskStatus, TaskDependency, TaskComplexityLevel, TaskComplexityThresholds, TaskComplexityAssessment, RelatedFile, } from "../types/index.js"; import fs from "fs/promises"; import path from "path"; import { v4 as uuidv4 } from "uuid"; import { fileURLToPath } from "url"; import { exec } from "child_process"; import { promisify } from "util"; // Ensure project folder path is obtained const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const PROJECT_ROOT = path.resolve(__dirname, "../.."); // Data file paths const DATA_DIR = process.env.DATA_DIR || path.join(PROJECT_ROOT, "data"); const TASKS_FILE = path.join(DATA_DIR, "tasks.json"); // Convert exec to Promise form const execPromise = promisify(exec); // Ensure data directory exists async function ensureDataDir() { try { await fs.access(DATA_DIR); } catch (error) { await fs.mkdir(DATA_DIR, { recursive: true }); } try { await fs.access(TASKS_FILE); } catch (error) { await fs.writeFile(TASKS_FILE, JSON.stringify({ tasks: [] })); } } // Read all tasks async function readTasks(): Promise<Task[]> { await ensureDataDir(); const data = await fs.readFile(TASKS_FILE, "utf-8"); const tasks = JSON.parse(data).tasks; // Convert date strings back to Date objects return tasks.map((task: any) => ({ ...task, createdAt: task.createdAt ? new Date(task.createdAt) : new Date(), updatedAt: task.updatedAt ? new Date(task.updatedAt) : new Date(), completedAt: task.completedAt ? new Date(task.completedAt) : undefined, })); } // Write all tasks async function writeTasks(tasks: Task[]): Promise<void> { await ensureDataDir(); await fs.writeFile(TASKS_FILE, JSON.stringify({ tasks }, null, 2)); } // Get all tasks export async function getAllTasks(): Promise<Task[]> { return await readTasks(); } // Get task by ID export async function getTaskById(taskId: string): Promise<Task | null> { const tasks = await readTasks(); return tasks.find((task) => task.id === taskId) || null; } // Create new task export async function createTask( name: string, description: string, notes?: string, dependencies: string[] = [], relatedFiles?: RelatedFile[] ): Promise<Task> { const tasks = await readTasks(); const dependencyObjects: TaskDependency[] = dependencies.map((taskId) => ({ taskId, })); const newTask: Task = { id: uuidv4(), name, description, notes, status: TaskStatus.PENDING, dependencies: dependencyObjects, createdAt: new Date(), updatedAt: new Date(), relatedFiles, }; tasks.push(newTask); await writeTasks(tasks); return newTask; } // Update task export async function updateTask( taskId: string, updates: Partial<Task> ): Promise<Task | null> { const tasks = await readTasks(); const taskIndex = tasks.findIndex((task) => task.id === taskId); if (taskIndex === -1) { return null; } // Check if task is completed, completed tasks cannot be updated (except for explicitly allowed fields) if (tasks[taskIndex].status === TaskStatus.COMPLETED) { // Only allow updating the summary field and relatedFiles field const allowedFields = ["summary", "relatedFiles"]; const attemptedFields = Object.keys(updates); const disallowedFields = attemptedFields.filter( (field) => !allowedFields.includes(field) ); if (disallowedFields.length > 0) { return null; } } tasks[taskIndex] = { ...tasks[taskIndex], ...updates, updatedAt: new Date(), }; await writeTasks(tasks); return tasks[taskIndex]; } // Update task status export async function updateTaskStatus( taskId: string, status: TaskStatus ): Promise<Task | null> { const updates: Partial<Task> = { status }; if (status === TaskStatus.COMPLETED) { updates.completedAt = new Date(); } return await updateTask(taskId, updates); } // Update task summary export async function updateTaskSummary( taskId: string, summary: string ): Promise<Task | null> { return await updateTask(taskId, { summary }); } /** * Update task conversation history * Adds a new message to the task's conversation history * @param taskId Unique identifier of the task * @param role Role of the message sender (user or assistant) * @param content Content of the message * @param toolName Optional name of the tool associated with the message * @returns Updated task or null if task not found */ export async function updateTaskConversationHistory( taskId: string, role: 'user' | 'assistant', content: string, toolName?: string ): Promise<Task | null> { // Get task and check if it exists const task = await getTaskById(taskId); if (!task) { return null; } // Create new conversation message const message = { timestamp: new Date(), role, content, toolName }; // Create conversation history array if it doesn't exist const conversationHistory = task.conversationHistory || []; // Add new message to conversation history const updatedConversationHistory = [...conversationHistory, message]; // Update task with new conversation history return await updateTask(taskId, { conversationHistory: updatedConversationHistory }); } // Update task content export async function updateTaskContent( taskId: string, updates: { name?: string; description?: string; notes?: string; relatedFiles?: RelatedFile[]; dependencies?: string[]; implementationGuide?: string; verificationCriteria?: string; } ): Promise<{ success: boolean; message: string; task?: Task }> { // Get task and check if it exists const task = await getTaskById(taskId); if (!task) { return { success: false, message: "Task not found" }; } // Check if task is completed if (task.status === TaskStatus.COMPLETED) { return { success: false, message: "Cannot update completed tasks" }; } // Build update object, only include fields that need to be updated const updateObj: Partial<Task> = {}; if (updates.name !== undefined) { updateObj.name = updates.name; } if (updates.description !== undefined) { updateObj.description = updates.description; } if (updates.notes !== undefined) { updateObj.notes = updates.notes; } if (updates.relatedFiles !== undefined) { updateObj.relatedFiles = updates.relatedFiles; } if (updates.dependencies !== undefined) { updateObj.dependencies = updates.dependencies.map((dep) => ({ taskId: dep, })); } if (updates.implementationGuide !== undefined) { updateObj.implementationGuide = updates.implementationGuide; } if (updates.verificationCriteria !== undefined) { updateObj.verificationCriteria = updates.verificationCriteria; } // If there's no content to update, return early if (Object.keys(updateObj).length === 0) { return { success: true, message: "No content provided to update", task }; } // Execute update const updatedTask = await updateTask(taskId, updateObj); if (!updatedTask) { return { success: false, message: "Error updating task" }; } return { success: true, message: "Task content updated successfully", task: updatedTask, }; } // Update task related files export async function updateTaskRelatedFiles( taskId: string, relatedFiles: RelatedFile[] ): Promise<{ success: boolean; message: string; task?: Task }> { // Get task and check if it exists const task = await getTaskById(taskId); if (!task) { return { success: false, message: "Task not found" }; } // Check if task is completed if (task.status === TaskStatus.COMPLETED) { return { success: false, message: "Cannot update completed tasks" }; } // Execute update const updatedTask = await updateTask(taskId, { relatedFiles }); if (!updatedTask) { return { success: false, message: "Error updating task related files" }; } return { success: true, message: `Task related files updated successfully, ${relatedFiles.length} files updated`, task: updatedTask, }; } // Batch create or update tasks export async function batchCreateOrUpdateTasks( taskDataList: Array<{ name: string; description: string; notes?: string; dependencies?: string[]; relatedFiles?: RelatedFile[]; implementationGuide?: string; // Added: Implementation Guide verificationCriteria?: string; // Added: Verification Criteria }>, updateMode: "append" | "overwrite" | "selective" | "clearAllTasks", // Required parameter, specify task update strategy globalAnalysisResult?: string // Added: Global Analysis Result ): Promise<Task[]> { // Read existing tasks const existingTasks = await readTasks(); // Process existing tasks based on update mode let tasksToKeep: Task[] = []; if (updateMode === "append") { // Append mode: keep all existing tasks tasksToKeep = [...existingTasks]; } else if (updateMode === "overwrite") { // Overwrite mode: keep only completed tasks, clear all incomplete tasks tasksToKeep = existingTasks.filter( (task) => task.status === TaskStatus.COMPLETED ); } else if (updateMode === "selective") { // Selective update mode: selectively update based on task name, keep tasks not in update list // 1. Extract list of tasks to be updated const updateTaskNames = new Set(taskDataList.map((task) => task.name)); // 2. Keep all tasks not appearing in update list tasksToKeep = existingTasks.filter( (task) => !updateTaskNames.has(task.name) ); } else if (updateMode === "clearAllTasks") { // Clear all tasks mode: empty task list tasksToKeep = []; } // This map will be used to store name to task ID mapping for supporting task reference by name const taskNameToIdMap = new Map<string, string>(); // For selective update mode, first record existing task names and IDs if (updateMode === "selective") { existingTasks.forEach((task) => { taskNameToIdMap.set(task.name, task.id); }); } // Record all task names and IDs, whether to keep tasks or new tasks // This will be used later for parsing dependencies tasksToKeep.forEach((task) => { taskNameToIdMap.set(task.name, task.id); }); // Create new task list const newTasks: Task[] = []; for (const taskData of taskDataList) { // Check if selective update mode and task name already exists if (updateMode === "selective" && taskNameToIdMap.has(taskData.name)) { // Get existing task ID const existingTaskId = taskNameToIdMap.get(taskData.name)!; // Find existing task const existingTaskIndex = existingTasks.findIndex( (task) => task.id === existingTaskId ); // If existing task found and it's not completed, update it if ( existingTaskIndex !== -1 && existingTasks[existingTaskIndex].status !== TaskStatus.COMPLETED ) { const taskToUpdate = existingTasks[existingTaskIndex]; // Update task basic information, but keep original ID, creation time, etc. const updatedTask: Task = { ...taskToUpdate, name: taskData.name, description: taskData.description, notes: taskData.notes, // Will handle dependencies later updatedAt: new Date(), // Added: Save Implementation Guide (if any) implementationGuide: taskData.implementationGuide, // Added: Save Verification Criteria (if any) verificationCriteria: taskData.verificationCriteria, // Added: Save Global Analysis Result (if any) analysisResult: globalAnalysisResult, }; // Handle related files (if any) if (taskData.relatedFiles) { updatedTask.relatedFiles = taskData.relatedFiles; } // Add updated task to new task list newTasks.push(updatedTask); // Remove this task from tasksToKeep as it's already updated and added to newTasks tasksToKeep = tasksToKeep.filter((task) => task.id !== existingTaskId); } } else { // Create new task const newTaskId = uuidv4(); // Add new task name and ID to map taskNameToIdMap.set(taskData.name, newTaskId); const newTask: Task = { id: newTaskId, name: taskData.name, description: taskData.description, notes: taskData.notes, status: TaskStatus.PENDING, dependencies: [], // Will fill later createdAt: new Date(), updatedAt: new Date(), relatedFiles: taskData.relatedFiles, // Added: Save Implementation Guide (if any) implementationGuide: taskData.implementationGuide, // Added: Save Verification Criteria (if any) verificationCriteria: taskData.verificationCriteria, // Added: Save Global Analysis Result (if any) analysisResult: globalAnalysisResult, }; newTasks.push(newTask); } } // Handle task dependencies for (let i = 0; i < taskDataList.length; i++) { const taskData = taskDataList[i]; const newTask = newTasks[i]; // If there are dependencies, handle them if (taskData.dependencies && taskData.dependencies.length > 0) { const resolvedDependencies: TaskDependency[] = []; for (const dependencyName of taskData.dependencies) { // First try to interpret dependency as task ID let dependencyTaskId = dependencyName; // If dependency looks not like UUID, try to interpret it as task name if ( !dependencyName.match( /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i ) ) { // If map has this name, use corresponding ID if (taskNameToIdMap.has(dependencyName)) { dependencyTaskId = taskNameToIdMap.get(dependencyName)!; } else { continue; // Skip this dependency } } else { // UUID format, but need to confirm this ID corresponds to actual existing task const idExists = [...tasksToKeep, ...newTasks].some( (task) => task.id === dependencyTaskId ); if (!idExists) { continue; // Skip this dependency } } resolvedDependencies.push({ taskId: dependencyTaskId }); } newTask.dependencies = resolvedDependencies; } } // Merge kept tasks and new tasks const allTasks = [...tasksToKeep, ...newTasks]; // Write updated task list await writeTasks(allTasks); return newTasks; } // Check if task can be executed (all dependencies are completed) export async function canExecuteTask( taskId: string ): Promise<{ canExecute: boolean; blockedBy?: string[] }> { const task = await getTaskById(taskId); if (!task) { return { canExecute: false }; } if (task.status === TaskStatus.COMPLETED) { return { canExecute: false }; // Completed tasks don't need to be executed } if (task.dependencies.length === 0) { return { canExecute: true }; // Tasks without dependencies can be executed directly } const allTasks = await readTasks(); const blockedBy: string[] = []; for (const dependency of task.dependencies) { const dependencyTask = allTasks.find((t) => t.id === dependency.taskId); if (!dependencyTask || dependencyTask.status !== TaskStatus.COMPLETED) { blockedBy.push(dependency.taskId); } } return { canExecute: blockedBy.length === 0, blockedBy: blockedBy.length > 0 ? blockedBy : undefined, }; } // Delete task export async function deleteTask( taskId: string ): Promise<{ success: boolean; message: string }> { const tasks = await readTasks(); const taskIndex = tasks.findIndex((task) => task.id === taskId); if (taskIndex === -1) { return { success: false, message: "Task not found" }; } // Check task status, completed tasks cannot be deleted if (tasks[taskIndex].status === TaskStatus.COMPLETED) { return { success: false, message: "Cannot delete completed tasks" }; } // Check if other tasks depend on this task const allTasks = tasks.filter((_, index) => index !== taskIndex); const dependentTasks = allTasks.filter((task) => task.dependencies.some((dep) => dep.taskId === taskId) ); if (dependentTasks.length > 0) { const dependentTaskNames = dependentTasks .map((task) => `"${task.name}" (ID: ${task.id})`) .join(", "); return { success: false, message: `Cannot delete this task, because the following tasks depend on it: ${dependentTaskNames}`, }; } // Execute delete operation tasks.splice(taskIndex, 1); await writeTasks(tasks); return { success: true, message: "Task deleted successfully" }; } // Assess task complexity export async function assessTaskComplexity( taskId: string ): Promise<TaskComplexityAssessment | null> { const task = await getTaskById(taskId); if (!task) { return null; } // Assess various indicators const descriptionLength = task.description.length; const dependenciesCount = task.dependencies.length; const notesLength = task.notes ? task.notes.length : 0; const hasNotes = !!task.notes; // Based on various indicators, assess complexity level let level = TaskComplexityLevel.LOW; // Description length assessment if ( descriptionLength >= TaskComplexityThresholds.DESCRIPTION_LENGTH.VERY_HIGH ) { level = TaskComplexityLevel.VERY_HIGH; } else if ( descriptionLength >= TaskComplexityThresholds.DESCRIPTION_LENGTH.HIGH ) { level = TaskComplexityLevel.HIGH; } else if ( descriptionLength >= TaskComplexityThresholds.DESCRIPTION_LENGTH.MEDIUM ) { level = TaskComplexityLevel.MEDIUM; } // Dependencies count assessment (take highest level) if ( dependenciesCount >= TaskComplexityThresholds.DEPENDENCIES_COUNT.VERY_HIGH ) { level = TaskComplexityLevel.VERY_HIGH; } else if ( dependenciesCount >= TaskComplexityThresholds.DEPENDENCIES_COUNT.HIGH && level !== TaskComplexityLevel.VERY_HIGH ) { level = TaskComplexityLevel.HIGH; } else if ( dependenciesCount >= TaskComplexityThresholds.DEPENDENCIES_COUNT.MEDIUM && level !== TaskComplexityLevel.HIGH && level !== TaskComplexityLevel.VERY_HIGH ) { level = TaskComplexityLevel.MEDIUM; } // Notes length assessment (take highest level) if (notesLength >= TaskComplexityThresholds.NOTES_LENGTH.VERY_HIGH) { level = TaskComplexityLevel.VERY_HIGH; } else if ( notesLength >= TaskComplexityThresholds.NOTES_LENGTH.HIGH && level !== TaskComplexityLevel.VERY_HIGH ) { level = TaskComplexityLevel.HIGH; } else if ( notesLength >= TaskComplexityThresholds.NOTES_LENGTH.MEDIUM && level !== TaskComplexityLevel.HIGH && level !== TaskComplexityLevel.VERY_HIGH ) { level = TaskComplexityLevel.MEDIUM; } // Based on complexity level, generate processing suggestions const recommendations: string[] = []; // Low complexity task suggestions if (level === TaskComplexityLevel.LOW) { recommendations.push("This task is low complexity, can be executed directly"); recommendations.push("Suggest setting clear completion standards, ensuring acceptance has clear basis"); } // Medium complexity task suggestions else if (level === TaskComplexityLevel.MEDIUM) { recommendations.push("This task has some complexity, suggest detailed planning execution steps"); recommendations.push("Can be executed in stages and periodically check progress, ensure understanding accurate and complete implementation"); if (dependenciesCount > 0) { recommendations.push("Pay attention to check completion status and output quality of all dependent tasks"); } } // High complexity task suggestions else if (level === TaskComplexityLevel.HIGH) { recommendations.push("This task has high complexity, suggest thorough analysis and planning"); recommendations.push("Consider breaking down the task into smaller, independently executable sub-tasks"); recommendations.push("Establish clear milestones and checkpoints, facilitate tracking progress and quality"); if ( dependenciesCount > TaskComplexityThresholds.DEPENDENCIES_COUNT.MEDIUM ) { recommendations.push( "Many dependent tasks, suggest making dependent relationship diagram, ensure execution sequence correct" ); } } // Very high complexity task suggestions else if (level === TaskComplexityLevel.VERY_HIGH) { recommendations.push("⚠️ This task is very high complexity, strongly suggest breaking down into multiple independent tasks"); recommendations.push( "Thorough analysis and planning before execution, clearly define scope and interface of each sub-task" ); recommendations.push( "Risk assessment for the task, identify possible obstacles and develop countermeasures" ); recommendations.push("Establish specific test and verification standards, ensure output quality of each sub-task"); if ( descriptionLength >= TaskComplexityThresholds.DESCRIPTION_LENGTH.VERY_HIGH ) { recommendations.push( "Task description is very long, suggest organizing key points and establishing structured execution list" ); } if (dependenciesCount >= TaskComplexityThresholds.DEPENDENCIES_COUNT.HIGH) { recommendations.push( "Too many dependent tasks, suggest re-evaluating task boundaries, ensure task splitting reasonable" ); } } return { level, metrics: { descriptionLength, dependenciesCount, notesLength, hasNotes, }, recommendations, }; } // Clear all tasks export async function clearAllTasks(): Promise<{ success: boolean; message: string; backupFile?: string; }> { try { // Ensure data directory exists await ensureDataDir(); // Read existing tasks const allTasks = await readTasks(); // If there are no tasks, return directly if (allTasks.length === 0) { return { success: true, message: "No tasks need to be cleared" }; } // Filter out completed tasks const completedTasks = allTasks.filter( (task) => task.status === TaskStatus.COMPLETED ); // Create backup file name const timestamp = new Date() .toISOString() .replace(/:/g, "-") .replace(/\..+/, ""); const backupFileName = `tasks_memory_${timestamp}.json`; // Ensure memory directory exists const MEMORY_DIR = path.join(DATA_DIR, "memory"); try { await fs.access(MEMORY_DIR); } catch (error) { await fs.mkdir(MEMORY_DIR, { recursive: true }); } // Create memory directory backup path const memoryFilePath = path.join(MEMORY_DIR, backupFileName); // Write to memory directory at the same time (only include completed tasks) await fs.writeFile( memoryFilePath, JSON.stringify({ tasks: completedTasks }, null, 2) ); // Clear task file await writeTasks([]); return { success: true, message: `All tasks cleared successfully, ${allTasks.length} tasks deleted, ${completedTasks.length} completed tasks backed up to memory directory`, backupFile: backupFileName, }; } catch (error) { return { success: false, message: `Error clearing tasks: ${ error instanceof Error ? error.message : String(error) }`, }; } } // Use system command to search task memory export async function searchTasksWithCommand( query: string, isId: boolean = false, page: number = 1, pageSize: number = 5 ): Promise<{ tasks: Task[]; pagination: { currentPage: number; totalPages: number; totalResults: number; hasMore: boolean; }; }> { // Read current task file tasks const currentTasks = await readTasks(); let memoryTasks: Task[] = []; // Search memory folder tasks const MEMORY_DIR = path.join(DATA_DIR, "memory"); try { // Ensure memory folder exists await fs.access(MEMORY_DIR); // Generate search command const cmd = generateSearchCommand(query, isId, MEMORY_DIR); // If there's search command, execute it if (cmd) { try { const { stdout } = await execPromise(cmd, { maxBuffer: 1024 * 1024 * 10, }); if (stdout) { // Parse search results, extract matching file paths const matchedFiles = new Set<string>(); stdout.split("\n").forEach((line) => { if (line.trim()) { // Format usually is: file path:matching content const filePath = line.split(":")[0]; if (filePath) { matchedFiles.add(filePath); } } }); // Limit read file quantity const MAX_FILES_TO_READ = 10; const sortedFiles = Array.from(matchedFiles) .sort() .reverse() .slice(0, MAX_FILES_TO_READ); // Only process files that meet conditions for (const filePath of sortedFiles) { try { const data = await fs.readFile(filePath, "utf-8"); const tasks = JSON.parse(data).tasks || []; // Format date fields const formattedTasks = tasks.map((task: any) => ({ ...task, createdAt: task.createdAt ? new Date(task.createdAt) : new Date(), updatedAt: task.updatedAt ? new Date(task.updatedAt) : new Date(), completedAt: task.completedAt ? new Date(task.completedAt) : undefined, })); // Further filter tasks to ensure conditions are met const filteredTasks = isId ? formattedTasks.filter((task: Task) => task.id === query) : formattedTasks.filter((task: Task) => { const keywords = query .split(/\s+/) .filter((k) => k.length > 0); if (keywords.length === 0) return true; return keywords.every((keyword) => { const lowerKeyword = keyword.toLowerCase(); return ( task.name.toLowerCase().includes(lowerKeyword) || task.description.toLowerCase().includes(lowerKeyword) || (task.notes && task.notes.toLowerCase().includes(lowerKeyword)) || (task.implementationGuide && task.implementationGuide .toLowerCase() .includes(lowerKeyword)) || (task.summary && task.summary.toLowerCase().includes(lowerKeyword)) ); }); }); memoryTasks.push(...filteredTasks); } catch (error: unknown) {} } } } catch (error: unknown) {} } } catch (error: unknown) {} // Filter tasks that match the conditions from current tasks const filteredCurrentTasks = filterCurrentTasks(currentTasks, query, isId); // Merge results and remove duplicates const taskMap = new Map<string, Task>(); // Current tasks take priority filteredCurrentTasks.forEach((task) => { taskMap.set(task.id, task); }); // Add memory tasks, avoid duplicates memoryTasks.forEach((task) => { if (!taskMap.has(task.id)) { taskMap.set(task.id, task); } }); // Combined results const allTasks = Array.from(taskMap.values()); // Sort - by update or completion time in descending order allTasks.sort((a, b) => { // Sort by completion time first if (a.completedAt && b.completedAt) { return b.completedAt.getTime() - a.completedAt.getTime(); } else if (a.completedAt) { return -1; // a is completed but b is not, a comes first } else if (b.completedAt) { return 1; // b is completed but a is not, b comes first } // Otherwise sort by update time return b.updatedAt.getTime() - a.updatedAt.getTime(); }); // Pagination processing const totalResults = allTasks.length; const totalPages = Math.ceil(totalResults / pageSize); const safePage = Math.max(1, Math.min(page, totalPages || 1)); // Ensure page number is valid const startIndex = (safePage - 1) * pageSize; const endIndex = Math.min(startIndex + pageSize, totalResults); const paginatedTasks = allTasks.slice(startIndex, endIndex); return { tasks: paginatedTasks, pagination: { currentPage: safePage, totalPages: totalPages || 1, totalResults, hasMore: safePage < totalPages, }, }; } // Generate appropriate search command based on platform function generateSearchCommand( query: string, isId: boolean, memoryDir: string ): string { // Safely escape user input const safeQuery = escapeShellArg(query); const keywords = safeQuery.split(/\s+/).filter((k) => k.length > 0); // Detect operating system type const isWindows = process.platform === "win32"; if (isWindows) { // Windows environment, use findstr command if (isId) { // ID search return `findstr /s /i /c:"${safeQuery}" "${memoryDir}\\*.json"`; } else if (keywords.length === 1) { // Single keyword return `findstr /s /i /c:"${safeQuery}" "${memoryDir}\\*.json"`; } else { // Multiple keyword search - Use PowerShell in Windows const keywordPatterns = keywords.map((k) => `'${k}'`).join(" -and "); return `powershell -Command "Get-ChildItem -Path '${memoryDir}' -Filter *.json -Recurse | Select-String -Pattern ${keywordPatterns} | ForEach-Object { $_.Path }"`; } } else { // Unix/Linux/MacOS environment, use grep command if (isId) { return `grep -r --include="*.json" "${safeQuery}" "${memoryDir}"`; } else if (keywords.length === 1) { return `grep -r --include="*.json" "${safeQuery}" "${memoryDir}"`; } else { // Multiple keywords connected with pipe for multiple grep commands const firstKeyword = escapeShellArg(keywords[0]); const otherKeywords = keywords.slice(1).map((k) => escapeShellArg(k)); let cmd = `grep -r --include="*.json" "${firstKeyword}" "${memoryDir}"`; for (const keyword of otherKeywords) { cmd += ` | grep "${keyword}"`; } return cmd; } } } /** * Safely escape shell parameters to prevent command injection */ function escapeShellArg(arg: string): string { if (!arg) return ""; // Remove all control characters and special characters return arg .replace(/[\x00-\x1F\x7F]/g, "") // Control characters .replace(/[&;`$"'<>|]/g, ""); // Shell special characters } // Filter current tasks function filterCurrentTasks( tasks: Task[], query: string, isId: boolean ): Task[] { if (isId) { return tasks.filter((task) => task.id === query); } else { const keywords = query.split(/\s+/).filter((k) => k.length > 0); if (keywords.length === 0) return tasks; return tasks.filter((task) => { return keywords.every((keyword) => { const lowerKeyword = keyword.toLowerCase(); return ( task.name.toLowerCase().includes(lowerKeyword) || task.description.toLowerCase().includes(lowerKeyword) || (task.notes && task.notes.toLowerCase().includes(lowerKeyword)) || (task.implementationGuide && task.implementationGuide.toLowerCase().includes(lowerKeyword)) || (task.summary && task.summary.toLowerCase().includes(lowerKeyword)) ); }); }); } }

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/liorfranko/mcp-chain-of-thought'

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