Skip to content

Set autoApprove to true by default, and provide better CLI approval intructions #53

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .cursor/rules/general.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ description:
globs:
alwaysApply: true
---
Work step-by-step. If presented with an implementation plan, implement the plan exactly. If the plan presents more than one implementation option, consult with the human user to decide between options. If you are tempted to embellish or imporve upon the plan, consult with the human user. Always complete the current task and wait for human review before proceeding to the next task.
Work step-by-step. If presented with an implementation plan, implement the plan exactly. If the plan presents more than one implementation option, consult with the human user to decide between options. If you are tempted to embellish or imporve upon the plan, consult with the human user. Always complete the current task and wait for human review before proceeding to the next task.

In developing this codebase, we are doing test-driven development with an integration testing (as opposed to a unit testing) verification strategy. Before writing any code (except perhaps for empty function or class signatures), we will write tests and run them to make sure they fail. The red phase is not complete until the tests are failing correctly.
2 changes: 1 addition & 1 deletion .cursor/rules/tests.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ description: Writing unit tests with `jest`
globs: tests/**/*
alwaysApply: false
---
Make use of the helpers in tests/mcp/test-helpers.ts.
Make use of the helpers in tests/mcp/test-helpers.ts for writing tests.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Usually you will set the tool configuration in Claude Desktop, Cursor, or anothe
}
```

To use the CLI utility, you can use the following command:
To use the CLI utility, you can install the package globally and then use the following command:

```bash
npx taskqueue --help
Expand Down Expand Up @@ -145,6 +145,8 @@ npx --package=taskqueue-mcp taskqueue --help

#### Task Approval

By default, all tasks and projects will be auto-approved when marked "done" by the AI agent. To require manual human task approval, set `autoApprove` to `false` when creating a project.

Task approval is controlled exclusively by the human user through the CLI:

