mcp-sunsama 0.9.0 → 0.10.1
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 +24 -0
- package/CLAUDE.md +3 -0
- package/README.md +1 -1
- package/dist/main.js +17 -11
- package/dist/schemas.d.ts +21 -24
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +23 -9
- package/dist/schemas.test.js +69 -1
- package/package.json +1 -1
- package/src/main.ts +19 -11
- package/src/schemas.test.ts +86 -0
- package/src/schemas.ts +28 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# mcp-sunsama
|
|
2
2
|
|
|
3
|
+
## 0.10.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 002cd07: Fix MCP Inspector compatibility for update-task-notes schema
|
|
8
|
+
|
|
9
|
+
- Replace ZodIntersection (.and()) with single z.object() using .refine() for XOR validation
|
|
10
|
+
- Ensure schema generates proper JSON Schema with "type": "object" for MCP Inspector
|
|
11
|
+
- Add clear parameter descriptions indicating mutual exclusivity
|
|
12
|
+
- Maintain same validation behavior while fixing compatibility issues
|
|
13
|
+
- Update documentation to reflect schema architecture improvements
|
|
14
|
+
|
|
15
|
+
## 0.10.0
|
|
16
|
+
|
|
17
|
+
### Minor Changes
|
|
18
|
+
|
|
19
|
+
- 52e464c: Refactor update-task-notes schema to use XOR pattern for content parameters
|
|
20
|
+
|
|
21
|
+
- Replace nested content object with separate html and markdown parameters
|
|
22
|
+
- Implement XOR validation ensuring exactly one content type is provided
|
|
23
|
+
- Add comprehensive test coverage for the new schema pattern
|
|
24
|
+
- Update tool implementation to handle simplified parameter structure
|
|
25
|
+
- Update documentation across README.md, CLAUDE.md, and API docs
|
|
26
|
+
|
|
3
27
|
## 0.9.0
|
|
4
28
|
|
|
5
29
|
### Minor Changes
|
package/CLAUDE.md
CHANGED
|
@@ -66,6 +66,8 @@ 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 using `.refine()` for MCP Inspector compatibility
|
|
70
|
+
- Example: `update-task-notes` requires either `html` OR `markdown`, but not both
|
|
69
71
|
|
|
70
72
|
## Key Patterns
|
|
71
73
|
|
|
@@ -106,6 +108,7 @@ server.addTool({
|
|
|
106
108
|
|
|
107
109
|
- `src/main.ts`: FastMCP server setup and tool definitions
|
|
108
110
|
- `src/schemas.ts`: Zod validation schemas for all tools
|
|
111
|
+
- `src/schemas.test.ts`: Comprehensive test suite for all Zod schemas including parameter validation, response schemas, and XOR patterns
|
|
109
112
|
- `src/auth/`: Authentication strategies per transport type
|
|
110
113
|
- `src/config/`: Environment configuration and validation
|
|
111
114
|
- `src/utils/`: Reusable utilities (client resolution, filtering, formatting)
|
package/README.md
CHANGED
|
@@ -95,7 +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
|
|
98
|
+
- `update-task-notes` - Update task notes content (requires either `html` or `markdown` parameter, mutually exclusive)
|
|
99
99
|
- `update-task-snooze-date` - Reschedule tasks to different dates
|
|
100
100
|
- `update-task-backlog` - Move tasks to the backlog
|
|
101
101
|
- `delete-task` - Delete tasks permanently
|
package/dist/main.js
CHANGED
|
@@ -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.
|
|
19
|
+
version: "0.10.1",
|
|
20
20
|
instructions: `
|
|
21
21
|
This MCP server provides access to the Sunsama API for task and project management.
|
|
22
22
|
|
|
@@ -575,11 +575,15 @@ server.addTool({
|
|
|
575
575
|
execute: async (args, { session, log }) => {
|
|
576
576
|
try {
|
|
577
577
|
// Extract parameters
|
|
578
|
-
const { taskId,
|
|
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 };
|
|
579
583
|
log.info("Updating task notes", {
|
|
580
584
|
taskId: taskId,
|
|
581
|
-
contentType:
|
|
582
|
-
contentLength:
|
|
585
|
+
contentType: content.type,
|
|
586
|
+
contentLength: content.value.length,
|
|
583
587
|
limitResponsePayload: limitResponsePayload
|
|
584
588
|
});
|
|
585
589
|
// Get the appropriate client based on transport type
|
|
@@ -588,12 +592,14 @@ server.addTool({
|
|
|
588
592
|
const options = {};
|
|
589
593
|
if (limitResponsePayload !== undefined)
|
|
590
594
|
options.limitResponsePayload = limitResponsePayload;
|
|
591
|
-
//
|
|
592
|
-
const
|
|
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);
|
|
593
599
|
log.info("Successfully updated task notes", {
|
|
594
600
|
taskId: taskId,
|
|
595
601
|
success: result.success,
|
|
596
|
-
contentType:
|
|
602
|
+
contentType: content.type
|
|
597
603
|
});
|
|
598
604
|
return {
|
|
599
605
|
content: [
|
|
@@ -612,7 +618,7 @@ server.addTool({
|
|
|
612
618
|
catch (error) {
|
|
613
619
|
log.error("Failed to update task notes", {
|
|
614
620
|
taskId: args.taskId,
|
|
615
|
-
contentType:
|
|
621
|
+
contentType: args.html ? 'html' : 'markdown',
|
|
616
622
|
error: error instanceof Error ? error.message : 'Unknown error'
|
|
617
623
|
});
|
|
618
624
|
throw new Error(`Failed to update task notes: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
@@ -752,11 +758,11 @@ Uses HTTP Basic Auth headers (per-request authentication):
|
|
|
752
758
|
- **update-task-notes**: Update task notes content
|
|
753
759
|
- Parameters:
|
|
754
760
|
- \`taskId\` (required): The ID of the task to update notes for
|
|
755
|
-
- \`
|
|
756
|
-
|
|
761
|
+
- \`html\` (required XOR): HTML content for the task notes
|
|
762
|
+
- \`markdown\` (required XOR): Markdown content for the task notes
|
|
757
763
|
- \`limitResponsePayload\` (optional): Whether to limit response size (defaults to true)
|
|
758
764
|
- Returns: JSON with update result
|
|
759
|
-
- Note:
|
|
765
|
+
- Note: Exactly one of \`html\` or \`markdown\` must be provided (mutually exclusive)
|
|
760
766
|
|
|
761
767
|
### Stream Operations
|
|
762
768
|
- **get-streams**: Get streams for the user's group
|
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,38 +145,31 @@ export declare const updateTaskPlannedTimeSchema: z.ZodObject<{
|
|
|
141
145
|
timeEstimateMinutes: number;
|
|
142
146
|
limitResponsePayload?: boolean | undefined;
|
|
143
147
|
}>;
|
|
144
|
-
export declare const updateTaskNotesSchema: z.ZodObject<{
|
|
148
|
+
export declare const updateTaskNotesSchema: z.ZodEffects<z.ZodObject<{
|
|
145
149
|
taskId: z.ZodString;
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}, "strip", z.ZodTypeAny, {
|
|
149
|
-
html: string;
|
|
150
|
-
}, {
|
|
151
|
-
html: string;
|
|
152
|
-
}>, z.ZodObject<{
|
|
153
|
-
markdown: z.ZodString;
|
|
154
|
-
}, "strip", z.ZodTypeAny, {
|
|
155
|
-
markdown: string;
|
|
156
|
-
}, {
|
|
157
|
-
markdown: string;
|
|
158
|
-
}>]>;
|
|
150
|
+
html: z.ZodOptional<z.ZodString>;
|
|
151
|
+
markdown: z.ZodOptional<z.ZodString>;
|
|
159
152
|
limitResponsePayload: z.ZodOptional<z.ZodBoolean>;
|
|
160
153
|
}, "strip", z.ZodTypeAny, {
|
|
161
154
|
taskId: string;
|
|
162
|
-
content: {
|
|
163
|
-
html: string;
|
|
164
|
-
} | {
|
|
165
|
-
markdown: string;
|
|
166
|
-
};
|
|
167
155
|
limitResponsePayload?: boolean | undefined;
|
|
156
|
+
html?: string | undefined;
|
|
157
|
+
markdown?: string | undefined;
|
|
158
|
+
}, {
|
|
159
|
+
taskId: string;
|
|
160
|
+
limitResponsePayload?: boolean | undefined;
|
|
161
|
+
html?: string | undefined;
|
|
162
|
+
markdown?: string | undefined;
|
|
163
|
+
}>, {
|
|
164
|
+
taskId: string;
|
|
165
|
+
limitResponsePayload?: boolean | undefined;
|
|
166
|
+
html?: string | undefined;
|
|
167
|
+
markdown?: string | undefined;
|
|
168
168
|
}, {
|
|
169
169
|
taskId: string;
|
|
170
|
-
content: {
|
|
171
|
-
html: string;
|
|
172
|
-
} | {
|
|
173
|
-
markdown: string;
|
|
174
|
-
};
|
|
175
170
|
limitResponsePayload?: boolean | undefined;
|
|
171
|
+
html?: string | undefined;
|
|
172
|
+
markdown?: string | undefined;
|
|
176
173
|
}>;
|
|
177
174
|
/**
|
|
178
175
|
* Response Type Schemas (for validation and documentation)
|
package/dist/schemas.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;GAEG;AAGH,eAAO,MAAM,sBAAsB,+CAA6C,CAAC;AAGjF,eAAO,MAAM,mBAAmB;;;;;;;;;;;;EAI9B,CAAC;AAGH,eAAO,MAAM,qBAAqB,gDAAe,CAAC;AAGlD,eAAO,MAAM,sBAAsB;;;;;;;;;EAGjC,CAAC;AAGH,eAAO,MAAM,iBAAiB;;;;;;EAE5B,CAAC;AAEH;;GAEG;AAGH,eAAO,MAAM,aAAa,gDAAe,CAAC;AAE1C;;GAEG;AAGH,eAAO,MAAM,gBAAgB,gDAAe,CAAC;AAE7C;;GAEG;AAGH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;EAS3B,CAAC;AAGH,eAAO,MAAM,wBAAwB;;;;;;;;;;;;EAInC,CAAC;AAGH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;EAI3B,CAAC;AAGH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;EAKrC,CAAC;AAGH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;EAIlC,CAAC;AAGH,eAAO,MAAM,2BAA2B;;;;;;;;;;;;EAItC,CAAC;AAGH,eAAO,MAAM,qBAAqB
|
|
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,18 +88,20 @@ 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
|
});
|
|
79
|
-
// Update task notes parameters
|
|
91
|
+
// Update task notes parameters with XOR content validation
|
|
80
92
|
export const updateTaskNotesSchema = z.object({
|
|
81
93
|
taskId: z.string().min(1, "Task ID is required").describe("The ID of the task to update notes for"),
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
html: z.string().describe("HTML content for the task notes")
|
|
85
|
-
}),
|
|
86
|
-
z.object({
|
|
87
|
-
markdown: z.string().describe("Markdown content for the task notes")
|
|
88
|
-
})
|
|
89
|
-
]).describe("Task notes content in either HTML or Markdown format"),
|
|
94
|
+
html: z.string().optional().describe("HTML content for the task notes (mutually exclusive with markdown)"),
|
|
95
|
+
markdown: z.string().optional().describe("Markdown content for the task notes (mutually exclusive with html)"),
|
|
90
96
|
limitResponsePayload: z.boolean().optional().describe("Whether to limit the response payload size (defaults to true)"),
|
|
97
|
+
}).refine((data) => {
|
|
98
|
+
// Exactly one of html or markdown must be provided
|
|
99
|
+
const hasHtml = data.html !== undefined;
|
|
100
|
+
const hasMarkdown = data.markdown !== undefined;
|
|
101
|
+
return hasHtml !== hasMarkdown; // XOR: exactly one must be true
|
|
102
|
+
}, {
|
|
103
|
+
message: "Exactly one of 'html' or 'markdown' must be provided",
|
|
104
|
+
path: [], // This will show the error at the root level
|
|
91
105
|
});
|
|
92
106
|
/**
|
|
93
107
|
* Response Type Schemas (for validation and documentation)
|
package/dist/schemas.test.js
CHANGED
|
@@ -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
package/src/main.ts
CHANGED
|
@@ -35,7 +35,7 @@ if (transportConfig.transportType === "stdio") {
|
|
|
35
35
|
|
|
36
36
|
const server = new FastMCP({
|
|
37
37
|
name: "Sunsama API Server",
|
|
38
|
-
version: "0.
|
|
38
|
+
version: "0.10.1",
|
|
39
39
|
instructions: `
|
|
40
40
|
This MCP server provides access to the Sunsama API for task and project management.
|
|
41
41
|
|
|
@@ -697,12 +697,17 @@ server.addTool({
|
|
|
697
697
|
execute: async (args, {session, log}) => {
|
|
698
698
|
try {
|
|
699
699
|
// Extract parameters
|
|
700
|
-
const {taskId,
|
|
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!};
|
|
701
706
|
|
|
702
707
|
log.info("Updating task notes", {
|
|
703
708
|
taskId: taskId,
|
|
704
|
-
contentType:
|
|
705
|
-
contentLength:
|
|
709
|
+
contentType: content.type,
|
|
710
|
+
contentLength: content.value.length,
|
|
706
711
|
limitResponsePayload: limitResponsePayload
|
|
707
712
|
});
|
|
708
713
|
|
|
@@ -715,17 +720,20 @@ server.addTool({
|
|
|
715
720
|
} = {};
|
|
716
721
|
if (limitResponsePayload !== undefined) options.limitResponsePayload = limitResponsePayload;
|
|
717
722
|
|
|
718
|
-
//
|
|
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)
|
|
719
727
|
const result = await sunsamaClient.updateTaskNotes(
|
|
720
728
|
taskId,
|
|
721
|
-
|
|
729
|
+
apiContent,
|
|
722
730
|
options
|
|
723
731
|
);
|
|
724
732
|
|
|
725
733
|
log.info("Successfully updated task notes", {
|
|
726
734
|
taskId: taskId,
|
|
727
735
|
success: result.success,
|
|
728
|
-
contentType:
|
|
736
|
+
contentType: content.type
|
|
729
737
|
});
|
|
730
738
|
|
|
731
739
|
return {
|
|
@@ -745,7 +753,7 @@ server.addTool({
|
|
|
745
753
|
} catch (error) {
|
|
746
754
|
log.error("Failed to update task notes", {
|
|
747
755
|
taskId: args.taskId,
|
|
748
|
-
contentType:
|
|
756
|
+
contentType: args.html ? 'html' : 'markdown',
|
|
749
757
|
error: error instanceof Error ? error.message : 'Unknown error'
|
|
750
758
|
});
|
|
751
759
|
|
|
@@ -893,11 +901,11 @@ Uses HTTP Basic Auth headers (per-request authentication):
|
|
|
893
901
|
- **update-task-notes**: Update task notes content
|
|
894
902
|
- Parameters:
|
|
895
903
|
- \`taskId\` (required): The ID of the task to update notes for
|
|
896
|
-
- \`
|
|
897
|
-
|
|
904
|
+
- \`html\` (required XOR): HTML content for the task notes
|
|
905
|
+
- \`markdown\` (required XOR): Markdown content for the task notes
|
|
898
906
|
- \`limitResponsePayload\` (optional): Whether to limit response size (defaults to true)
|
|
899
907
|
- Returns: JSON with update result
|
|
900
|
-
- Note:
|
|
908
|
+
- Note: Exactly one of \`html\` or \`markdown\` must be provided (mutually exclusive)
|
|
901
909
|
|
|
902
910
|
### Stream Operations
|
|
903
911
|
- **get-streams**: Get streams for the user's group
|
package/src/schemas.test.ts
CHANGED
|
@@ -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,19 +107,24 @@ export const updateTaskPlannedTimeSchema = z.object({
|
|
|
94
107
|
limitResponsePayload: z.boolean().optional().describe("Whether to limit the response payload size"),
|
|
95
108
|
});
|
|
96
109
|
|
|
97
|
-
// Update task notes parameters
|
|
110
|
+
// Update task notes parameters with XOR content validation
|
|
98
111
|
export const updateTaskNotesSchema = z.object({
|
|
99
112
|
taskId: z.string().min(1, "Task ID is required").describe("The ID of the task to update notes for"),
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
html: z.string().describe("HTML content for the task notes")
|
|
103
|
-
}),
|
|
104
|
-
z.object({
|
|
105
|
-
markdown: z.string().describe("Markdown content for the task notes")
|
|
106
|
-
})
|
|
107
|
-
]).describe("Task notes content in either HTML or Markdown format"),
|
|
113
|
+
html: z.string().optional().describe("HTML content for the task notes (mutually exclusive with markdown)"),
|
|
114
|
+
markdown: z.string().optional().describe("Markdown content for the task notes (mutually exclusive with html)"),
|
|
108
115
|
limitResponsePayload: z.boolean().optional().describe("Whether to limit the response payload size (defaults to true)"),
|
|
109
|
-
})
|
|
116
|
+
}).refine(
|
|
117
|
+
(data) => {
|
|
118
|
+
// Exactly one of html or markdown must be provided
|
|
119
|
+
const hasHtml = data.html !== undefined;
|
|
120
|
+
const hasMarkdown = data.markdown !== undefined;
|
|
121
|
+
return hasHtml !== hasMarkdown; // XOR: exactly one must be true
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
message: "Exactly one of 'html' or 'markdown' must be provided",
|
|
125
|
+
path: [], // This will show the error at the root level
|
|
126
|
+
}
|
|
127
|
+
);
|
|
110
128
|
|
|
111
129
|
/**
|
|
112
130
|
* Response Type Schemas (for validation and documentation)
|