@dalmasonto/taskflow-mcp 1.0.11 → 1.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -202,6 +202,10 @@ export async function updateTask(params) {
202
202
  updates.due_date = params.due_date;
203
203
  if (params.estimated_time !== undefined)
204
204
  updates.estimated_time = params.estimated_time;
205
+ if (params.allowed_tools !== undefined)
206
+ updates.allowed_tools = JSON.stringify(params.allowed_tools);
207
+ if (params.denied_tools !== undefined)
208
+ updates.denied_tools = JSON.stringify(params.denied_tools);
205
209
  if (Object.keys(updates).length === 0) {
206
210
  return successResponse(oldParsed);
207
211
  }
@@ -356,14 +360,14 @@ export function registerTaskTools(server) {
356
360
  links: z.array(LinkSchema).optional(),
357
361
  due_date: z.string().optional(),
358
362
  estimated_time: z.number().optional(),
359
- }, async (params) => createTask(params));
363
+ }, { readOnlyHint: false }, async (params) => createTask(params));
360
364
  server.tool('list_tasks', 'List tasks with optional filters. Use at conversation start to see what is in progress or blocked. Filter by status, project, priority, or tag.', {
361
365
  status: TaskStatus.optional(),
362
366
  project_id: z.number().optional(),
363
367
  priority: TaskPriority.optional(),
364
368
  tag: z.string().optional(),
365
- }, async (params) => listTasks(params));
366
- server.tool('get_task', 'Get a task by ID with time tracking info. Read the description carefully — it often contains implementation details and acceptance criteria.', { id: z.number() }, async (params) => getTask(params));
369
+ }, { readOnlyHint: true }, async (params) => listTasks(params));
370
+ server.tool('get_task', 'Get a task by ID with time tracking info. Read the description carefully — it often contains implementation details and acceptance criteria.', { id: z.number() }, { readOnlyHint: true }, async (params) => getTask(params));
367
371
  server.tool('update_task', 'Update task fields. Use this to add details, update descriptions with progress notes, or adjust priority as you learn more.', {
368
372
  id: z.number(),
369
373
  title: z.string().optional(),
@@ -376,12 +380,14 @@ export function registerTaskTools(server) {
376
380
  links: z.array(LinkSchema).optional(),
377
381
  due_date: z.string().optional(),
378
382
  estimated_time: z.number().optional(),
379
- }, async (params) => updateTask(params));
383
+ allowed_tools: z.array(z.string()).optional().describe('Allowlist of MCP tool names for this task. If set, only these tools can be used while timer is active.'),
384
+ denied_tools: z.array(z.string()).optional().describe('Denylist of MCP tool names for this task. These tools are blocked while timer is active.'),
385
+ }, { readOnlyHint: false }, async (params) => updateTask(params));
380
386
  server.tool('update_task_status', 'Update task status with transition validation. Use when a task becomes blocked, is partially done, or needs to be reopened.', {
381
387
  id: z.number(),
382
388
  status: TaskStatus,
383
- }, async (params) => updateTaskStatus(params));
384
- server.tool('delete_task', 'Delete a task by ID. Use sparingly — prefer updating status to "done" instead of deleting.', { id: z.number() }, async (params) => deleteTask(params));
389
+ }, { readOnlyHint: false }, async (params) => updateTaskStatus(params));
390
+ server.tool('delete_task', 'Delete a task by ID. Use sparingly — prefer updating status to "done" instead of deleting.', { id: z.number() }, { destructiveHint: true }, async (params) => deleteTask(params));
385
391
  server.tool('bulk_create_tasks', 'Create multiple tasks in a single transaction. Useful when breaking down a feature into subtasks.', {
386
392
  tasks: z.array(z.object({
387
393
  title: z.string(),
@@ -392,6 +398,6 @@ export function registerTaskTools(server) {
392
398
  dependencies: z.array(z.number()).optional(),
393
399
  tags: z.array(z.string()).optional(),
394
400
  })),
395
- }, async (params) => bulkCreateTasks(params));
396
- server.tool('search_tasks', 'Search tasks by title or description. Use this to find tasks related to your current work before creating duplicates.', { query: z.string() }, async (params) => searchTasks(params));
401
+ }, { readOnlyHint: false }, async (params) => bulkCreateTasks(params));
402
+ server.tool('search_tasks', 'Search tasks by title or description. Use this to find tasks related to your current work before creating duplicates.', { query: z.string() }, { readOnlyHint: true }, async (params) => searchTasks(params));
397
403
  }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerTerminalTools(server: McpServer): void;
