@prmichaelsen/task-mcp 0.2.0
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.
- package/.env.example +19 -0
- package/AGENT.md +1165 -0
- package/CHANGELOG.md +72 -0
- package/agent/commands/acp.commit.md +511 -0
- package/agent/commands/acp.init.md +376 -0
- package/agent/commands/acp.package-install.md +347 -0
- package/agent/commands/acp.proceed.md +311 -0
- package/agent/commands/acp.report.md +392 -0
- package/agent/commands/acp.status.md +280 -0
- package/agent/commands/acp.sync.md +323 -0
- package/agent/commands/acp.update.md +301 -0
- package/agent/commands/acp.validate.md +385 -0
- package/agent/commands/acp.version-check-for-updates.md +275 -0
- package/agent/commands/acp.version-check.md +190 -0
- package/agent/commands/acp.version-update.md +288 -0
- package/agent/commands/command.template.md +273 -0
- package/agent/commands/git.commit.md +511 -0
- package/agent/commands/git.init.md +513 -0
- package/agent/design/.gitkeep +0 -0
- package/agent/design/acp-task-execution-requirements.md +555 -0
- package/agent/design/api-dto-design.md +394 -0
- package/agent/design/code-extraction-guide.md +827 -0
- package/agent/design/design.template.md +136 -0
- package/agent/design/requirements.template.md +387 -0
- package/agent/design/rest-api-integration.md +489 -0
- package/agent/design/sdk-export-requirements.md +549 -0
- package/agent/milestones/.gitkeep +0 -0
- package/agent/milestones/milestone-1-{title}.template.md +206 -0
- package/agent/milestones/milestone-2-task-infrastructure.md +232 -0
- package/agent/milestones/milestone-4-autonomous-execution.md +235 -0
- package/agent/patterns/.gitkeep +0 -0
- package/agent/patterns/bootstrap.md +1271 -0
- package/agent/patterns/bootstrap.template.md +1237 -0
- package/agent/patterns/pattern.template.md +364 -0
- package/agent/progress.template.yaml +158 -0
- package/agent/progress.yaml +375 -0
- package/agent/scripts/check-for-updates.sh +88 -0
- package/agent/scripts/install.sh +157 -0
- package/agent/scripts/uninstall.sh +75 -0
- package/agent/scripts/update.sh +139 -0
- package/agent/scripts/version.sh +35 -0
- package/agent/tasks/.gitkeep +0 -0
- package/agent/tasks/task-1-{title}.template.md +225 -0
- package/agent/tasks/task-86-task-data-model-schemas.md +143 -0
- package/agent/tasks/task-87-task-database-service.md +220 -0
- package/agent/tasks/task-88-firebase-client-wrapper.md +139 -0
- package/agent/tasks/task-88-task-execution-engine.md +277 -0
- package/agent/tasks/task-89-mcp-server-implementation.md +197 -0
- package/agent/tasks/task-90-build-configuration.md +146 -0
- package/agent/tasks/task-91-deployment-configuration.md +128 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +191 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +191 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov-report/src/client.ts.html +1030 -0
- package/coverage/lcov-report/src/constant/collections.ts.html +469 -0
- package/coverage/lcov-report/src/constant/index.html +116 -0
- package/coverage/lcov-report/src/dto/index.html +116 -0
- package/coverage/lcov-report/src/dto/transformers.ts.html +568 -0
- package/coverage/lcov-report/src/index.html +146 -0
- package/coverage/lcov-report/src/schemas/index.html +116 -0
- package/coverage/lcov-report/src/schemas/task.ts.html +547 -0
- package/coverage/lcov-report/src/server-factory.ts.html +418 -0
- package/coverage/lcov-report/src/server.ts.html +289 -0
- package/coverage/lcov-report/src/services/index.html +116 -0
- package/coverage/lcov-report/src/services/task-database.service.ts.html +1495 -0
- package/coverage/lcov-report/src/tools/index.html +236 -0
- package/coverage/lcov-report/src/tools/index.ts.html +292 -0
- package/coverage/lcov-report/src/tools/task-add-message.ts.html +277 -0
- package/coverage/lcov-report/src/tools/task-complete-task-item.ts.html +343 -0
- package/coverage/lcov-report/src/tools/task-create-milestone.ts.html +286 -0
- package/coverage/lcov-report/src/tools/task-create-task-item.ts.html +358 -0
- package/coverage/lcov-report/src/tools/task-get-next-step.ts.html +460 -0
- package/coverage/lcov-report/src/tools/task-get-status.ts.html +316 -0
- package/coverage/lcov-report/src/tools/task-report-completion.ts.html +343 -0
- package/coverage/lcov-report/src/tools/task-update-progress.ts.html +232 -0
- package/coverage/lcov.info +974 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/coverage/src/client.ts.html +1030 -0
- package/coverage/src/constant/collections.ts.html +469 -0
- package/coverage/src/constant/index.html +116 -0
- package/coverage/src/dto/index.html +116 -0
- package/coverage/src/dto/transformers.ts.html +568 -0
- package/coverage/src/index.html +146 -0
- package/coverage/src/schemas/index.html +116 -0
- package/coverage/src/schemas/task.ts.html +547 -0
- package/coverage/src/server-factory.ts.html +418 -0
- package/coverage/src/server.ts.html +289 -0
- package/coverage/src/services/index.html +116 -0
- package/coverage/src/services/task-database.service.ts.html +1495 -0
- package/coverage/src/tools/index.html +236 -0
- package/coverage/src/tools/index.ts.html +292 -0
- package/coverage/src/tools/task-add-message.ts.html +277 -0
- package/coverage/src/tools/task-complete-task-item.ts.html +343 -0
- package/coverage/src/tools/task-create-milestone.ts.html +286 -0
- package/coverage/src/tools/task-create-task-item.ts.html +358 -0
- package/coverage/src/tools/task-get-next-step.ts.html +460 -0
- package/coverage/src/tools/task-get-status.ts.html +316 -0
- package/coverage/src/tools/task-report-completion.ts.html +343 -0
- package/coverage/src/tools/task-update-progress.ts.html +232 -0
- package/firestore.rules +95 -0
- package/jest.config.js +31 -0
- package/package.json +67 -0
- package/src/client.spec.ts +199 -0
- package/src/client.ts +315 -0
- package/src/constant/collections.ts +128 -0
- package/src/dto/index.ts +47 -0
- package/src/dto/task-api.dto.ts +219 -0
- package/src/dto/transformers.spec.ts +462 -0
- package/src/dto/transformers.ts +161 -0
- package/src/schemas/task.ts +154 -0
- package/src/server-factory.spec.ts +70 -0
- package/src/server-factory.ts +111 -0
- package/src/server.ts +68 -0
- package/src/services/task-database.service.e2e.ts +116 -0
- package/src/services/task-database.service.spec.ts +479 -0
- package/src/services/task-database.service.ts +470 -0
- package/src/test-schemas.ts +161 -0
- package/src/tools/index.ts +69 -0
- package/src/tools/task-add-message.ts +64 -0
- package/src/tools/task-complete-task-item.ts +86 -0
- package/src/tools/task-create-milestone.ts +67 -0
- package/src/tools/task-create-task-item.ts +91 -0
- package/src/tools/task-get-next-step.spec.ts +136 -0
- package/src/tools/task-get-next-step.ts +125 -0
- package/src/tools/task-get-status.spec.ts +213 -0
- package/src/tools/task-get-status.ts +77 -0
- package/src/tools/task-report-completion.ts +86 -0
- package/src/tools/task-update-progress.ts +49 -0
- package/src/tools/tools.spec.ts +194 -0
- package/tsconfig.json +31 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for task_get_status tool
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { handleTaskGetStatus, taskGetStatusTool } from './task-get-status.js'
|
|
6
|
+
import { FirebaseClient } from '@/client.js'
|
|
7
|
+
import type { Task } from '@/schemas/task.js'
|
|
8
|
+
|
|
9
|
+
// Mock FirebaseClient
|
|
10
|
+
jest.mock('@/client.js')
|
|
11
|
+
|
|
12
|
+
describe('task_get_status', () => {
|
|
13
|
+
let mockClient: jest.Mocked<FirebaseClient>
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
mockClient = {
|
|
17
|
+
getTask: jest.fn()
|
|
18
|
+
} as any
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
describe('Tool Definition', () => {
|
|
22
|
+
it('should have correct name', () => {
|
|
23
|
+
expect(taskGetStatusTool.name).toBe('task_get_status')
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('should have description', () => {
|
|
27
|
+
expect(taskGetStatusTool.description).toBeTruthy()
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('should require task_id parameter', () => {
|
|
31
|
+
expect(taskGetStatusTool.inputSchema.required).toContain('task_id')
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
describe('Handler', () => {
|
|
36
|
+
it('should return task status for valid task', async () => {
|
|
37
|
+
const mockTask: Task = {
|
|
38
|
+
id: 'task-123',
|
|
39
|
+
user_id: 'user-456',
|
|
40
|
+
title: 'Test Task',
|
|
41
|
+
description: 'Test description',
|
|
42
|
+
status: 'in_progress',
|
|
43
|
+
created_at: '2026-02-16T00:00:00Z',
|
|
44
|
+
updated_at: '2026-02-16T00:00:00Z',
|
|
45
|
+
progress: {
|
|
46
|
+
current_milestone: 'milestone-1',
|
|
47
|
+
current_task: 'task-1',
|
|
48
|
+
overall_percentage: 50,
|
|
49
|
+
milestones: [
|
|
50
|
+
{
|
|
51
|
+
id: 'milestone-1',
|
|
52
|
+
name: 'Milestone 1',
|
|
53
|
+
description: 'First milestone',
|
|
54
|
+
status: 'in_progress',
|
|
55
|
+
progress: 50,
|
|
56
|
+
tasks_completed: 1,
|
|
57
|
+
tasks_total: 2
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
tasks: {
|
|
61
|
+
'milestone-1': [
|
|
62
|
+
{
|
|
63
|
+
id: 'task-1',
|
|
64
|
+
name: 'Task 1',
|
|
65
|
+
description: 'First task',
|
|
66
|
+
status: 'in_progress'
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
execution: {
|
|
72
|
+
api_messages: [],
|
|
73
|
+
task_messages: [],
|
|
74
|
+
tool_results: []
|
|
75
|
+
},
|
|
76
|
+
config: {
|
|
77
|
+
|
|
78
|
+
system_prompt: '',
|
|
79
|
+
auto_approve: true
|
|
80
|
+
},
|
|
81
|
+
metadata: undefined
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
mockClient.getTask.mockResolvedValue(mockTask)
|
|
85
|
+
|
|
86
|
+
const result = await handleTaskGetStatus(mockClient, { task_id: 'task-123' })
|
|
87
|
+
const parsed = JSON.parse(result)
|
|
88
|
+
|
|
89
|
+
expect(parsed.task_id).toBe('task-123')
|
|
90
|
+
expect(parsed.task_title).toBe('Test Task')
|
|
91
|
+
expect(parsed.status).toBe('in_progress')
|
|
92
|
+
expect(parsed.overall_progress).toBe(50)
|
|
93
|
+
expect(parsed.current_milestone).toBeTruthy()
|
|
94
|
+
expect(parsed.current_milestone.name).toBe('Milestone 1')
|
|
95
|
+
expect(parsed.current_task).toBeTruthy()
|
|
96
|
+
expect(parsed.current_task.name).toBe('Task 1')
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('should throw error if task not found', async () => {
|
|
100
|
+
mockClient.getTask.mockResolvedValue(null)
|
|
101
|
+
|
|
102
|
+
await expect(
|
|
103
|
+
handleTaskGetStatus(mockClient, { task_id: 'nonexistent' })
|
|
104
|
+
).rejects.toThrow('Task not found: nonexistent')
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('should handle task with no current milestone', async () => {
|
|
108
|
+
const mockTask: Task = {
|
|
109
|
+
id: 'task-123',
|
|
110
|
+
user_id: 'user-456',
|
|
111
|
+
title: 'Test Task',
|
|
112
|
+
description: 'Test description',
|
|
113
|
+
status: 'not_started',
|
|
114
|
+
created_at: '2026-02-16T00:00:00Z',
|
|
115
|
+
updated_at: '2026-02-16T00:00:00Z',
|
|
116
|
+
progress: {
|
|
117
|
+
current_milestone: '',
|
|
118
|
+
current_task: '',
|
|
119
|
+
overall_percentage: 0,
|
|
120
|
+
milestones: [],
|
|
121
|
+
tasks: {}
|
|
122
|
+
},
|
|
123
|
+
execution: {
|
|
124
|
+
api_messages: [],
|
|
125
|
+
task_messages: [],
|
|
126
|
+
tool_results: []
|
|
127
|
+
},
|
|
128
|
+
config: {
|
|
129
|
+
|
|
130
|
+
system_prompt: '',
|
|
131
|
+
auto_approve: true
|
|
132
|
+
},
|
|
133
|
+
metadata: undefined
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
mockClient.getTask.mockResolvedValue(mockTask)
|
|
137
|
+
|
|
138
|
+
const result = await handleTaskGetStatus(mockClient, { task_id: 'task-123' })
|
|
139
|
+
const parsed = JSON.parse(result)
|
|
140
|
+
|
|
141
|
+
expect(parsed.current_milestone).toBeNull()
|
|
142
|
+
expect(parsed.current_task).toBeNull()
|
|
143
|
+
expect(parsed.milestones_summary.total).toBe(0)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('should include milestones summary', async () => {
|
|
147
|
+
const mockTask: Task = {
|
|
148
|
+
id: 'task-123',
|
|
149
|
+
user_id: 'user-456',
|
|
150
|
+
title: 'Test Task',
|
|
151
|
+
description: 'Test description',
|
|
152
|
+
status: 'in_progress',
|
|
153
|
+
created_at: '2026-02-16T00:00:00Z',
|
|
154
|
+
updated_at: '2026-02-16T00:00:00Z',
|
|
155
|
+
progress: {
|
|
156
|
+
current_milestone: 'milestone-2',
|
|
157
|
+
current_task: '',
|
|
158
|
+
overall_percentage: 50,
|
|
159
|
+
milestones: [
|
|
160
|
+
{
|
|
161
|
+
id: 'milestone-1',
|
|
162
|
+
name: 'Milestone 1',
|
|
163
|
+
description: 'First',
|
|
164
|
+
status: 'completed',
|
|
165
|
+
progress: 100,
|
|
166
|
+
tasks_completed: 2,
|
|
167
|
+
tasks_total: 2
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: 'milestone-2',
|
|
171
|
+
name: 'Milestone 2',
|
|
172
|
+
description: 'Second',
|
|
173
|
+
status: 'in_progress',
|
|
174
|
+
progress: 50,
|
|
175
|
+
tasks_completed: 1,
|
|
176
|
+
tasks_total: 2
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
id: 'milestone-3',
|
|
180
|
+
name: 'Milestone 3',
|
|
181
|
+
description: 'Third',
|
|
182
|
+
status: 'not_started',
|
|
183
|
+
progress: 0,
|
|
184
|
+
tasks_completed: 0,
|
|
185
|
+
tasks_total: 2
|
|
186
|
+
}
|
|
187
|
+
],
|
|
188
|
+
tasks: {}
|
|
189
|
+
},
|
|
190
|
+
execution: {
|
|
191
|
+
api_messages: [],
|
|
192
|
+
task_messages: [],
|
|
193
|
+
tool_results: []
|
|
194
|
+
},
|
|
195
|
+
config: {
|
|
196
|
+
|
|
197
|
+
system_prompt: '',
|
|
198
|
+
auto_approve: true
|
|
199
|
+
},
|
|
200
|
+
metadata: undefined
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
mockClient.getTask.mockResolvedValue(mockTask)
|
|
204
|
+
|
|
205
|
+
const result = await handleTaskGetStatus(mockClient, { task_id: 'task-123' })
|
|
206
|
+
const parsed = JSON.parse(result)
|
|
207
|
+
|
|
208
|
+
expect(parsed.milestones_summary.total).toBe(3)
|
|
209
|
+
expect(parsed.milestones_summary.completed).toBe(1)
|
|
210
|
+
expect(parsed.milestones_summary.in_progress).toBe(1)
|
|
211
|
+
})
|
|
212
|
+
})
|
|
213
|
+
})
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool: task_get_status
|
|
3
|
+
*
|
|
4
|
+
* Get current task status and progress information.
|
|
5
|
+
* Returns task title, status, current milestone, and overall progress.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { FirebaseClient } from '@/client.js'
|
|
9
|
+
|
|
10
|
+
export const taskGetStatusTool = {
|
|
11
|
+
name: 'task_get_status',
|
|
12
|
+
description: 'Get current task status and progress',
|
|
13
|
+
inputSchema: {
|
|
14
|
+
type: 'object',
|
|
15
|
+
properties: {
|
|
16
|
+
task_id: {
|
|
17
|
+
type: 'string',
|
|
18
|
+
description: 'Task ID to get status for'
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
required: ['task_id']
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function handleTaskGetStatus(
|
|
26
|
+
client: FirebaseClient,
|
|
27
|
+
args: { task_id: string }
|
|
28
|
+
): Promise<string> {
|
|
29
|
+
try {
|
|
30
|
+
const task = await client.getTask(args.task_id)
|
|
31
|
+
|
|
32
|
+
if (!task) {
|
|
33
|
+
throw new Error(`Task not found: ${args.task_id}`)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Find current milestone details
|
|
37
|
+
const currentMilestone = task.progress.milestones.find(
|
|
38
|
+
m => m.id === task.progress.current_milestone
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
// Find current task item details
|
|
42
|
+
let currentTaskItem = null
|
|
43
|
+
if (task.progress.current_task && task.progress.current_milestone) {
|
|
44
|
+
const milestoneItems = task.progress.tasks[task.progress.current_milestone] || []
|
|
45
|
+
currentTaskItem = milestoneItems.find(
|
|
46
|
+
item => item.id === task.progress.current_task
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return JSON.stringify({
|
|
51
|
+
task_id: task.id,
|
|
52
|
+
task_title: task.title,
|
|
53
|
+
status: task.status,
|
|
54
|
+
overall_progress: task.progress.overall_percentage,
|
|
55
|
+
current_milestone: currentMilestone ? {
|
|
56
|
+
id: currentMilestone.id,
|
|
57
|
+
name: currentMilestone.name,
|
|
58
|
+
status: currentMilestone.status,
|
|
59
|
+
progress: currentMilestone.progress,
|
|
60
|
+
tasks_completed: currentMilestone.tasks_completed,
|
|
61
|
+
tasks_total: currentMilestone.tasks_total
|
|
62
|
+
} : null,
|
|
63
|
+
current_task: currentTaskItem ? {
|
|
64
|
+
id: currentTaskItem.id,
|
|
65
|
+
name: currentTaskItem.name,
|
|
66
|
+
status: currentTaskItem.status
|
|
67
|
+
} : null,
|
|
68
|
+
milestones_summary: {
|
|
69
|
+
total: task.progress.milestones.length,
|
|
70
|
+
completed: task.progress.milestones.filter(m => m.status === 'completed').length,
|
|
71
|
+
in_progress: task.progress.milestones.filter(m => m.status === 'in_progress').length
|
|
72
|
+
}
|
|
73
|
+
}, null, 2)
|
|
74
|
+
} catch (error) {
|
|
75
|
+
throw new Error(`Failed to get task status: ${error instanceof Error ? error.message : String(error)}`)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool: task_report_completion
|
|
3
|
+
*
|
|
4
|
+
* Agent reports completion of a task item and gets next instructions.
|
|
5
|
+
* This is a convenience tool that combines completing a task item and getting the next step.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { FirebaseClient } from '@/client.js'
|
|
9
|
+
import { handleTaskCompleteTaskItem } from './task-complete-task-item.js'
|
|
10
|
+
import { handleTaskGetNextStep } from './task-get-next-step.js'
|
|
11
|
+
|
|
12
|
+
export const taskReportCompletionTool = {
|
|
13
|
+
name: 'task_report_completion',
|
|
14
|
+
description: 'Report completion of a task item and get next instructions',
|
|
15
|
+
inputSchema: {
|
|
16
|
+
type: 'object',
|
|
17
|
+
properties: {
|
|
18
|
+
task_id: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
description: 'Task ID'
|
|
21
|
+
},
|
|
22
|
+
milestone_id: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
description: 'Milestone ID'
|
|
25
|
+
},
|
|
26
|
+
task_item_id: {
|
|
27
|
+
type: 'string',
|
|
28
|
+
description: 'Task item ID that was completed'
|
|
29
|
+
},
|
|
30
|
+
notes: {
|
|
31
|
+
type: 'string',
|
|
32
|
+
description: 'Optional notes about the completion'
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
required: ['task_id', 'milestone_id', 'task_item_id']
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function handleTaskReportCompletion(
|
|
40
|
+
client: FirebaseClient,
|
|
41
|
+
args: {
|
|
42
|
+
task_id: string
|
|
43
|
+
milestone_id: string
|
|
44
|
+
task_item_id: string
|
|
45
|
+
notes?: string
|
|
46
|
+
}
|
|
47
|
+
): Promise<string> {
|
|
48
|
+
try {
|
|
49
|
+
// Add notes if provided
|
|
50
|
+
if (args.notes) {
|
|
51
|
+
await client.updateTaskItem(
|
|
52
|
+
args.task_id,
|
|
53
|
+
args.milestone_id,
|
|
54
|
+
args.task_item_id,
|
|
55
|
+
{ notes: args.notes }
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Complete the task item
|
|
60
|
+
const completionResult = await handleTaskCompleteTaskItem(client, {
|
|
61
|
+
task_id: args.task_id,
|
|
62
|
+
milestone_id: args.milestone_id,
|
|
63
|
+
task_item_id: args.task_item_id
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
// Get next step
|
|
67
|
+
const nextStepResult = await handleTaskGetNextStep(client, {
|
|
68
|
+
task_id: args.task_id
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
const completion = JSON.parse(completionResult)
|
|
72
|
+
const nextStep = JSON.parse(nextStepResult)
|
|
73
|
+
|
|
74
|
+
return JSON.stringify({
|
|
75
|
+
completion: {
|
|
76
|
+
success: completion.success,
|
|
77
|
+
completed_task: completion.completed_task,
|
|
78
|
+
milestone_progress: completion.milestone_progress
|
|
79
|
+
},
|
|
80
|
+
next_step: nextStep,
|
|
81
|
+
message: completion.message
|
|
82
|
+
}, null, 2)
|
|
83
|
+
} catch (error) {
|
|
84
|
+
throw new Error(`Failed to report completion: ${error instanceof Error ? error.message : String(error)}`)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool: task_update_progress
|
|
3
|
+
*
|
|
4
|
+
* Update the overall progress percentage for a task.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { FirebaseClient } from '@/client.js'
|
|
8
|
+
|
|
9
|
+
export const taskUpdateProgressTool = {
|
|
10
|
+
name: 'task_update_progress',
|
|
11
|
+
description: 'Update the overall progress percentage for a task',
|
|
12
|
+
inputSchema: {
|
|
13
|
+
type: 'object',
|
|
14
|
+
properties: {
|
|
15
|
+
task_id: {
|
|
16
|
+
type: 'string',
|
|
17
|
+
description: 'Task ID'
|
|
18
|
+
},
|
|
19
|
+
percentage: {
|
|
20
|
+
type: 'number',
|
|
21
|
+
description: 'Progress percentage (0-100)',
|
|
22
|
+
minimum: 0,
|
|
23
|
+
maximum: 100
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
required: ['task_id', 'percentage']
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function handleTaskUpdateProgress(
|
|
31
|
+
client: FirebaseClient,
|
|
32
|
+
args: { task_id: string; percentage: number }
|
|
33
|
+
): Promise<string> {
|
|
34
|
+
try {
|
|
35
|
+
// Validate percentage
|
|
36
|
+
const percentage = Math.min(100, Math.max(0, args.percentage))
|
|
37
|
+
|
|
38
|
+
await client.updateOverallProgress(args.task_id, percentage)
|
|
39
|
+
|
|
40
|
+
return JSON.stringify({
|
|
41
|
+
success: true,
|
|
42
|
+
task_id: args.task_id,
|
|
43
|
+
progress: percentage,
|
|
44
|
+
message: `Progress updated to ${percentage}%`
|
|
45
|
+
}, null, 2)
|
|
46
|
+
} catch (error) {
|
|
47
|
+
throw new Error(`Failed to update progress: ${error instanceof Error ? error.message : String(error)}`)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for remaining MCP tools
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { handleTaskUpdateProgress } from './task-update-progress.js'
|
|
6
|
+
import { handleTaskCompleteTaskItem } from './task-complete-task-item.js'
|
|
7
|
+
import { handleTaskCreateMilestone } from './task-create-milestone.js'
|
|
8
|
+
import { handleTaskCreateTaskItem } from './task-create-task-item.js'
|
|
9
|
+
import { handleTaskAddMessage } from './task-add-message.js'
|
|
10
|
+
import { FirebaseClient } from '@/client.js'
|
|
11
|
+
import type { Task } from '@/schemas/task.js'
|
|
12
|
+
|
|
13
|
+
jest.mock('@/client.js')
|
|
14
|
+
|
|
15
|
+
describe('MCP Tools', () => {
|
|
16
|
+
let mockClient: jest.Mocked<FirebaseClient>
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
mockClient = {
|
|
20
|
+
updateOverallProgress: jest.fn(),
|
|
21
|
+
completeTaskItem: jest.fn(),
|
|
22
|
+
getTask: jest.fn(),
|
|
23
|
+
updateMilestone: jest.fn(),
|
|
24
|
+
createMilestone: jest.fn(),
|
|
25
|
+
createTaskItem: jest.fn(),
|
|
26
|
+
addMessage: jest.fn()
|
|
27
|
+
} as any
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
describe('task_update_progress', () => {
|
|
31
|
+
it('should update progress successfully', async () => {
|
|
32
|
+
mockClient.updateOverallProgress.mockResolvedValue(undefined)
|
|
33
|
+
|
|
34
|
+
const result = await handleTaskUpdateProgress(mockClient, {
|
|
35
|
+
task_id: 'task-123',
|
|
36
|
+
percentage: 75
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const parsed = JSON.parse(result)
|
|
40
|
+
expect(parsed.success).toBe(true)
|
|
41
|
+
expect(parsed.progress).toBe(75)
|
|
42
|
+
expect(mockClient.updateOverallProgress).toHaveBeenCalledWith('task-123', 75)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('should clamp percentage to 0-100 range', async () => {
|
|
46
|
+
mockClient.updateOverallProgress.mockResolvedValue(undefined)
|
|
47
|
+
|
|
48
|
+
await handleTaskUpdateProgress(mockClient, {
|
|
49
|
+
task_id: 'task-123',
|
|
50
|
+
percentage: 150
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
expect(mockClient.updateOverallProgress).toHaveBeenCalledWith('task-123', 100)
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
describe('task_complete_task_item', () => {
|
|
58
|
+
it('should complete task item and return next task', async () => {
|
|
59
|
+
const mockTask: Task = {
|
|
60
|
+
id: 'task-123',
|
|
61
|
+
user_id: 'user-456',
|
|
62
|
+
title: 'Test',
|
|
63
|
+
description: 'Test',
|
|
64
|
+
status: 'in_progress',
|
|
65
|
+
created_at: '2026-02-16T00:00:00Z',
|
|
66
|
+
updated_at: '2026-02-16T00:00:00Z',
|
|
67
|
+
progress: {
|
|
68
|
+
current_milestone: 'milestone-1',
|
|
69
|
+
current_task: 'task-1',
|
|
70
|
+
overall_percentage: 0,
|
|
71
|
+
milestones: [{
|
|
72
|
+
id: 'milestone-1',
|
|
73
|
+
name: 'Milestone 1',
|
|
74
|
+
description: 'Test',
|
|
75
|
+
status: 'in_progress',
|
|
76
|
+
progress: 0,
|
|
77
|
+
tasks_completed: 0,
|
|
78
|
+
tasks_total: 2
|
|
79
|
+
}],
|
|
80
|
+
tasks: {
|
|
81
|
+
'milestone-1': [
|
|
82
|
+
{ id: 'task-1', name: 'Task 1', description: 'Test', status: 'completed' },
|
|
83
|
+
{ id: 'task-2', name: 'Task 2', description: 'Test', status: 'not_started' }
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
execution: { api_messages: [], task_messages: [], tool_results: [] },
|
|
88
|
+
config: { system_prompt: '', auto_approve: true },
|
|
89
|
+
metadata: undefined
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
mockClient.completeTaskItem.mockResolvedValue(undefined)
|
|
93
|
+
mockClient.getTask.mockResolvedValue(mockTask)
|
|
94
|
+
mockClient.updateMilestone.mockResolvedValue(undefined)
|
|
95
|
+
|
|
96
|
+
const result = await handleTaskCompleteTaskItem(mockClient, {
|
|
97
|
+
task_id: 'task-123',
|
|
98
|
+
milestone_id: 'milestone-1',
|
|
99
|
+
task_item_id: 'task-1'
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
const parsed = JSON.parse(result)
|
|
103
|
+
expect(parsed.success).toBe(true)
|
|
104
|
+
expect(parsed.next_task).toBeTruthy()
|
|
105
|
+
expect(parsed.next_task.name).toBe('Task 2')
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
describe('task_create_milestone', () => {
|
|
110
|
+
it('should create milestone successfully', async () => {
|
|
111
|
+
mockClient.createMilestone.mockResolvedValue(undefined)
|
|
112
|
+
|
|
113
|
+
const result = await handleTaskCreateMilestone(mockClient, {
|
|
114
|
+
task_id: 'task-123',
|
|
115
|
+
milestone_id: 'milestone-1',
|
|
116
|
+
name: 'Milestone 1',
|
|
117
|
+
description: 'Test milestone'
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
const parsed = JSON.parse(result)
|
|
121
|
+
expect(parsed.success).toBe(true)
|
|
122
|
+
expect(parsed.milestone.name).toBe('Milestone 1')
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
describe('task_create_task_item', () => {
|
|
127
|
+
it('should create task item successfully', async () => {
|
|
128
|
+
const mockTask: Task = {
|
|
129
|
+
id: 'task-123',
|
|
130
|
+
user_id: 'user-456',
|
|
131
|
+
title: 'Test',
|
|
132
|
+
description: 'Test',
|
|
133
|
+
status: 'in_progress',
|
|
134
|
+
created_at: '2026-02-16T00:00:00Z',
|
|
135
|
+
updated_at: '2026-02-16T00:00:00Z',
|
|
136
|
+
progress: {
|
|
137
|
+
current_milestone: 'milestone-1',
|
|
138
|
+
current_task: '',
|
|
139
|
+
overall_percentage: 0,
|
|
140
|
+
milestones: [{
|
|
141
|
+
id: 'milestone-1',
|
|
142
|
+
name: 'Milestone 1',
|
|
143
|
+
description: 'Test',
|
|
144
|
+
status: 'in_progress',
|
|
145
|
+
progress: 0,
|
|
146
|
+
tasks_completed: 0,
|
|
147
|
+
tasks_total: 0
|
|
148
|
+
}],
|
|
149
|
+
tasks: {
|
|
150
|
+
'milestone-1': [
|
|
151
|
+
{ id: 'task-1', name: 'Task 1', description: 'Test', status: 'not_started' }
|
|
152
|
+
]
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
execution: { api_messages: [], task_messages: [], tool_results: [] },
|
|
156
|
+
config: { system_prompt: '', auto_approve: true },
|
|
157
|
+
metadata: undefined
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
mockClient.createTaskItem.mockResolvedValue(undefined)
|
|
161
|
+
mockClient.getTask.mockResolvedValue(mockTask)
|
|
162
|
+
mockClient.updateMilestone.mockResolvedValue(undefined)
|
|
163
|
+
|
|
164
|
+
const result = await handleTaskCreateTaskItem(mockClient, {
|
|
165
|
+
task_id: 'task-123',
|
|
166
|
+
milestone_id: 'milestone-1',
|
|
167
|
+
task_item_id: 'task-1',
|
|
168
|
+
name: 'Task 1',
|
|
169
|
+
description: 'Test task'
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
const parsed = JSON.parse(result)
|
|
173
|
+
expect(parsed.success).toBe(true)
|
|
174
|
+
expect(parsed.task_item.name).toBe('Task 1')
|
|
175
|
+
})
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
describe('task_add_message', () => {
|
|
179
|
+
it('should add message successfully', async () => {
|
|
180
|
+
mockClient.addMessage.mockResolvedValue('message-123')
|
|
181
|
+
|
|
182
|
+
const result = await handleTaskAddMessage(mockClient, {
|
|
183
|
+
task_id: 'task-123',
|
|
184
|
+
role: 'assistant',
|
|
185
|
+
content: 'Test message'
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
const parsed = JSON.parse(result)
|
|
189
|
+
expect(parsed.success).toBe(true)
|
|
190
|
+
expect(parsed.message_id).toBe('message-123')
|
|
191
|
+
expect(parsed.role).toBe('assistant')
|
|
192
|
+
})
|
|
193
|
+
})
|
|
194
|
+
})
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ES2022",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"types": ["node", "jest"],
|
|
8
|
+
|
|
9
|
+
"outDir": "./dist",
|
|
10
|
+
"rootDir": "./src",
|
|
11
|
+
|
|
12
|
+
"esModuleInterop": true,
|
|
13
|
+
"allowSyntheticDefaultImports": true,
|
|
14
|
+
"strict": true,
|
|
15
|
+
"skipLibCheck": true,
|
|
16
|
+
"forceConsistentCasingInFileNames": true,
|
|
17
|
+
"resolveJsonModule": true,
|
|
18
|
+
|
|
19
|
+
"declaration": true,
|
|
20
|
+
"declarationMap": true,
|
|
21
|
+
"sourceMap": true,
|
|
22
|
+
|
|
23
|
+
"baseUrl": ".",
|
|
24
|
+
"paths": {
|
|
25
|
+
"@/*": ["src/*"]
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
"include": ["src/**/*"],
|
|
30
|
+
"exclude": ["node_modules", "dist"]
|
|
31
|
+
}
|