```bash
Expand All @@ -154,7 +156,7 @@ npx taskqueue approve-task -- <projectId> <taskId>
Options:
- `-f, --force`: Force approval even if the task is not marked as done

Note: Tasks must be marked as "done" with completed details before they can be approved (unless using --force).
Note: Tasks must be marked as "done" with completed details by the AI agent before they can be approved (unless using --force).

#### Listing Tasks and Projects

Expand Down Expand Up @@ -221,6 +223,7 @@ TaskManagerFile
├── initialPrompt: string # Original user request text
├── projectPlan: string # Additional project details
├── completed: boolean # Project completion status
├── autoApprove: boolean # Set `false` to require manual user approval
└── tasks: Task[] # Array of tasks
├── id: string # Format: "task-{number}"
├── title: string # Short task title
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "taskqueue-mcp",
"version": "1.4.0",
"version": "1.4.1",
"description": "Task Queue MCP Server",
"author": "Christopher C. Smith ([email protected])",
"main": "dist/src/server/index.js",
Expand Down
2 changes: 1 addition & 1 deletion src/client/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const program = new Command();
program
.name("taskqueue")
.description("CLI for the Task Manager MCP Server")
.version("1.4.0")
.version("1.4.1")
.option(
'-f, --file-path <path>',
'Specify the path to the tasks JSON file. Overrides TASK_MANAGER_FILE_PATH env var.'
Expand Down
13 changes: 10 additions & 3 deletions src/server/TaskManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
AddTasksSuccessData,
DeleteTaskSuccessData,
ReadProjectSuccessData,
UpdateTaskSuccessData
} from "../types/response.js";
import { AppError, AppErrorCode } from "../types/errors.js";
import { FileSystemService } from "./FileSystemService.js";
Expand Down Expand Up @@ -134,7 +135,7 @@ export class TaskManager {
projectPlan: projectPlan || initialPrompt,
tasks: newTasks,
completed: false,
autoApprove: autoApprove === true ? true : false,
autoApprove: autoApprove === false ? false : true,
};

this.data.projects.push(newProject);
Expand Down Expand Up @@ -519,7 +520,7 @@ export class TaskManager {
status?: "not started" | "in progress" | "done";
completedDetails?: string;
}
): Promise<Task> {
): Promise<UpdateTaskSuccessData> {
await this.ensureInitialized();
await this.reloadFromDisk();

Expand All @@ -544,8 +545,14 @@ export class TaskManager {
// Apply updates
Object.assign(task, updates);

// Generate message if needed
let message: string | undefined = undefined;
if (updates.status === 'done' && proj.autoApprove === false) {
message = `Task marked as done but requires human approval.\nTo approve, user should run: npx taskqueue approve-task -- ${projectId} ${taskId}`;
}

await this.saveTasks();
return task;
return { task, message };
}

public async deleteTask(projectId: string, taskId: string): Promise<DeleteTaskSuccessData> {
Expand Down
2 changes: 1 addition & 1 deletion src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprot
const server = new Server(
{
name: "task-manager-server",
version: "1.4.0"
version: "1.4.1"
},
{
capabilities: {
Expand Down
2 changes: 1 addition & 1 deletion src/server/toolExecutors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ const createProjectToolExecutor: ToolExecutor = {
const initialPrompt = validateRequiredStringParam(args.initialPrompt, "initialPrompt");
const validatedTasks = validateTaskObjects(args.tasks);
const projectPlan = args.projectPlan !== undefined ? String(args.projectPlan) : undefined;
const autoApprove = args.autoApprove === true;
const autoApprove = args.autoApprove as boolean | undefined;

if (args.projectPlan !== undefined && typeof args.projectPlan !== 'string') {
throw new AppError(
Expand Down
7 changes: 5 additions & 2 deletions src/server/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { TaskManager } from "./TaskManager.js";
import { toolExecutorMap } from "./toolExecutors.js";
import { AppError, AppErrorCode } from "../types/errors.js";
import { McpError, CallToolResult, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
import { UpdateTaskSuccessData } from '../types/response.js';

// ---------------------- PROJECT TOOLS ----------------------

Expand Down Expand Up @@ -468,9 +469,11 @@ export async function executeToolAndHandleErrors(
// 2. Execute the tool - Validation errors (protocol) or TaskManager errors (execution) might be thrown
const resultData = await executor.execute(taskManager, args);

// 3. Format successful execution result
// 3. Format successful execution result using standard stringify
const responseText = JSON.stringify(resultData, null, 2);

return {
content: [{ type: "text", text: JSON.stringify(resultData, null, 2) }]
content: [{ type: "text", text: responseText }]
};

} catch (error: AppError | unknown) {
Expand Down
8 changes: 7 additions & 1 deletion src/types/response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export interface ProjectCreationSuccessData {
export interface DeleteTaskSuccessData {
message: string;
}

export interface ReadProjectSuccessData {
projectId: string;
initialPrompt: string;
Expand All @@ -64,4 +64,10 @@ export interface ProjectCreationSuccessData {
autoApprove?: boolean;
tasks: Task[];
}

// Add the new interface for update_task success
export interface UpdateTaskSuccessData {
task: Task; // The updated task object
message?: string; // Optional message (e.g., approval reminder)
}

1 change: 1 addition & 0 deletions tests/mcp/tools/create-project.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ describe('create_project Tool', () => {
"Task 2",
"Task 3"
]);
expect(project).toHaveProperty('autoApprove', true);
});

it('should create a project with auto-approve enabled', async () => {
Expand Down
48 changes: 48 additions & 0 deletions tests/mcp/tools/update-task.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,54 @@ describe('update_task Tool', () => {
});
});

it('should return reminder message when marking task done in a project requiring approval', async () => {
// Create a project that requires approval
const project = await createTestProjectInFile(context.testFilePath, {
initialPrompt: "Project Requiring Approval",
autoApprove: false // Explicitly set for clarity
});
const task = await createTestTaskInFile(context.testFilePath, project.projectId, {
title: "Task to be Approved",
status: "in progress"
});

// Mark the task as done
const result = await context.client.callTool({
name: "update_task",
arguments: {
projectId: project.projectId,
taskId: task.id,
status: "done",
completedDetails: "Task finished, awaiting approval."
}
}) as CallToolResult;

// Verify the response includes the approval reminder within the JSON structure
verifyCallToolResult(result); // Basic verification
expect(result.isError).toBeFalsy();
const responseText = (result.content[0] as { text: string }).text;
// Parse the JSON response
const responseData = JSON.parse(responseText);

// Check the message property
expect(responseData).toHaveProperty('message');
const expectedMessage = `Task marked as done but requires human approval.\nTo approve, user should run: npx taskqueue approve-task -- ${project.projectId} ${task.id}`;
expect(responseData.message).toBe(expectedMessage);

// Check that the core task data is present under the 'task' key
expect(responseData).toHaveProperty('task');
expect(responseData.task.id).toBe(task.id);
expect(responseData.task.status).toBe('done');
expect(responseData.task.completedDetails).toBe("Task finished, awaiting approval.");

// Also verify the task state in the file
await verifyTaskInFile(context.testFilePath, project.projectId, task.id, {
status: "done",
completedDetails: "Task finished, awaiting approval.",
approved: false // Should not be approved yet
});
});

it('should update task title and description', async () => {
const project = await createTestProjectInFile(context.testFilePath, {
initialPrompt: "Test Project"
Expand Down