mcp-sunsama 0.16.1 → 0.17.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,22 @@
1
1
  # mcp-sunsama
2
2
 
3
+ ## 0.17.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 26000bd: Add subtask management support
8
+
9
+ - Update sunsama-api dependency to 0.13.1
10
+ - Add 5 new subtask management tools:
11
+ - `add-subtask` - Create a subtask with a title in one call (recommended)
12
+ - `create-subtasks` - Create multiple subtasks for a task (bulk operations)
13
+ - `update-subtask-title` - Update the title of a subtask
14
+ - `complete-subtask` - Mark a subtask as complete
15
+ - `uncomplete-subtask` - Mark a subtask as incomplete
16
+ - Update README and CLAUDE.md documentation
17
+
18
+ - e3336cf: Add session token authentication support for users with SSO/OAuth login. Users can now authenticate using `SUNSAMA_SESSION_TOKEN` environment variable (stdio) or Bearer token authentication (HTTP) as an alternative to email/password credentials.
19
+
3
20
  ## 0.16.1
4
21
 
5
22
  ### Patch Changes
package/CLAUDE.md CHANGED
@@ -182,7 +182,7 @@ src/
182
182
  ├── tools/
183
183
  │ ├── shared.ts # Common utilities and tool wrapper patterns
184
184
  │ ├── user-tools.ts # User operations (get-user)
185
- │ ├── task-tools.ts # Task operations (15 tools)
185
+ │ ├── task-tools.ts # Task operations (20 tools)
186
186
  │ ├── stream-tools.ts # Stream operations (get-streams)
187
187
  │ └── index.ts # Export all tools
188
188
  ├── resources/
@@ -226,7 +226,7 @@ __tests__/
226
226
 
227
227
  **Resource-Based Organization**:
228
228
  - **User Tools**: Single tool for user operations
229
- - **Task Tools**: 15 tools organized by function (query, lifecycle, update)
229
+ - **Task Tools**: 20 tools organized by function (query, lifecycle, update, subtasks)
230
230
  - **Stream Tools**: Single tool for stream operations
231
231
 
232
232
  **Type Safety Improvements**:
@@ -278,6 +278,7 @@ Optional:
278
278
  Full CRUD support:
279
279
  - **Read**: `get-tasks-by-day`, `get-tasks-backlog`, `get-archived-tasks`, `get-task-by-id`, `get-streams`
280
280
  - **Write**: `create-task` (with GitHub/Gmail integration support), `update-task-complete`, `update-task-planned-time`, `update-task-notes`, `update-task-snooze-date`, `update-task-backlog`, `update-task-stream`, `update-task-text`, `update-task-due-date`, `delete-task`
281
+ - **Subtasks**: `add-subtask` (recommended), `create-subtasks` (bulk), `update-subtask-title`, `complete-subtask`, `uncomplete-subtask`
281
282
 
282
283
  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.
283
284
 
package/README.md CHANGED
@@ -12,6 +12,7 @@ A Model Context Protocol (MCP) server that provides comprehensive task managemen
12
12
  - **Create Tasks** - Create new tasks with notes, time estimates, due dates, stream assignments, and GitHub/Gmail integrations
13
13
  - **Read Tasks** - Get tasks by day with completion filtering, access backlog tasks, retrieve archived task history
14
14
  - **Update Tasks** - Mark tasks as complete with custom timestamps, reschedule tasks or move to backlog
15
+ - **Subtasks** - Add, update, complete, and manage subtasks within tasks
15
16
  - **Delete Tasks** - Permanently remove tasks from your workspace
16
17
 
17
18
  ### User & Stream Operations
@@ -111,6 +112,23 @@ Add this configuration to your Claude Desktop MCP settings:
111
112
  }
