mcp-sunsama 0.2.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.
Files changed (53) hide show
  1. package/.changeset/README.md +8 -0
  2. package/.changeset/config.json +11 -0
  3. package/.claude/settings.local.json +29 -0
  4. package/.env.example +10 -0
  5. package/CHANGELOG.md +29 -0
  6. package/CLAUDE.md +137 -0
  7. package/LICENSE +21 -0
  8. package/README.md +143 -0
  9. package/TODO_PREPUBLISH.md +182 -0
  10. package/bun.lock +515 -0
  11. package/dist/auth/http.d.ts +20 -0
  12. package/dist/auth/http.d.ts.map +1 -0
  13. package/dist/auth/http.js +52 -0
  14. package/dist/auth/stdio.d.ts +13 -0
  15. package/dist/auth/stdio.d.ts.map +1 -0
  16. package/dist/auth/stdio.js +27 -0
  17. package/dist/auth/types.d.ts +9 -0
  18. package/dist/auth/types.d.ts.map +1 -0
  19. package/dist/auth/types.js +1 -0
  20. package/dist/config/transport.d.ts +32 -0
  21. package/dist/config/transport.d.ts.map +1 -0
  22. package/dist/config/transport.js +62 -0
  23. package/dist/main.d.ts +2 -0
  24. package/dist/main.d.ts.map +1 -0
  25. package/dist/main.js +473 -0
  26. package/dist/schemas.d.ts +522 -0
  27. package/dist/schemas.d.ts.map +1 -0
  28. package/dist/schemas.js +124 -0
  29. package/dist/utils/client-resolver.d.ts +10 -0
  30. package/dist/utils/client-resolver.d.ts.map +1 -0
  31. package/dist/utils/client-resolver.js +19 -0
  32. package/dist/utils/task-filters.d.ts +29 -0
  33. package/dist/utils/task-filters.d.ts.map +1 -0
  34. package/dist/utils/task-filters.js +42 -0
  35. package/dist/utils/task-trimmer.d.ts +47 -0
  36. package/dist/utils/task-trimmer.d.ts.map +1 -0
  37. package/dist/utils/task-trimmer.js +50 -0
  38. package/dist/utils/to-tsv.d.ts +8 -0
  39. package/dist/utils/to-tsv.d.ts.map +1 -0
  40. package/dist/utils/to-tsv.js +64 -0
  41. package/mcp-inspector.json +14 -0
  42. package/package.json +56 -0
  43. package/src/auth/http.ts +61 -0
  44. package/src/auth/stdio.ts +33 -0
  45. package/src/auth/types.ts +9 -0
  46. package/src/config/transport.ts +80 -0
  47. package/src/main.ts +542 -0
  48. package/src/schemas.ts +169 -0
  49. package/src/utils/client-resolver.ts +23 -0
  50. package/src/utils/task-filters.ts +49 -0
  51. package/src/utils/task-trimmer.ts +81 -0
  52. package/src/utils/to-tsv.ts +73 -0
  53. package/tsconfig.json +36 -0
