mcp-sunsama 0.8.0 → 0.10.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,30 @@
1
1
  # mcp-sunsama
2
2
 
3
+ ## 0.10.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 52e464c: Refactor update-task-notes schema to use XOR pattern for content parameters
8
+
9
+ - Replace nested content object with separate html and markdown parameters
10
+ - Implement XOR validation ensuring exactly one content type is provided
11
+ - Add comprehensive test coverage for the new schema pattern
12
+ - Update tool implementation to handle simplified parameter structure
13
+ - Update documentation across README.md, CLAUDE.md, and API docs
14
+
15
+ ## 0.9.0
16
+
17
+ ### Minor Changes
18
+
19
+ - 953ecb7: Add update-task-notes tool for updating task notes content
20
+
21
+ - New `update-task-notes` tool supports updating task notes with HTML or Markdown content
22
+ - Accepts either `{html: string}` or `{markdown: string}` content format (mutually exclusive)
23
+ - Includes optional `limitResponsePayload` parameter for response optimization
24
+ - Integrates with Sunsama's collaborative editing system for proper synchronization
25
+ - Follows established patterns for tool implementation and error handling
26
+ - Updates API documentation, README, and CLAUDE.md with new tool information
27
+
3
28
  ## 0.8.0
4
29
 
5
30
  ### Minor Changes
package/CLAUDE.md CHANGED
@@ -66,6 +66,7 @@ All tools use Zod schemas from `schemas.ts`:
66
66
  - Automatic TypeScript inference
67
67
  - Comprehensive parameter documentation
68
68
  - Union types for completion filters
69
+ - XOR schema patterns for mutually exclusive parameters (e.g., `update-task-notes` requires either `html` OR `markdown`, but not both)
69
70
 
70
71
  ## Key Patterns
71
72
 