112
113
  ```
113
114
 
115
+ ### Claude Code Configuration
116
+
117
+ Add the Sunsama MCP server using the Claude Code CLI:
118
+
119
+ ```bash
120
+ claude mcp add sunsama --scope user \
121
+ -e SUNSAMA_EMAIL=your-email@example.com \
122
+ -e SUNSAMA_PASSWORD=your-password \
123
+ -- npx mcp-sunsama
124
+ ```
125
+
126
+ **Scope Options:**
127
+ - `--scope user` - Available across all projects (recommended)
128
+ - `--scope project` - Only available in the current project
129
+
130
+ After adding the server, restart Claude Code to connect to the Sunsama MCP server.
131
+
114
132
  ## API Tools
115
133
 
116
134
  ### Task Management
@@ -129,6 +147,13 @@ Add this configuration to your Claude Desktop MCP settings:
129
147
  - `update-task-backlog` - Move tasks to the backlog
130
148
  - `delete-task` - Delete tasks permanently
131
149
 
150
+ #### Subtask Management
151
+ - `add-subtask` - Create a subtask with a title in one call (recommended for single subtask creation)
152
+ - `create-subtasks` - Create multiple subtasks for a task (low-level API for bulk operations)
153
+ - `update-subtask-title` - Update the title of a subtask
154
+ - `complete-subtask` - Mark a subtask as complete with optional completion timestamp
155
+ - `uncomplete-subtask` - Mark a subtask as incomplete
156
+
132
157
  ### User & Stream Operations
133
158
  - `get-user` - Get current user information
134
159
  - `get-streams` - Get streams/channels for project organization
package/bun.lock CHANGED
@@ -10,7 +10,7 @@
10
10
  "cors": "^2.8.5",
11
11
  "express": "^5.1.0",
12
12
  "papaparse": "^5.5.3",
13
- "sunsama-api": "0.12.1",
13
+ "sunsama-api": "0.13.1",
14
14
  "zod": "3.24.4",
15
15
  },
16
16
  "devDependencies": {
@@ -397,7 +397,7 @@
397
397
 
398
398
  "strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="],
399
399
 
400
- "sunsama-api": ["sunsama-api@0.12.1", "", { "dependencies": { "graphql": "^16.11.0", "graphql-tag": "^2.12.6", "marked": "^14.1.3", "tough-cookie": "^5.1.2", "tslib": "^2.8.1", "turndown": "^7.2.0", "yjs": "^13.6.27", "zod": "^3.25.64" } }, "sha512-F2GLJjanAeboP4vYg3VIKP8xxQ/8LLqHhNFx3lm9oONOSvLzVAXEJlwonESuUI/iIf93b2i/jYCJ/2guoUhRYQ=="],
400
+ "sunsama-api": ["sunsama-api@0.13.1", "", { "dependencies": { "graphql": "^16.11.0", "graphql-tag": "^2.12.6", "marked": "^14.1.3", "tough-cookie": "^5.1.2", "tslib": "^2.8.1", "turndown": "^7.2.0", "yjs": "^13.6.27", "zod": "^3.25.64" } }, "sha512-d60j9tLvH/HoF1K/CD8B5rphQ6UW+9XOkJHdABFVfmKCRvA+XNhN73ZzSm78dd+nRJXzaZqKG96+saN8jyBjCg=="],
401
401
 
402
402
  "term-size": ["term-size@2.2.1", "", {}, "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg=="],
403
403
 
@@ -17,6 +17,7 @@ export declare function stopClientCacheCleanup(): void;
17
17
  export declare function cleanupAllClients(): void;
18
18
  /**
19
19
  * Authenticate HTTP request and get or create cached client
20
+ * Supports both Basic Auth (email/password) and Bearer token authentication
20
21
  * Uses secure cache key (password hash) and race condition protection
21
22
  */
22
23
  export declare function authenticateHttpRequest(authHeader?: string): Promise<SessionData>;
@@ -1 +1 @@
1
- {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/auth/http.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAe9C;;GAEG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAiBtF;AA+CD,wBAAgB,uBAAuB,IAAI,IAAI,CAQ9C;AAED;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAM7C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAYxC;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,WAAW,CAAC,CAiEtB"}
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/auth/http.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAe9C;;GAEG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAiBtF;AAwDD,wBAAgB,uBAAuB,IAAI,IAAI,CAQ9C;AAED;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAM7C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAYxC;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC3C,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,WAAW,CAAC,CAsItB"}
package/dist/auth/http.js CHANGED
@@ -36,6 +36,14 @@ function getCacheKey(email, password) {
36
36
  .update(`${email}:${password}`)
37
37
  .digest('hex');
38
38
  }
39
+ /**
40
+ * Generate secure cache key from session token
41
+ */
42
+ function getTokenCacheKey(token) {
43
+ return createHash('sha256')
44
+ .update(`token:${token}`)
45
+ .digest('hex');
46
+ }
39
47
  /**
40
48
  * Check if a cached client is still valid based on TTL
41
49
  */
@@ -102,18 +110,80 @@ export function cleanupAllClients() {
102
110
  }
103
111
  /**
104
112
  * Authenticate HTTP request and get or create cached client
113
+ * Supports both Basic Auth (email/password) and Bearer token authentication
105
114
  * Uses secure cache key (password hash) and race condition protection
106
115
  */
107
116
  export async function authenticateHttpRequest(authHeader) {
108
- if (!authHeader || !authHeader.startsWith('Basic ')) {
109
- throw new Error("Basic Auth required");
117
+ if (!authHeader) {
118
+ throw new Error("Authorization header required (Basic or Bearer)");
119
+ }
120
+ const isBearer = authHeader.startsWith('Bearer ');
121
+ const isBasic = authHeader.startsWith('Basic ');
122
+ if (!isBearer && !isBasic) {
123
+ throw new Error("Authorization header must be Basic or Bearer");
110
124
  }
111
- const { email, password } = parseBasicAuth(authHeader);
112
- const cacheKey = getCacheKey(email, password);
113
125
  const now = Date.now();
126
+ let cacheKey;
127
+ let identifier;
128
+ if (isBearer) {
129
+ const token = authHeader.replace('Bearer ', '').trim();
130
+ if (!token) {
131
+ throw new Error("Invalid Bearer token");
132
+ }
133
+ cacheKey = getTokenCacheKey(token);
134
+ identifier = 'token-user';
135
+ // Check for pending authentication (race condition protection)
136
+ if (authPromises.has(cacheKey)) {
137
+ console.error(`[Client Cache] Waiting for pending authentication for ${identifier}`);
138
+ return await authPromises.get(cacheKey);
139
+ }
140
+ // Check cache first
141
+ if (clientCache.has(cacheKey)) {
142
+ const cached = clientCache.get(cacheKey);
143
+ if (isClientValid(cached)) {
144
+ console.error(`[Client Cache] Reusing cached client for ${identifier}`);
145
+ cached.lastAccessedAt = now;
146
+ return cached;
147
+ }
148
+ else {
149
+ console.error(`[Client Cache] Cached client expired for ${identifier}, re-authenticating`);
150
+ try {
151
+ cached.sunsamaClient.logout();
152
+ }
153
+ catch (err) {
154
+ console.error(`[Client Cache] Error logging out expired client:`, err);
155
+ }
156
+ clientCache.delete(cacheKey);
157
+ }
158
+ }
159
+ // Create authentication promise for Bearer token
160
+ console.error(`[Client Cache] Creating new client for ${identifier}`);
161
+ const authPromise = (async () => {
162
+ try {
163
+ const sunsamaClient = new SunsamaClient({ sessionToken: token });
164
+ const sessionData = {
165
+ sunsamaClient,
166
+ createdAt: now,
167
+ lastAccessedAt: now
168
+ };
169
+ clientCache.set(cacheKey, sessionData);
170
+ console.error(`[Client Cache] Cached new client for ${identifier} (total: ${clientCache.size})`);
171
+ return sessionData;
172
+ }
173
+ finally {
174
+ authPromises.delete(cacheKey);
175
+ }
176
+ })();
177
+ authPromises.set(cacheKey, authPromise);
178
+ return authPromise;
179
+ }
180
+ // Basic Auth flow
181
+ const { email, password } = parseBasicAuth(authHeader);
182
+ cacheKey = getCacheKey(email, password);
183
+ identifier = email;
114
184
  // Check for pending authentication (race condition protection)
115
185
  if (authPromises.has(cacheKey)) {
116
- console.error(`[Client Cache] Waiting for pending authentication for ${email}`);
186
+ console.error(`[Client Cache] Waiting for pending authentication for ${identifier}`);
117
187
  return await authPromises.get(cacheKey);
118
188
  }
119
189
  // Check cache first
@@ -121,13 +191,13 @@ export async function authenticateHttpRequest(authHeader) {
121
191
  const cached = clientCache.get(cacheKey);
122
192
  // Check if still valid (lazy expiration)
123
193
  if (isClientValid(cached)) {
124
- console.error(`[Client Cache] Reusing cached client for ${email}`);
194
+ console.error(`[Client Cache] Reusing cached client for ${identifier}`);
125
195
  // Update last accessed time (sliding window)
126
196
  cached.lastAccessedAt = now;
127
197
  return cached;
128
198
  }
129
199
  else {
130
- console.error(`[Client Cache] Cached client expired for ${email}, re-authenticating`);
200
+ console.error(`[Client Cache] Cached client expired for ${identifier}, re-authenticating`);
131
201
  // Cleanup expired client
132
202
  try {
133
203
  cached.sunsamaClient.logout();
@@ -139,7 +209,7 @@ export async function authenticateHttpRequest(authHeader) {
139
209
  }
140
210
  }
141
211
  // Create authentication promise to prevent concurrent authentications
142
- console.error(`[Client Cache] Creating new client for ${email}`);
212
+ console.error(`[Client Cache] Creating new client for ${identifier}`);
143
213
  const authPromise = (async () => {
144
214
  try {
145
215
  const sunsamaClient = new SunsamaClient();
@@ -151,7 +221,7 @@ export async function authenticateHttpRequest(authHeader) {
151
221
  lastAccessedAt: now
152
222
  };
153
223
  clientCache.set(cacheKey, sessionData);
154
- console.error(`[Client Cache] Cached new client for ${email} (total: ${clientCache.size})`);
224
+ console.error(`[Client Cache] Cached new client for ${identifier} (total: ${clientCache.size})`);
155
225
  return sessionData;
156
226
  }
157
227
  finally {
@@ -1,6 +1,7 @@
1
1
  import { SunsamaClient } from "sunsama-api/client";
2
2
  /**
3
3
  * Initialize stdio authentication using environment variables
4
+ * Supports session token (SUNSAMA_SESSION_TOKEN) or email/password (SUNSAMA_EMAIL, SUNSAMA_PASSWORD)
4
5
  * @throws {Error} If credentials are missing or authentication fails
5
6
  */
6
7
  export declare function initializeStdioAuth(): Promise<SunsamaClient>;
@@ -1 +1 @@
1
- {"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../../src/auth/stdio.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAOnD;;;GAGG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,aAAa,CAAC,CAWlE;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,aAAa,CAAC,CAMrE"}
1
+ {"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../../src/auth/stdio.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAOnD;;;;GAIG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,aAAa,CAAC,CAoBlE;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,aAAa,CAAC,CAMrE"}
@@ -5,11 +5,20 @@ import { SunsamaClient } from "sunsama-api/client";
5
5
  let authenticationPromise = null;
6
6
  /**
7
7
  * Initialize stdio authentication using environment variables
8
+ * Supports session token (SUNSAMA_SESSION_TOKEN) or email/password (SUNSAMA_EMAIL, SUNSAMA_PASSWORD)
8
9
  * @throws {Error} If credentials are missing or authentication fails
9
10
  */
10
11
  export async function initializeStdioAuth() {
12
+ // Prefer session token if available (useful for Google SSO users)
13
+ if (process.env.SUNSAMA_SESSION_TOKEN) {
14
+ const sunsamaClient = new SunsamaClient({
15
+ sessionToken: process.env.SUNSAMA_SESSION_TOKEN
16
+ });
17
+ return sunsamaClient;
18
+ }
19
+ // Fall back to email/password authentication
11
20
  if (!process.env.SUNSAMA_EMAIL || !process.env.SUNSAMA_PASSWORD) {
12
- throw new Error("Sunsama credentials not configured. Please set SUNSAMA_EMAIL and SUNSAMA_PASSWORD environment variables.");
21
+ throw new Error("Sunsama credentials not configured. Please set SUNSAMA_SESSION_TOKEN or both SUNSAMA_EMAIL and SUNSAMA_PASSWORD environment variables.");
13
22
  }
14
23
  const sunsamaClient = new SunsamaClient();
15
24
  await sunsamaClient.login(process.env.SUNSAMA_EMAIL, process.env.SUNSAMA_PASSWORD);
@@ -4,7 +4,7 @@ import { SunsamaClient } from "sunsama-api/client";
4
4
  */
5
5
  export interface SessionData extends Record<string, unknown> {
6
6
  sunsamaClient: SunsamaClient;
7
- email: string;
7
+ email?: string;
8
8
  createdAt: number;
9
9
  lastAccessedAt: number;
10
10
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/auth/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD;;GAEG;AACH,MAAM,WAAW,WAAY,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC1D,aAAa,EAAE,aAAa,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;CACxB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/auth/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD;;GAEG;AACH,MAAM,WAAW,WAAY,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC1D,aAAa,EAAE,aAAa,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;CACxB"}
@@ -4,7 +4,7 @@
4
4
  * VERSION is automatically synced from package.json by scripts/sync-version.ts
5
5
  * when running `bun run version`
6
6
  */
7
- export declare const VERSION = "0.16.1";
7
+ export declare const VERSION = "0.17.0";
8
8
  export declare const SERVER_NAME = "Sunsama API Server";
9
9
  export declare const PACKAGE_NAME = "mcp-sunsama";
10
10
  //# sourceMappingURL=constants.d.ts.map
package/dist/constants.js CHANGED
@@ -4,6 +4,6 @@
4
4
  * VERSION is automatically synced from package.json by scripts/sync-version.ts
5
5
  * when running `bun run version`
6
6
  */
7
- export const VERSION = "0.16.1";
7
+ export const VERSION = "0.17.0";
8
8
  export const SERVER_NAME = "Sunsama API Server";
9
9
  export const PACKAGE_NAME = "mcp-sunsama";
package/dist/schemas.d.ts CHANGED
@@ -340,6 +340,74 @@ export declare const updateTaskStreamSchema: z.ZodObject<{
340
340
  streamId: string;
341
341
  limitResponsePayload?: boolean | undefined;
342
342
  }>;
343
+ /**
344
+ * Subtask Operation Schemas
345
+ */
346
+ export declare const createSubtasksSchema: z.ZodObject<{
347
+ taskId: z.ZodString;
348
+ subtaskIds: z.ZodArray<z.ZodString, "many">;
349
+ limitResponsePayload: z.ZodOptional<z.ZodBoolean>;
350
+ }, "strip", z.ZodTypeAny, {
351
+ taskId: string;
352
+ subtaskIds: string[];
353
+ limitResponsePayload?: boolean | undefined;
354
+ }, {
355
+ taskId: string;
356
+ subtaskIds: string[];
357
+ limitResponsePayload?: boolean | undefined;
358
+ }>;
359
+ export declare const updateSubtaskTitleSchema: z.ZodObject<{
360
+ taskId: z.ZodString;
361
+ subtaskId: z.ZodString;
362
+ title: z.ZodString;
363
+ }, "strip", z.ZodTypeAny, {
364
+ taskId: string;
365
+ subtaskId: string;
366
+ title: string;
367
+ }, {
368
+ taskId: string;
369
+ subtaskId: string;
370
+ title: string;
371
+ }>;
372
+ export declare const completeSubtaskSchema: z.ZodObject<{
373
+ taskId: z.ZodString;
374
+ subtaskId: z.ZodString;
375
+ completedDate: z.ZodOptional<z.ZodString>;
376
+ limitResponsePayload: z.ZodOptional<z.ZodBoolean>;
377
+ }, "strip", z.ZodTypeAny, {
378
+ taskId: string;
379
+ subtaskId: string;
380
+ limitResponsePayload?: boolean | undefined;
381
+ completedDate?: string | undefined;
382
+ }, {
383
+ taskId: string;
384
+ subtaskId: string;
385
+ limitResponsePayload?: boolean | undefined;
386
+ completedDate?: string | undefined;
387
+ }>;
388
+ export declare const uncompleteSubtaskSchema: z.ZodObject<{
389
+ taskId: z.ZodString;
390
+ subtaskId: z.ZodString;
391
+ limitResponsePayload: z.ZodOptional<z.ZodBoolean>;
392
+ }, "strip", z.ZodTypeAny, {
393
+ taskId: string;
394
+ subtaskId: string;
395
+ limitResponsePayload?: boolean | undefined;
396
+ }, {
397
+ taskId: string;
398
+ subtaskId: string;
399
+ limitResponsePayload?: boolean | undefined;
400
+ }>;
401
+ export declare const addSubtaskSchema: z.ZodObject<{
402
+ taskId: z.ZodString;
403
+ title: z.ZodString;
404
+ }, "strip", z.ZodTypeAny, {
405
+ taskId: string;
406
+ title: string;
407
+ }, {
408
+ taskId: string;
409
+ title: string;
410
+ }>;
343
411
  /**
344
412
  * Response Type Schemas (for validation and documentation)
345
413
  */
@@ -464,9 +532,9 @@ export declare const taskSchema: z.ZodObject<{
464
532
  }, "strip", z.ZodTypeAny, {
465
533
  status: string;
466
534
  createdAt: string;
535
+ title: string;
467
536
  _id: string;
468
537
  groupId: string;
469
- title: string;
470
538
  updatedAt: string;
471
539
  userId: string;
472
540
  streamId?: string | undefined;
@@ -476,9 +544,9 @@ export declare const taskSchema: z.ZodObject<{
476
544
  }, {
477
545
  status: string;
478
546
  createdAt: string;
547
+ title: string;
479
548
  _id: string;
480
549
  groupId: string;
481
- title: string;
482
550
  updatedAt: string;
483
551
  userId: string;
484
552
  streamId?: string | undefined;
@@ -639,9 +707,9 @@ export declare const tasksResponseSchema: z.ZodObject<{
639
707
  }, "strip", z.ZodTypeAny, {
640
708
  status: string;
641
709
  createdAt: string;
710
+ title: string;
642
711
  _id: string;
643
712
  groupId: string;
644
- title: string;
645
713
  updatedAt: string;
646
714
  userId: string;
647
715
  streamId?: string | undefined;
@@ -651,9 +719,9 @@ export declare const tasksResponseSchema: z.ZodObject<{
651
719
  }, {
652
720
  status: string;
653
721
  createdAt: string;
722
+ title: string;
654
723
  _id: string;
655
724
  groupId: string;
656
- title: string;
657
725
  updatedAt: string;
658
726
  userId: string;
659
727
  streamId?: string | undefined;
@@ -666,9 +734,9 @@ export declare const tasksResponseSchema: z.ZodObject<{
666
734
  tasks: {
667
735
  status: string;
668
736
  createdAt: string;
737
+ title: string;
669
738
  _id: string;
670
739
  groupId: string;
671
- title: string;
672
740
  updatedAt: string;
673
741
  userId: string;
674
742
  streamId?: string | undefined;
@@ -681,9 +749,9 @@ export declare const tasksResponseSchema: z.ZodObject<{
681
749
  tasks: {
682
750
  status: string;
683
751
  createdAt: string;
752
+ title: string;
684
753
  _id: string;
685
754
  groupId: string;
686
- title: string;
687
755
  updatedAt: string;
688
756
  userId: string;
689
757
  streamId?: string | undefined;
@@ -779,6 +847,11 @@ export type UpdateTaskNotesInput = z.infer<typeof updateTaskNotesSchema>;
779
847
  export type UpdateTaskDueDateInput = z.infer<typeof updateTaskDueDateSchema>;
780
848
  export type UpdateTaskTextInput = z.infer<typeof updateTaskTextSchema>;
781
849
  export type UpdateTaskStreamInput = z.infer<typeof updateTaskStreamSchema>;
850
+ export type CreateSubtasksInput = z.infer<typeof createSubtasksSchema>;
851
+ export type UpdateSubtaskTitleInput = z.infer<typeof updateSubtaskTitleSchema>;
852
+ export type CompleteSubtaskInput = z.infer<typeof completeSubtaskSchema>;
853
+ export type UncompleteSubtaskInput = z.infer<typeof uncompleteSubtaskSchema>;
854
+ export type AddSubtaskInput = z.infer<typeof addSubtaskSchema>;
782
855
  export type User = z.infer<typeof userSchema>;
783
856
  export type Task = z.infer<typeof taskSchema>;
784
857
  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
@@ -150,6 +150,39 @@ export const updateTaskStreamSchema = z.object({
150
150
  streamId: z.string().min(1, "Stream ID is required").describe("Stream ID to assign to the task"),
151
151
  limitResponsePayload: z.boolean().optional().describe("Whether to limit the response payload size"),
152
152
  });
153
+ /**
154
+ * Subtask Operation Schemas
155
+ */
156
+ // Create subtasks parameters
157
+ export const createSubtasksSchema = z.object({
158
+ taskId: z.string().min(1, "Task ID is required").describe("The ID of the parent task"),
159
+ subtaskIds: z.array(z.string().min(1)).min(1, "At least one subtask ID is required").describe("Array of subtask IDs to create (use SunsamaClient.generateTaskId() to generate IDs)"),
160
+ limitResponsePayload: z.boolean().optional().describe("Whether to limit the response payload size"),
161
+ });
162
+ // Update subtask title parameters
163
+ export const updateSubtaskTitleSchema = z.object({
164
+ taskId: z.string().min(1, "Task ID is required").describe("The ID of the parent task"),
165
+ subtaskId: z.string().min(1, "Subtask ID is required").describe("The ID of the subtask to update"),
166
+ title: z.string().min(1, "Subtask title is required").describe("The new title for the subtask"),
167
+ });
168
+ // Complete subtask parameters
169
+ export const completeSubtaskSchema = z.object({
170
+ taskId: z.string().min(1, "Task ID is required").describe("The ID of the parent task"),
171
+ subtaskId: z.string().min(1, "Subtask ID is required").describe("The ID of the subtask to mark as complete"),
172
+ completedDate: z.string().optional().describe("Completion timestamp (ISO format). Defaults to current time"),
173
+ limitResponsePayload: z.boolean().optional().describe("Whether to limit the response payload size"),
174
+ });
175
+ // Uncomplete subtask parameters
176
+ export const uncompleteSubtaskSchema = z.object({
177
+ taskId: z.string().min(1, "Task ID is required").describe("The ID of the parent task"),
178
+ subtaskId: z.string().min(1, "Subtask ID is required").describe("The ID of the subtask to mark as incomplete"),
179
+ limitResponsePayload: z.boolean().optional().describe("Whether to limit the response payload size"),
180
+ });
181
+ // Add subtask parameters (convenience method)
182
+ export const addSubtaskSchema = z.object({
183
+ taskId: z.string().min(1, "Task ID is required").describe("The ID of the parent task"),
184
+ title: z.string().min(1, "Subtask title is required").describe("The title for the new subtask"),
185
+ });
153
186
  /**
154
187
  * Response Type Schemas (for validation and documentation)
155
188
  */
@@ -283,20 +283,22 @@ describe("Tool Parameter Schemas", () => {
283
283
  };
284
284
  expect(() => updateTaskNotesSchema.parse(minimalMarkdownInput)).not.toThrow();
285
285
  });
286
- test("should reject both HTML and Markdown provided", () => {
287
- const invalidInput = {
286
+ test("should accept both HTML and Markdown provided (XOR validation is in tool execute)", () => {
287
+ const inputWithBoth = {
288
288
  taskId: "task-123",
289
289
  html: "<p>HTML content</p>",
290
290
  markdown: "Markdown content",
291
291
  };
292
- expect(() => updateTaskNotesSchema.parse(invalidInput)).toThrow();
292
+ // Schema accepts both - XOR validation happens in the tool's execute function
293
+ expect(() => updateTaskNotesSchema.parse(inputWithBoth)).not.toThrow();
293
294
  });
294
- test("should reject neither HTML nor Markdown provided", () => {
295
- const invalidInput = {
295
+ test("should accept neither HTML nor Markdown provided (XOR validation is in tool execute)", () => {
296
+ const inputWithNeither = {
296
297
  taskId: "task-123",
297
298
  limitResponsePayload: true,
298
299
  };
299
- expect(() => updateTaskNotesSchema.parse(invalidInput)).toThrow();
300
+ // Schema accepts neither - XOR validation happens in the tool's execute function
301
+ expect(() => updateTaskNotesSchema.parse(inputWithNeither)).not.toThrow();
300
302
  });
301
303
  test("should reject empty task ID", () => {
302
304
  expect(() => updateTaskNotesSchema.parse({
@@ -82,6 +82,36 @@ export declare const updateTaskStreamTool: {
82
82
  parameters: any;
83
83
  execute: (args: any, extra?: any) => Promise<any>;
84
84
  };
85
+ export declare const createSubtasksTool: {
86
+ name: string;
87
+ description: string;
88
+ parameters: any;
89
+ execute: (args: any, extra?: any) => Promise<any>;
90
+ };
91
+ export declare const updateSubtaskTitleTool: {
92
+ name: string;
93
+ description: string;
94
+ parameters: any;
95
+ execute: (args: any, extra?: any) => Promise<any>;
96
+ };
97
+ export declare const completeSubtaskTool: {
98
+ name: string;
99
+ description: string;
100
+ parameters: any;
101
+ execute: (args: any, extra?: any) => Promise<any>;
102
+ };
103
+ export declare const uncompleteSubtaskTool: {
104
+ name: string;
105
+ description: string;
106
+ parameters: any;
107
+ execute: (args: any, extra?: any) => Promise<any>;
108
+ };
109
+ export declare const addSubtaskTool: {
110
+ name: string;
111
+ description: string;
112
+ parameters: any;
113
+ execute: (args: any, extra?: any) => Promise<any>;
114
+ };
85
115
  export declare const taskTools: {
86
116
  name: string;
87
117
  description: string;
@@ -1 +1 @@
1
- {"version":3,"file":"task-tools.d.ts","sourceRoot":"","sources":["../../src/tools/task-tools.ts"],"names":[],"mappings":"AA0CA,eAAO,MAAM,mBAAmB;;;;;CAU9B,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;;;CAqB5B,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;;CA2B/B,CAAC;AAEH,eAAO,MAAM,eAAe;;;;;CAS1B,CAAC;AAGH,eAAO,MAAM,cAAc;;;;;CAsCzB,CAAC;AAEH,eAAO,MAAM,cAAc;;;;;CAqBzB,CAAC;AAGH,eAAO,MAAM,sBAAsB;;;;;CAqBjC,CAAC;AAEH,eAAO,MAAM,wBAAwB;;;;;CA6BnC,CAAC;AAEH,eAAO,MAAM,qBAAqB;;;;;CA2BhC,CAAC;AAEH,eAAO,MAAM,yBAAyB;;;;;CAsBpC,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;CAwC9B,CAAC;AAEH,eAAO,MAAM,qBAAqB;;;;;CAsBhC,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;;CA8B7B,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;;CAsB/B,CAAC;AAGH,eAAO,MAAM,SAAS;;;;;GAoBrB,CAAC"}
1
+ {"version":3,"file":"task-tools.d.ts","sourceRoot":"","sources":["../../src/tools/task-tools.ts"],"names":[],"mappings":"AAoDA,eAAO,MAAM,mBAAmB;;;;;CAU9B,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;;;CAqB5B,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;;CA2B/B,CAAC;AAEH,eAAO,MAAM,eAAe;;;;;CAS1B,CAAC;AAGH,eAAO,MAAM,cAAc;;;;;CAsCzB,CAAC;AAEH,eAAO,MAAM,cAAc;;;;;CAqBzB,CAAC;AAGH,eAAO,MAAM,sBAAsB;;;;;CAqBjC,CAAC;AAEH,eAAO,MAAM,wBAAwB;;;;;CA6BnC,CAAC;AAEH,eAAO,MAAM,qBAAqB;;;;;CA2BhC,CAAC;AAEH,eAAO,MAAM,yBAAyB;;;;;CAsBpC,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;CAwC9B,CAAC;AAEH,eAAO,MAAM,qBAAqB;;;;;CAsBhC,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;;CA8B7B,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;;CAsB/B,CAAC;AAGH,eAAO,MAAM,kBAAkB;;;;;CAuB7B,CAAC;AAEH,eAAO,MAAM,sBAAsB;;;;;CAuBjC,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;CAwB9B,CAAC;AAEH,eAAO,MAAM,qBAAqB;;;;;CAsBhC,CAAC;AAEH,eAAO,MAAM,cAAc;;;;;CAmBzB,CAAC;AAGH,eAAO,MAAM,SAAS;;;;;GA2BrB,CAAC"}
@@ -1,4 +1,4 @@
1
- import { createTaskSchema, deleteTaskSchema, getArchivedTasksSchema, getTaskByIdSchema, getTasksBacklogSchema, getTasksByDaySchema, updateTaskBacklogSchema, updateTaskCompleteSchema, updateTaskDueDateSchema, updateTaskNotesSchema, updateTaskPlannedTimeSchema, updateTaskSnoozeDateSchema, updateTaskStreamSchema, updateTaskTextSchema, } from "../schemas.js";
1
+ import { addSubtaskSchema, completeSubtaskSchema, createSubtasksSchema, createTaskSchema, deleteTaskSchema, getArchivedTasksSchema, getTaskByIdSchema, getTasksBacklogSchema, getTasksByDaySchema, uncompleteSubtaskSchema, updateSubtaskTitleSchema, updateTaskBacklogSchema, updateTaskCompleteSchema, updateTaskDueDateSchema, updateTaskNotesSchema, updateTaskPlannedTimeSchema, updateTaskSnoozeDateSchema, updateTaskStreamSchema, updateTaskTextSchema, } from "../schemas.js";
2
2
  import { filterTasksByCompletion } from "../utils/task-filters.js";
3
3
  import { trimTasksForResponse } from "../utils/task-trimmer.js";
4
4
  import { formatJsonResponse, formatPaginatedTsvResponse, formatTsvResponse, withTransportClient, } from "./shared.js";
@@ -257,6 +257,86 @@ export const updateTaskStreamTool = withTransportClient({
257
257
  });
258
258
  },
259
259
  });
260
+ // Subtask Management Tools
261
+ export const createSubtasksTool = withTransportClient({
262
+ name: "create-subtasks",
263
+ description: "Create multiple subtasks for a task (low-level API for bulk operations)",
264
+ parameters: createSubtasksSchema,
265
+ execute: async ({ taskId, subtaskIds, limitResponsePayload }, context) => {
266
+ const result = await context.client.createSubtasks(taskId, subtaskIds, limitResponsePayload);
267
+ return formatJsonResponse({
268
+ success: result.success,
269
+ taskId,
270
+ subtaskIds,
271
+ subtasksCreated: true,
272
+ count: subtaskIds.length,
273
+ updatedFields: result.updatedFields,
274
+ });
275
+ },
276
+ });
277
+ export const updateSubtaskTitleTool = withTransportClient({
278
+ name: "update-subtask-title",
279
+ description: "Update the title of a subtask",
280
+ parameters: updateSubtaskTitleSchema,
281
+ execute: async ({ taskId, subtaskId, title }, context) => {
282
+ const result = await context.client.updateSubtaskTitle(taskId, subtaskId, title);
283
+ return formatJsonResponse({
284
+ success: result.success,
285
+ taskId,
286
+ subtaskId,
287
+ title,
288
+ subtaskTitleUpdated: true,
289
+ updatedFields: result.updatedFields,
290
+ });
291
+ },
292
+ });
293
+ export const completeSubtaskTool = withTransportClient({
294
+ name: "complete-subtask",
295
+ description: "Mark a subtask as complete with optional completion timestamp",
296
+ parameters: completeSubtaskSchema,
297
+ execute: async ({ taskId, subtaskId, completedDate, limitResponsePayload }, context) => {
298
+ const result = await context.client.completeSubtask(taskId, subtaskId, completedDate, limitResponsePayload);
299
+ return formatJsonResponse({
300
+ success: result.success,
301
+ taskId,
302
+ subtaskId,
303
+ subtaskCompleted: true,
304
+ completedDate: completedDate || new Date().toISOString(),
305
+ updatedFields: result.updatedFields,
306
+ });
307
+ },
308
+ });
309
+ export const uncompleteSubtaskTool = withTransportClient({
310
+ name: "uncomplete-subtask",
311
+ description: "Mark a subtask as incomplete (uncomplete it)",
312
+ parameters: uncompleteSubtaskSchema,
313
+ execute: async ({ taskId, subtaskId, limitResponsePayload }, context) => {
314
+ const result = await context.client.uncompleteSubtask(taskId, subtaskId, limitResponsePayload);
315
+ return formatJsonResponse({
316
+ success: result.success,
317
+ taskId,
318
+ subtaskId,
319
+ subtaskUncompleted: true,
320
+ updatedFields: result.updatedFields,
321
+ });
322
+ },
323
+ });
324
+ export const addSubtaskTool = withTransportClient({
325
+ name: "add-subtask",
326
+ description: "Convenience method to create a subtask with a title in one call (recommended for single subtask creation)",
327
+ parameters: addSubtaskSchema,
328
+ execute: async ({ taskId, title }, context) => {
329
+ const result = await context.client.addSubtask(taskId, title);
330
+ return formatJsonResponse({
331
+ success: result.result.success,
332
+ taskId,
333
+ subtaskId: result.subtaskId,
334
+ title,
335
+ subtaskAdded: true,
336
+ updatedFields: result.result.updatedFields,
337
+ });
338
+ },
339
+ });
260
340
  // Export all task tools
261
341
  export const taskTools = [
262
342
  // Query tools
@@ -276,4 +356,10 @@ export const taskTools = [
276
356
  updateTaskDueDateTool,
277
357
  updateTaskTextTool,
278
358
  updateTaskStreamTool,
359
+ // Subtask management tools
360
+ createSubtasksTool,
361
+ updateSubtaskTitleTool,
362
+ completeSubtaskTool,
363
+ uncompleteSubtaskTool,
364
+ addSubtaskTool,
279
365
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-sunsama",
3
- "version": "0.16.1",
3
+ "version": "0.17.0",
4
4
  "description": "MCP server for Sunsama API integration",
5
5
  "type": "module",
6
6
  "private": false,
@@ -34,7 +34,7 @@
34
34
  "cors": "^2.8.5",
35
35
  "express": "^5.1.0",
36
36
  "papaparse": "^5.5.3",
37
- "sunsama-api": "0.12.1",
37
+ "sunsama-api": "0.13.1",
38
38
  "zod": "3.24.4"
39
39
  },
40
40
  "devDependencies": {
@@ -64,4 +64,4 @@
64
64
  "publishConfig": {
65
65
  "access": "public"
66
66
  }
67
- }
67
+ }
package/src/auth/http.ts CHANGED
@@ -47,6 +47,15 @@ function getCacheKey(email: string, password: string): string {
47
47
  .digest('hex');
48
48
  }
49
49
 
50
+ /**
51
+ * Generate secure cache key from session token
52
+ */
53
+ function getTokenCacheKey(token: string): string {
54
+ return createHash('sha256')
55
+ .update(`token:${token}`)
56
+ .digest('hex');
57
+ }
58
+
50
59
  /**
51
60
  * Check if a cached client is still valid based on TTL
52
61
  */
@@ -122,22 +131,92 @@ export function cleanupAllClients(): void {
122
131
 
123
132
  /**
124
133
  * Authenticate HTTP request and get or create cached client
134
+ * Supports both Basic Auth (email/password) and Bearer token authentication
125
135
  * Uses secure cache key (password hash) and race condition protection
126
136
  */
127
137
  export async function authenticateHttpRequest(
128
138
  authHeader?: string
129
139
  ): Promise<SessionData> {
130
- if (!authHeader || !authHeader.startsWith('Basic ')) {
131
- throw new Error("Basic Auth required");
140
+ if (!authHeader) {
141
+ throw new Error("Authorization header required (Basic or Bearer)");
142
+ }
143
+
144
+ const isBearer = authHeader.startsWith('Bearer ');
145
+ const isBasic = authHeader.startsWith('Basic ');
146
+
147
+ if (!isBearer && !isBasic) {
148
+ throw new Error("Authorization header must be Basic or Bearer");
132
149
  }
133
150
 
134
- const { email, password } = parseBasicAuth(authHeader);
135
- const cacheKey = getCacheKey(email, password);
136
151
  const now = Date.now();
152
+ let cacheKey: string;
153
+ let identifier: string;
154
+
155
+ if (isBearer) {
156
+ const token = authHeader.replace('Bearer ', '').trim();
157
+ if (!token) {
158
+ throw new Error("Invalid Bearer token");
159
+ }
160
+ cacheKey = getTokenCacheKey(token);
161
+ identifier = 'token-user';
162
+
163
+ // Check for pending authentication (race condition protection)
164
+ if (authPromises.has(cacheKey)) {
165
+ console.error(`[Client Cache] Waiting for pending authentication for ${identifier}`);
166
+ return await authPromises.get(cacheKey)!;
167
+ }
168
+
169
+ // Check cache first
170
+ if (clientCache.has(cacheKey)) {
171
+ const cached = clientCache.get(cacheKey)!;
172
+ if (isClientValid(cached)) {
173
+ console.error(`[Client Cache] Reusing cached client for ${identifier}`);
174
+ cached.lastAccessedAt = now;
175
+ return cached;
176
+ } else {
177
+ console.error(`[Client Cache] Cached client expired for ${identifier}, re-authenticating`);
178
+ try {
179
+ cached.sunsamaClient.logout();
180
+ } catch (err) {
181
+ console.error(`[Client Cache] Error logging out expired client:`, err);
182
+ }
183
+ clientCache.delete(cacheKey);
184
+ }
185
+ }
186
+
187
+ // Create authentication promise for Bearer token
188
+ console.error(`[Client Cache] Creating new client for ${identifier}`);
189
+ const authPromise = (async () => {
190
+ try {
191
+ const sunsamaClient = new SunsamaClient({ sessionToken: token });
192
+
193
+ const sessionData: SessionData = {
194
+ sunsamaClient,
195
+ createdAt: now,
196
+ lastAccessedAt: now
197
+ };
198
+
199
+ clientCache.set(cacheKey, sessionData);
200
+ console.error(`[Client Cache] Cached new client for ${identifier} (total: ${clientCache.size})`);
201
+
202
+ return sessionData;
203
+ } finally {
204
+ authPromises.delete(cacheKey);
205
+ }
206
+ })();
207
+
208
+ authPromises.set(cacheKey, authPromise);
209
+ return authPromise;
210
+ }
211
+
212
+ // Basic Auth flow
213
+ const { email, password } = parseBasicAuth(authHeader);
214
+ cacheKey = getCacheKey(email, password);
215
+ identifier = email;
137
216
 
138
217
  // Check for pending authentication (race condition protection)
139
218
  if (authPromises.has(cacheKey)) {
140
- console.error(`[Client Cache] Waiting for pending authentication for ${email}`);
219
+ console.error(`[Client Cache] Waiting for pending authentication for ${identifier}`);
141
220
  return await authPromises.get(cacheKey)!;
142
221
  }
143
222
 
@@ -147,12 +226,12 @@ export async function authenticateHttpRequest(
147
226
 
148
227
  // Check if still valid (lazy expiration)
149
228
  if (isClientValid(cached)) {
150
- console.error(`[Client Cache] Reusing cached client for ${email}`);
229
+ console.error(`[Client Cache] Reusing cached client for ${identifier}`);
151
230
  // Update last accessed time (sliding window)
152
231
  cached.lastAccessedAt = now;
153
232
  return cached;
154
233
  } else {
155
- console.error(`[Client Cache] Cached client expired for ${email}, re-authenticating`);
234
+ console.error(`[Client Cache] Cached client expired for ${identifier}, re-authenticating`);
156
235
  // Cleanup expired client
157
236
  try {
158
237
  cached.sunsamaClient.logout();
@@ -164,7 +243,7 @@ export async function authenticateHttpRequest(
164
243
  }
165
244
 
166
245
  // Create authentication promise to prevent concurrent authentications
167
- console.error(`[Client Cache] Creating new client for ${email}`);
246
+ console.error(`[Client Cache] Creating new client for ${identifier}`);
168
247
  const authPromise = (async () => {
169
248
  try {
170
249
  const sunsamaClient = new SunsamaClient();
@@ -178,7 +257,7 @@ export async function authenticateHttpRequest(
178
257
  };
179
258
 
180
259
  clientCache.set(cacheKey, sessionData);
181
- console.error(`[Client Cache] Cached new client for ${email} (total: ${clientCache.size})`);
260
+ console.error(`[Client Cache] Cached new client for ${identifier} (total: ${clientCache.size})`);
182
261
 
183
262
  return sessionData;
184
263
  } finally {
package/src/auth/stdio.ts CHANGED
@@ -7,12 +7,22 @@ let authenticationPromise: Promise<SunsamaClient> | null = null;
7
7
 
8
8
  /**
9
9
  * Initialize stdio authentication using environment variables
10
+ * Supports session token (SUNSAMA_SESSION_TOKEN) or email/password (SUNSAMA_EMAIL, SUNSAMA_PASSWORD)
10
11
  * @throws {Error} If credentials are missing or authentication fails
11
12
  */
12
13
  export async function initializeStdioAuth(): Promise<SunsamaClient> {
14
+ // Prefer session token if available (useful for Google SSO users)
15
+ if (process.env.SUNSAMA_SESSION_TOKEN) {
16
+ const sunsamaClient = new SunsamaClient({
17
+ sessionToken: process.env.SUNSAMA_SESSION_TOKEN
18
+ });
19
+ return sunsamaClient;
20
+ }
21
+
22
+ // Fall back to email/password authentication
13
23
  if (!process.env.SUNSAMA_EMAIL || !process.env.SUNSAMA_PASSWORD) {
14
24
  throw new Error(
15
- "Sunsama credentials not configured. Please set SUNSAMA_EMAIL and SUNSAMA_PASSWORD environment variables."
25
+ "Sunsama credentials not configured. Please set SUNSAMA_SESSION_TOKEN or both SUNSAMA_EMAIL and SUNSAMA_PASSWORD environment variables."
16
26
  );
17
27
  }
18
28
 
package/src/auth/types.ts CHANGED
@@ -5,7 +5,7 @@ import { SunsamaClient } from "sunsama-api/client";
5
5
  */
6
6
  export interface SessionData extends Record<string, unknown> {
7
7
  sunsamaClient: SunsamaClient;
8
- email: string;
8
+ email?: string;
9
9
  createdAt: number;
10
10
  lastAccessedAt: number;
11
11
  }
package/src/constants.ts CHANGED
@@ -5,6 +5,6 @@
5
5
  * when running `bun run version`
6
6
  */
7
7
 
8
- export const VERSION = "0.16.1";
8
+ export const VERSION = "0.17.0";
9
9
  export const SERVER_NAME = "Sunsama API Server";
10
10
  export const PACKAGE_NAME = "mcp-sunsama";
@@ -391,21 +391,23 @@ describe("Tool Parameter Schemas", () => {
391
391
  expect(() => updateTaskNotesSchema.parse(minimalMarkdownInput)).not.toThrow();
392
392
  });
393
393
 
394
- test("should reject both HTML and Markdown provided", () => {
395
- const invalidInput = {
394
+ test("should accept both HTML and Markdown provided (XOR validation is in tool execute)", () => {
395
+ const inputWithBoth = {
396
396
  taskId: "task-123",
397
397
  html: "<p>HTML content</p>",
398
398
  markdown: "Markdown content",
399
399
  };
400
- expect(() => updateTaskNotesSchema.parse(invalidInput)).toThrow();
400
+ // Schema accepts both - XOR validation happens in the tool's execute function
401
+ expect(() => updateTaskNotesSchema.parse(inputWithBoth)).not.toThrow();
401
402
  });
402
403
 
403
- test("should reject neither HTML nor Markdown provided", () => {
404
- const invalidInput = {
404
+ test("should accept neither HTML nor Markdown provided (XOR validation is in tool execute)", () => {
405
+ const inputWithNeither = {
405
406
  taskId: "task-123",
406
407
  limitResponsePayload: true,
407
408
  };
408
- expect(() => updateTaskNotesSchema.parse(invalidInput)).toThrow();
409
+ // Schema accepts neither - XOR validation happens in the tool's execute function
410
+ expect(() => updateTaskNotesSchema.parse(inputWithNeither)).not.toThrow();
409
411
  });
410
412
 
411
413
  test("should reject empty task ID", () => {
package/src/schemas.ts CHANGED
@@ -263,6 +263,75 @@ export const updateTaskStreamSchema = z.object({
263
263
  ),
264
264
  });
265
265
 
266
+ /**
267
+ * Subtask Operation Schemas
268
+ */
269
+
270
+ // Create subtasks parameters
271
+ export const createSubtasksSchema = z.object({
272
+ taskId: z.string().min(1, "Task ID is required").describe(
273
+ "The ID of the parent task",
274
+ ),
275
+ subtaskIds: z.array(z.string().min(1)).min(1, "At least one subtask ID is required").describe(
276
+ "Array of subtask IDs to create (use SunsamaClient.generateTaskId() to generate IDs)",
277
+ ),
278
+ limitResponsePayload: z.boolean().optional().describe(
279
+ "Whether to limit the response payload size",
280
+ ),
281
+ });
282
+
283
+ // Update subtask title parameters
284
+ export const updateSubtaskTitleSchema = z.object({
285
+ taskId: z.string().min(1, "Task ID is required").describe(
286
+ "The ID of the parent task",
287
+ ),
288
+ subtaskId: z.string().min(1, "Subtask ID is required").describe(
289
+ "The ID of the subtask to update",
290
+ ),
291
+ title: z.string().min(1, "Subtask title is required").describe(
292
+ "The new title for the subtask",
293
+ ),
294
+ });
295
+
296
+ // Complete subtask parameters
297
+ export const completeSubtaskSchema = z.object({
298
+ taskId: z.string().min(1, "Task ID is required").describe(
299
+ "The ID of the parent task",
300
+ ),
301
+ subtaskId: z.string().min(1, "Subtask ID is required").describe(
302
+ "The ID of the subtask to mark as complete",
303
+ ),
304
+ completedDate: z.string().optional().describe(
305
+ "Completion timestamp (ISO format). Defaults to current time",
306
+ ),
307
+ limitResponsePayload: z.boolean().optional().describe(
308
+ "Whether to limit the response payload size",
309
+ ),
310
+ });
311
+
312
+ // Uncomplete subtask parameters
313
+ export const uncompleteSubtaskSchema = z.object({
314
+ taskId: z.string().min(1, "Task ID is required").describe(
315
+ "The ID of the parent task",
316
+ ),
317
+ subtaskId: z.string().min(1, "Subtask ID is required").describe(
318
+ "The ID of the subtask to mark as incomplete",
319
+ ),
320
+ limitResponsePayload: z.boolean().optional().describe(
321
+ "Whether to limit the response payload size",
322
+ ),
323
+ });
324
+
325
+ // Add subtask parameters (convenience method)
326
+ export const addSubtaskSchema = z.object({
327
+ taskId: z.string().min(1, "Task ID is required").describe(
328
+ "The ID of the parent task",
329
+ ),
330
+ title: z.string().min(1, "Subtask title is required").describe(
331
+ "The title for the new subtask",
332
+ ),
333
+ });
334
+
266
335
  /**
267
336
  * Response Type Schemas (for validation and documentation)
268
337
  */
@@ -375,6 +444,12 @@ export type UpdateTaskDueDateInput = z.infer<typeof updateTaskDueDateSchema>;
375
444
  export type UpdateTaskTextInput = z.infer<typeof updateTaskTextSchema>;
376
445
  export type UpdateTaskStreamInput = z.infer<typeof updateTaskStreamSchema>;
377
446
 
447
+ export type CreateSubtasksInput = z.infer<typeof createSubtasksSchema>;
448
+ export type UpdateSubtaskTitleInput = z.infer<typeof updateSubtaskTitleSchema>;
449
+ export type CompleteSubtaskInput = z.infer<typeof completeSubtaskSchema>;
450
+ export type UncompleteSubtaskInput = z.infer<typeof uncompleteSubtaskSchema>;
451
+ export type AddSubtaskInput = z.infer<typeof addSubtaskSchema>;
452
+
378
453
  export type User = z.infer<typeof userSchema>;
379
454
  export type Task = z.infer<typeof taskSchema>;
380
455
  export type Stream = z.infer<typeof streamSchema>;
@@ -1,5 +1,11 @@
1
1
  import type { CreateTaskOptions } from "sunsama-api/types";
2
2
  import {
3
+ type AddSubtaskInput,
4
+ addSubtaskSchema,
5
+ type CompleteSubtaskInput,
6
+ completeSubtaskSchema,
7
+ type CreateSubtasksInput,
8
+ createSubtasksSchema,
3
9
  type CreateTaskInput,
4
10
  createTaskSchema,
5
11
  type DeleteTaskInput,
@@ -12,6 +18,10 @@ import {
12
18
  getTasksBacklogSchema,
13
19
  type GetTasksByDayInput,
14
20
  getTasksByDaySchema,
21
+ type UncompleteSubtaskInput,
22
+ uncompleteSubtaskSchema,
23
+ type UpdateSubtaskTitleInput,
24
+ updateSubtaskTitleSchema,
15
25
  type UpdateTaskBacklogInput,
16
26
  updateTaskBacklogSchema,
17
27
  type UpdateTaskCompleteInput,
@@ -409,6 +419,128 @@ export const updateTaskStreamTool = withTransportClient({
409
419
  },
410
420
  });
411
421
 
422
+ // Subtask Management Tools
423
+ export const createSubtasksTool = withTransportClient({
424
+ name: "create-subtasks",
425
+ description: "Create multiple subtasks for a task (low-level API for bulk operations)",
426
+ parameters: createSubtasksSchema,
427
+ execute: async (
428
+ { taskId, subtaskIds, limitResponsePayload }: CreateSubtasksInput,
429
+ context: ToolContext,
430
+ ) => {
431
+ const result = await context.client.createSubtasks(
432
+ taskId,
433
+ subtaskIds,
434
+ limitResponsePayload,
435
+ );
436
+
437
+ return formatJsonResponse({
438
+ success: result.success,
439
+ taskId,
440
+ subtaskIds,
441
+ subtasksCreated: true,
442
+ count: subtaskIds.length,
443
+ updatedFields: result.updatedFields,
444
+ });
445
+ },
446
+ });
447
+
448
+ export const updateSubtaskTitleTool = withTransportClient({
449
+ name: "update-subtask-title",
450
+ description: "Update the title of a subtask",
451
+ parameters: updateSubtaskTitleSchema,
452
+ execute: async (
453
+ { taskId, subtaskId, title }: UpdateSubtaskTitleInput,
454
+ context: ToolContext,
455
+ ) => {
456
+ const result = await context.client.updateSubtaskTitle(
457
+ taskId,
458
+ subtaskId,
459
+ title,
460
+ );
461
+
462
+ return formatJsonResponse({
463
+ success: result.success,
464
+ taskId,
465
+ subtaskId,
466
+ title,
467
+ subtaskTitleUpdated: true,
468
+ updatedFields: result.updatedFields,
469
+ });
470
+ },
471
+ });
472
+
473
+ export const completeSubtaskTool = withTransportClient({
474
+ name: "complete-subtask",
475
+ description: "Mark a subtask as complete with optional completion timestamp",
476
+ parameters: completeSubtaskSchema,
477
+ execute: async (
478
+ { taskId, subtaskId, completedDate, limitResponsePayload }: CompleteSubtaskInput,
479
+ context: ToolContext,
480
+ ) => {
481
+ const result = await context.client.completeSubtask(
482
+ taskId,
483
+ subtaskId,
484
+ completedDate,
485
+ limitResponsePayload,
486
+ );
487
+
488
+ return formatJsonResponse({
489
+ success: result.success,
490
+ taskId,
491
+ subtaskId,
492
+ subtaskCompleted: true,
493
+ completedDate: completedDate || new Date().toISOString(),
494
+ updatedFields: result.updatedFields,
495
+ });
496
+ },
497
+ });
498
+
499
+ export const uncompleteSubtaskTool = withTransportClient({
500
+ name: "uncomplete-subtask",
501
+ description: "Mark a subtask as incomplete (uncomplete it)",
502
+ parameters: uncompleteSubtaskSchema,
503
+ execute: async (
504
+ { taskId, subtaskId, limitResponsePayload }: UncompleteSubtaskInput,
505
+ context: ToolContext,
506
+ ) => {
507
+ const result = await context.client.uncompleteSubtask(
508
+ taskId,
509
+ subtaskId,
510
+ limitResponsePayload,
511
+ );
512
+
513
+ return formatJsonResponse({
514
+ success: result.success,
515
+ taskId,
516
+ subtaskId,
517
+ subtaskUncompleted: true,
518
+ updatedFields: result.updatedFields,
519
+ });
520
+ },
521
+ });
522
+
523
+ export const addSubtaskTool = withTransportClient({
524
+ name: "add-subtask",
525
+ description: "Convenience method to create a subtask with a title in one call (recommended for single subtask creation)",
526
+ parameters: addSubtaskSchema,
527
+ execute: async (
528
+ { taskId, title }: AddSubtaskInput,
529
+ context: ToolContext,
530
+ ) => {
531
+ const result = await context.client.addSubtask(taskId, title);
532
+
533
+ return formatJsonResponse({
534
+ success: result.result.success,
535
+ taskId,
536
+ subtaskId: result.subtaskId,
537
+ title,
538
+ subtaskAdded: true,
539
+ updatedFields: result.result.updatedFields,
540
+ });
541
+ },
542
+ });
543
+
412
544
  // Export all task tools
413
545
  export const taskTools = [
414
546
  // Query tools
@@ -430,4 +562,11 @@ export const taskTools = [
430
562
  updateTaskDueDateTool,
431
563
  updateTaskTextTool,
432
564
  updateTaskStreamTool,
565
+
566
+ // Subtask management tools
567
+ createSubtasksTool,
568
+ updateSubtaskTitleTool,
569
+ completeSubtaskTool,
570
+ uncompleteSubtaskTool,
571
+ addSubtaskTool,
433
572
  ];