@@ -0,0 +1,23 @@
1
+ import { SunsamaClient } from "sunsama-api";
2
+ import { getGlobalSunsamaClient } from "../auth/stdio.js";
3
+ import type { SessionData } from "../auth/types.js";
4
+ import { getTransportConfig } from "../config/transport.js";
5
+
6
+ /**
7
+ * Gets the appropriate SunsamaClient instance based on transport type
8
+ * @param session - Session data for HTTP transport (null for stdio)
9
+ * @returns Authenticated SunsamaClient instance
10
+ * @throws {Error} If session is not available for HTTP transport or global client not initialized for stdio
11
+ */
12
+ export function getSunsamaClient(session: SessionData | null): SunsamaClient {
13
+ const transportConfig = getTransportConfig();
14
+
15
+ if (transportConfig.transportType === "httpStream") {
16
+ if (!session?.sunsamaClient) {
17
+ throw new Error("Session not available. Authentication may have failed.");
18
+ }
19
+ return session.sunsamaClient;
20
+ }
21
+
22
+ return getGlobalSunsamaClient();
23
+ }
@@ -0,0 +1,49 @@
1
+ import type { Task } from "sunsama-api";
2
+ import type { CompletionFilter } from "../schemas.js";
3
+
4
+ /**
5
+ * Filters an array of tasks based on completion status.
6
+ *
7
+ * @param tasks - Array of Task objects to filter
8
+ * @param filter - Completion filter mode:
9
+ * - "all": Return all tasks (no filtering)
10
+ * - "incomplete": Return only tasks where completed is false
11
+ * - "completed": Return only tasks where completed is true
12
+ * @returns Filtered array of tasks
13
+ * @throws {Error} If an invalid filter value is provided
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * const allTasks = await sunsamaClient.getTasksByDay("2024-01-15", "UTC");
18
+ *
19
+ * // Get only incomplete tasks for focused work
20
+ * const incompleteTasks = filterTasksByCompletion(allTasks, "incomplete");
21
+ *
22
+ * // Get only completed tasks for review
23
+ * const completedTasks = filterTasksByCompletion(allTasks, "completed");
24
+ *
25
+ * // Get all tasks (no filtering)
26
+ * const allTasksFiltered = filterTasksByCompletion(allTasks, "all");
27
+ * ```
28
+ */
29
+ export function filterTasksByCompletion(tasks: Task[], filter: CompletionFilter): Task[] {
30
+ // Validate filter parameter
31
+ if (!["all", "incomplete", "completed"].includes(filter)) {
32
+ throw new Error(`Invalid completion filter: ${filter}. Must be 'all', 'incomplete', or 'completed'.`);
33
+ }
34
+
35
+ switch (filter) {
36
+ case "all":
37
+ return tasks; // No filtering - return all tasks
38
+
39
+ case "incomplete":
40
+ return tasks.filter(task => !task.completed);
41
+
42
+ case "completed":
43
+ return tasks.filter(task => task.completed);
44
+
45
+ default:
46
+ // TypeScript exhaustiveness check - should never reach here
47
+ throw new Error(`Unhandled completion filter: ${filter satisfies never}`);
48
+ }
49
+ }
@@ -0,0 +1,81 @@
1
+ import type { Task } from "sunsama-api";
2
+
3
+ /**
4
+ * Trimmed task type containing only essential properties for API responses.
5
+ * Reduces response size by 60-80% while preserving core task information.
6
+ *
7
+ * Extends core Task properties with simplified versions of complex fields:
8
+ * - integration: Service name only (instead of full nested object)
9
+ * - subtasks: Array of titles only (instead of full subtask objects)
10
+ */
11
+ export type TrimmedTask = Pick<Task,
12
+ | '_id'
13
+ | 'text'
14
+ | 'completed'
15
+ | 'assigneeId'
16
+ | 'createdAt'
17
+ | 'lastModified'
18
+ | 'objectiveId'
19
+ | 'completeDate'
20
+ | 'timeEstimate'
21
+ | 'dueDate'
22
+ | 'notes'
23
+ | 'streamIds'
24
+ > & {
25
+ /** Integration service name (e.g., 'website', 'googleCalendar') or null */
26
+ integration: string | null;
27
+ /** Array of subtask titles only (simplified from full subtask objects) */
28
+ subtasks: string[];
29
+ };
30
+
31
+ /**
32
+ * Trims a task object to include only essential properties for API responses.
33
+ *
34
+ * Included properties:
35
+ * - Core identifiers: _id, assigneeId, objectiveId
36
+ * - Content: text, notes
37
+ * - Status: completed, completeDate
38
+ * - Timestamps: createdAt, lastModified
39
+ * - Planning: timeEstimate, dueDate, streamIds
40
+ * - Simplified integration: service name only (not full nested object)
41
+ * - Simplified subtasks: titles only (not full objects with metadata)
42
+ *
43
+ * Excluded properties (for size reduction):
44
+ * - Internal metadata: notesChecksum, editorVersion, collabSnapshot, __typename
45
+ * - Complex nested objects: full integration objects, sequence, ritual, eventInfo, runDate, timeHorizon
46
+ * - Large arrays: comments, orderings, backlogOrderings, actualTime, scheduledTime, full subtask objects
47
+ * - UI state: subtasksCollapsed, seededEventIds, followers
48
+ * - Redundant fields: completedBy, completeOn, recommendedTimeEstimate, recommendedStreamId, notesMarkdown
49
+ * - Metadata: groupId, taskType, private, deleted, createdBy, archivedAt, duration
50
+ *
51
+ * @param task - Full task object from Sunsama API
52
+ * @returns Trimmed task object with only essential properties
53
+ */
54
+ export function trimTaskForResponse(task: Task): TrimmedTask {
55
+ return {
56
+ _id: task._id,
57
+ assigneeId: task.assigneeId,
58
+ completeDate: task.completeDate,
59
+ completed: task.completed,
60
+ createdAt: task.createdAt,
61
+ dueDate: task.dueDate,
62
+ integration: task.integration?.service || null,
63
+ lastModified: task.lastModified,
64
+ notes: task.notes,
65
+ objectiveId: task.objectiveId,
66
+ streamIds: task.streamIds,
67
+ subtasks: task.subtasks.map((st) => st.title),
68
+ text: task.text,
69
+ timeEstimate: task.timeEstimate
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Trims an array of task objects to include only essential properties.
75
+ *
76
+ * @param tasks - Array of full task objects from Sunsama API
77
+ * @returns Array of trimmed task objects
78
+ */
79
+ export function trimTasksForResponse(tasks: Task[]): TrimmedTask[] {
80
+ return tasks.map(trimTaskForResponse);
81
+ }
@@ -0,0 +1,73 @@
1
+ import Papa from 'papaparse';
2
+
3
+ /**
4
+ * Recursively converts nested objects and arrays to JSON strings
5
+ * @param obj - The object to process
6
+ * @returns The object with nested structures converted to strings
7
+ */
8
+ function stringifyNestedObjects(obj: any): any {
9
+ if (obj === null || obj === undefined) {
10
+ return obj;
11
+ }
12
+
13
+ if (Array.isArray(obj)) {
14
+ return JSON.stringify(obj);
15
+ }
16
+
17
+ if (typeof obj === 'object') {
18
+ const result: any = {};
19
+ for (const [key, value] of Object.entries(obj)) {
20
+ if (value === null || value === undefined) {
21
+ result[key] = value;
22
+ } else if (typeof value === 'object') {
23
+ // Convert nested objects and arrays to JSON strings
24
+ result[key] = JSON.stringify(value);
25
+ } else {
26
+ result[key] = value;
27
+ }
28
+ }
29
+ return result;
30
+ }
31
+
32
+ return obj;
33
+ }
34
+
35
+ /**
36
+ * Converts data to TSV (Tab-Separated Values) format
37
+ * @param data - The data to convert (object or array of objects)
38
+ * @param fields - Optional array of field names to include in specific order
39
+ * @returns TSV string representation of the data
40
+ */
41
+ export function toTsv(data: any, fields?: string[]): string {
42
+ // Handle null/undefined data
43
+ if (data === null || data === undefined) {
44
+ return '';
45
+ }
46
+
47
+ // If data is not an array, wrap it in an array
48
+ const arrayData = Array.isArray(data) ? data : [data];
49
+
50
+ // Handle empty arrays
51
+ if (arrayData.length === 0) {
52
+ return '';
53
+ }
54
+
55
+ // Process each item to stringify nested objects
56
+ const processedData = arrayData.map(item => stringifyNestedObjects(item));
57
+
58
+ // Configure papaparse options
59
+ const parseOptions: any = {
60
+ delimiter: '\t', // Use tab as delimiter
61
+ header: true, // Include column headers
62
+ skipEmptyLines: true, // Skip empty rows
63
+ quotes: true // Quote fields to handle JSON strings properly
64
+ };
65
+
66
+ // If specific fields are requested, use them to control column order
67
+ if (fields && fields.length > 0) {
68
+ parseOptions.columns = fields;
69
+ }
70
+
71
+ // Convert to TSV using papaparse
72
+ return Papa.unparse(processedData, parseOptions);
73
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "compilerOptions": {
3
+ // Environment setup & latest features
4
+ "lib": ["ESNext"],
5
+ "target": "ESNext",
6
+ "module": "ESNext",
7
+ "moduleDetection": "force",
8
+ "jsx": "react-jsx",
9
+ "allowJs": true,
10
+
11
+ // Bundler mode
12
+ "moduleResolution": "bundler",
13
+ "verbatimModuleSyntax": true,
14
+ "noEmit": false,
15
+
16
+ // Build output configuration
17
+ "outDir": "dist",
18
+ "declaration": true,
19
+ "declarationMap": true,
20
+
21
+ // Best practices
22
+ "strict": true,
23
+ "skipLibCheck": true,
24
+ "noFallthroughCasesInSwitch": true,
25
+ "noUncheckedIndexedAccess": true,
26
+
27
+ // Some stricter flags (disabled by default)
28
+ "noUnusedLocals": false,
29
+ "noUnusedParameters": false,
30
+ "noPropertyAccessFromIndexSignature": false
31
+ },
32
+ "exclude": [
33
+ "dist",
34
+ "node_modules"
35
+ ]
36
+ }