mcp-think-tank
Version:
Structured thinking and knowledge management tool for Model Context Protocol
230 lines (229 loc) • 10.4 kB
JavaScript
import { z } from 'zod';
import { v4 as uuidv4 } from 'uuid';
import { TaskSchema } from './schemas.js';
import { taskStorage } from './storage.js';
import { graph as knowledgeGraph, graphStorage } from '../memory/storage.js';
export function registerTaskTools(server) {
// No need to initialize, use shared persistent knowledge graph
// 1. Plan tasks - Create multiple tasks at once
server.addTool({
name: 'plan_tasks',
description: 'Create multiple tasks from a plan. Generates IDs and syncs with knowledge graph.',
parameters: z.object({
tasks: z.array(TaskSchema.omit({ id: true, status: true })
.extend({
priority: z.enum(['low', 'medium', 'high']).default('medium')
})).describe("List of tasks to create with their details")
}),
execute: async ({ tasks }, context) => {
const log = context && context.log ? context.log : { info() { }, error() { }, warn() { }, debug() { } };
const createdTasks = [];
const entities = [];
for (const taskData of tasks) {
const task = {
id: uuidv4(),
status: 'todo',
...taskData
};
taskStorage.addTask(task);
createdTasks.push(task);
// Create entities in knowledge graph
entities.push({
name: `Task-${task.id}`,
entityType: 'task',
observations: [
`Description: ${task.description}`,
`Priority: ${task.priority}`,
`Status: ${task.status}`,
`Created: ${new Date().toISOString()}`
]
});
}
// Create knowledge graph entities if possible
if (entities.length > 0) {
try {
for (const entity of entities) {
// Use the knowledgeGraph directly, avoiding any console logs
if (!knowledgeGraph.entities.has(entity.name)) {
knowledgeGraph.entities.set(entity.name, {
name: entity.name,
entityType: entity.entityType,
observations: [...entity.observations]
});
}
}
graphStorage.save();
if (log)
log.info(`Created ${entities.length} task entities in knowledge graph`);
}
catch (err) {
if (log)
log.error(`Failed to sync tasks with knowledge graph: ${err}`);
}
}
taskStorage.save();
return JSON.stringify({
tasks: createdTasks,
message: `Created ${createdTasks.length} tasks`
});
}
});
// 2. List tasks - Get tasks with optional filtering
server.addTool({
name: 'list_tasks',
description: 'List tasks with optional filtering by status and priority.',
parameters: z.object({
status: z.enum(['todo', 'in-progress', 'blocked', 'done']).describe("Filter tasks by status").optional(),
priority: z.enum(['low', 'medium', 'high']).describe("Filter tasks by priority level").optional()
}),
execute: async ({ status, priority }, context) => {
const _log = context && context.log ? context.log : { info() { }, error() { }, warn() { }, debug() { } };
const filter = {};
if (status)
filter.status = status;
if (priority)
filter.priority = priority;
const tasks = Object.keys(filter).length > 0
? taskStorage.getTasksBy(filter)
: taskStorage.getAllTasks();
return JSON.stringify({
tasks,
count: tasks.length,
filter: Object.keys(filter).length > 0 ? filter : 'none'
});
}
});
// 3. Next task - Get the next highest priority todo task and mark it in-progress
server.addTool({
name: 'next_task',
description: 'Get the next highest priority todo task and mark it as in-progress.',
parameters: z.object({
random_string: z.string().describe("Dummy parameter for no-parameter tools").optional()
}),
execute: async (_args, context) => {
const log = context && context.log ? context.log : { info() { }, error() { }, warn() { }, debug() { } };
const todoTasks = taskStorage.getTasksBy({ status: 'todo' });
if (todoTasks.length === 0) {
return JSON.stringify({
message: 'No todo tasks available',
task: null
});
}
// Sort by priority (high -> medium -> low)
const priorityOrder = { high: 0, medium: 1, low: 2 };
const nextTask = todoTasks.sort((a, b) => priorityOrder[a.priority] -
priorityOrder[b.priority])[0];
// Update status to in-progress
const updatedTask = taskStorage.updateTask(nextTask.id, {
status: 'in-progress'
});
// Add observation to knowledge graph
if (knowledgeGraph && updatedTask) {
try {
const observation = `Started: ${new Date().toISOString()}`;
knowledgeGraph.addObservations(`Task-${updatedTask.id}`, [observation]);
graphStorage.save();
}
catch (err) {
if (log)
log.error(`Failed to update task in knowledge graph: ${err}`);
}
}
return JSON.stringify({
message: updatedTask ? 'Task marked as in-progress' : 'Failed to update task',
task: updatedTask
});
}
});
// 4. Complete task - Mark a task as done
server.addTool({
name: 'complete_task',
description: 'Mark a task as completed.',
parameters: z.object({
id: z.string().uuid().describe("UUID of the task to mark as completed")
}),
execute: async ({ id }, context) => {
const log = context && context.log ? context.log : { info() { }, error() { }, warn() { }, debug() { } };
const updatedTask = taskStorage.updateTask(id, { status: 'done' });
if (!updatedTask) {
return JSON.stringify({
success: false,
message: `Task with ID ${id} not found`
});
}
// Add observation to knowledge graph
if (knowledgeGraph) {
try {
const observation = `Completed: ${new Date().toISOString()}`;
knowledgeGraph.addObservations(`Task-${id}`, [observation]);
graphStorage.save();
}
catch (err) {
if (log)
log.error(`Failed to update task completion in knowledge graph: ${err}`);
}
}
return JSON.stringify({
success: true,
message: 'Task marked as done',
task: updatedTask
});
}
});
// 5. Update tasks - Update multiple tasks at once
server.addTool({
name: 'update_tasks',
description: 'Update multiple tasks with new values.',
parameters: z.object({
updates: z.array(z.object({
id: z.string().uuid().describe("UUID of the task to update"),
description: z.string().min(3).describe("New task description").optional(),
status: z.enum(['todo', 'in-progress', 'blocked', 'done']).describe("New task status").optional(),
priority: z.enum(['low', 'medium', 'high']).describe("New priority level").optional(),
due: z.string().datetime().describe("New due date in ISO format").optional(),
tags: z.array(z.string()).describe("New tags for the task").optional()
})).describe("List of task updates to apply")
}),
execute: async ({ updates }, context) => {
const log = context && context.log ? context.log : { info() { }, error() { }, warn() { }, debug() { } };
const results = {
success: [],
failed: []
};
for (const update of updates) {
const { id, ...changes } = update;
const updatedTask = taskStorage.updateTask(id, changes);
if (updatedTask) {
results.success.push(updatedTask);
// Update knowledge graph
if (knowledgeGraph && (changes.description || changes.priority || changes.status)) {
try {
const observations = [];
if (changes.description)
observations.push(`Description: ${changes.description}`);
if (changes.priority)
observations.push(`Priority: ${changes.priority}`);
if (changes.status)
observations.push(`Status: ${changes.status}`);
knowledgeGraph.addObservations(`Task-${id}`, observations);
graphStorage.save();
}
catch (err) {
if (log)
log.error(`Failed to update task entity in knowledge graph: ${err}`);
}
}
}
else {
results.failed.push(id);
}
}
taskStorage.save();
return JSON.stringify({
message: `Updated ${results.success.length} tasks, ${results.failed.length} failed`,
success: results.success,
failed: results.failed
});
}
});
}