mcp-sunsama 0.10.2 → 0.11.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/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # mcp-sunsama
2
2
 
3
+ ## 0.11.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 9d0d694: feat: add update-task-due-date tool for setting and clearing task due dates
8
+
9
+ - Adds new `update-task-due-date` tool to set or clear task due dates using ISO datetime format or null
10
+ - Includes comprehensive parameter validation with Zod schema for taskId, dueDate, and limitResponsePayload
11
+ - Supports both setting due dates with ISO datetime strings and clearing due dates with null values
12
+ - Adds extensive test coverage with 11 test cases covering valid inputs, error cases, and edge conditions
13
+ - Updates API documentation and README with complete tool information
14
+ - Follows established patterns for dual transport support (stdio/httpStream) and response optimization
15
+
3
16
  ## 0.10.2
4
17
 
5
18
  ### Patch Changes
package/README.md CHANGED
@@ -96,6 +96,7 @@ Add this configuration to your Claude Desktop MCP settings:
96
96
  - `update-task-complete` - Mark tasks as complete
97
97
  - `update-task-planned-time` - Update the planned time (time estimate) for tasks
98
98
  - `update-task-notes` - Update task notes content (requires either `html` or `markdown` parameter, mutually exclusive)
99
+ - `update-task-due-date` - Update the due date for tasks (set or clear due dates)
99
100
  - `update-task-snooze-date` - Reschedule tasks to different dates
100
101
  - `update-task-backlog` - Move tasks to the backlog
101
102
  - `delete-task` - Delete tasks permanently
package/bun.lock CHANGED
@@ -7,7 +7,7 @@
7
7
  "@types/papaparse": "^5.3.16",
8
8
  "fastmcp": "3.3.1",
9
9
  "papaparse": "^5.5.3",
10
- "sunsama-api": "0.8.1",
10
+ "sunsama-api": "0.9.0",
11
11
  "zod": "3.24.4",
12
12
  },