@@ -106,6 +107,7 @@ server.addTool({
106
107
 
107
108
  - `src/main.ts`: FastMCP server setup and tool definitions
108
109
  - `src/schemas.ts`: Zod validation schemas for all tools
110
+ - `src/schemas.test.ts`: Comprehensive test suite for all Zod schemas including parameter validation, response schemas, and XOR patterns
109
111
  - `src/auth/`: Authentication strategies per transport type
110
112
  - `src/config/`: Environment configuration and validation
111
113
  - `src/utils/`: Reusable utilities (client resolution, filtering, formatting)
@@ -127,7 +129,7 @@ Optional:
127
129
  ### Task Operations
128
130
  Full CRUD support:
129
131
  - **Read**: `get-tasks-by-day`, `get-tasks-backlog`, `get-archived-tasks`, `get-task-by-id`, `get-streams`
130
- - **Write**: `create-task`, `update-task-complete`, `update-task-planned-time`, `update-task-snooze-date`, `update-task-backlog`, `delete-task`
132
+ - **Write**: `create-task`, `update-task-complete`, `update-task-planned-time`, `update-task-notes`, `update-task-snooze-date`, `update-task-backlog`, `delete-task`
131
133
 
132
134
  Task read operations support response trimming. `get-tasks-by-day` includes completion filtering. `get-archived-tasks` includes enhanced pagination with hasMore flag for LLM decision-making.
133
135
 
package/README.md CHANGED
@@ -95,6 +95,7 @@ Add this configuration to your Claude Desktop MCP settings:
95
95
  - `get-task-by-id` - Get a specific task by its ID
96
96
  - `update-task-complete` - Mark tasks as complete
97
97
  - `update-task-planned-time` - Update the planned time (time estimate) for tasks
98
+ - `update-task-notes` - Update task notes content (requires either `html` or `markdown` parameter)
98
99
  - `update-task-snooze-date` - Reschedule tasks to different dates
99
100
  - `update-task-backlog` - Move tasks to the backlog
100
101
  - `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.7.0",
10
+ "sunsama-api": "0.8.0",
11
11
  "zod": "3.24.4",
12
12
  },
13
13
  "devDependencies": {
@@ -58,6 +58,8 @@
58
58
 
59
59
  "@manypkg/get-packages": ["@manypkg/get-packages@1.1.3", "", { "dependencies": { "@babel/runtime": "^7.5.5", "@changesets/types": "^4.0.1", "@manypkg/find-root": "^1.1.0", "fs-extra": "^8.1.0", "globby": "^11.0.0", "read-yaml-file": "^1.1.0" } }, "sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A=="],
60
60
 
61
+ "@mixmark-io/domino": ["@mixmark-io/domino@2.2.0", "", {}, "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="],
62
+
61
63
  "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.12.1", "", { "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-KG1CZhZfWg+u8pxeM/mByJDScJSrjjxLc8fwQqbsS8xCjBmQfMNEBTotYdNanKekepnfRI85GtgQlctLFpcYPw=="],
62
64
 
63
65
  "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
@@ -282,6 +284,8 @@
282
284
 
283
285
  "lodash.startcase": ["lodash.startcase@4.4.0", "", {}, "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg=="],
284
286
 
287
+ "marked": ["marked@14.1.4", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-vkVZ8ONmUdPnjCKc5uTRvmkRbx4EAi2OkTOXmfTDhZz3OFqMNBM1oTTWwTr4HY4uAEojhzPf+Fy8F1DWa3Sndg=="],
288
+
285
289
  "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
286
290
 
287
291
  "mcp-proxy": ["mcp-proxy@5.1.0", "", { "dependencies": { "@modelcontextprotocol/sdk": "^1.12.1", "eventsource": "^4.0.0", "yargs": "^18.0.0" }, "bin": { "mcp-proxy": "dist/bin/mcp-proxy.js" } }, "sha512-6pfAdXFOvfpqse9dMLuQo8WTSFdD0al8vhr0Nx5EWv+dR6aTAHphdQj9NUP2xej2bhaWzJ5p8BRwbvXufOjJHA=="],
@@ -426,7 +430,7 @@
426
430
 
427
431
  "strtok3": ["strtok3@10.3.1", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-3JWEZM6mfix/GCJBBUrkA8p2Id2pBkyTkVCJKto55w080QBKZ+8R171fGrbiSp+yMO/u6F8/yUh7K4V9K+YCnw=="],
428
432
 
429
- "sunsama-api": ["sunsama-api@0.7.0", "", { "dependencies": { "graphql": "^16.11.0", "graphql-tag": "^2.12.6", "tough-cookie": "^5.1.2", "tslib": "^2.8.1", "yjs": "^13.6.27", "zod": "^3.25.64" } }, "sha512-/wPvJrtE0rUftl7c+OuQdu27OYkTvd23jzcQoAUP2SilQ6QYnFeG/Fjdf6nfl/MFuz5B8mpviGJdSgqYjAkd0g=="],
433
+ "sunsama-api": ["sunsama-api@0.8.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-EiUqEc2OIDsmfCHQX54Pu+xZDfJBTMjHAcwhWqHVIWTp2emMyitUuSDvABEqupoXrzITZw+XW0lxNZUQaW4lqQ=="],
430
434
 
431
435
  "term-size": ["term-size@2.2.1", "", {}, "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg=="],
432
436
 
@@ -446,6 +450,8 @@
446
450
 
447
451
  "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
448
452
 
453
+ "turndown": ["turndown@7.2.0", "", { "dependencies": { "@mixmark-io/domino": "^2.2.0" } }, "sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A=="],
454
+
449
455
  "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
450
456
 
451
457
  "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
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, updateTaskPlannedTimeSchema, updateTaskSnoozeDateSchema } from "./schemas.js";
6
+ import { createTaskSchema, deleteTaskSchema, getArchivedTasksSchema, getStreamsSchema, getTaskByIdSchema, getTasksBacklogSchema, getTasksByDaySchema, getUserSchema, updateTaskBacklogSchema, updateTaskCompleteSchema, 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.8.0",
19
+ version: "0.10.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
27
+ - Task mutations: create tasks, mark complete, delete tasks, reschedule tasks, update planned time, update task notes
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:
@@ -568,6 +568,63 @@ server.addTool({
568
568
  }
569
569
  }
570
570
  });
571
+ server.addTool({
572
+ name: "update-task-notes",
573
+ description: "Update the notes content for a task",
574
+ parameters: updateTaskNotesSchema,
575
+ execute: async (args, { session, log }) => {
576
+ try {
577
+ // Extract parameters
578
+ const { taskId, html, markdown, limitResponsePayload } = args;
579
+ // Create simplified content object with type and value
580
+ const content = html
581
+ ? { type: "html", value: html }
582
+ : { type: "markdown", value: markdown };
583
+ log.info("Updating task notes", {
584
+ taskId: taskId,
585
+ contentType: content.type,
586
+ contentLength: content.value.length,
587
+ limitResponsePayload: limitResponsePayload
588
+ });
589
+ // Get the appropriate client based on transport type
590
+ const sunsamaClient = getSunsamaClient(session);
591
+ // Build options object
592
+ const options = {};
593
+ if (limitResponsePayload !== undefined)
594
+ options.limitResponsePayload = limitResponsePayload;
595
+ // Build content object for API call
596
+ const apiContent = content.type === "html" ? { html: content.value } : { markdown: content.value };
597
+ // Call sunsamaClient.updateTaskNotes(taskId, apiContent, options)
598
+ const result = await sunsamaClient.updateTaskNotes(taskId, apiContent, options);
599
+ log.info("Successfully updated task notes", {
600
+ taskId: taskId,
601
+ success: result.success,
602
+ contentType: content.type
603
+ });
604
+ return {
605
+ content: [
606
+ {
607
+ type: "text",
608
+ text: JSON.stringify({
609
+ success: result.success,
610
+ taskId: taskId,
611
+ notesUpdated: true,
612
+ updatedFields: result.updatedFields
613
+ })
614
+ }
615
+ ]
616
+ };
617
+ }
618
+ catch (error) {
619
+ log.error("Failed to update task notes", {
620
+ taskId: args.taskId,
621
+ contentType: args.html ? 'html' : 'markdown',
622
+ error: error instanceof Error ? error.message : 'Unknown error'
623
+ });
624
+ throw new Error(`Failed to update task notes: ${error instanceof Error ? error.message : 'Unknown error'}`);
625
+ }
626
+ }
627
+ });
571
628
  // Stream Operations
572
629
  server.addTool({
573
630
  name: "get-streams",
@@ -698,6 +755,15 @@ Uses HTTP Basic Auth headers (per-request authentication):
698
755
  - \`limitResponsePayload\` (optional): Whether to limit response size
699
756
  - Returns: JSON with update result
700
757
 
758
+ - **update-task-notes**: Update task notes content
759
+ - Parameters:
760
+ - \`taskId\` (required): The ID of the task to update notes for
761
+ - \`html\` (required XOR): HTML content for the task notes
762
+ - \`markdown\` (required XOR): Markdown content for the task notes
763
+ - \`limitResponsePayload\` (optional): Whether to limit response size (defaults to true)
764
+ - Returns: JSON with update result
765
+ - Note: Exactly one of \`html\` or \`markdown\` must be provided (mutually exclusive)
766
+
701
767
  ### Stream Operations
702
768
  - **get-streams**: Get streams for the user's group
703
769
  - Parameters: none
package/dist/schemas.d.ts CHANGED
@@ -1,4 +1,8 @@
1
1
  import { z } from "zod";
2
+ /**
3
+ * Helper Schemas
4
+ */
5
+ export declare const jsonStringSchema: z.ZodEffects<z.ZodString, any, string>;
2
6
  /**
3
7
  * Task Operation Schemas
4
8
  */
@@ -141,6 +145,34 @@ export declare const updateTaskPlannedTimeSchema: z.ZodObject<{
141
145
  timeEstimateMinutes: number;
142
146
  limitResponsePayload?: boolean | undefined;
143
147
  }>;
148
+ export declare const updateTaskNotesSchema: z.ZodIntersection<z.ZodObject<{
149
+ taskId: z.ZodString;
150
+ limitResponsePayload: z.ZodOptional<z.ZodBoolean>;
151
+ }, "strip", z.ZodTypeAny, {
152
+ taskId: string;
153
+ limitResponsePayload?: boolean | undefined;
154
+ }, {
155
+ taskId: string;
156
+ limitResponsePayload?: boolean | undefined;
157
+ }>, z.ZodUnion<[z.ZodObject<{
158
+ html: z.ZodString;
159
+ markdown: z.ZodOptional<z.ZodNever>;
160
+ }, "strip", z.ZodTypeAny, {
161
+ html: string;
162
+ markdown?: undefined;
163
+ }, {
164
+ html: string;
165
+ markdown?: undefined;
166
+ }>, z.ZodObject<{
167
+ markdown: z.ZodString;
168
+ html: z.ZodOptional<z.ZodNever>;
169
+ }, "strip", z.ZodTypeAny, {
170
+ markdown: string;
171
+ html?: undefined;
172
+ }, {
173
+ markdown: string;
174
+ html?: undefined;
175
+ }>]>>;
144
176
  /**
145
177
  * Response Type Schemas (for validation and documentation)
146
178
  */
@@ -575,6 +607,7 @@ export type UpdateTaskCompleteInput = z.infer<typeof updateTaskCompleteSchema>;
575
607
  export type DeleteTaskInput = z.infer<typeof deleteTaskSchema>;
576
608
  export type UpdateTaskSnoozeDateInput = z.infer<typeof updateTaskSnoozeDateSchema>;
577
609
  export type UpdateTaskPlannedTimeInput = z.infer<typeof updateTaskPlannedTimeSchema>;
610
+ export type UpdateTaskNotesInput = z.infer<typeof updateTaskNotesSchema>;
578
611
  export type User = z.infer<typeof userSchema>;
579
612
  export type Task = z.infer<typeof taskSchema>;
580
613
  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
@@ -1,4 +1,16 @@
1
1
  import { z } from "zod";
2
+ /**
3
+ * Helper Schemas
4
+ */
5
+ export const jsonStringSchema = z.string().transform((str, ctx) => {
6
+ try {
7
+ return JSON.parse(str);
8
+ }
9
+ catch (error) {
10
+ ctx.addIssue({ code: 'custom', message: 'Invalid JSON' });
11
+ return z.NEVER;
12
+ }
13
+ });
2
14
  /**
3
15
  * Task Operation Schemas
4
16
  */
@@ -76,6 +88,22 @@ export const updateTaskPlannedTimeSchema = z.object({
76
88
  timeEstimateMinutes: z.number().int().min(0).describe("Time estimate in minutes (use 0 to clear the time estimate)"),
77
89
  limitResponsePayload: z.boolean().optional().describe("Whether to limit the response payload size"),
78
90
  });
91
+ // Update task notes base parameters (without content)
92
+ const updateTaskNotesBaseSchema = z.object({
93
+ taskId: z.string().min(1, "Task ID is required").describe("The ID of the task to update notes for"),
94
+ limitResponsePayload: z.boolean().optional().describe("Whether to limit the response payload size (defaults to true)"),
95
+ });
96
+ // Update task notes parameters with XOR content
97
+ export const updateTaskNotesSchema = updateTaskNotesBaseSchema.and(z.union([
98
+ z.object({
99
+ html: z.string().describe("HTML content for the task notes"),
100
+ markdown: z.never().optional()
101
+ }),
102
+ z.object({
103
+ markdown: z.string().describe("Markdown content for the task notes"),
104
+ html: z.never().optional()
105
+ })
106
+ ]));
79
107
  /**
80
108
  * Response Type Schemas (for validation and documentation)
81
109
  */
@@ -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, 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, 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", () => {
@@ -252,6 +252,74 @@ describe("Tool Parameter Schemas", () => {
252
252
  })).toThrow();
253
253
  });
254
254
  });
255
+ describe("updateTaskNotesSchema", () => {
256
+ test("should accept valid HTML content", () => {
257
+ const htmlInput = {
258
+ taskId: "task-123",
259
+ html: "<p>This is HTML content</p>",
260
+ limitResponsePayload: true,
261
+ };
262
+ expect(() => updateTaskNotesSchema.parse(htmlInput)).not.toThrow();
263
+ });
264
+ test("should accept valid Markdown content", () => {
265
+ const markdownInput = {
266
+ taskId: "task-123",
267
+ markdown: "# This is Markdown content",
268
+ limitResponsePayload: true,
269
+ };
270
+ expect(() => updateTaskNotesSchema.parse(markdownInput)).not.toThrow();
271
+ });
272
+ test("should accept minimal HTML input", () => {
273
+ const minimalHtmlInput = {
274
+ taskId: "task-123",
275
+ html: "<p>Simple HTML</p>",
276
+ };
277
+ expect(() => updateTaskNotesSchema.parse(minimalHtmlInput)).not.toThrow();
278
+ });
279
+ test("should accept minimal Markdown input", () => {
280
+ const minimalMarkdownInput = {
281
+ taskId: "task-123",
282
+ markdown: "Simple markdown",
283
+ };
284
+ expect(() => updateTaskNotesSchema.parse(minimalMarkdownInput)).not.toThrow();
285
+ });
286
+ test("should reject both HTML and Markdown provided", () => {
287
+ const invalidInput = {
288
+ taskId: "task-123",
289
+ html: "<p>HTML content</p>",
290
+ markdown: "Markdown content",
291
+ };
292
+ expect(() => updateTaskNotesSchema.parse(invalidInput)).toThrow();
293
+ });
294
+ test("should reject neither HTML nor Markdown provided", () => {
295
+ const invalidInput = {
296
+ taskId: "task-123",
297
+ limitResponsePayload: true,
298
+ };
299
+ expect(() => updateTaskNotesSchema.parse(invalidInput)).toThrow();
300
+ });
301
+ test("should reject empty task ID", () => {
302
+ expect(() => updateTaskNotesSchema.parse({
303
+ taskId: "",
304
+ html: "<p>Content</p>",
305
+ })).toThrow();
306
+ expect(() => updateTaskNotesSchema.parse({
307
+ markdown: "Content",
308
+ })).toThrow();
309
+ });
310
+ test("should accept empty HTML content", () => {
311
+ expect(() => updateTaskNotesSchema.parse({
312
+ taskId: "task-123",
313
+ html: "",
314
+ })).not.toThrow();
315
+ });
316
+ test("should accept empty Markdown content", () => {
317
+ expect(() => updateTaskNotesSchema.parse({
318
+ taskId: "task-123",
319
+ markdown: "",
320
+ })).not.toThrow();
321
+ });
322
+ });
255
323
  });
256
324
  describe("Response Schemas", () => {
257
325
  describe("userProfileSchema", () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-sunsama",
3
- "version": "0.8.0",
3
+ "version": "0.10.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.7.0",
29
+ "sunsama-api": "0.8.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
+ updateTaskNotesSchema,
19
20
  updateTaskPlannedTimeSchema,
20
21
  updateTaskSnoozeDateSchema
21
22
  } from "./schemas.js";
@@ -34,7 +35,7 @@ if (transportConfig.transportType === "stdio") {
34
35
 
35
36
  const server = new FastMCP({
36
37
  name: "Sunsama API Server",
37
- version: "0.8.0",
38
+ version: "0.10.0",
38
39
  instructions: `
39
40
  This MCP server provides access to the Sunsama API for task and project management.
40
41
 
@@ -42,7 +43,7 @@ Available tools:
42
43
  - Authentication: login, logout, check authentication status
43
44
  - User operations: get current user information
44
45
  - Task operations: get tasks by day, get backlog tasks, get archived tasks, get task by ID
45
- - Task mutations: create tasks, mark complete, delete tasks, reschedule tasks, update planned time
46
+ - Task mutations: create tasks, mark complete, delete tasks, reschedule tasks, update planned time, update task notes
46
47
  - Stream operations: get streams/channels for the user's group
47
48
 
48
49
  Authentication is required for all operations. You can either:
@@ -689,6 +690,78 @@ server.addTool({
689
690
  }
690
691
  });
691
692
 
693
+ server.addTool({
694
+ name: "update-task-notes",
695
+ description: "Update the notes content for a task",
696
+ parameters: updateTaskNotesSchema,
697
+ execute: async (args, {session, log}) => {
698
+ try {
699
+ // Extract parameters
700
+ const {taskId, html, markdown, limitResponsePayload} = args;
701
+
702
+ // Create simplified content object with type and value
703
+ const content = html
704
+ ? {type: "html" as const, value: html}
705
+ : {type: "markdown" as const, value: markdown!};
706
+
707
+ log.info("Updating task notes", {
708
+ taskId: taskId,
709
+ contentType: content.type,
710
+ contentLength: content.value.length,
711
+ limitResponsePayload: limitResponsePayload
712
+ });
713
+
714
+ // Get the appropriate client based on transport type
715
+ const sunsamaClient = getSunsamaClient(session as SessionData | null);
716
+
717
+ // Build options object
718
+ const options: {
719
+ limitResponsePayload?: boolean;
720
+ } = {};
721
+ if (limitResponsePayload !== undefined) options.limitResponsePayload = limitResponsePayload;
722
+
723
+ // Build content object for API call
724
+ const apiContent = content.type === "html" ? {html: content.value} : {markdown: content.value};
725
+
726
+ // Call sunsamaClient.updateTaskNotes(taskId, apiContent, options)
727
+ const result = await sunsamaClient.updateTaskNotes(
728
+ taskId,
729
+ apiContent,
730
+ options
731
+ );
732
+
733
+ log.info("Successfully updated task notes", {
734
+ taskId: taskId,
735
+ success: result.success,
736
+ contentType: content.type
737
+ });
738
+
739
+ return {
740
+ content: [
741
+ {
742
+ type: "text",
743
+ text: JSON.stringify({
744
+ success: result.success,
745
+ taskId: taskId,
746
+ notesUpdated: true,
747
+ updatedFields: result.updatedFields
748
+ })
749
+ }
750
+ ]
751
+ };
752
+
753
+ } catch (error) {
754
+ log.error("Failed to update task notes", {
755
+ taskId: args.taskId,
756
+ contentType: args.html ? 'html' : 'markdown',
757
+ error: error instanceof Error ? error.message : 'Unknown error'
758
+ });
759
+
760
+ throw new Error(`Failed to update task notes: ${error instanceof Error ? error.message : 'Unknown error'}`);
761
+ }
762
+ }
763
+ });
764
+
692
765
  // Stream Operations
693
766
  server.addTool({
694
767
  name: "get-streams",
@@ -825,6 +898,15 @@ Uses HTTP Basic Auth headers (per-request authentication):
825
898
  - \`limitResponsePayload\` (optional): Whether to limit response size
826
899
  - Returns: JSON with update result
827
900
 
901
+ - **update-task-notes**: Update task notes content
902
+ - Parameters:
903
+ - \`taskId\` (required): The ID of the task to update notes for
904
+ - \`html\` (required XOR): HTML content for the task notes
905
+ - \`markdown\` (required XOR): Markdown content for the task notes
906
+ - \`limitResponsePayload\` (optional): Whether to limit response size (defaults to true)
907
+ - Returns: JSON with update result
908
+ - Note: Exactly one of \`html\` or \`markdown\` must be provided (mutually exclusive)
909
+
828
910
  ### Stream Operations
829
911
  - **get-streams**: Get streams for the user's group
830
912
  - Parameters: none
@@ -12,6 +12,7 @@ import {
12
12
  updateTaskSnoozeDateSchema,
13
13
  updateTaskBacklogSchema,
14
14
  updateTaskPlannedTimeSchema,
15
+ updateTaskNotesSchema,
15
16
  userProfileSchema,
16
17
  groupSchema,
17
18
  userSchema,
@@ -351,6 +352,91 @@ describe("Tool Parameter Schemas", () => {
351
352
  ).toThrow();
352
353
  });
353
354
  });
355
+
356
+ describe("updateTaskNotesSchema", () => {
357
+ test("should accept valid HTML content", () => {
358
+ const htmlInput = {
359
+ taskId: "task-123",
360
+ html: "<p>This is HTML content</p>",
361
+ limitResponsePayload: true,
362
+ };
363
+ expect(() => updateTaskNotesSchema.parse(htmlInput)).not.toThrow();
364
+ });
365
+
366
+ test("should accept valid Markdown content", () => {
367
+ const markdownInput = {
368
+ taskId: "task-123",
369
+ markdown: "# This is Markdown content",
370
+ limitResponsePayload: true,
371
+ };
372
+ expect(() => updateTaskNotesSchema.parse(markdownInput)).not.toThrow();
373
+ });
374
+
375
+ test("should accept minimal HTML input", () => {
376
+ const minimalHtmlInput = {
377
+ taskId: "task-123",
378
+ html: "<p>Simple HTML</p>",
379
+ };
380
+ expect(() => updateTaskNotesSchema.parse(minimalHtmlInput)).not.toThrow();
381
+ });
382
+
383
+ test("should accept minimal Markdown input", () => {
384
+ const minimalMarkdownInput = {
385
+ taskId: "task-123",
386
+ markdown: "Simple markdown",
387
+ };
388
+ expect(() => updateTaskNotesSchema.parse(minimalMarkdownInput)).not.toThrow();
389
+ });
390
+
391
+ test("should reject both HTML and Markdown provided", () => {
392
+ const invalidInput = {
393
+ taskId: "task-123",
394
+ html: "<p>HTML content</p>",
395
+ markdown: "Markdown content",
396
+ };
397
+ expect(() => updateTaskNotesSchema.parse(invalidInput)).toThrow();
398
+ });
399
+
400
+ test("should reject neither HTML nor Markdown provided", () => {
401
+ const invalidInput = {
402
+ taskId: "task-123",
403
+ limitResponsePayload: true,
404
+ };
405
+ expect(() => updateTaskNotesSchema.parse(invalidInput)).toThrow();
406
+ });
407
+
408
+ test("should reject empty task ID", () => {
409
+ expect(() =>
410
+ updateTaskNotesSchema.parse({
411
+ taskId: "",
412
+ html: "<p>Content</p>",
413
+ })
414
+ ).toThrow();
415
+ expect(() =>
416
+ updateTaskNotesSchema.parse({
417
+ markdown: "Content",
418
+ })
419
+ ).toThrow();
420
+ });
421
+
422
+ test("should accept empty HTML content", () => {
423
+ expect(() =>
424
+ updateTaskNotesSchema.parse({
425
+ taskId: "task-123",
426
+ html: "",
427
+ })
428
+ ).not.toThrow();
429
+ });
430
+
431
+ test("should accept empty Markdown content", () => {
432
+ expect(() =>
433
+ updateTaskNotesSchema.parse({
434
+ taskId: "task-123",
435
+ markdown: "",
436
+ })
437
+ ).not.toThrow();
438
+ });
439
+ });
354
440
  });
355
441
 
356
442
  describe("Response Schemas", () => {
package/src/schemas.ts CHANGED
@@ -1,5 +1,18 @@
1
1
  import { z } from "zod";
2
2
 
3
+ /**
4
+ * Helper Schemas
5
+ */
6
+
7
+ export const jsonStringSchema = z.string().transform((str, ctx) => {
8
+ try {
9
+ return JSON.parse(str);
10
+ } catch (error) {
11
+ ctx.addIssue({ code: 'custom', message: 'Invalid JSON' });
12
+ return z.NEVER;
13
+ }
14
+ });
15
+
3
16
  /**
4
17
  * Task Operation Schemas
5
18
  */
@@ -94,6 +107,26 @@ export const updateTaskPlannedTimeSchema = z.object({
94
107
  limitResponsePayload: z.boolean().optional().describe("Whether to limit the response payload size"),
95
108
  });
96
109
 
110
+ // Update task notes base parameters (without content)
111
+ const updateTaskNotesBaseSchema = z.object({
112
+ taskId: z.string().min(1, "Task ID is required").describe("The ID of the task to update notes for"),
113
+ limitResponsePayload: z.boolean().optional().describe("Whether to limit the response payload size (defaults to true)"),
114
+ });
115
+
116
+ // Update task notes parameters with XOR content
117
+ export const updateTaskNotesSchema = updateTaskNotesBaseSchema.and(
118
+ z.union([
119
+ z.object({
120
+ html: z.string().describe("HTML content for the task notes"),
121
+ markdown: z.never().optional()
122
+ }),
123
+ z.object({
124
+ markdown: z.string().describe("Markdown content for the task notes"),
125
+ html: z.never().optional()
126
+ })
127
+ ])
128
+ );
129
+
97
130
  /**
98
131
  * Response Type Schemas (for validation and documentation)
99
132
  */
@@ -196,6 +229,7 @@ export type UpdateTaskCompleteInput = z.infer<typeof updateTaskCompleteSchema>;
196
229
  export type DeleteTaskInput = z.infer<typeof deleteTaskSchema>;
197
230
  export type UpdateTaskSnoozeDateInput = z.infer<typeof updateTaskSnoozeDateSchema>;
198
231
  export type UpdateTaskPlannedTimeInput = z.infer<typeof updateTaskPlannedTimeSchema>;
232
+ export type UpdateTaskNotesInput = z.infer<typeof updateTaskNotesSchema>;
199
233
 
200
234
  export type User = z.infer<typeof userSchema>;
201
235
  export type Task = z.infer<typeof taskSchema>;