tododdle-mcp 2.7.1 → 3.0.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/.codex-plugin/plugin.json +4 -4
- package/README.md +58 -34
- package/config/external-api-parity.json +46 -41
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +12 -3
- package/dist/api-client.js.map +1 -1
- package/dist/compatibility.d.ts +10 -0
- package/dist/compatibility.d.ts.map +1 -0
- package/dist/compatibility.js +17 -0
- package/dist/compatibility.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +128 -78
- package/dist/server.js.map +1 -1
- package/examples/AGENTS.tododdle.md +7 -5
- package/package.json +1 -1
- package/skills/tododdle-workflow/SKILL.md +21 -19
- package/skills/tododdle-workflow/agents/openai.yaml +1 -1
package/dist/server.js
CHANGED
|
@@ -186,7 +186,7 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
186
186
|
[
|
|
187
187
|
'archive_project',
|
|
188
188
|
true,
|
|
189
|
-
'Archive a project without deleting its
|
|
189
|
+
'Archive a project without deleting its tickets. Requires explicit approval.',
|
|
190
190
|
true,
|
|
191
191
|
],
|
|
192
192
|
['restore_project', false, 'Restore an archived project.', false],
|
|
@@ -205,8 +205,8 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
205
205
|
},
|
|
206
206
|
}, async ({ projectId, expectedUpdatedAt }) => toolResult(await api.put(`/api/external/projects/${projectId}`, { archived, expectedUpdatedAt })));
|
|
207
207
|
}
|
|
208
|
-
server.registerTool('
|
|
209
|
-
description: 'List bounded active or archived
|
|
208
|
+
server.registerTool('list_boards', {
|
|
209
|
+
description: 'List bounded active or archived boards in a project.',
|
|
210
210
|
inputSchema: z.object({
|
|
211
211
|
projectId: z.string().min(1),
|
|
212
212
|
search: z.string().optional(),
|
|
@@ -221,8 +221,8 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
221
221
|
openWorldHint: false,
|
|
222
222
|
},
|
|
223
223
|
}, async ({ projectId, ...query }) => toolResult(await api.get(`/api/external/projects/${projectId}/plans`, query)));
|
|
224
|
-
server.registerTool('
|
|
225
|
-
description: 'Get one
|
|
224
|
+
server.registerTool('get_board', {
|
|
225
|
+
description: 'Get one board in a project.',
|
|
226
226
|
inputSchema: z.object({ projectId: z.string().min(1), planId: z.string().min(1) }),
|
|
227
227
|
annotations: {
|
|
228
228
|
readOnlyHint: true,
|
|
@@ -231,8 +231,8 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
231
231
|
openWorldHint: false,
|
|
232
232
|
},
|
|
233
233
|
}, async ({ projectId, planId }) => toolResult(await api.get(`/api/external/projects/${projectId}/plans/${planId}`)));
|
|
234
|
-
server.registerTool('
|
|
235
|
-
description: 'Create a
|
|
234
|
+
server.registerTool('create_board', {
|
|
235
|
+
description: 'Create a board in a project.',
|
|
236
236
|
inputSchema: z.object({
|
|
237
237
|
projectId: z.string().min(1),
|
|
238
238
|
name: z.string().min(1).max(500),
|
|
@@ -248,8 +248,8 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
248
248
|
openWorldHint: false,
|
|
249
249
|
},
|
|
250
250
|
}, async ({ projectId, idempotencyKey, ...body }) => toolResult(await api.post(`/api/external/projects/${projectId}/plans`, body, idempotencyKey)));
|
|
251
|
-
server.registerTool('
|
|
252
|
-
description: 'Update
|
|
251
|
+
server.registerTool('update_board', {
|
|
252
|
+
description: 'Update board details using optimistic concurrency.',
|
|
253
253
|
inputSchema: z.object({
|
|
254
254
|
projectId: z.string().min(1),
|
|
255
255
|
planId: z.string().min(1),
|
|
@@ -266,8 +266,8 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
266
266
|
openWorldHint: false,
|
|
267
267
|
},
|
|
268
268
|
}, async ({ projectId, planId, ...body }) => toolResult(await api.put(`/api/external/projects/${projectId}/plans/${planId}`, body)));
|
|
269
|
-
server.registerTool('
|
|
270
|
-
description: 'Reorder a
|
|
269
|
+
server.registerTool('move_board', {
|
|
270
|
+
description: 'Reorder a board within its project using optimistic concurrency.',
|
|
271
271
|
inputSchema: z.object({
|
|
272
272
|
projectId: z.string().min(1),
|
|
273
273
|
planId: z.string().min(1),
|
|
@@ -283,12 +283,12 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
283
283
|
}, async ({ projectId, planId, ...body }) => toolResult(await api.put(`/api/external/projects/${projectId}/plans/${planId}`, body)));
|
|
284
284
|
for (const [name, archived, description, destructiveHint] of [
|
|
285
285
|
[
|
|
286
|
-
'
|
|
286
|
+
'archive_board',
|
|
287
287
|
true,
|
|
288
|
-
'Archive a
|
|
288
|
+
'Archive a board without changing its tickets. Requires explicit approval.',
|
|
289
289
|
true,
|
|
290
290
|
],
|
|
291
|
-
['
|
|
291
|
+
['restore_board', false, 'Restore an archived board.', false],
|
|
292
292
|
]) {
|
|
293
293
|
server.registerTool(name, {
|
|
294
294
|
description,
|
|
@@ -308,8 +308,8 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
308
308
|
expectedUpdatedAt,
|
|
309
309
|
})));
|
|
310
310
|
}
|
|
311
|
-
server.registerTool('
|
|
312
|
-
description: 'List bounded
|
|
311
|
+
server.registerTool('list_lanes', {
|
|
312
|
+
description: 'List bounded lanes and entry automations in a board.',
|
|
313
313
|
inputSchema: z.object({
|
|
314
314
|
projectId: z.string().min(1),
|
|
315
315
|
planId: z.string().min(1),
|
|
@@ -325,8 +325,8 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
325
325
|
openWorldHint: false,
|
|
326
326
|
},
|
|
327
327
|
}, async ({ projectId, planId, ...query }) => toolResult(await api.get(`/api/external/projects/${projectId}/plans/${planId}/sections`, query)));
|
|
328
|
-
server.registerTool('
|
|
329
|
-
description: 'Get one
|
|
328
|
+
server.registerTool('get_lane', {
|
|
329
|
+
description: 'Get one board lane and its entry automations.',
|
|
330
330
|
inputSchema: z.object({
|
|
331
331
|
projectId: z.string().min(1),
|
|
332
332
|
planId: z.string().min(1),
|
|
@@ -339,8 +339,8 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
339
339
|
openWorldHint: false,
|
|
340
340
|
},
|
|
341
341
|
}, async ({ projectId, planId, sectionId }) => toolResult(await api.get(`/api/external/projects/${projectId}/plans/${planId}/sections/${sectionId}`)));
|
|
342
|
-
server.registerTool('
|
|
343
|
-
description: 'Create a
|
|
342
|
+
server.registerTool('create_lane', {
|
|
343
|
+
description: 'Create a lane with optional typed entry automations.',
|
|
344
344
|
inputSchema: z.object({
|
|
345
345
|
projectId: z.string().min(1),
|
|
346
346
|
planId: z.string().min(1),
|
|
@@ -360,8 +360,8 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
360
360
|
openWorldHint: false,
|
|
361
361
|
},
|
|
362
362
|
}, async ({ projectId, planId, idempotencyKey, ...body }) => toolResult(await api.post(`/api/external/projects/${projectId}/plans/${planId}/sections`, body, idempotencyKey)));
|
|
363
|
-
server.registerTool('
|
|
364
|
-
description: 'Update a
|
|
363
|
+
server.registerTool('update_lane', {
|
|
364
|
+
description: 'Update a lane and its entry automations using optimistic concurrency.',
|
|
365
365
|
inputSchema: z.object({
|
|
366
366
|
projectId: z.string().min(1),
|
|
367
367
|
planId: z.string().min(1),
|
|
@@ -382,8 +382,8 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
382
382
|
openWorldHint: false,
|
|
383
383
|
},
|
|
384
384
|
}, async ({ projectId, planId, sectionId, ...body }) => toolResult(await api.put(`/api/external/projects/${projectId}/plans/${planId}/sections/${sectionId}`, body)));
|
|
385
|
-
server.registerTool('
|
|
386
|
-
description: 'Reorder a
|
|
385
|
+
server.registerTool('move_lane', {
|
|
386
|
+
description: 'Reorder a lane within its board using optimistic concurrency.',
|
|
387
387
|
inputSchema: z.object({
|
|
388
388
|
projectId: z.string().min(1),
|
|
389
389
|
planId: z.string().min(1),
|
|
@@ -400,12 +400,12 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
400
400
|
}, async ({ projectId, planId, sectionId, ...body }) => toolResult(await api.put(`/api/external/projects/${projectId}/plans/${planId}/sections/${sectionId}`, body)));
|
|
401
401
|
for (const [name, archived, description, destructiveHint] of [
|
|
402
402
|
[
|
|
403
|
-
'
|
|
403
|
+
'archive_lane',
|
|
404
404
|
true,
|
|
405
|
-
'Archive a
|
|
405
|
+
'Archive a lane without changing its tickets. Requires explicit approval.',
|
|
406
406
|
true,
|
|
407
407
|
],
|
|
408
|
-
['
|
|
408
|
+
['restore_lane', false, 'Restore an archived lane.', false],
|
|
409
409
|
]) {
|
|
410
410
|
server.registerTool(name, {
|
|
411
411
|
description,
|
|
@@ -540,7 +540,7 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
540
540
|
},
|
|
541
541
|
}, async ({ noteId, expectedUpdatedAt }) => toolResult(await api.delete(`/api/external/notes/${noteId}`, { expectedUpdatedAt })));
|
|
542
542
|
server.registerTool('get_project_context', {
|
|
543
|
-
description: 'Get bounded
|
|
543
|
+
description: 'Get bounded board and lane context for ticket planning.',
|
|
544
544
|
inputSchema: z.object({ projectId: z.string().min(1) }),
|
|
545
545
|
annotations: {
|
|
546
546
|
readOnlyHint: true,
|
|
@@ -559,8 +559,8 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
559
559
|
openWorldHint: true,
|
|
560
560
|
},
|
|
561
561
|
}, async (input) => toolResult(await uploadDocument(api, options, input)));
|
|
562
|
-
server.registerTool('
|
|
563
|
-
description: 'Upload an approved local file or HTTPS URL and attach it to a
|
|
562
|
+
server.registerTool('attach_file_to_ticket', {
|
|
563
|
+
description: 'Upload an approved local file or HTTPS URL and attach it to a ticket in the same project.',
|
|
564
564
|
inputSchema: taskUploadInputSchema,
|
|
565
565
|
annotations: {
|
|
566
566
|
readOnlyHint: false,
|
|
@@ -586,8 +586,43 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
586
586
|
openWorldHint: false,
|
|
587
587
|
},
|
|
588
588
|
}, async (input) => toolResult(await api.get('/api/external/work-queue', input)));
|
|
589
|
+
server.registerTool('list_available_work', {
|
|
590
|
+
description: 'List bounded, unblocked, currently unclaimed tickets eligible for an Agent Connection in one project, ordered by priority, due date, and project position.',
|
|
591
|
+
inputSchema: z.object({
|
|
592
|
+
projectId: z.string().min(1),
|
|
593
|
+
status: z.enum(['TODO', 'IN_PROGRESS']).default('TODO'),
|
|
594
|
+
priority: prioritySchema.optional(),
|
|
595
|
+
search: z.string().optional(),
|
|
596
|
+
page: z.number().int().min(1).default(1),
|
|
597
|
+
limit: z.number().int().min(1).max(100).default(50),
|
|
598
|
+
}),
|
|
599
|
+
annotations: {
|
|
600
|
+
readOnlyHint: true,
|
|
601
|
+
destructiveHint: false,
|
|
602
|
+
idempotentHint: true,
|
|
603
|
+
openWorldHint: false,
|
|
604
|
+
},
|
|
605
|
+
}, async (input) => toolResult(await api.get('/api/external/work-queue/available', input)));
|
|
606
|
+
server.registerTool('claim_next_ticket', {
|
|
607
|
+
description: 'Atomically claim the highest-ranked eligible ticket in one project. Reuse a unique stable runId to replay the same live claim safely.',
|
|
608
|
+
inputSchema: z.object({
|
|
609
|
+
projectId: z.string().min(1),
|
|
610
|
+
runId: z.string().min(1).max(200),
|
|
611
|
+
status: z.enum(['TODO', 'IN_PROGRESS']).default('TODO'),
|
|
612
|
+
priority: prioritySchema.optional(),
|
|
613
|
+
search: z.string().optional(),
|
|
614
|
+
state: z.enum(['ACTIVE', 'WAITING']).default('ACTIVE'),
|
|
615
|
+
leaseSeconds: z.number().int().min(60).max(3600).default(900),
|
|
616
|
+
}),
|
|
617
|
+
annotations: {
|
|
618
|
+
readOnlyHint: false,
|
|
619
|
+
destructiveHint: false,
|
|
620
|
+
idempotentHint: true,
|
|
621
|
+
openWorldHint: false,
|
|
622
|
+
},
|
|
623
|
+
}, async (body) => toolResult(await api.post('/api/external/work-queue/available', body)));
|
|
589
624
|
server.registerTool('get_focus_list', {
|
|
590
|
-
description: 'List the current user’s deliberately curated Focus
|
|
625
|
+
description: 'List the current user’s deliberately curated Focus tickets.',
|
|
591
626
|
inputSchema: z.object({
|
|
592
627
|
projectId: z.string().optional(),
|
|
593
628
|
status: statusSchema.optional(),
|
|
@@ -603,8 +638,8 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
603
638
|
openWorldHint: false,
|
|
604
639
|
},
|
|
605
640
|
}, async (input) => toolResult(await api.get('/api/external/focus', input)));
|
|
606
|
-
server.registerTool('
|
|
607
|
-
description: 'Add an accessible active
|
|
641
|
+
server.registerTool('add_ticket_to_focus', {
|
|
642
|
+
description: 'Add an accessible active ticket to Focus. Repeated additions are idempotent.',
|
|
608
643
|
inputSchema: z.object({
|
|
609
644
|
taskId: z.string().min(1),
|
|
610
645
|
bucket: focusBucketSchema.default('NEXT'),
|
|
@@ -617,8 +652,8 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
617
652
|
openWorldHint: false,
|
|
618
653
|
},
|
|
619
654
|
}, async ({ idempotencyKey, ...body }) => toolResult(await api.post('/api/external/focus', body, idempotencyKey || randomUUID())));
|
|
620
|
-
server.registerTool('
|
|
621
|
-
description: 'Move or reorder a Focus
|
|
655
|
+
server.registerTool('move_focus_ticket', {
|
|
656
|
+
description: 'Move or reorder a Focus ticket using optimistic concurrency.',
|
|
622
657
|
inputSchema: z.object({
|
|
623
658
|
taskId: z.string().min(1),
|
|
624
659
|
bucket: focusBucketSchema,
|
|
@@ -632,8 +667,8 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
632
667
|
openWorldHint: false,
|
|
633
668
|
},
|
|
634
669
|
}, async ({ taskId, ...body }) => toolResult(await api.patch(`/api/external/focus/${taskId}`, body)));
|
|
635
|
-
server.registerTool('
|
|
636
|
-
description: 'Remove personal Focus metadata without changing the underlying
|
|
670
|
+
server.registerTool('remove_ticket_from_focus', {
|
|
671
|
+
description: 'Remove personal Focus metadata without changing the underlying ticket.',
|
|
637
672
|
inputSchema: z.object({ taskId: z.string().min(1) }),
|
|
638
673
|
annotations: {
|
|
639
674
|
readOnlyHint: false,
|
|
@@ -642,8 +677,8 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
642
677
|
openWorldHint: false,
|
|
643
678
|
},
|
|
644
679
|
}, async ({ taskId }) => toolResult(await api.delete(`/api/external/focus/${taskId}`, {})));
|
|
645
|
-
server.registerTool('
|
|
646
|
-
description: 'List bounded
|
|
680
|
+
server.registerTool('list_tickets', {
|
|
681
|
+
description: 'List bounded tickets with optional project, board, lane, assignee, status, priority, search, and archive filters. The stable planId and sectionId fields identify the board and lane.',
|
|
647
682
|
inputSchema: z.object({
|
|
648
683
|
projectId: z.string().min(1).optional(),
|
|
649
684
|
planId: z.string().min(1).optional(),
|
|
@@ -663,8 +698,8 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
663
698
|
openWorldHint: false,
|
|
664
699
|
},
|
|
665
700
|
}, async (input) => toolResult(await api.get('/api/external/tasks', input)));
|
|
666
|
-
server.registerTool('
|
|
667
|
-
description: 'Get complete
|
|
701
|
+
server.registerTool('get_ticket', {
|
|
702
|
+
description: 'Get complete ticket context including comments and blocker information.',
|
|
668
703
|
inputSchema: z.object({ taskId: z.string().min(1) }),
|
|
669
704
|
annotations: {
|
|
670
705
|
readOnlyHint: true,
|
|
@@ -673,8 +708,23 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
673
708
|
openWorldHint: false,
|
|
674
709
|
},
|
|
675
710
|
}, async ({ taskId }) => toolResult(await api.get(`/api/external/tasks/${taskId}`)));
|
|
676
|
-
server.registerTool('
|
|
677
|
-
description: 'Claim or refresh a
|
|
711
|
+
server.registerTool('claim_ticket', {
|
|
712
|
+
description: 'Claim or refresh a ticket for the current Agent Connection without changing its human assignee. Reuse a stable runId and refresh before the lease expires.',
|
|
713
|
+
inputSchema: z.object({
|
|
714
|
+
taskId: z.string().min(1),
|
|
715
|
+
runId: z.string().min(1).max(200),
|
|
716
|
+
state: z.enum(['ACTIVE', 'WAITING']).default('ACTIVE'),
|
|
717
|
+
leaseSeconds: z.number().int().min(60).max(3600).default(900),
|
|
718
|
+
}),
|
|
719
|
+
annotations: {
|
|
720
|
+
readOnlyHint: false,
|
|
721
|
+
destructiveHint: false,
|
|
722
|
+
idempotentHint: true,
|
|
723
|
+
openWorldHint: false,
|
|
724
|
+
},
|
|
725
|
+
}, async ({ taskId, ...body }) => toolResult(await api.put(`/api/external/tasks/${taskId}/claim`, body)));
|
|
726
|
+
server.registerTool('renew_ticket_claim', {
|
|
727
|
+
description: 'Renew the current Agent Connection lease for a specific ticket and stable runId before it expires.',
|
|
678
728
|
inputSchema: z.object({
|
|
679
729
|
taskId: z.string().min(1),
|
|
680
730
|
runId: z.string().min(1).max(200),
|
|
@@ -688,8 +738,8 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
688
738
|
openWorldHint: false,
|
|
689
739
|
},
|
|
690
740
|
}, async ({ taskId, ...body }) => toolResult(await api.put(`/api/external/tasks/${taskId}/claim`, body)));
|
|
691
|
-
server.registerTool('
|
|
692
|
-
description: 'Release the current Agent Connection work claim without changing
|
|
741
|
+
server.registerTool('release_ticket', {
|
|
742
|
+
description: 'Release the current Agent Connection work claim without changing ticket status or human assignment.',
|
|
693
743
|
inputSchema: z.object({
|
|
694
744
|
taskId: z.string().min(1),
|
|
695
745
|
runId: z.string().min(1).max(200),
|
|
@@ -701,8 +751,8 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
701
751
|
openWorldHint: false,
|
|
702
752
|
},
|
|
703
753
|
}, async ({ taskId, ...body }) => toolResult(await api.delete(`/api/external/tasks/${taskId}/claim`, body)));
|
|
704
|
-
server.registerTool('
|
|
705
|
-
description: 'Create a
|
|
754
|
+
server.registerTool('create_ticket', {
|
|
755
|
+
description: 'Create a ticket in an active board lane. The stable planId and sectionId fields identify the board and lane.',
|
|
706
756
|
inputSchema: z.object({
|
|
707
757
|
projectId: z.string().min(1),
|
|
708
758
|
planId: z.string().min(1),
|
|
@@ -724,8 +774,8 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
724
774
|
openWorldHint: false,
|
|
725
775
|
},
|
|
726
776
|
}, async ({ idempotencyKey, ...body }) => toolResult(await api.post('/api/external/tasks', body, idempotencyKey || randomUUID())));
|
|
727
|
-
server.registerTool('
|
|
728
|
-
description: 'Update editable
|
|
777
|
+
server.registerTool('update_ticket', {
|
|
778
|
+
description: 'Update editable ticket fields. Requires the latest updatedAt value to prevent overwriting newer changes.',
|
|
729
779
|
inputSchema: z.object({
|
|
730
780
|
taskId: z.string().min(1),
|
|
731
781
|
expectedUpdatedAt: expectedUpdatedAtSchema,
|
|
@@ -744,8 +794,8 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
744
794
|
openWorldHint: false,
|
|
745
795
|
},
|
|
746
796
|
}, async ({ taskId, ...body }) => toolResult(await api.put(`/api/external/tasks/${taskId}`, body)));
|
|
747
|
-
server.registerTool('
|
|
748
|
-
description: 'Transition a
|
|
797
|
+
server.registerTool('transition_ticket', {
|
|
798
|
+
description: 'Transition a ticket to a canonical status.',
|
|
749
799
|
inputSchema: z.object({
|
|
750
800
|
taskId: z.string().min(1),
|
|
751
801
|
status: statusSchema,
|
|
@@ -758,8 +808,8 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
758
808
|
openWorldHint: false,
|
|
759
809
|
},
|
|
760
810
|
}, async ({ taskId, ...body }) => toolResult(await api.put(`/api/external/tasks/${taskId}`, body)));
|
|
761
|
-
server.registerTool('
|
|
762
|
-
description: 'Move a
|
|
811
|
+
server.registerTool('move_ticket', {
|
|
812
|
+
description: 'Move a ticket to an active lane, optionally on another board in the same project. Preview first because lane actions may archive the ticket.',
|
|
763
813
|
inputSchema: z.object({
|
|
764
814
|
taskId: z.string().min(1),
|
|
765
815
|
planId: z.string().min(1).optional(),
|
|
@@ -774,8 +824,8 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
774
824
|
openWorldHint: false,
|
|
775
825
|
},
|
|
776
826
|
}, async ({ taskId, ...body }) => toolResult(await api.put(`/api/external/tasks/${taskId}/move`, body)));
|
|
777
|
-
server.registerTool('
|
|
778
|
-
description: 'Preview destination
|
|
827
|
+
server.registerTool('preview_ticket_move', {
|
|
828
|
+
description: 'Preview destination lane actions, warnings, destructive effects, and hierarchy blockers before moving a ticket.',
|
|
779
829
|
inputSchema: z.object({
|
|
780
830
|
taskId: z.string().min(1),
|
|
781
831
|
planId: z.string().min(1).optional(),
|
|
@@ -789,8 +839,8 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
789
839
|
openWorldHint: false,
|
|
790
840
|
},
|
|
791
841
|
}, async ({ taskId, ...body }) => toolResult(await api.post(`/api/external/tasks/${taskId}/move/preview`, body)));
|
|
792
|
-
server.registerTool('
|
|
793
|
-
description: 'Set or clear the active same-project
|
|
842
|
+
server.registerTool('set_ticket_blocker', {
|
|
843
|
+
description: 'Set or clear the active same-project ticket blocking this ticket.',
|
|
794
844
|
inputSchema: z.object({
|
|
795
845
|
taskId: z.string().min(1),
|
|
796
846
|
blockedByTaskId: z.string().nullable(),
|
|
@@ -803,8 +853,8 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
803
853
|
openWorldHint: false,
|
|
804
854
|
},
|
|
805
855
|
}, async ({ taskId, ...body }) => toolResult(await api.put(`/api/external/tasks/${taskId}`, body)));
|
|
806
|
-
server.registerTool('
|
|
807
|
-
description: 'Add an implementation note, finding, decision, or completion evidence to a
|
|
856
|
+
server.registerTool('add_ticket_comment', {
|
|
857
|
+
description: 'Add an implementation note, finding, decision, or completion evidence to a ticket.',
|
|
808
858
|
inputSchema: z.object({
|
|
809
859
|
taskId: z.string().min(1),
|
|
810
860
|
content: z.string().min(1),
|
|
@@ -843,8 +893,8 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
843
893
|
openWorldHint: false,
|
|
844
894
|
},
|
|
845
895
|
}, async ({ commentId }) => toolResult(await api.post(`/api/external/agent-inbox/${commentId}/acknowledge`, {})));
|
|
846
|
-
server.registerTool('
|
|
847
|
-
description: 'Archive a
|
|
896
|
+
server.registerTool('archive_ticket', {
|
|
897
|
+
description: 'Archive a ticket and clear dependent blocker relationships. Requires explicit human approval.',
|
|
848
898
|
inputSchema: z.object({
|
|
849
899
|
taskId: z.string().min(1),
|
|
850
900
|
expectedUpdatedAt: expectedUpdatedAtSchema,
|
|
@@ -889,8 +939,8 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
889
939
|
openWorldHint: false,
|
|
890
940
|
},
|
|
891
941
|
}, async ({ projectId, ...query }) => toolResult(await api.get(`/api/external/projects/${projectId}/time-entries`, query)));
|
|
892
|
-
server.registerTool('
|
|
893
|
-
description: 'List bounded time entries and totals for one
|
|
942
|
+
server.registerTool('list_ticket_time', {
|
|
943
|
+
description: 'List bounded time entries and totals for one ticket.',
|
|
894
944
|
inputSchema: z.object({
|
|
895
945
|
projectId: z.string().min(1),
|
|
896
946
|
taskId: z.string().min(1),
|
|
@@ -904,8 +954,8 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
904
954
|
openWorldHint: false,
|
|
905
955
|
},
|
|
906
956
|
}, async ({ projectId, taskId, ...query }) => toolResult(await api.get(`/api/external/projects/${projectId}/tasks/${taskId}/time-entries`, query)));
|
|
907
|
-
server.registerTool('
|
|
908
|
-
description: 'Start a live timer on an active
|
|
957
|
+
server.registerTool('start_ticket_timer', {
|
|
958
|
+
description: 'Start a live timer on an active ticket. A ticket allows one active timer; different tickets may run concurrently.',
|
|
909
959
|
inputSchema: z.object({
|
|
910
960
|
projectId: z.string().min(1),
|
|
911
961
|
taskId: z.string().min(1),
|
|
@@ -921,8 +971,8 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
921
971
|
openWorldHint: false,
|
|
922
972
|
},
|
|
923
973
|
}, async ({ projectId, taskId, idempotencyKey, startedAt, ...body }) => toolResult(await api.post(`/api/external/projects/${projectId}/tasks/${taskId}/time-entries`, { ...body, startedAt: startedAt ?? new Date().toISOString() }, idempotencyKey || randomUUID())));
|
|
924
|
-
server.registerTool('
|
|
925
|
-
description: 'Stop an active
|
|
974
|
+
server.registerTool('stop_ticket_timer', {
|
|
975
|
+
description: 'Stop an active ticket timer using optimistic concurrency.',
|
|
926
976
|
inputSchema: z.object({
|
|
927
977
|
projectId: z.string().min(1),
|
|
928
978
|
taskId: z.string().min(1),
|
|
@@ -936,9 +986,9 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
936
986
|
idempotentHint: true,
|
|
937
987
|
openWorldHint: false,
|
|
938
988
|
},
|
|
939
|
-
}, async ({ projectId, taskId, timeEntryId, endedAt, expectedUpdatedAt }) => toolResult(await api.post(`/api/external/projects/${projectId}/tasks/${taskId}/time-entries/${timeEntryId}/stop`, { endedAt
|
|
940
|
-
server.registerTool('
|
|
941
|
-
description: 'Log a completed historical interval against a
|
|
989
|
+
}, async ({ projectId, taskId, timeEntryId, endedAt, expectedUpdatedAt }) => toolResult(await api.post(`/api/external/projects/${projectId}/tasks/${taskId}/time-entries/${timeEntryId}/stop`, { ...(endedAt ? { endedAt } : {}), expectedUpdatedAt })));
|
|
990
|
+
server.registerTool('log_ticket_time', {
|
|
991
|
+
description: 'Log a completed historical interval against a ticket.',
|
|
942
992
|
inputSchema: z.object({
|
|
943
993
|
projectId: z.string().min(1),
|
|
944
994
|
taskId: z.string().min(1),
|
|
@@ -990,7 +1040,7 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
990
1040
|
},
|
|
991
1041
|
}, async ({ projectId, taskId, timeEntryId, expectedUpdatedAt }) => toolResult(await api.delete(`/api/external/projects/${projectId}/tasks/${taskId}/time-entries/${timeEntryId}`, { expectedUpdatedAt })));
|
|
992
1042
|
server.registerTool('get_project_brief', {
|
|
993
|
-
description: 'Read the canonical project brief before planning or changing
|
|
1043
|
+
description: 'Read the canonical project brief before planning or changing tickets.',
|
|
994
1044
|
inputSchema: z.object({ projectId: z.string().min(1) }),
|
|
995
1045
|
annotations: {
|
|
996
1046
|
readOnlyHint: true,
|
|
@@ -1017,7 +1067,7 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
1017
1067
|
},
|
|
1018
1068
|
}, async ({ projectId, ...query }) => toolResult(await api.get(`/api/external/projects/${projectId}/artifacts`, query)));
|
|
1019
1069
|
server.registerTool('get_project_artifact', {
|
|
1020
|
-
description: 'Get complete Markdown, relationships, comments, and
|
|
1070
|
+
description: 'Get complete Markdown, relationships, comments, and ticket links for one project artifact.',
|
|
1021
1071
|
inputSchema: z.object({ projectId: z.string().min(1), artifactId: z.string().min(1) }),
|
|
1022
1072
|
annotations: {
|
|
1023
1073
|
readOnlyHint: true,
|
|
@@ -1108,7 +1158,7 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
1108
1158
|
openWorldHint: false,
|
|
1109
1159
|
},
|
|
1110
1160
|
}, async ({ projectId, artifactId, ...body }) => toolResult(await api.post(`/api/external/projects/${projectId}/artifacts/${artifactId}/transition`, body)));
|
|
1111
|
-
server.registerTool('
|
|
1161
|
+
server.registerTool('link_artifact_ticket', {
|
|
1112
1162
|
description: 'Link same-project execution work to the context it implements, informs, evidences, or came from.',
|
|
1113
1163
|
inputSchema: z.object({
|
|
1114
1164
|
projectId: z.string().min(1),
|
|
@@ -1143,22 +1193,22 @@ export function createToDoddleMcpServer(api, serverOptions = {}) {
|
|
|
1143
1193
|
openWorldHint: false,
|
|
1144
1194
|
},
|
|
1145
1195
|
}, async ({ projectId, artifactId, idempotencyKey, ...body }) => toolResult(await api.post(`/api/external/projects/${projectId}/artifacts/${artifactId}/comments`, body, idempotencyKey || randomUUID())));
|
|
1146
|
-
server.registerResource('
|
|
1147
|
-
title: 'ToDoddle
|
|
1148
|
-
description: 'Complete
|
|
1196
|
+
server.registerResource('ticket', new ResourceTemplate('tododdle://tickets/{ticketId}', { list: undefined }), {
|
|
1197
|
+
title: 'ToDoddle ticket',
|
|
1198
|
+
description: 'Complete ticket context by ID.',
|
|
1149
1199
|
mimeType: 'application/json',
|
|
1150
1200
|
}, async (uri, variables) => ({
|
|
1151
1201
|
contents: [
|
|
1152
1202
|
{
|
|
1153
1203
|
uri: uri.href,
|
|
1154
1204
|
mimeType: 'application/json',
|
|
1155
|
-
text: JSON.stringify(await api.get(`/api/external/tasks/${String(variables.
|
|
1205
|
+
text: JSON.stringify(await api.get(`/api/external/tasks/${String(variables.ticketId)}`), null, 2),
|
|
1156
1206
|
},
|
|
1157
1207
|
],
|
|
1158
1208
|
}));
|
|
1159
1209
|
server.registerResource('project', new ResourceTemplate('tododdle://projects/{projectId}', { list: undefined }), {
|
|
1160
1210
|
title: 'ToDoddle project context',
|
|
1161
|
-
description: 'Bounded project
|
|
1211
|
+
description: 'Bounded project board and lane context by project ID.',
|
|
1162
1212
|
mimeType: 'application/json',
|
|
1163
1213
|
}, async (uri, variables) => ({
|
|
1164
1214
|
contents: [
|