mcp-memory-taskmanager
Version:
Intelligent MCP Memory System with Task Manager - Domain-specific knowledge organization and autonomous task management for AI assistants
525 lines • 24.5 kB
JavaScript
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js';
import * as fs from 'fs-extra';
import * as path from 'path';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
class MCPMemoryTaskManager {
config;
server;
constructor() {
this.config = {
memoryPath: process.env.MEMORY_FILE_PATH || path.join(process.cwd(), 'memory', 'memory.json'),
tasksPath: process.env.TASK_MANAGER_FILE_PATH || path.join(process.cwd(), 'tasks', 'tasks.json'),
memoriesBasePath: process.env.MEMORIES_BASE_PATH || path.join(process.cwd(), 'memories'),
enableBackup: process.env.ENABLE_BACKUP === 'true',
maxMemorySize: parseInt(process.env.MAX_MEMORY_SIZE || '10000')
};
this.server = new Server({
name: 'mcp-memory-taskmanager',
version: '1.0.0',
}, {
capabilities: {
tools: {},
},
});
this.setupToolHandlers();
this.ensureDirectories();
}
async ensureDirectories() {
const memoryDir = path.dirname(this.config.memoryPath);
const tasksDir = path.dirname(this.config.tasksPath);
await fs.ensureDir(memoryDir);
await fs.ensureDir(tasksDir);
await fs.ensureDir(this.config.memoriesBasePath);
// Initialize files if they don't exist
if (!await fs.pathExists(this.config.memoryPath)) {
await fs.outputFile(this.config.memoryPath, JSON.stringify({ memories: [], domains: {} }, null, 2));
}
if (!await fs.pathExists(this.config.tasksPath)) {
await fs.outputFile(this.config.tasksPath, JSON.stringify({ tasks: [], requests: {} }, null, 2));
}
// Ensure domain-specific memory directories exist
const domains = ['frontend', 'backend', 'ui_design', 'ux_design', 'devops', 'architecture', 'mobile', 'database', 'testing', 'security'];
for (const domain of domains) {
const domainDir = path.join(this.config.memoriesBasePath, domain);
await fs.ensureDir(domainDir);
const domainMemoryPath = path.join(domainDir, 'memory.json');
if (!await fs.pathExists(domainMemoryPath)) {
const defaultDomainMemory = {
domain,
description: `Specialized memory for ${domain} development`,
entries: [],
metadata: {
created_at: new Date().toISOString(),
last_updated: new Date().toISOString(),
total_entries: 0,
technologies: [],
patterns: []
},
stats: {
success_rate: 0.0,
most_used_technologies: [],
recent_learnings: [],
knowledge_gaps: []
}
};
await fs.outputFile(domainMemoryPath, JSON.stringify(defaultDomainMemory, null, 2));
}
}
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'store_memory',
description: 'Store information in the memory system with domain classification',
inputSchema: {
type: 'object',
properties: {
content: {
type: 'string',
description: 'The information to store'
},
domain: {
type: 'string',
description: 'Domain classification. Specialized domains: frontend, backend, ui_design, ux_design, devops, architecture, mobile, database, testing, security. Other domains will use general memory.'
},
technology: {
type: 'string',
description: 'Specific technology or framework (e.g., React, Node.js, Docker, etc.)'
},
tags: {
type: 'array',
items: { type: 'string' },
description: 'Tags for categorization'
},
importance: {
type: 'number',
description: 'Importance level (1-10)',
minimum: 1,
maximum: 10
}
},
required: ['content', 'domain']
}
},
{
name: 'search_memory',
description: 'Search stored memories by content, domain, or tags',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Search query'
},
domain: {
type: 'string',
description: 'Filter by domain'
},
tags: {
type: 'array',
items: { type: 'string' },
description: 'Filter by tags'
},
limit: {
type: 'number',
description: 'Maximum number of results',
default: 10
}
},
required: ['query']
}
},
{
name: 'request_planning',
description: 'Register a new user request and plan its associated tasks',
inputSchema: {
type: 'object',
properties: {
originalRequest: {
type: 'string',
description: 'The original user request'
},
splitDetails: {
type: 'string',
description: 'Details about how the request was split'
},
tasks: {
type: 'array',
items: {
type: 'object',
properties: {
title: { type: 'string' },
description: { type: 'string' }
},
required: ['title', 'description']
}
}
},
required: ['originalRequest', 'tasks']
}
},
{
name: 'get_next_task',
description: 'Get the next pending task for a request',
inputSchema: {
type: 'object',
properties: {
requestId: {
type: 'string',
description: 'The request ID'
}
},
required: ['requestId']
}
},
{
name: 'mark_task_done',
description: 'Mark a task as completed',
inputSchema: {
type: 'object',
properties: {
requestId: {
type: 'string',
description: 'The request ID'
},
taskId: {
type: 'string',
description: 'The task ID'
},
completedDetails: {
type: 'string',
description: 'Details about task completion'
}
},
required: ['requestId', 'taskId']
}
},
{
name: 'approve_task_completion',
description: 'Approve a completed task',
inputSchema: {
type: 'object',
properties: {
requestId: {
type: 'string',
description: 'The request ID'
},
taskId: {
type: 'string',
description: 'The task ID'
}
},
required: ['requestId', 'taskId']
}
},
{
name: 'approve_request_completion',
description: 'Approve the completion of an entire request',
inputSchema: {
type: 'object',
properties: {
requestId: {
type: 'string',
description: 'The request ID'
}
},
required: ['requestId']
}
}
]
};
});
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'store_memory':
return await this.storeMemory(args);
case 'search_memory':
return await this.searchMemory(args);
case 'request_planning':
return await this.requestPlanning(args);
case 'get_next_task':
return await this.getNextTask(args);
case 'mark_task_done':
return await this.markTaskDone(args);
case 'approve_task_completion':
return await this.approveTaskCompletion(args);
case 'approve_request_completion':
return await this.approveRequestCompletion(args);
default:
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
}
}
catch (error) {
throw new McpError(ErrorCode.InternalError, `Error executing tool ${name}: ${error instanceof Error ? error.message : String(error)}`);
}
});
}
async getDomainMemoryPath(domain) {
return path.join(this.config.memoriesBasePath, domain, 'memory.json');
}
async loadDomainMemory(domain) {
const domainPath = await this.getDomainMemoryPath(domain);
if (await fs.pathExists(domainPath)) {
return await fs.readJson(domainPath);
}
throw new Error(`Domain memory not found: ${domain}`);
}
async saveDomainMemory(domain, memoryData) {
const domainPath = await this.getDomainMemoryPath(domain);
memoryData.metadata.last_updated = new Date().toISOString();
memoryData.metadata.total_entries = memoryData.entries.length;
await fs.writeJson(domainPath, memoryData, { spaces: 2 });
}
async storeMemory(args) {
try {
const { content, domain = 'general', tags = [], importance = 5, technology = '' } = args;
// Store in domain-specific memory if domain is recognized
const recognizedDomains = ['frontend', 'backend', 'ui_design', 'ux_design', 'devops', 'architecture', 'mobile', 'database', 'testing', 'security'];
if (recognizedDomains.includes(domain)) {
const domainMemory = await this.loadDomainMemory(domain);
const newEntry = {
id: Date.now().toString(),
timestamp: new Date().toISOString(),
content,
technology,
tags,
importance,
context: { source: 'mcp-memory-taskmanager' }
};
domainMemory.entries.push(newEntry);
// Update metadata
if (technology && !domainMemory.metadata.technologies.includes(technology)) {
domainMemory.metadata.technologies.push(technology);
}
await this.saveDomainMemory(domain, domainMemory);
return {
content: [{
type: 'text',
text: `Memory stored successfully in ${domain} domain with ID: ${newEntry.id}${technology ? ` (Technology: ${technology})` : ''}`
}]
};
}
else {
// Fallback to general memory system
const memoryData = await fs.readJson(this.config.memoryPath);
const newMemory = {
id: Date.now().toString(),
timestamp: new Date().toISOString(),
content,
domain,
tags,
importance
};
memoryData.memories.push(newMemory);
if (!memoryData.domains[domain]) {
memoryData.domains[domain] = [];
}
memoryData.domains[domain].push(newMemory.id);
await fs.writeJson(this.config.memoryPath, memoryData, { spaces: 2 });
return {
content: [{
type: 'text',
text: `Memory stored successfully with ID: ${newMemory.id} in domain: ${domain}`
}]
};
}
}
catch (error) {
throw new McpError(ErrorCode.InternalError, `Failed to store memory: ${error}`);
}
}
async searchMemory(args) {
try {
const { query, domain, limit = 10 } = args;
let allResults = [];
const recognizedDomains = ['frontend', 'backend', 'ui_design', 'ux_design', 'devops', 'architecture', 'mobile', 'database', 'testing', 'security'];
// Search in domain-specific memories
if (domain && recognizedDomains.includes(domain)) {
try {
const domainMemory = await this.loadDomainMemory(domain);
const domainResults = domainMemory.entries.filter((entry) => {
return entry.content.toLowerCase().includes(query.toLowerCase()) ||
entry.tags.some((tag) => tag.toLowerCase().includes(query.toLowerCase())) ||
(entry.technology && entry.technology.toLowerCase().includes(query.toLowerCase()));
}).map(entry => ({ ...entry, source: `${domain} domain` }));
allResults.push(...domainResults);
}
catch (error) {
console.warn(`Could not search in ${domain} domain:`, error);
}
}
else if (!domain) {
// Search in all domain-specific memories
for (const domainName of recognizedDomains) {
try {
const domainMemory = await this.loadDomainMemory(domainName);
const domainResults = domainMemory.entries.filter((entry) => {
return entry.content.toLowerCase().includes(query.toLowerCase()) ||
entry.tags.some((tag) => tag.toLowerCase().includes(query.toLowerCase())) ||
(entry.technology && entry.technology.toLowerCase().includes(query.toLowerCase()));
}).map(entry => ({ ...entry, source: `${domainName} domain` }));
allResults.push(...domainResults);
}
catch (error) {
console.warn(`Could not search in ${domainName} domain:`, error);
}
}
}
// Also search in general memory system
try {
const data = await fs.readJson(this.config.memoryPath);
const generalResults = data.memories.filter((memory) => {
const matchesQuery = memory.content.toLowerCase().includes(query.toLowerCase()) ||
memory.tags.some((tag) => tag.toLowerCase().includes(query.toLowerCase()));
const matchesDomain = !domain || memory.domain === domain;
return matchesQuery && matchesDomain;
}).map((memory) => ({ ...memory, source: 'general memory' }));
allResults.push(...generalResults);
}
catch (error) {
console.warn('Could not search in general memory:', error);
}
// Sort by importance and recency
allResults.sort((a, b) => {
const importanceDiff = b.importance - a.importance;
if (importanceDiff !== 0)
return importanceDiff;
return new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime();
});
const results = allResults.slice(0, limit);
return {
content: [{
type: 'text',
text: `Found ${results.length} memories:\n\n${results.map((memory) => `ID: ${memory.id}\nSource: ${memory.source}\nDomain: ${memory.domain || 'N/A'}\nTechnology: ${memory.technology || 'N/A'}\nContent: ${memory.content}\nTags: ${memory.tags.join(', ')}\nImportance: ${memory.importance}\nTimestamp: ${memory.timestamp}\n`).join('\n')}`
}]
};
}
catch (error) {
throw new McpError(ErrorCode.InternalError, `Failed to search memory: ${error}`);
}
}
async requestPlanning(args) {
const data = await fs.readJson(this.config.tasksPath);
const requestId = Date.now().toString();
const request = {
id: requestId,
originalRequest: args.originalRequest,
splitDetails: args.splitDetails,
tasks: args.tasks.map((task, index) => ({
id: `${requestId}_${index}`,
title: task.title,
description: task.description,
status: 'pending',
createdAt: new Date().toISOString()
})),
status: 'active',
createdAt: new Date().toISOString()
};
data.requests[requestId] = request;
await fs.outputFile(this.config.tasksPath, JSON.stringify(data, null, 2));
return {
content: [{
type: 'text',
text: `Request planned successfully with ID: ${requestId}\n\nTasks created:\n${request.tasks.map((t) => `- ${t.title}: ${t.description}`).join('\n')}`
}]
};
}
async getNextTask(args) {
const data = await fs.readJson(this.config.tasksPath);
const request = data.requests[args.requestId];
if (!request) {
throw new Error(`Request ${args.requestId} not found`);
}
const nextTask = request.tasks.find((task) => task.status === 'pending');
if (!nextTask) {
return {
content: [{
type: 'text',
text: 'All tasks completed. Request awaits approval for completion.'
}]
};
}
return {
content: [{
type: 'text',
text: JSON.stringify(nextTask, null, 2)
}]
};
}
async markTaskDone(args) {
const data = await fs.readJson(this.config.tasksPath);
const request = data.requests[args.requestId];
if (!request) {
throw new Error(`Request ${args.requestId} not found`);
}
const task = request.tasks.find((t) => t.id === args.taskId);
if (!task) {
throw new Error(`Task ${args.taskId} not found`);
}
task.status = 'done';
task.completedAt = new Date().toISOString();
task.completedDetails = args.completedDetails;
await fs.outputFile(this.config.tasksPath, JSON.stringify(data, null, 2));
return {
content: [{
type: 'text',
text: `Task ${args.taskId} marked as done. Awaiting approval.`
}]
};
}
async approveTaskCompletion(args) {
const data = await fs.readJson(this.config.tasksPath);
const request = data.requests[args.requestId];
if (!request) {
throw new Error(`Request ${args.requestId} not found`);
}
const task = request.tasks.find((t) => t.id === args.taskId);
if (!task) {
throw new Error(`Task ${args.taskId} not found`);
}
task.status = 'approved';
task.approvedAt = new Date().toISOString();
await fs.outputFile(this.config.tasksPath, JSON.stringify(data, null, 2));
return {
content: [{
type: 'text',
text: `Task ${args.taskId} approved. You can proceed to the next task.`
}]
};
}
async approveRequestCompletion(args) {
const data = await fs.readJson(this.config.tasksPath);
const request = data.requests[args.requestId];
if (!request) {
throw new Error(`Request ${args.requestId} not found`);
}
request.status = 'completed';
request.completedAt = new Date().toISOString();
await fs.outputFile(this.config.tasksPath, JSON.stringify(data, null, 2));
return {
content: [{
type: 'text',
text: `Request ${args.requestId} fully completed and approved!`
}]
};
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('MCP Memory TaskManager server running on stdio');
}
}
// Run the server
const server = new MCPMemoryTaskManager();
server.run().catch(console.error);
//# sourceMappingURL=index.js.map