@endlessblink/like-i-said-v2
Version:
Task Management & Memory for Claude - Track tasks, remember context, and maintain continuity across sessions with 27 powerful tools. Works with Claude Desktop and Claude Code.
1,390 lines (1,272 loc) • 152 kB
JavaScript
#!/usr/bin/env node
// Detect if we're running in MCP mode by checking for MCP_MODE env or non-TTY stdin
// Windows doesn't always properly detect TTY, so we also check for MCP_MODE env
const isMCPMode = process.env.MCP_MODE === 'true' || !process.stdin.isTTY || process.env.MCP_QUIET === 'true';
import fs from 'fs';
import path from 'path';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { DropoffGenerator } from './lib/dropoff-generator.js';
import { TaskStorage } from './lib/task-storage.js';
import { TaskMemoryLinker } from './lib/task-memory-linker.js';
// import { VectorStorage } from './lib/vector-storage.js'; // Removed to prevent @xenova dependency
import { TitleSummaryGenerator } from './lib/title-summary-generator.js';
import { OllamaClient } from './lib/ollama-client.js';
import { MemoryDeduplicator } from './lib/memory-deduplicator.js';
import { TaskNLPProcessor } from './lib/task-nlp-processor.js';
import { TaskAutomation } from './lib/task-automation.js';
import { ConversationMonitor } from './lib/conversation-monitor.js';
import { TaskStatusValidator } from './lib/task-status-validator.js';
import { TaskAnalytics } from './lib/task-analytics.js';
import { MemoryTaskAutomator } from './lib/memory-task-automator.cjs';
import { QueryIntelligence } from './lib/query-intelligence.js';
import { BehavioralAnalyzer } from './lib/behavioral-analyzer.js';
import { MemoryEnrichment } from './lib/memory-enrichment.js';
import { SessionTracker } from './lib/session-tracker.js';
import { QueryAnalyzer, RelevanceScorer, ContentClassifier, CircuitBreaker } from './lib/claude-historian-features.js';
import { FuzzyMatcher } from './lib/fuzzy-matching.js';
import { WorkDetectorWrapper } from './lib/work-detector-wrapper.js';
// Removed ConnectionProtection and DataIntegrity imports to prevent any exit calls
// import { createRequire } from 'module';
// const require = createRequire(import.meta.url);
// const { ConnectionProtection } = require('./lib/connection-protection.cjs');
// const { DataIntegrity } = require('./lib/data-integrity.cjs');
/**
* Auto-detect optimal output path for session dropoffs
* Option 2: Project-aware detection with Option 5: Parameter override
*/
function getDropoffOutputPath(customPath = null) {
// Option 5: If custom path is provided, use it
if (customPath) {
const resolvedPath = path.resolve(process.cwd(), customPath);
// Ensure the directory exists
if (!fs.existsSync(resolvedPath)) {
fs.mkdirSync(resolvedPath, { recursive: true });
}
return resolvedPath;
}
// Option 2: Auto-detect session-dropoffs folder
const sessionDropoffsPath = path.join(process.cwd(), 'session-dropoffs');
if (fs.existsSync(sessionDropoffsPath)) {
return sessionDropoffsPath;
}
// Fallback: Use current directory
return process.cwd();
}
// Markdown storage implementation
class MarkdownStorage {
constructor(baseDir = 'memories', defaultProject = 'default') {
this.baseDir = baseDir;
this.defaultProject = defaultProject;
this.ensureDirectories();
}
ensureDirectories() {
try {
if (!fs.existsSync(this.baseDir)) {
fs.mkdirSync(this.baseDir, { recursive: true });
}
const defaultProjectDir = path.join(this.baseDir, this.defaultProject);
if (!fs.existsSync(defaultProjectDir)) {
fs.mkdirSync(defaultProjectDir, { recursive: true });
}
} catch (error) {
// In NPX mode, directories might need different handling
console.error(`Directory creation issue: ${error.message}`);
// Continue anyway - the directories might exist but not be detectable
}
}
generateFilename(memory) {
const date = new Date(memory.timestamp || Date.now());
const dateStr = date.toISOString().split('T')[0];
const content = memory.content || 'memory';
const slug = content
.toLowerCase()
.replace(/[^\w\s-]/g, '')
.replace(/\s+/g, '-')
.slice(0, 30)
.replace(/-+$/, '');
const timestamp = Date.now().toString().slice(-6);
return `${dateStr}-${slug}-${timestamp}.md`;
}
getProjectDir(project) {
const projectName = project || this.defaultProject;
// Security: Sanitize project name to prevent path traversal
const sanitizedProject = projectName
.replace(/[^a-zA-Z0-9_-]/g, '')
.slice(0, 50);
if (!sanitizedProject) {
throw new Error('Invalid project name');
}
const projectDir = path.join(this.baseDir, sanitizedProject);
// Security: Ensure the path doesn't escape the base directory
const resolvedProjectDir = path.resolve(projectDir);
const resolvedBaseDir = path.resolve(this.baseDir);
// Detect if we're using an absolute path (NPX mode or custom path)
const isAbsolutePath = path.isAbsolute(this.baseDir);
// Only enforce strict path traversal checks for relative paths
// For absolute paths (NPX mode), we trust the configured directory
if (!isAbsolutePath) {
// On Windows, normalize paths for comparison
const normalizedProjectDir = process.platform === 'win32'
? resolvedProjectDir.toLowerCase().replace(/\\/g, '/')
: resolvedProjectDir;
const normalizedBaseDir = process.platform === 'win32'
? resolvedBaseDir.toLowerCase().replace(/\\/g, '/')
: resolvedBaseDir;
if (!normalizedProjectDir.startsWith(normalizedBaseDir)) {
if (process.env.DEBUG_MCP) {
console.error(`[DEBUG] Path traversal check failed:`);
console.error(`[DEBUG] Project dir: ${normalizedProjectDir}`);
console.error(`[DEBUG] Base dir: ${normalizedBaseDir}`);
}
throw new Error('Invalid project path - path traversal attempt detected');
}
}
if (!fs.existsSync(projectDir)) {
fs.mkdirSync(projectDir, { recursive: true });
}
return projectDir;
}
generateMarkdownContent(memory) {
// Detect complexity level based on content and metadata
const complexity = this.detectComplexityLevel(memory);
const frontmatter = [
'---',
`id: ${memory.id}`,
`timestamp: ${memory.timestamp}`,
`complexity: ${complexity}`,
memory.category ? `category: ${memory.category}` : null,
memory.project ? `project: ${memory.project}` : null,
memory.tags && memory.tags.length > 0 ? `tags: [${memory.tags.map(t => `"${t}"`).join(', ')}]` : null,
memory.priority ? `priority: ${memory.priority}` : 'priority: medium',
memory.status ? `status: ${memory.status}` : 'status: active',
memory.related_memories && memory.related_memories.length > 0 ? `related_memories: [${memory.related_memories.map(id => `"${id}"`).join(', ')}]` : null,
`access_count: ${memory.access_count || 0}`,
`last_accessed: ${memory.last_accessed || memory.timestamp}`,
'metadata:',
` content_type: ${this.detectContentType(memory.content)}`,
memory.language ? ` language: ${memory.language}` : null,
` size: ${memory.content.length}`,
` mermaid_diagram: ${this.hasMermaidDiagram(memory.content)}`,
'---',
''
].filter(Boolean).join('\n');
return frontmatter + memory.content;
}
// Detect complexity level (1-4) based on cursor-memory-bank principles
detectComplexityLevel(memory) {
let complexity = 1; // Default: Simple memory operations
// Level 2: Enhanced operations with categorization and tagging
if (memory.category || (memory.tags && memory.tags.length > 2)) {
complexity = 2;
}
// Level 3: Project-based organization with cross-references
if (memory.project || (memory.related_memories && memory.related_memories.length > 0)) {
complexity = 3;
}
// Level 4: Advanced analytics, relationships, and automation
if (memory.content.length > 1000 ||
(memory.tags && memory.tags.length > 5) ||
this.hasMermaidDiagram(memory.content) ||
(memory.related_memories && memory.related_memories.length > 2)) {
complexity = 4;
}
return complexity;
}
// Detect content type for enhanced metadata
detectContentType(content) {
// Check for code patterns
if (content.includes('```') ||
content.includes('function') ||
content.includes('class ') ||
content.includes('import ') ||
content.includes('const ') ||
content.includes('def ') ||
content.includes('<script') ||
content.includes('SELECT ') ||
content.includes('FROM ')) {
return 'code';
}
// Check for structured data
if (content.includes('```json') ||
content.includes('```yaml') ||
content.includes('```mermaid') ||
content.startsWith('{') ||
content.startsWith('[') ||
content.includes('---\n')) {
return 'structured';
}
return 'text';
}
// Check if content contains Mermaid diagrams
hasMermaidDiagram(content) {
return content.includes('```mermaid') ||
content.includes('graph ') ||
content.includes('flowchart ') ||
content.includes('sequenceDiagram') ||
content.includes('classDiagram') ||
content.includes('erDiagram');
}
parseMarkdownContent(content) {
const frontmatterRegex = /^---\n([\s\S]*?)\n---([\s\S]*)$/;
const match = content.match(frontmatterRegex);
if (!match) {
return {
id: Date.now().toString(),
content: content.trim(),
timestamp: new Date().toISOString(),
tags: [],
complexity: 1,
priority: 'medium',
status: 'active',
access_count: 0
};
}
const [, frontmatter, bodyContent] = match;
const memory = { content: bodyContent.trim(), metadata: {} };
const lines = frontmatter.split('\n');
let inMetadata = false;
lines.forEach(line => {
// Handle metadata section
if (line.trim() === 'metadata:') {
inMetadata = true;
return;
}
if (inMetadata && line.startsWith(' ')) {
// Parse metadata fields
const metaLine = line.slice(2); // Remove 2-space indent
const colonIndex = metaLine.indexOf(':');
if (colonIndex === -1) return;
const key = metaLine.slice(0, colonIndex).trim();
const value = metaLine.slice(colonIndex + 1).trim();
switch (key) {
case 'content_type':
memory.metadata.content_type = value;
break;
case 'language':
memory.metadata.language = value;
break;
case 'size':
memory.metadata.size = parseInt(value) || 0;
break;
case 'mermaid_diagram':
memory.metadata.mermaid_diagram = value === 'true';
break;
}
return;
}
inMetadata = false;
// Parse main frontmatter fields
const colonIndex = line.indexOf(':');
if (colonIndex === -1) return;
const key = line.slice(0, colonIndex).trim();
const value = line.slice(colonIndex + 1).trim();
switch (key) {
case 'id':
memory.id = value;
break;
case 'timestamp':
case 'created':
memory.timestamp = value;
break;
case 'complexity':
memory.complexity = parseInt(value) || 1;
break;
case 'category':
memory.category = value;
break;
case 'priority':
memory.priority = value;
break;
case 'status':
memory.status = value;
break;
case 'access_count':
memory.access_count = parseInt(value) || 0;
break;
case 'last_accessed':
memory.last_accessed = value;
break;
case 'tags':
if (value.startsWith('[') && value.endsWith(']')) {
memory.tags = value.slice(1, -1).split(',').map(t => t.trim().replace(/['"]/g, ''));
} else {
memory.tags = value.split(',').map(t => t.trim()).filter(Boolean);
}
break;
case 'related_memories':
if (value.startsWith('[') && value.endsWith(']')) {
memory.related_memories = value.slice(1, -1).split(',').map(t => t.trim().replace(/['"]/g, ''));
} else {
memory.related_memories = value.split(',').map(t => t.trim()).filter(Boolean);
}
break;
case 'project':
memory.project = value;
break;
}
});
return memory;
}
parseMarkdownFile(filepath) {
try {
const content = fs.readFileSync(filepath, 'utf8');
const parsed = this.parseMarkdownContent(content);
if (!parsed) return null;
const filename = path.basename(filepath);
const projectName = path.basename(path.dirname(filepath));
return {
...parsed,
filename,
filepath,
project: projectName === this.defaultProject ? undefined : projectName
};
} catch (error) {
console.error(`Error reading markdown file ${filepath}:`, error);
return null;
}
}
async saveMemory(memory) {
try {
// Check if this memory already exists
const existingMemory = await this.getMemory(memory.id);
let filepath;
if (existingMemory && existingMemory.filepath) {
// Update existing memory file at its current location
filepath = existingMemory.filepath;
} else {
// Create new memory file
const projectDir = this.getProjectDir(memory.project);
const filename = this.generateFilename(memory);
filepath = path.join(projectDir, filename);
}
const markdownContent = this.generateMarkdownContent(memory);
// Ensure directory exists before writing
const dir = path.dirname(filepath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(filepath, markdownContent, 'utf8');
return filepath;
} catch (error) {
if (process.env.DEBUG_MCP) console.error(`[ERROR] saveMemory failed:`, error);
throw error;
}
}
async getMemory(id) {
const memories = await this.listMemories();
return memories.find(m => m.id === id) || null;
}
async listMemories(project) {
const memories = [];
// Safeguard: Only load from the designated memories directory
if (!fs.existsSync(this.baseDir)) {
console.warn(`Memories directory '${this.baseDir}' does not exist. Creating it...`);
fs.mkdirSync(this.baseDir, { recursive: true });
return memories;
}
// Safeguard: Basic validation only - ensure it's a valid directory path
const resolvedBaseDir = path.resolve(this.baseDir);
// Remove the restrictive check that breaks NPX installations
// The directory is already validated during initialization
if (project) {
const projectDir = this.getProjectDir(project);
// Project directory validation removed - trust the configured paths
if (!fs.existsSync(projectDir)) {
return memories;
}
const files = fs.readdirSync(projectDir).filter(f => f.endsWith('.md'));
for (const file of files) {
const filepath = path.join(projectDir, file);
const memory = this.parseMarkdownFile(filepath);
if (memory) memories.push(memory);
}
} else {
const projectDirs = fs.readdirSync(this.baseDir).filter(dir => {
const dirPath = path.join(this.baseDir, dir);
return fs.statSync(dirPath).isDirectory();
});
for (const projectDir of projectDirs) {
const projectPath = path.join(this.baseDir, projectDir);
// Trust the project paths - no validation needed
const files = fs.readdirSync(projectPath).filter(f => f.endsWith('.md'));
for (const file of files) {
const filepath = path.join(projectPath, file);
// Trust the file paths - no validation needed
const memory = this.parseMarkdownFile(filepath);
if (memory) memories.push(memory);
}
}
}
return memories.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
}
async updateMemory(id, updates) {
const existingMemory = await this.getMemory(id);
if (!existingMemory) return false;
const updatedMemory = {
...existingMemory,
...updates,
id: existingMemory.id,
timestamp: existingMemory.timestamp
};
await this.saveMemory(updatedMemory);
return true;
}
async deleteMemory(id) {
const memory = await this.getMemory(id);
if (!memory) return false;
fs.unlinkSync(memory.filepath);
return true;
}
async searchMemories(query, project) {
const memories = await this.listMemories(project);
const lowerQuery = query.toLowerCase();
return memories.filter(memory =>
memory.content.toLowerCase().includes(lowerQuery) ||
memory.tags?.some(tag => tag.toLowerCase().includes(lowerQuery)) ||
memory.category?.toLowerCase().includes(lowerQuery)
);
}
// Migration from JSON
async migrateFromJSON(jsonFilePath) {
if (!fs.existsSync(jsonFilePath)) {
if (process.env.DEBUG_MCP) console.error('No JSON file to migrate from');
return 0;
}
const jsonContent = fs.readFileSync(jsonFilePath, 'utf8');
const memories = JSON.parse(jsonContent);
let migrated = 0;
for (const memory of memories) {
try {
await this.saveMemory(memory);
migrated++;
} catch (error) {
console.error(`Failed to migrate memory ${memory.id}:`, error);
}
}
const backupPath = jsonFilePath + '.backup';
fs.copyFileSync(jsonFilePath, backupPath);
if (!isMCPMode && !process.env.MCP_QUIET) console.error(`Migrated ${migrated} memories. JSON backup saved to ${backupPath}`);
return migrated;
}
}
// Load saved configuration if it exists
let savedConfig = {};
const configPath = path.join(process.cwd(), '.like-i-said-config.json');
if (fs.existsSync(configPath)) {
try {
savedConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
} catch (e) {
// Ignore parse errors
}
}
// Get memory and task directories from saved config, environment variables, or use defaults
let MEMORY_DIR = savedConfig.memoryDir || process.env.MEMORY_DIR || process.env.LIKE_I_SAID_MEMORY_DIR || 'memories';
let TASK_DIR = savedConfig.taskDir || process.env.TASK_DIR || process.env.LIKE_I_SAID_TASK_DIR || 'tasks';
// Log the directories being used (only in non-MCP mode)
if (!isMCPMode) {
console.log(`Memory directory: ${path.resolve(MEMORY_DIR)}`);
console.log(`Task directory: ${path.resolve(TASK_DIR)}`);
}
// Initialize storage with custom directory
let storage = new MarkdownStorage(MEMORY_DIR);
// Initialize vector storage - stub to remove @xenova dependency
const vectorStorage = {
initialized: false,
initialize: async () => {},
addMemory: async () => {},
addTask: async () => {},
searchSimilar: async () => [],
rebuildIndex: async () => {}
};
// Initialize conversation monitor for automatic memory detection
const conversationMonitor = new ConversationMonitor(storage, vectorStorage);
// Listen for automatic memory creation events
conversationMonitor.on('memory-created', (event) => {
console.error(`🤖 Auto-captured memory: ${event.memory.id} - ${event.reason}`);
});
// Initialize advanced memory systems
const queryIntelligence = new QueryIntelligence();
const behavioralAnalyzer = new BehavioralAnalyzer();
const memoryEnrichment = new MemoryEnrichment(storage, vectorStorage);
const sessionTracker = new SessionTracker(storage);
// Initialize Universal Work Detector (disabled by default for safety)
const workDetector = new WorkDetectorWrapper({
enabled: false, // Start disabled for safety
debugMode: true,
safeMode: true
});
// Initialize claude-historian inspired features
const queryAnalyzer = new QueryAnalyzer();
const relevanceScorer = new RelevanceScorer();
const contentClassifier = new ContentClassifier();
const circuitBreaker = new CircuitBreaker();
const fuzzyMatcher = new FuzzyMatcher();
// Initialize task storage and linker with custom directory
let taskStorage = new TaskStorage(TASK_DIR, storage);
let taskMemoryLinker = new TaskMemoryLinker(storage, taskStorage);
// Initialize memory-task automator
const memoryTaskAutomator = new MemoryTaskAutomator(storage, taskStorage, {
enabled: true,
minConfidence: 0.5,
autoExecuteThreshold: 0.8,
logAutomatedActions: true
});
// Initialize dropoff generator
const dropoffGenerator = new DropoffGenerator();
// Initialize connection protection system (disabled in MCP mode to prevent disconnections)
// Disable modules that cause exits - ALWAYS disabled for MCP stability
let connectionProtection = null;
let dataIntegrity = null;
// These modules are disabled to prevent process.exit() calls that break MCP connection
// Auto-migrate from JSON if it exists (only once)
const jsonFile = path.join(process.cwd(), 'memories.json');
const migrationMarker = path.join(process.cwd(), '.migration-complete');
if (fs.existsSync(jsonFile) && !fs.existsSync(migrationMarker)) {
// Suppress ALL migration messages in MCP mode
if (!isMCPMode) console.error('Migrating from JSON to markdown files...');
storage.migrateFromJSON(jsonFile).then(count => {
if (!isMCPMode) console.error(`Migration complete: ${count} memories converted to markdown`);
// Create marker to prevent re-migration
fs.writeFileSync(migrationMarker, new Date().toISOString());
});
}
// NEVER show startup messages in MCP mode
// Startup message disabled to prevent MCP protocol corruption
// Create MCP server
const server = new Server(
{
name: 'like-i-said-memory-v2',
version: '2.0.0',
},
{
capabilities: {
tools: {},
},
}
);
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'add_memory',
description: 'AUTOMATICALLY use when user shares important information, code snippets, decisions, learnings, or context that should be remembered for future sessions. Includes smart categorization and auto-linking.',
inputSchema: {
type: 'object',
properties: {
content: {
type: 'string',
description: 'The memory content to store',
},
tags: {
type: 'array',
items: { type: 'string' },
description: 'Optional tags for the memory',
},
category: {
type: 'string',
description: 'Memory category (personal, work, code, research, conversations, preferences)',
},
project: {
type: 'string',
description: 'Project name to organize memory files',
},
priority: {
type: 'string',
description: 'Priority level (low, medium, high)',
},
status: {
type: 'string',
description: 'Memory status (active, archived, reference)',
},
related_memories: {
type: 'array',
items: { type: 'string' },
description: 'IDs of related memories for cross-referencing',
},
language: {
type: 'string',
description: 'Programming language for code content',
},
},
required: ['content'],
},
},
{
name: 'get_memory',
description: 'Retrieve a memory by ID',
inputSchema: {
type: 'object',
properties: {
id: {
type: 'string',
description: 'The memory ID to retrieve',
},
},
required: ['id'],
},
},
{
name: 'list_memories',
description: 'List all stored memories or memories from a specific project',
inputSchema: {
type: 'object',
properties: {
limit: {
type: 'number',
description: 'Maximum number of memories to return',
},
project: {
type: 'string',
description: 'Filter by project name',
},
},
},
},
{
name: 'delete_memory',
description: 'Delete a memory by ID',
inputSchema: {
type: 'object',
properties: {
id: {
type: 'string',
description: 'The memory ID to delete',
},
},
required: ['id'],
},
},
{
name: 'search_memories',
description: 'AUTOMATICALLY use when user asks about past work, previous decisions, looking for examples, or needs context from earlier sessions. Provides semantic and keyword-based search.',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Search query',
},
project: {
type: 'string',
description: 'Limit search to specific project',
},
},
required: ['query'],
},
},
{
name: 'test_tool',
description: 'Simple test tool to verify MCP is working',
inputSchema: {
type: 'object',
properties: {
message: {
type: 'string',
description: 'Test message',
},
},
required: ['message'],
},
},
{
name: 'generate_dropoff',
description: 'Generate conversation dropoff document for session handoff with context from recent memories, git status, and project info',
inputSchema: {
type: 'object',
properties: {
session_summary: {
type: 'string',
description: 'Brief summary of work done in this session',
default: 'Session work completed',
},
include_recent_memories: {
type: 'boolean',
description: 'Include recent memories in the dropoff',
default: true,
},
include_git_status: {
type: 'boolean',
description: 'Include git status and recent commits',
default: true,
},
recent_memory_count: {
type: 'number',
description: 'Number of recent memories to include',
default: 5,
},
output_format: {
type: 'string',
description: 'Output format: markdown or json',
enum: ['markdown', 'json'],
default: 'markdown',
},
output_path: {
type: 'string',
description: 'Custom output directory path. If not provided, auto-detects session-dropoffs/ folder or defaults to current directory',
default: null,
},
},
},
},
{
name: 'create_task',
description: 'Create a new task with intelligent memory linking. Tasks start in "todo" status. IMPORTANT: After creating a task, remember to update its status to "in_progress" when you begin working on it. Proper state management helps track workflow and productivity.',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'Task title',
},
description: {
type: 'string',
description: 'Detailed task description',
},
project: {
type: 'string',
description: 'Project identifier',
},
category: {
type: 'string',
enum: ['personal', 'work', 'code', 'research'],
description: 'Task category',
},
priority: {
type: 'string',
enum: ['low', 'medium', 'high', 'urgent'],
default: 'medium',
description: 'Task priority',
},
parent_task: {
type: 'string',
description: 'Parent task ID for subtasks',
},
manual_memories: {
type: 'array',
items: { type: 'string' },
description: 'Memory IDs to manually link',
},
tags: {
type: 'array',
items: { type: 'string' },
description: 'Task tags',
},
auto_link: {
type: 'boolean',
default: true,
description: 'Automatically find and link relevant memories',
},
},
required: ['title', 'project'],
},
},
{
name: 'update_task',
description: `Update task status and details.
STATE MANAGEMENT GUIDELINES:
- Always mark tasks as "in_progress" when starting work on them
- Update to "done" immediately after completing a task
- Set to "blocked" when encountering obstacles or dependencies
- Use "todo" for tasks not yet started
IMPORTANT: Proactively manage task states throughout the work lifecycle. Don't wait for user prompts - update states as work progresses to maintain accurate workflow visibility.`,
inputSchema: {
type: 'object',
properties: {
task_id: {
type: 'string',
description: 'Task ID to update',
},
status: {
type: 'string',
enum: ['todo', 'in_progress', 'done', 'blocked'],
description: 'New task status',
},
title: {
type: 'string',
description: 'New task title',
},
description: {
type: 'string',
description: 'New task description',
},
add_subtasks: {
type: 'array',
items: { type: 'string' },
description: 'Task titles to add as subtasks',
},
add_memories: {
type: 'array',
items: { type: 'string' },
description: 'Memory IDs to link',
},
remove_memories: {
type: 'array',
items: { type: 'string' },
description: 'Memory IDs to unlink',
},
},
required: ['task_id'],
},
},
{
name: 'list_tasks',
description: 'List tasks with filtering options. Shows task status distribution and workflow health. Use this to monitor work progress and identify tasks that need status updates.',
inputSchema: {
type: 'object',
properties: {
project: {
type: 'string',
description: 'Filter by project',
},
status: {
type: 'string',
enum: ['todo', 'in_progress', 'done', 'blocked'],
description: 'Filter by status',
},
category: {
type: 'string',
description: 'Filter by category',
},
has_memory: {
type: 'string',
description: 'Filter by memory connection',
},
include_subtasks: {
type: 'boolean',
default: true,
description: 'Include subtasks in results',
},
limit: {
type: 'number',
default: 20,
description: 'Maximum tasks to return',
},
},
},
},
{
name: 'get_task_context',
description: 'Get detailed task information including status, relationships, and connected memories. Use this to understand task context and determine if status updates are needed.',
inputSchema: {
type: 'object',
properties: {
task_id: {
type: 'string',
description: 'Task ID',
},
depth: {
type: 'string',
enum: ['direct', 'deep'],
default: 'direct',
description: 'How many levels of connections to traverse',
},
},
required: ['task_id'],
},
},
{
name: 'delete_task',
description: 'Delete a task and its subtasks',
inputSchema: {
type: 'object',
properties: {
task_id: {
type: 'string',
description: 'Task ID to delete',
},
},
required: ['task_id'],
},
},
{
name: 'enhance_memory_metadata',
description: 'Generate optimized title and summary for a memory to improve dashboard card display. Uses intelligent content analysis to create concise, meaningful titles (max 60 chars) and summaries (max 150 chars).',
inputSchema: {
type: 'object',
properties: {
memory_id: {
type: 'string',
description: 'The ID of the memory to enhance with title and summary',
},
regenerate: {
type: 'boolean',
description: 'Force regeneration even if title/summary already exist',
},
},
required: ['memory_id'],
},
},
{
name: 'batch_enhance_memories',
description: 'Batch process multiple memories to add optimized titles and summaries. Useful for enhancing existing memories that lack proper metadata for dashboard display.',
inputSchema: {
type: 'object',
properties: {
project: {
type: 'string',
description: 'Filter by project name (optional)',
},
category: {
type: 'string',
description: 'Filter by category (optional)',
},
limit: {
type: 'number',
description: 'Maximum number of memories to process (default: 50)',
},
skip_existing: {
type: 'boolean',
description: 'Skip memories that already have titles/summaries (default: true)',
},
},
},
},
{
name: 'smart_status_update',
description: 'AUTOMATICALLY use when user mentions status changes in natural language. Intelligently parses natural language to determine intended status changes with validation and automation.',
inputSchema: {
type: 'object',
properties: {
task_id: {
type: 'string',
description: 'Task ID to update (optional - can be inferred from natural language)',
},
natural_language_input: {
type: 'string',
description: 'Natural language description of the status change (e.g., "I finished the auth module", "the API work is blocked")',
},
context: {
type: 'object',
properties: {
force_complete: { type: 'boolean' },
skip_validation: { type: 'boolean' },
blocking_reason: { type: 'string' },
completion_evidence: { type: 'string' }
},
description: 'Additional context for intelligent processing',
},
apply_automation: {
type: 'boolean',
description: 'Whether to apply automation suggestions (default: true)',
},
},
required: ['natural_language_input'],
},
},
{
name: 'get_task_status_analytics',
description: 'AUTOMATICALLY use when user asks about task progress, status overview, productivity metrics, or wants analytics. Provides comprehensive status insights and recommendations.',
inputSchema: {
type: 'object',
properties: {
project: {
type: 'string',
description: 'Filter analytics by project (optional)',
},
time_range: {
type: 'string',
enum: ['day', 'week', 'month', 'quarter'],
description: 'Time range for analytics (default: week)',
},
include_trends: {
type: 'boolean',
description: 'Include trend analysis (default: true)',
},
include_recommendations: {
type: 'boolean',
description: 'Include actionable recommendations (default: true)',
},
include_project_breakdown: {
type: 'boolean',
description: 'Include project-by-project analysis (default: true)',
},
},
},
},
{
name: 'validate_task_workflow',
description: 'Validate a proposed task status change with intelligent suggestions and workflow analysis. Use when you need to check if a status change makes sense.',
inputSchema: {
type: 'object',
properties: {
task_id: {
type: 'string',
description: 'Task ID to validate',
},
proposed_status: {
type: 'string',
enum: ['todo', 'in_progress', 'done', 'blocked'],
description: 'Proposed new status',
},
context: {
type: 'object',
properties: {
force_complete: { type: 'boolean' },
skip_testing: { type: 'boolean' },
skip_review: { type: 'boolean' },
blocking_reason: { type: 'string' }
},
description: 'Additional context for validation',
},
},
required: ['task_id', 'proposed_status'],
},
},
{
name: 'get_automation_suggestions',
description: 'Get intelligent automation suggestions for a task based on context analysis. Use when you want to see what automated actions are possible.',
inputSchema: {
type: 'object',
properties: {
task_id: {
type: 'string',
description: 'Task ID to analyze for automation opportunities',
},
},
required: ['task_id'],
},
},
{
name: 'batch_enhance_memories_ollama',
description: 'Batch process memories using local AI (Ollama) for privacy-focused title/summary generation. Processes large numbers of memories efficiently without external API calls.',
inputSchema: {
type: 'object',
properties: {
project: {
type: 'string',
description: 'Filter by project name (optional)',
},
category: {
type: 'string',
description: 'Filter by category (optional)',
},
limit: {
type: 'number',
description: 'Maximum number of memories to process (default: 50)',
},
skip_existing: {
type: 'boolean',
description: 'Skip memories that already have titles/summaries (default: true)',
},
model: {
type: 'string',
description: 'Ollama model to use (default: llama3.1:8b)',
},
batch_size: {
type: 'number',
description: 'Number of memories to process in parallel (default: 5)',
},
},
},
},
{
name: 'batch_enhance_tasks_ollama',
description: 'Batch process tasks using local AI (Ollama) for privacy-focused title/description enhancement. Processes large numbers of tasks efficiently without external API calls.',
inputSchema: {
type: 'object',
properties: {
project: {
type: 'string',
description: 'Filter by project name (optional)',
},
category: {
type: 'string',
description: 'Filter by category (optional)',
},
status: {
type: 'string',
description: 'Filter by task status (optional)',
},
limit: {
type: 'number',
description: 'Maximum number of tasks to process (default: 50)',
},
skip_existing: {
type: 'boolean',
description: 'Skip tasks that already have enhanced titles/descriptions (default: true)',
},
model: {
type: 'string',
description: 'Ollama model to use (default: llama3.1:8b)',
},
batch_size: {
type: 'number',
description: 'Number of tasks to process in parallel (default: 5)',
},
},
},
},
{
name: 'check_ollama_status',
description: 'Check if Ollama server is running and list available models for local AI processing.',
inputSchema: {
type: 'object',
properties: {
show_models: {
type: 'boolean',
description: 'Whether to list available models (default: true)',
},
},
},
},
{
name: 'enhance_memory_ollama',
description: 'Enhance a single memory with local AI (Ollama) for privacy-focused title/summary generation.',
inputSchema: {
type: 'object',
properties: {
memory_id: {
type: 'string',
description: 'ID of the memory to enhance',
},
model: {
type: 'string',
description: 'Ollama model to use (default: llama3.1:8b)',
},
force_update: {
type: 'boolean',
description: 'Force update even if memory already has title/summary (default: false)',
},
},
required: ['memory_id'],
},
},
{
name: 'deduplicate_memories',
description: 'Find and remove duplicate memory files, keeping the newest version of each memory ID. Use this to clean up duplicate memories caused by batch operations.',
inputSchema: {
type: 'object',
properties: {
preview_only: {
type: 'boolean',
description: 'Preview what would be removed without actually deleting files (default: false)',
},
},
},
},
{
name: 'work_detector_control',
description: 'Control the Universal Work Detector for automatic memory creation based on work patterns.',
inputSchema: {
type: 'object',
properties: {
action: {
type: 'string',
enum: ['enable', 'disable', 'status', 'stats'],
description: 'Action to perform: enable, disable, status, or stats',
},
},
required: ['action'],
},
},
{
name: 'set_memory_path',
description: 'Change where memories are stored. Updates the path dynamically without requiring restart.',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'string',
description: 'New absolute path for memory storage (e.g., D:\\MyDocuments\\AI-Memories)',
},
},
required: ['path'],
},
},
{
name: 'set_task_path',
description: 'Change where tasks are stored. Updates the path dynamically without requiring restart.',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'string',
description: 'New absolute path for task storage (e.g., D:\\MyDocuments\\AI-Tasks)',
},
},
required: ['path'],
},
},
{
name: 'get_current_paths',
description: 'Get the current memory and task storage paths.',
inputSchema: {
type: 'object',
properties: {},
},
},
],
};
});
// Enhanced tool selection logic with auto-trigger detection
function shouldAutoTrigger(toolName, userContext = '') {
const autoTriggerKeywords = {
'create_task': ['create', 'add', 'start', 'implement', 'build', 'make', 'work on', 'new task', 'new feature', 'new bug', 'todo', 'need to'],
'update_task': ['update', 'modify', 'change', 'complete', 'finish', 'done', 'block', 'progress', 'status', 'mark as'],
'smart_status_update': ['finished', 'completed', 'done with', 'blocked on', 'stuck on', 'working on', 'started', 'began', 'wrapped up', 'closed'],
'add_memory': ['remember', 'save', 'store', 'important', 'note', 'learned', 'decision', 'context', 'keep track'],
'search_memories': ['what did', 'how did', 'previous', 'before', 'earlier', 'last time', 'remember when', 'find', 'look for'],
'list_tasks': ['what am I', 'current tasks', 'working on', 'todo list', 'project status', 'progress', 'overview'],
'get_task_context': ['details about', 'more info', 'task info', 'related to', 'context for', 'tell me about', 'tell me more', 'more about'],
'get_task_status_analytics': ['analytics', 'metrics', 'progress report', 'how am I doing', 'productivity', 'status overview', 'completion rate', 'trends'],
'validate_task_workflow': ['is it okay to', 'can I', 'should I', 'validate', 'check if', 'makes sense to'],
'get_automation_suggestions': ['automation', 'suggestions', 'what can be automated', 'smart suggestions', 'recommendations']
};
const keywords = autoTriggerKeywords[toolName] || [];
const lowerContext = userContext.toLowerCase();
return keywords.some(keyword => lowerContext.includes(keyword));
}
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
// Track tool usage for behavioral analysis
const toolStartTime = Date.now();
let toolResult = null;
let toolError = null;
try {
// Track activity
sessionTracker.trackActivity('tool_use', { tool: name, args });
// Universal Work Detector tracking (safe mode enabled)
const workDetection = workDetector.trackActivity(name, args, null);
switch (name) {
case 'add_memory': {
const {
content,
tags = [],
category,
project,
priority = 'medium',
status = 'active',
related_memories = [],
language
} = args;
// Safeguard: Validate against mock data patterns
if (!content || typeof content !== 'string' || content.trim().length === 0) {
throw new Error('Invalid memory: Content is required and must be a non-empty string');
}
// Safeguard: Reject mock data indicators
const mockDataPatterns = [
/mock-\d+/i,
/test.*data/i,
/sample.*content/i,
/lorem ipsum/i,
/fake.*data/i,
/placeholder/i
];
const containsMockPattern = mockDataPatterns.some(pattern =>
pattern.test(content) ||
(typeof project === 'string' && pattern.test(project)) ||
(Array.isArray(tags) && tags.some(tag => pattern.test(tag)))
);
if (containsMockPattern) {
throw new Error('Invalid memory: Mock data patterns detected. Only real memories are allowed.');
}
// Safeguard: Validate real content requirements
if (content.trim().length < 10) {
throw new Erro