13
13
  "devDependencies": {
@@ -430,7 +430,7 @@
430
430
 
431
431
  "strtok3": ["strtok3@10.3.1", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-3JWEZM6mfix/GCJBBUrkA8p2Id2pBkyTkVCJKto55w080QBKZ+8R171fGrbiSp+yMO/u6F8/yUh7K4V9K+YCnw=="],
432
432
 
433
- "sunsama-api": ["sunsama-api@0.8.1", "", { "dependencies": { "graphql": "^16.11.0", "graphql-tag": "^2.12.6", "marked": "^14.1.3", "tough-cookie": "^5.1.2", "tslib": "^2.8.1", "turndown": "^7.2.0", "yjs": "^13.6.27", "zod": "^3.25.64" } }, "sha512-CcLyMwrUnpQi9GmRrNqgqb0A7f2Ba5qxaI1uJdAivcqZQRNfg+g5V9RFzh64mGrN/7M3v+Meiv+Ev6AsiSAz3A=="],
433
+ "sunsama-api": ["sunsama-api@0.9.0", "", { "dependencies": { "graphql": "^16.11.0", "graphql-tag": "^2.12.6", "marked": "^14.1.3", "tough-cookie": "^5.1.2", "tslib": "^2.8.1", "turndown": "^7.2.0", "yjs": "^13.6.27", "zod": "^3.25.64" } }, "sha512-pSTS7+ZPjToeTnMiPuXDMrR/nbISl/fM5MqpdXV6LRBYlOmM/h9xUZO01BP9jhQYJWkasdw05KDHfCWYhUPjIg=="],
434
434
 
435
435
  "term-size": ["term-size@2.2.1", "", {}, "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg=="],
436
436
 
@@ -0,0 +1,128 @@
1
+ # Product Requirements Document: update-task-due-date
2
+
3
+ ## Overview
4
+ Implement a new MCP tool `update-task-due-date` that allows updating the due date of an existing task in Sunsama.
5
+
6
+ ## Research Findings
7
+
8
+ ### Current State
9
+ - Tasks in Sunsama have a `dueDate` field (optional string, ISO format)
10
+ - The sunsama-api library doesn't have a dedicated `updateTaskDueDate` method
11
+ - Other update operations use GraphQL mutations with standardized patterns
12
+ - All existing update tools follow the same pattern: parameters → schema validation → client call → JSON response
13
+
14
+ ### Technical Implementation Approach
15
+ Since there's no dedicated `updateTaskDueDate` method in the sunsama-api library, we need to implement this functionality by leveraging the existing patterns. Based on analysis of other task mutations, we have two options:
16
+
17
+ 1. **Option A**: Use a general task update mutation (if available)
18
+ 2. **Option B**: Extend the sunsama-api library functionality directly in our MCP server
19
+
20
+ For this implementation, we'll use **Option A** if a general update method exists, or create a custom GraphQL call following the existing patterns.
21
+
22
+ ## Requirements
23
+
24
+ ### Functional Requirements
25
+ 1. Update a task's due date to a specific ISO date string
26
+ 2. Clear a task's due date (set to null)
27
+ 3. Validate the task exists before updating
28
+ 4. Return success/failure status with updated fields
29
+ 5. Support optional response payload limiting
30
+
31
+ ### Input Parameters
32
+ - `taskId` (required): The ID of the task to update
33
+ - `dueDate` (required): New due date in ISO format (YYYY-MM-DD or ISO datetime), or null to clear
34
+ - `limitResponsePayload` (optional): Whether to limit response size (defaults to true)
35
+
36
+ ### Output Format
37
+ JSON response with:
38
+ - `success`: Boolean indicating operation success
39
+ - `taskId`: The ID of the updated task
40
+ - `dueDate`: The new due date value
41
+ - `updatedFields`: Partial task data (null if limitResponsePayload is true)
42
+
43
+ ### Error Handling
44
+ - Validate taskId format
45
+ - Validate dueDate format (ISO string or null)
46
+ - Handle task not found scenarios
47
+ - Handle API authentication errors
48
+ - Handle network/GraphQL errors
49
+
50
+ ## Implementation Plan
51
+
52
+ ### 1. Schema Definition (`schemas.ts`)
53
+ ```typescript
54
+ export const updateTaskDueDateSchema = z.object({
55
+ taskId: z.string().min(1, "Task ID is required"),
56
+ dueDate: z.string().datetime().nullable().describe("Due date in ISO format or null to clear"),
57
+ limitResponsePayload: z.boolean().optional().describe("Whether to limit response payload size")
58
+ });
59
+ ```
60
+
61
+ ### 2. Tool Implementation (`main.ts`)
62
+ - Follow existing pattern from `update-task-planned-time`
63
+ - Extract parameters with validation
64
+ - Call sunsama-api method or custom GraphQL mutation
65
+ - Format and return JSON response
66
+ - Include comprehensive error handling and logging
67
+
68
+ ### 3. API Extension Strategy
69
+ If no direct method exists in sunsama-api:
70
+ - Create custom GraphQL mutation similar to `updateTaskPlannedTime`
71
+ - Use `UPDATE_TASK_PAYLOAD_FRAGMENT` for consistent response format
72
+ - Follow existing authentication and request patterns
73
+
74
+ ### 4. Test Coverage
75
+ - Parameter validation tests
76
+ - Success scenarios (set date, clear date)
77
+ - Error scenarios (invalid taskId, invalid date format, task not found)
78
+ - Response format validation
79
+
80
+ ## Technical Notes
81
+
82
+ ### Due Date Format
83
+ - Accept ISO 8601 date strings (both date-only and datetime formats)
84
+ - Store as ISO string in Sunsama's format
85
+ - Allow null to clear the due date
86
+
87
+ ### Consistency with Existing Tools
88
+ - Use same error handling patterns as other update tools
89
+ - Use same logging format and parameter extraction
90
+ - Use same JSON response structure
91
+ - Apply same optional response payload limiting
92
+
93
+ ### GraphQL Implementation
94
+ If custom mutation needed:
95
+ ```graphql
96
+ mutation updateTaskDueDate($input: UpdateTaskDueDateInput!) {
97
+ updateTaskDueDate(input: $input) {
98
+ ...UpdateTaskPayload
99
+ __typename
100
+ }
101
+ }
102
+ ```
103
+
104
+ ## Integration Points
105
+
106
+ ### MCP Server Documentation
107
+ - Update `sunsama://api/docs` resource with new tool information
108
+ - Include usage examples and parameter descriptions
109
+ - Document relationship to other task update tools
110
+
111
+ ### README Updates
112
+ - Add to task management section
113
+ - Include usage examples
114
+ - Update tool list
115
+
116
+ ## Success Criteria
117
+ 1. Tool successfully updates task due dates via MCP interface
118
+ 2. Tool properly validates all input parameters
119
+ 3. Tool handles all error scenarios gracefully
120
+ 4. Tool follows established patterns and conventions
121
+ 5. Comprehensive test coverage passes
122
+ 6. Documentation is complete and accurate
123
+
124
+ ## Dependencies
125
+ - Existing sunsama-api GraphQL infrastructure
126
+ - Zod schema validation system
127
+ - FastMCP tool registration system
128
+ - Task identification and authentication systems
package/dist/main.js CHANGED
@@ -3,7 +3,7 @@ import { FastMCP } from "fastmcp";
3
3
  import { httpStreamAuthenticator } from "./auth/http.js";
4
4
  import { initializeStdioAuth } from "./auth/stdio.js";
5
5
  import { getTransportConfig } from "./config/transport.js";
6
- import { createTaskSchema, deleteTaskSchema, getArchivedTasksSchema, getStreamsSchema, getTaskByIdSchema, getTasksBacklogSchema, getTasksByDaySchema, getUserSchema, updateTaskBacklogSchema, updateTaskCompleteSchema, updateTaskNotesSchema, updateTaskPlannedTimeSchema, updateTaskSnoozeDateSchema } from "./schemas.js";
6
+ import { createTaskSchema, deleteTaskSchema, getArchivedTasksSchema, getStreamsSchema, getTaskByIdSchema, getTasksBacklogSchema, getTasksByDaySchema, getUserSchema, updateTaskBacklogSchema, updateTaskCompleteSchema, updateTaskDueDateSchema, updateTaskNotesSchema, updateTaskPlannedTimeSchema, updateTaskSnoozeDateSchema } from "./schemas.js";
7
7
  import { getSunsamaClient } from "./utils/client-resolver.js";
8
8
  import { filterTasksByCompletion } from "./utils/task-filters.js";
9
9
  import { trimTasksForResponse } from "./utils/task-trimmer.js";
@@ -16,7 +16,7 @@ if (transportConfig.transportType === "stdio") {
16
16
  }
17
17
  const server = new FastMCP({
18
18
  name: "Sunsama API Server",
19
- version: "0.10.2",
19
+ version: "0.11.0",
20
20
  instructions: `
21
21
  This MCP server provides access to the Sunsama API for task and project management.
22
22
 
@@ -24,7 +24,7 @@ Available tools:
24
24
  - Authentication: login, logout, check authentication status
25
25
  - User operations: get current user information
26
26
  - Task operations: get tasks by day, get backlog tasks, get archived tasks, get task by ID
27
- - Task mutations: create tasks, mark complete, delete tasks, reschedule tasks, update planned time, update task notes
27
+ - Task mutations: create tasks, mark complete, delete tasks, reschedule tasks, update planned time, update task notes, update task due date
28
28
  - Stream operations: get streams/channels for the user's group
29
29
 
30
30
  Authentication is required for all operations. You can either:
@@ -625,6 +625,53 @@ server.addTool({
625
625
  }
626
626
  }
627
627
  });
628
+ server.addTool({
629
+ name: "update-task-due-date",
630
+ description: "Update the due date for a task",
631
+ parameters: updateTaskDueDateSchema,
632
+ execute: async (args, { session, log }) => {
633
+ try {
634
+ // Extract parameters
635
+ const { taskId, dueDate, limitResponsePayload } = args;
636
+ log.info("Updating task due date", {
637
+ taskId: taskId,
638
+ dueDate: dueDate,
639
+ limitResponsePayload: limitResponsePayload
640
+ });
641
+ // Get the appropriate client based on transport type
642
+ const sunsamaClient = getSunsamaClient(session);
643
+ // Call sunsamaClient.updateTaskDueDate(taskId, dueDate, limitResponsePayload)
644
+ const result = await sunsamaClient.updateTaskDueDate(taskId, dueDate, limitResponsePayload);
645
+ log.info("Successfully updated task due date", {
646
+ taskId: taskId,
647
+ dueDate: dueDate,
648
+ success: result.success
649
+ });
650
+ return {
651
+ content: [
652
+ {
653
+ type: "text",
654
+ text: JSON.stringify({
655
+ success: result.success,
656
+ taskId: taskId,
657
+ dueDate: dueDate,
658
+ dueDateUpdated: true,
659
+ updatedFields: result.updatedFields
660
+ })
661
+ }
662
+ ]
663
+ };
664
+ }
665
+ catch (error) {
666
+ log.error("Failed to update task due date", {
667
+ taskId: args.taskId,
668
+ dueDate: args.dueDate,
669
+ error: error instanceof Error ? error.message : 'Unknown error'
670
+ });
671
+ throw new Error(`Failed to update task due date: ${error instanceof Error ? error.message : 'Unknown error'}`);
672
+ }
673
+ }
674
+ });
628
675
  // Stream Operations
629
676
  server.addTool({
630
677
  name: "get-streams",
@@ -764,6 +811,13 @@ Uses HTTP Basic Auth headers (per-request authentication):
764
811
  - Returns: JSON with update result
765
812
  - Note: Exactly one of \`html\` or \`markdown\` must be provided (mutually exclusive)
766
813
 
814
+ - **update-task-due-date**: Update the due date for a task
815
+ - Parameters:
816
+ - \`taskId\` (required): The ID of the task to update due date for
817
+ - \`dueDate\` (required): Due date in ISO format (YYYY-MM-DDTHH:mm:ssZ) or null to clear the due date
818
+ - \`limitResponsePayload\` (optional): Whether to limit response size
819
+ - Returns: JSON with update result
820
+
767
821
  ### Stream Operations
768
822
  - **get-streams**: Get streams for the user's group
769
823
  - Parameters: none
package/dist/schemas.d.ts CHANGED
@@ -171,6 +171,19 @@ export declare const updateTaskNotesSchema: z.ZodEffects<z.ZodObject<{
171
171
  html?: string | undefined;
172
172
  markdown?: string | undefined;
173
173
  }>;
174
+ export declare const updateTaskDueDateSchema: z.ZodObject<{
175
+ taskId: z.ZodString;
176
+ dueDate: z.ZodUnion<[z.ZodString, z.ZodNull]>;
177
+ limitResponsePayload: z.ZodOptional<z.ZodBoolean>;
178
+ }, "strip", z.ZodTypeAny, {
179
+ taskId: string;
180
+ dueDate: string | null;
181
+ limitResponsePayload?: boolean | undefined;
182
+ }, {
183
+ taskId: string;
184
+ dueDate: string | null;
185
+ limitResponsePayload?: boolean | undefined;
186
+ }>;
174
187
  /**
175
188
  * Response Type Schemas (for validation and documentation)
176
189
  */
@@ -606,6 +619,7 @@ export type DeleteTaskInput = z.infer<typeof deleteTaskSchema>;
606
619
  export type UpdateTaskSnoozeDateInput = z.infer<typeof updateTaskSnoozeDateSchema>;
607
620
  export type UpdateTaskPlannedTimeInput = z.infer<typeof updateTaskPlannedTimeSchema>;
608
621
  export type UpdateTaskNotesInput = z.infer<typeof updateTaskNotesSchema>;
622
+ export type UpdateTaskDueDateInput = z.infer<typeof updateTaskDueDateSchema>;
609
623
  export type User = z.infer<typeof userSchema>;
610
624
  export type Task = z.infer<typeof taskSchema>;
611
625
  export type Stream = z.infer<typeof streamSchema>;
@@ -1 +1 @@
1
- {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"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"}
package/dist/schemas.js CHANGED
@@ -103,6 +103,15 @@ export const updateTaskNotesSchema = z.object({
103
103
  message: "Exactly one of 'html' or 'markdown' must be provided",
104
104
  path: [], // This will show the error at the root level
105
105
  });
106
+ // Update task due date parameters
107
+ export const updateTaskDueDateSchema = z.object({
108
+ taskId: z.string().min(1, "Task ID is required").describe("The ID of the task to update due date for"),
109
+ dueDate: z.union([
110
+ z.string().datetime("Must be a valid ISO date-time string"),
111
+ z.null()
112
+ ]).describe("Due date in ISO format (YYYY-MM-DDTHH:mm:ssZ) or null to clear the due date"),
113
+ limitResponsePayload: z.boolean().optional().describe("Whether to limit the response payload size"),
114
+ });
106
115
  /**
107
116
  * Response Type Schemas (for validation and documentation)
108
117
  */
@@ -1,5 +1,5 @@
1
1
  import { describe, test, expect } from "bun:test";
2
- import { completionFilterSchema, getTasksByDaySchema, getTasksBacklogSchema, getArchivedTasksSchema, getUserSchema, getStreamsSchema, createTaskSchema, updateTaskCompleteSchema, deleteTaskSchema, updateTaskSnoozeDateSchema, updateTaskBacklogSchema, updateTaskPlannedTimeSchema, updateTaskNotesSchema, userProfileSchema, groupSchema, userSchema, taskSchema, streamSchema, userResponseSchema, tasksResponseSchema, streamsResponseSchema, errorResponseSchema, } from "./schemas.js";
2
+ import { completionFilterSchema, getTasksByDaySchema, getTasksBacklogSchema, getArchivedTasksSchema, getUserSchema, getStreamsSchema, createTaskSchema, updateTaskCompleteSchema, deleteTaskSchema, updateTaskSnoozeDateSchema, updateTaskBacklogSchema, updateTaskPlannedTimeSchema, updateTaskNotesSchema, updateTaskDueDateSchema, userProfileSchema, groupSchema, userSchema, taskSchema, streamSchema, userResponseSchema, tasksResponseSchema, streamsResponseSchema, errorResponseSchema, } from "./schemas.js";
3
3
  describe("Tool Parameter Schemas", () => {
4
4
  describe("completionFilterSchema", () => {
5
5
  test("should accept valid completion filter values", () => {
@@ -320,6 +320,107 @@ describe("Tool Parameter Schemas", () => {
320
320
  })).not.toThrow();
321
321
  });
322
322
  });
323
+ describe("updateTaskDueDateSchema", () => {
324
+ test("should accept valid ISO date string", () => {
325
+ const validInput = {
326
+ taskId: "task-123",
327
+ dueDate: "2024-06-21T04:00:00.000Z",
328
+ limitResponsePayload: true,
329
+ };
330
+ expect(() => updateTaskDueDateSchema.parse(validInput)).not.toThrow();
331
+ });
332
+ test("should accept valid ISO date-time string with Z timezone", () => {
333
+ const validInput = {
334
+ taskId: "task-123",
335
+ dueDate: "2024-06-21T14:30:00.000Z",
336
+ limitResponsePayload: false,
337
+ };
338
+ expect(() => updateTaskDueDateSchema.parse(validInput)).not.toThrow();
339
+ });
340
+ test("should accept null to clear due date", () => {
341
+ const validInput = {
342
+ taskId: "task-123",
343
+ dueDate: null,
344
+ limitResponsePayload: true,
345
+ };
346
+ expect(() => updateTaskDueDateSchema.parse(validInput)).not.toThrow();
347
+ });
348
+ test("should accept minimal required input with ISO date", () => {
349
+ const minimalInput = {
350
+ taskId: "task-123",
351
+ dueDate: "2024-06-21T09:00:00Z",
352
+ };
353
+ expect(() => updateTaskDueDateSchema.parse(minimalInput)).not.toThrow();
354
+ });
355
+ test("should accept minimal required input with null", () => {
356
+ const minimalInput = {
357
+ taskId: "task-123",
358
+ dueDate: null,
359
+ };
360
+ expect(() => updateTaskDueDateSchema.parse(minimalInput)).not.toThrow();
361
+ });
362
+ test("should reject empty task ID", () => {
363
+ expect(() => updateTaskDueDateSchema.parse({
364
+ taskId: "",
365
+ dueDate: "2024-06-21T09:00:00Z",
366
+ })).toThrow();
367
+ expect(() => updateTaskDueDateSchema.parse({
368
+ dueDate: "2024-06-21T09:00:00Z",
369
+ })).toThrow();
370
+ });
371
+ test("should reject invalid date formats", () => {
372
+ // Date-only format
373
+ expect(() => updateTaskDueDateSchema.parse({
374
+ taskId: "task-123",
375
+ dueDate: "2024-06-21",
376
+ })).toThrow();
377
+ // Non-ISO format
378
+ expect(() => updateTaskDueDateSchema.parse({
379
+ taskId: "task-123",
380
+ dueDate: "06/21/2024",
381
+ })).toThrow();
382
+ // Invalid ISO format
383
+ expect(() => updateTaskDueDateSchema.parse({
384
+ taskId: "task-123",
385
+ dueDate: "2024-06-21T25:00:00Z",
386
+ })).toThrow();
387
+ // Plain text
388
+ expect(() => updateTaskDueDateSchema.parse({
389
+ taskId: "task-123",
390
+ dueDate: "tomorrow",
391
+ })).toThrow();
392
+ // Empty string
393
+ expect(() => updateTaskDueDateSchema.parse({
394
+ taskId: "task-123",
395
+ dueDate: "",
396
+ })).toThrow();
397
+ });
398
+ test("should reject undefined due date", () => {
399
+ expect(() => updateTaskDueDateSchema.parse({
400
+ taskId: "task-123",
401
+ dueDate: undefined,
402
+ })).toThrow();
403
+ });
404
+ test("should reject missing due date", () => {
405
+ expect(() => updateTaskDueDateSchema.parse({
406
+ taskId: "task-123",
407
+ })).toThrow();
408
+ });
409
+ test("should reject non-string, non-null due date values", () => {
410
+ expect(() => updateTaskDueDateSchema.parse({
411
+ taskId: "task-123",
412
+ dueDate: new Date(),
413
+ })).toThrow();
414
+ expect(() => updateTaskDueDateSchema.parse({
415
+ taskId: "task-123",
416
+ dueDate: 1640995200000, // timestamp
417
+ })).toThrow();
418
+ expect(() => updateTaskDueDateSchema.parse({
419
+ taskId: "task-123",
420
+ dueDate: true,
421
+ })).toThrow();
422
+ });
423
+ });
323
424
  });
324
425
  describe("Response Schemas", () => {
325
426
  describe("userProfileSchema", () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-sunsama",
3
- "version": "0.10.2",
3
+ "version": "0.11.0",
4
4
  "description": "MCP server for Sunsama API integration",
5
5
  "type": "module",
6
6
  "private": false,
@@ -26,7 +26,7 @@
26
26
  "@types/papaparse": "^5.3.16",
27
27
  "fastmcp": "3.3.1",
28
28
  "papaparse": "^5.5.3",
29
- "sunsama-api": "0.8.1",
29
+ "sunsama-api": "0.9.0",
30
30
  "zod": "3.24.4"
31
31
  },
32
32
  "devDependencies": {
package/src/main.ts CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  getUserSchema,
17
17
  updateTaskBacklogSchema,
18
18
  updateTaskCompleteSchema,
19
+ updateTaskDueDateSchema,
19
20
  updateTaskNotesSchema,
20
21
  updateTaskPlannedTimeSchema,
21
22
  updateTaskSnoozeDateSchema
@@ -35,7 +36,7 @@ if (transportConfig.transportType === "stdio") {
35
36
 
36
37
  const server = new FastMCP({
37
38
  name: "Sunsama API Server",
38
- version: "0.10.2",
39
+ version: "0.11.0",
39
40
  instructions: `
40
41
  This MCP server provides access to the Sunsama API for task and project management.
41
42
 
@@ -43,7 +44,7 @@ Available tools:
43
44
  - Authentication: login, logout, check authentication status
44
45
  - User operations: get current user information
45
46
  - Task operations: get tasks by day, get backlog tasks, get archived tasks, get task by ID
46
- - Task mutations: create tasks, mark complete, delete tasks, reschedule tasks, update planned time, update task notes
47
+ - Task mutations: create tasks, mark complete, delete tasks, reschedule tasks, update planned time, update task notes, update task due date
47
48
  - Stream operations: get streams/channels for the user's group
48
49
 
49
50
  Authentication is required for all operations. You can either:
@@ -762,6 +763,64 @@ server.addTool({
762
763
  }
763
764
  });
764
765
 
766
+ server.addTool({
767
+ name: "update-task-due-date",
768
+ description: "Update the due date for a task",
769
+ parameters: updateTaskDueDateSchema,
770
+ execute: async (args, {session, log}) => {
771
+ try {
772
+ // Extract parameters
773
+ const {taskId, dueDate, limitResponsePayload} = args;
774
+
775
+ log.info("Updating task due date", {
776
+ taskId: taskId,
777
+ dueDate: dueDate,
778
+ limitResponsePayload: limitResponsePayload
779
+ });
780
+
781
+ // Get the appropriate client based on transport type
782
+ const sunsamaClient = getSunsamaClient(session as SessionData | null);
783
+
784
+ // Call sunsamaClient.updateTaskDueDate(taskId, dueDate, limitResponsePayload)
785
+ const result = await sunsamaClient.updateTaskDueDate(
786
+ taskId,
787
+ dueDate,
788
+ limitResponsePayload
789
+ );
790
+
791
+ log.info("Successfully updated task due date", {
792
+ taskId: taskId,
793
+ dueDate: dueDate,
794
+ success: result.success
795
+ });
796
+
797
+ return {
798
+ content: [
799
+ {
800
+ type: "text",
801
+ text: JSON.stringify({
802
+ success: result.success,
803
+ taskId: taskId,
804
+ dueDate: dueDate,
805
+ dueDateUpdated: true,
806
+ updatedFields: result.updatedFields
807
+ })
808
+ }
809
+ ]
810
+ };
811
+
812
+ } catch (error) {
813
+ log.error("Failed to update task due date", {
814
+ taskId: args.taskId,
815
+ dueDate: args.dueDate,
816
+ error: error instanceof Error ? error.message : 'Unknown error'
817
+ });
818
+
819
+ throw new Error(`Failed to update task due date: ${error instanceof Error ? error.message : 'Unknown error'}`);
820
+ }
821
+ }
822
+ });
823
+
765
824
  // Stream Operations
766
825
  server.addTool({
767
826
  name: "get-streams",
@@ -907,6 +966,13 @@ Uses HTTP Basic Auth headers (per-request authentication):
907
966
  - Returns: JSON with update result
908
967
  - Note: Exactly one of \`html\` or \`markdown\` must be provided (mutually exclusive)
909
968
 
969
+ - **update-task-due-date**: Update the due date for a task
970
+ - Parameters:
971
+ - \`taskId\` (required): The ID of the task to update due date for
972
+ - \`dueDate\` (required): Due date in ISO format (YYYY-MM-DDTHH:mm:ssZ) or null to clear the due date
973
+ - \`limitResponsePayload\` (optional): Whether to limit response size
974
+ - Returns: JSON with update result
975
+
910
976
  ### Stream Operations
911
977
  - **get-streams**: Get streams for the user's group
912
978
  - Parameters: none
@@ -13,6 +13,7 @@ import {
13
13
  updateTaskBacklogSchema,
14
14
  updateTaskPlannedTimeSchema,
15
15
  updateTaskNotesSchema,
16
+ updateTaskDueDateSchema,
16
17
  userProfileSchema,
17
18
  groupSchema,
18
19
  userSchema,
@@ -437,6 +438,147 @@ describe("Tool Parameter Schemas", () => {
437
438
  ).not.toThrow();
438
439
  });
439
440
  });
441
+
442
+ describe("updateTaskDueDateSchema", () => {
443
+ test("should accept valid ISO date string", () => {
444
+ const validInput = {
445
+ taskId: "task-123",
446
+ dueDate: "2024-06-21T04:00:00.000Z",
447
+ limitResponsePayload: true,
448
+ };
449
+ expect(() => updateTaskDueDateSchema.parse(validInput)).not.toThrow();
450
+ });
451
+
452
+ test("should accept valid ISO date-time string with Z timezone", () => {
453
+ const validInput = {
454
+ taskId: "task-123",
455
+ dueDate: "2024-06-21T14:30:00.000Z",
456
+ limitResponsePayload: false,
457
+ };
458
+ expect(() => updateTaskDueDateSchema.parse(validInput)).not.toThrow();
459
+ });
460
+
461
+ test("should accept null to clear due date", () => {
462
+ const validInput = {
463
+ taskId: "task-123",
464
+ dueDate: null,
465
+ limitResponsePayload: true,
466
+ };
467
+ expect(() => updateTaskDueDateSchema.parse(validInput)).not.toThrow();
468
+ });
469
+
470
+ test("should accept minimal required input with ISO date", () => {
471
+ const minimalInput = {
472
+ taskId: "task-123",
473
+ dueDate: "2024-06-21T09:00:00Z",
474
+ };
475
+ expect(() => updateTaskDueDateSchema.parse(minimalInput)).not.toThrow();
476
+ });
477
+
478
+ test("should accept minimal required input with null", () => {
479
+ const minimalInput = {
480
+ taskId: "task-123",
481
+ dueDate: null,
482
+ };
483
+ expect(() => updateTaskDueDateSchema.parse(minimalInput)).not.toThrow();
484
+ });
485
+
486
+ test("should reject empty task ID", () => {
487
+ expect(() =>
488
+ updateTaskDueDateSchema.parse({
489
+ taskId: "",
490
+ dueDate: "2024-06-21T09:00:00Z",
491
+ })
492
+ ).toThrow();
493
+ expect(() =>
494
+ updateTaskDueDateSchema.parse({
495
+ dueDate: "2024-06-21T09:00:00Z",
496
+ })
497
+ ).toThrow();
498
+ });
499
+
500
+ test("should reject invalid date formats", () => {
501
+ // Date-only format
502
+ expect(() =>
503
+ updateTaskDueDateSchema.parse({
504
+ taskId: "task-123",
505
+ dueDate: "2024-06-21",
506
+ })
507
+ ).toThrow();
508
+
509
+ // Non-ISO format
510
+ expect(() =>
511
+ updateTaskDueDateSchema.parse({
512
+ taskId: "task-123",
513
+ dueDate: "06/21/2024",
514
+ })
515
+ ).toThrow();
516
+
517
+ // Invalid ISO format
518
+ expect(() =>
519
+ updateTaskDueDateSchema.parse({
520
+ taskId: "task-123",
521
+ dueDate: "2024-06-21T25:00:00Z",
522
+ })
523
+ ).toThrow();
524
+
525
+ // Plain text
526
+ expect(() =>
527
+ updateTaskDueDateSchema.parse({
528
+ taskId: "task-123",
529
+ dueDate: "tomorrow",
530
+ })
531
+ ).toThrow();
532
+
533
+ // Empty string
534
+ expect(() =>
535
+ updateTaskDueDateSchema.parse({
536
+ taskId: "task-123",
537
+ dueDate: "",
538
+ })
539
+ ).toThrow();
540
+ });
541
+
542
+ test("should reject undefined due date", () => {
543
+ expect(() =>
544
+ updateTaskDueDateSchema.parse({
545
+ taskId: "task-123",
546
+ dueDate: undefined,
547
+ })
548
+ ).toThrow();
549
+ });
550
+
551
+ test("should reject missing due date", () => {
552
+ expect(() =>
553
+ updateTaskDueDateSchema.parse({
554
+ taskId: "task-123",
555
+ })
556
+ ).toThrow();
557
+ });
558
+
559
+ test("should reject non-string, non-null due date values", () => {
560
+ expect(() =>
561
+ updateTaskDueDateSchema.parse({
562
+ taskId: "task-123",
563
+ dueDate: new Date(),
564
+ })
565
+ ).toThrow();
566
+
567
+ expect(() =>
568
+ updateTaskDueDateSchema.parse({
569
+ taskId: "task-123",
570
+ dueDate: 1640995200000, // timestamp
571
+ })
572
+ ).toThrow();
573
+
574
+ expect(() =>
575
+ updateTaskDueDateSchema.parse({
576
+ taskId: "task-123",
577
+ dueDate: true,
578
+ })
579
+ ).toThrow();
580
+ });
581
+ });
440
582
  });
441
583
 
442
584
  describe("Response Schemas", () => {
package/src/schemas.ts CHANGED
@@ -126,6 +126,16 @@ export const updateTaskNotesSchema = z.object({
126
126
  }
127
127
  );
128
128
 
129
+ // Update task due date parameters
130
+ export const updateTaskDueDateSchema = z.object({
131
+ taskId: z.string().min(1, "Task ID is required").describe("The ID of the task to update due date for"),
132
+ dueDate: z.union([
133
+ z.string().datetime("Must be a valid ISO date-time string"),
134
+ z.null()
135
+ ]).describe("Due date in ISO format (YYYY-MM-DDTHH:mm:ssZ) or null to clear the due date"),
136
+ limitResponsePayload: z.boolean().optional().describe("Whether to limit the response payload size"),
137
+ });
138
+
129
139
  /**
130
140
  * Response Type Schemas (for validation and documentation)
131
141
  */
@@ -229,6 +239,7 @@ export type DeleteTaskInput = z.infer<typeof deleteTaskSchema>;
229
239
  export type UpdateTaskSnoozeDateInput = z.infer<typeof updateTaskSnoozeDateSchema>;
230
240
  export type UpdateTaskPlannedTimeInput = z.infer<typeof updateTaskPlannedTimeSchema>;
231
241
  export type UpdateTaskNotesInput = z.infer<typeof updateTaskNotesSchema>;
242
+ export type UpdateTaskDueDateInput = z.infer<typeof updateTaskDueDateSchema>;
232
243
 
233
244
  export type User = z.infer<typeof userSchema>;
234
245
  export type Task = z.infer<typeof taskSchema>;