@@ -0,0 +1,73 @@
1
+ import { z } from 'zod';
2
+ import { execFileSync } from 'child_process';
3
+ import { getDb } from '../db.js';
4
+ import { logActivity, errorResponse, successResponse } from '../helpers.js';
5
+ function getAgentPane(agentName) {
6
+ const db = getDb();
7
+ const agent = db.prepare('SELECT * FROM agent_registry WHERE name = ?').get(agentName);
8
+ if (!agent)
9
+ return { error: errorResponse(`Agent "${agentName}" not found`, 'NOT_FOUND') };
10
+ if (!agent.tmux_pane)
11
+ return { error: errorResponse(`Agent "${agentName}" has no tmux pane`, 'VALIDATION_ERROR') };
12
+ return { agent };
13
+ }
14
+ export function registerTerminalTools(server) {
15
+ server.tool('capture_terminal', 'Capture the current terminal content of an agent\'s tmux pane. Returns the visible text on screen. Useful for seeing what prompts or output an agent is displaying.', {
16
+ agent_name: z.string().describe('Name of the agent whose terminal to capture'),
17
+ }, { readOnlyHint: true }, async (params) => {
18
+ const result = getAgentPane(params.agent_name);
19
+ if (result.error)
20
+ return result.error;
21
+ try {
22
+ const output = execFileSync('tmux', ['capture-pane', '-p', '-t', result.agent.tmux_pane], { timeout: 5000 }).toString();
23
+ return successResponse({
24
+ agent: params.agent_name,
25
+ pane: result.agent.tmux_pane,
26
+ content: output,
27
+ });
28
+ }
29
+ catch (err) {
30
+ return errorResponse(`Failed to capture pane: ${err.message}`, 'VALIDATION_ERROR');
31
+ }
32
+ });
33
+ server.tool('send_keys', 'Send raw keystrokes to an agent\'s tmux pane. Use this to respond to interactive prompts (yes/no, numbered choices, permission approvals) displayed in an agent\'s terminal. By default appends Enter after the keys.', {
34
+ agent_name: z.string().describe('Name of the agent whose terminal to send keys to'),
35
+ keys: z.string().describe('The keys/text to send (e.g. "yes", "1", "y") or tmux key names (e.g. "Escape", "Up", "Down", "BTab")'),
36
+ enter: z.boolean().optional().describe('Whether to press Enter after the keys (default: true)'),
37
+ literal: z.boolean().optional().describe('Send as literal text with -l flag (default: true). Set false for tmux key names like Escape, Up, Down, Left, Right, BTab'),
38
+ }, { destructiveHint: true }, async (params) => {
39
+ const result = getAgentPane(params.agent_name);
40
+ if (result.error)
41
+ return result.error;
42
+ const sendEnter = params.enter !== false;
43
+ const isLiteral = params.literal !== false;
44
+ try {
45
+ const pane = result.agent.tmux_pane;
46
+ if (isLiteral) {
47
+ // Send as literal text (like typing on a keyboard)
48
+ execFileSync('tmux', ['send-keys', '-t', pane, '-l', params.keys], { stdio: 'ignore', timeout: 5000 });
49
+ if (sendEnter) {
50
+ execFileSync('tmux', ['send-keys', '-t', pane, 'Enter'], { stdio: 'ignore', timeout: 5000 });
51
+ }
52
+ }
53
+ else {
54
+ // Send as tmux key names (Escape, Up, Down, etc.)
55
+ const args = ['send-keys', '-t', pane, params.keys];
56
+ if (sendEnter)
57
+ args.push('Enter');
58
+ execFileSync('tmux', args, { stdio: 'ignore', timeout: 5000 });
59
+ }
60
+ logActivity('terminal_send_keys', `Sent keys to ${params.agent_name}: ${params.keys.slice(0, 50)}`, { entityType: 'agent' });
61
+ return successResponse({
62
+ agent: params.agent_name,
63
+ pane: result.agent.tmux_pane,
64
+ keys: params.keys,
65
+ enter: sendEnter,
66
+ sent: true,
67
+ });
68
+ }
69
+ catch (err) {
70
+ return errorResponse(`Failed to send keys: ${err.message}`, 'VALIDATION_ERROR');
71
+ }
72
+ });
73
+ }
@@ -140,15 +140,15 @@ export async function listSessions(params) {
140
140
  }
