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 +25 -0
- package/CLAUDE.md +3 -1
- package/README.md +1 -0
- package/bun.lock +8 -2
- package/dist/main.js +69 -3
- package/dist/schemas.d.ts +33 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +28 -0
- package/dist/schemas.test.js +69 -1
- package/package.json +2 -2
- package/src/main.ts +84 -2
- package/src/schemas.test.ts +86 -0
- package/src/schemas.ts +34 -0
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.
|
|
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.
|
|
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.
|
|
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>;
|
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;
|
|
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
|
*/
|
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-sunsama",
|
|
3
|
-
"version": "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.
|
|
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.
|
|
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
|
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,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>;
|