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.
- package/.changeset/README.md +8 -0
- package/.changeset/config.json +11 -0
- package/.claude/settings.local.json +29 -0
- package/.env.example +10 -0
- package/CHANGELOG.md +29 -0
- package/CLAUDE.md +137 -0
- package/LICENSE +21 -0
- package/README.md +143 -0
- package/TODO_PREPUBLISH.md +182 -0
- package/bun.lock +515 -0
- package/dist/auth/http.d.ts +20 -0
- package/dist/auth/http.d.ts.map +1 -0
- package/dist/auth/http.js +52 -0
- package/dist/auth/stdio.d.ts +13 -0
- package/dist/auth/stdio.d.ts.map +1 -0
- package/dist/auth/stdio.js +27 -0
- package/dist/auth/types.d.ts +9 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +1 -0
- package/dist/config/transport.d.ts +32 -0
- package/dist/config/transport.d.ts.map +1 -0
- package/dist/config/transport.js +62 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +473 -0
- package/dist/schemas.d.ts +522 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +124 -0
- package/dist/utils/client-resolver.d.ts +10 -0
- package/dist/utils/client-resolver.d.ts.map +1 -0
- package/dist/utils/client-resolver.js +19 -0
- package/dist/utils/task-filters.d.ts +29 -0
- package/dist/utils/task-filters.d.ts.map +1 -0
- package/dist/utils/task-filters.js +42 -0
- package/dist/utils/task-trimmer.d.ts +47 -0
- package/dist/utils/task-trimmer.d.ts.map +1 -0
- package/dist/utils/task-trimmer.js +50 -0
- package/dist/utils/to-tsv.d.ts +8 -0
- package/dist/utils/to-tsv.d.ts.map +1 -0
- package/dist/utils/to-tsv.js +64 -0
- package/mcp-inspector.json +14 -0
- package/package.json +56 -0
- package/src/auth/http.ts +61 -0
- package/src/auth/stdio.ts +33 -0
- package/src/auth/types.ts +9 -0
- package/src/config/transport.ts +80 -0
- package/src/main.ts +542 -0
- package/src/schemas.ts +169 -0
- package/src/utils/client-resolver.ts +23 -0
- package/src/utils/task-filters.ts +49 -0
- package/src/utils/task-trimmer.ts +81 -0
- package/src/utils/to-tsv.ts +73 -0
- 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
|
+
}
|