141
141
  // ─── MCP registration ─────────────────────────────────────────────────
142
142
  export function registerTimerTools(server) {
143
- server.tool('start_timer', 'Start a timer session for a task. Call this before beginning work on any task to track focused time. Automatically transitions the task to "in_progress".', { task_id: z.number() }, async (params) => startTimer(params));
144
- server.tool('pause_timer', 'Pause the active timer session for a task. Call when switching context or waiting for user input. The task transitions to "paused".', { task_id: z.number() }, async (params) => pauseTimer(params));
143
+ server.tool('start_timer', 'Start a timer session for a task. Call this before beginning work on any task to track focused time. Automatically transitions the task to "in_progress".', { task_id: z.number() }, { readOnlyHint: false }, async (params) => startTimer(params));
144
+ server.tool('pause_timer', 'Pause the active timer session for a task. Call when switching context or waiting for user input. The task transitions to "paused".', { task_id: z.number() }, { readOnlyHint: false }, async (params) => pauseTimer(params));
145
145
  server.tool('stop_timer', 'Stop the active timer and set a final status. Call when finishing work — use "done" if complete, "partial_done" if more remains, "blocked" if stuck.', {
146
146
  task_id: z.number(),
147
147
  final_status: z.enum(['done', 'partial_done', 'blocked']).optional(),
148
- }, async (params) => stopTimer(params));
148
+ }, { readOnlyHint: false }, async (params) => stopTimer(params));
149
149
  server.tool('list_sessions', 'List timer sessions with optional filters. Use to review time spent on a task or across a date range.', {
150
150
  task_id: z.number().optional(),
151
151
  start_date: z.string().optional(),
152
152
  end_date: z.string().optional(),
153
- }, async (params) => listSessions(params));
153
+ }, { readOnlyHint: true }, async (params) => listSessions(params));
154
154
  }
package/dist/types.d.ts CHANGED
@@ -22,9 +22,9 @@ export declare const ProjectType: z.ZodEnum<{
22
22
  export type ProjectType = z.infer<typeof ProjectType>;
23
23
  export declare const NotificationType: z.ZodEnum<{
24
24
  info: "info";
25
+ error: "error";
25
26
  success: "success";
26
27
  warning: "warning";
27
- error: "error";
28
28
  }>;
29
29
  export type NotificationType = z.infer<typeof NotificationType>;
30
30
  export declare const AgentMessageStatus: z.ZodEnum<{
@@ -66,6 +66,10 @@ export declare const ActivityAction: z.ZodEnum<{
66
66
  agent_question_answered: "agent_question_answered";
67
67
  agent_connected: "agent_connected";
68
68
  agent_disconnected: "agent_disconnected";
69
+ terminal_send_keys: "terminal_send_keys";
70
+ terminal_captured: "terminal_captured";
71
+ compaction_summary: "compaction_summary";
72
+ activity_compacted: "activity_compacted";
69
73
  }>;
70
74
  export type ActivityAction = z.infer<typeof ActivityAction>;
71
75
  export declare const VALID_TRANSITIONS: Record<TaskStatus, TaskStatus[]>;
package/dist/types.js CHANGED
@@ -16,6 +16,8 @@ export const ActivityAction = z.enum([
16
16
  'link_added', 'tag_added', 'tag_removed', 'debug_log',
17
17
  'agent_question', 'agent_question_answered',
18
18
  'agent_connected', 'agent_disconnected',
19
+ 'terminal_send_keys', 'terminal_captured',
20
+ 'compaction_summary', 'activity_compacted',
19
21
  ]);
20
22
  export const VALID_TRANSITIONS = {
21
23
  not_started: ['in_progress', 'blocked'],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dalmasonto/taskflow-mcp",
3
- "version": "1.0.11",
3
+ "version": "1.0.15",
4
4
  "description": "MCP server for TaskFlow — manage projects, tasks, timers, analytics via AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",