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
package/dist/main.js
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
import { FastMCP } from "fastmcp";
|
|
2
|
+
import { httpStreamAuthenticator } from "./auth/http.js";
|
|
3
|
+
import { initializeStdioAuth } from "./auth/stdio.js";
|
|
4
|
+
import { getTransportConfig } from "./config/transport.js";
|
|
5
|
+
import { createTaskSchema, deleteTaskSchema, getStreamsSchema, getTasksBacklogSchema, getTasksByDaySchema, getUserSchema, updateTaskCompleteSchema } from "./schemas.js";
|
|
6
|
+
import { getSunsamaClient } from "./utils/client-resolver.js";
|
|
7
|
+
import { filterTasksByCompletion } from "./utils/task-filters.js";
|
|
8
|
+
import { trimTasksForResponse } from "./utils/task-trimmer.js";
|
|
9
|
+
import { toTsv } from "./utils/to-tsv.js";
|
|
10
|
+
// Get transport configuration with validation
|
|
11
|
+
const transportConfig = getTransportConfig();
|
|
12
|
+
// For stdio transport, authenticate at startup with environment variables
|
|
13
|
+
if (transportConfig.transportType === "stdio") {
|
|
14
|
+
await initializeStdioAuth();
|
|
15
|
+
}
|
|
16
|
+
const server = new FastMCP({
|
|
17
|
+
name: "Sunsama API Server",
|
|
18
|
+
version: "0.2.0",
|
|
19
|
+
instructions: `
|
|
20
|
+
This MCP server provides access to the Sunsama API for task and project management.
|
|
21
|
+
|
|
22
|
+
Available tools:
|
|
23
|
+
- Authentication: login, logout, check authentication status
|
|
24
|
+
- User operations: get current user information
|
|
25
|
+
- Task operations: get tasks by day, get backlog tasks
|
|
26
|
+
- Stream operations: get streams/channels for the user's group
|
|
27
|
+
|
|
28
|
+
Authentication is required for all operations. You can either:
|
|
29
|
+
1. Login with email/password using the 'login' tool
|
|
30
|
+
2. Use a session token if you have one
|
|
31
|
+
|
|
32
|
+
The server maintains session state per MCP connection, so you only need to authenticate once per session.
|
|
33
|
+
`.trim(),
|
|
34
|
+
// dynamically handle authentication
|
|
35
|
+
...(transportConfig.transportType === "httpStream" ? {
|
|
36
|
+
authenticate: httpStreamAuthenticator,
|
|
37
|
+
} : {})
|
|
38
|
+
});
|
|
39
|
+
// User Operations
|
|
40
|
+
server.addTool({
|
|
41
|
+
name: "get-user",
|
|
42
|
+
description: "Get current user information including profile, timezone, and group details",
|
|
43
|
+
parameters: getUserSchema,
|
|
44
|
+
execute: async (_args, { session, log }) => {
|
|
45
|
+
try {
|
|
46
|
+
log.info("Getting user information");
|
|
47
|
+
// Get the appropriate client based on transport type
|
|
48
|
+
const sunsamaClient = getSunsamaClient(session);
|
|
49
|
+
// Get user information
|
|
50
|
+
const user = await sunsamaClient.getUser();
|
|
51
|
+
log.info("Successfully retrieved user information", { userId: user._id });
|
|
52
|
+
return {
|
|
53
|
+
content: [
|
|
54
|
+
{
|
|
55
|
+
type: "text",
|
|
56
|
+
text: JSON.stringify(user, null, 2)
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
log.error("Failed to get user information", { error: error instanceof Error ? error.message : 'Unknown error' });
|
|
63
|
+
throw new Error(`Failed to get user information: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
// Task Operations
|
|
68
|
+
server.addTool({
|
|
69
|
+
name: "get-tasks-backlog",
|
|
70
|
+
description: "Get tasks from the backlog",
|
|
71
|
+
parameters: getTasksBacklogSchema,
|
|
72
|
+
execute: async (_args, { session, log }) => {
|
|
73
|
+
try {
|
|
74
|
+
log.info("Getting backlog tasks");
|
|
75
|
+
// Get the appropriate client based on transport type
|
|
76
|
+
const sunsamaClient = getSunsamaClient(session);
|
|
77
|
+
// Get backlog tasks
|
|
78
|
+
const tasks = await sunsamaClient.getTasksBacklog();
|
|
79
|
+
// Trim tasks to reduce response size while preserving essential data
|
|
80
|
+
const trimmedTasks = trimTasksForResponse(tasks);
|
|
81
|
+
log.info("Successfully retrieved backlog tasks", { count: tasks.length });
|
|
82
|
+
return {
|
|
83
|
+
content: [
|
|
84
|
+
{
|
|
85
|
+
type: "text",
|
|
86
|
+
text: toTsv(trimmedTasks)
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
log.error("Failed to get backlog tasks", { error: error instanceof Error ? error.message : 'Unknown error' });
|
|
93
|
+
throw new Error(`Failed to get backlog tasks: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
server.addTool({
|
|
98
|
+
name: "get-tasks-by-day",
|
|
99
|
+
description: "Get tasks for a specific day with optional filtering by completion status",
|
|
100
|
+
parameters: getTasksByDaySchema,
|
|
101
|
+
execute: async (args, { session, log }) => {
|
|
102
|
+
try {
|
|
103
|
+
// Extract and set defaults for parameters
|
|
104
|
+
const completionFilter = args.completionFilter || "all";
|
|
105
|
+
log.info("Getting tasks for day", {
|
|
106
|
+
day: args.day,
|
|
107
|
+
timezone: args.timezone,
|
|
108
|
+
completionFilter: completionFilter
|
|
109
|
+
});
|
|
110
|
+
// Get the appropriate client based on transport type
|
|
111
|
+
const sunsamaClient = getSunsamaClient(session);
|
|
112
|
+
// If no timezone provided, we need to get the user's default timezone
|
|
113
|
+
let timezone = args.timezone;
|
|
114
|
+
if (!timezone) {
|
|
115
|
+
timezone = await sunsamaClient.getUserTimezone();
|
|
116
|
+
log.info("Using user's default timezone", { timezone });
|
|
117
|
+
}
|
|
118
|
+
// Get tasks for the specified day with the determined timezone
|
|
119
|
+
const tasks = await sunsamaClient.getTasksByDay(args.day, timezone);
|
|
120
|
+
// Apply completion filter BEFORE trimming for efficiency
|
|
121
|
+
const filteredTasks = filterTasksByCompletion(tasks, completionFilter);
|
|
122
|
+
// Trim tasks to reduce response size while preserving essential data
|
|
123
|
+
const trimmedTasks = trimTasksForResponse(filteredTasks);
|
|
124
|
+
log.info("Successfully retrieved tasks for day", {
|
|
125
|
+
day: args.day,
|
|
126
|
+
totalCount: tasks.length,
|
|
127
|
+
filteredCount: filteredTasks.length,
|
|
128
|
+
filter: completionFilter,
|
|
129
|
+
timezone: timezone
|
|
130
|
+
});
|
|
131
|
+
return {
|
|
132
|
+
content: [
|
|
133
|
+
{
|
|
134
|
+
type: "text",
|
|
135
|
+
text: toTsv(trimmedTasks)
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
log.error("Failed to get tasks by day", {
|
|
142
|
+
day: args.day,
|
|
143
|
+
timezone: args.timezone,
|
|
144
|
+
completionFilter: args.completionFilter,
|
|
145
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
146
|
+
});
|
|
147
|
+
throw new Error(`Failed to get tasks for ${args.day}: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
// Task Mutation Operations
|
|
152
|
+
server.addTool({
|
|
153
|
+
name: "create-task",
|
|
154
|
+
description: "Create a new task with optional properties",
|
|
155
|
+
parameters: createTaskSchema,
|
|
156
|
+
execute: async (args, { session, log }) => {
|
|
157
|
+
try {
|
|
158
|
+
// Extract parameters from args
|
|
159
|
+
const { text, notes, streamIds, timeEstimate, dueDate, snoozeUntil, private: isPrivate, taskId } = args;
|
|
160
|
+
log.info("Creating new task", {
|
|
161
|
+
text: text,
|
|
162
|
+
hasNotes: !!notes,
|
|
163
|
+
streamCount: streamIds?.length || 0,
|
|
164
|
+
timeEstimate: timeEstimate,
|
|
165
|
+
hasDueDate: !!dueDate,
|
|
166
|
+
hasSnooze: !!snoozeUntil,
|
|
167
|
+
isPrivate: isPrivate,
|
|
168
|
+
customTaskId: !!taskId
|
|
169
|
+
});
|
|
170
|
+
// Get the appropriate client based on transport type
|
|
171
|
+
const sunsamaClient = getSunsamaClient(session);
|
|
172
|
+
// Build options object for createTask
|
|
173
|
+
const options = {};
|
|
174
|
+
if (notes)
|
|
175
|
+
options.notes = notes;
|
|
176
|
+
if (streamIds)
|
|
177
|
+
options.streamIds = streamIds;
|
|
178
|
+
if (timeEstimate)
|
|
179
|
+
options.timeEstimate = timeEstimate;
|
|
180
|
+
if (dueDate)
|
|
181
|
+
options.dueDate = dueDate;
|
|
182
|
+
if (snoozeUntil)
|
|
183
|
+
options.snoozeUntil = snoozeUntil;
|
|
184
|
+
if (isPrivate !== undefined)
|
|
185
|
+
options.private = isPrivate;
|
|
186
|
+
if (taskId)
|
|
187
|
+
options.taskId = taskId;
|
|
188
|
+
// Call sunsamaClient.createTask(text, options)
|
|
189
|
+
const result = await sunsamaClient.createTask(text, options);
|
|
190
|
+
log.info("Successfully created task", {
|
|
191
|
+
taskId: result.updatedFields?._id || 'unknown',
|
|
192
|
+
title: text,
|
|
193
|
+
success: result.success
|
|
194
|
+
});
|
|
195
|
+
return {
|
|
196
|
+
content: [
|
|
197
|
+
{
|
|
198
|
+
type: "text",
|
|
199
|
+
text: JSON.stringify({
|
|
200
|
+
success: result.success,
|
|
201
|
+
taskId: result.updatedFields?._id,
|
|
202
|
+
title: text,
|
|
203
|
+
created: true,
|
|
204
|
+
updatedFields: result.updatedFields
|
|
205
|
+
})
|
|
206
|
+
}
|
|
207
|
+
]
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
log.error("Failed to create task", {
|
|
212
|
+
text: args.text,
|
|
213
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
214
|
+
});
|
|
215
|
+
throw new Error(`Failed to create task: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
server.addTool({
|
|
220
|
+
name: "update-task-complete",
|
|
221
|
+
description: "Mark a task as complete with optional completion timestamp",
|
|
222
|
+
parameters: updateTaskCompleteSchema,
|
|
223
|
+
execute: async (args, { session, log }) => {
|
|
224
|
+
try {
|
|
225
|
+
// Extract taskId and optional parameters
|
|
226
|
+
const { taskId, completeOn, limitResponsePayload } = args;
|
|
227
|
+
log.info("Marking task as complete", {
|
|
228
|
+
taskId: taskId,
|
|
229
|
+
hasCustomCompleteOn: !!completeOn,
|
|
230
|
+
limitResponsePayload: limitResponsePayload
|
|
231
|
+
});
|
|
232
|
+
// Get the appropriate client based on transport type
|
|
233
|
+
const sunsamaClient = getSunsamaClient(session);
|
|
234
|
+
// Call sunsamaClient.updateTaskComplete(taskId, completeOn, limitResponsePayload)
|
|
235
|
+
const result = await sunsamaClient.updateTaskComplete(taskId, completeOn, limitResponsePayload);
|
|
236
|
+
log.info("Successfully marked task as complete", {
|
|
237
|
+
taskId: taskId,
|
|
238
|
+
success: result.success,
|
|
239
|
+
updatedFields: !!result.updatedFields
|
|
240
|
+
});
|
|
241
|
+
return {
|
|
242
|
+
content: [
|
|
243
|
+
{
|
|
244
|
+
type: "text",
|
|
245
|
+
text: JSON.stringify({
|
|
246
|
+
success: result.success,
|
|
247
|
+
taskId: taskId,
|
|
248
|
+
completed: true,
|
|
249
|
+
updatedFields: result.updatedFields
|
|
250
|
+
})
|
|
251
|
+
}
|
|
252
|
+
]
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
log.error("Failed to mark task as complete", {
|
|
257
|
+
taskId: args.taskId,
|
|
258
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
259
|
+
});
|
|
260
|
+
throw new Error(`Failed to mark task as complete: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
server.addTool({
|
|
265
|
+
name: "delete-task",
|
|
266
|
+
description: "Delete a task permanently",
|
|
267
|
+
parameters: deleteTaskSchema,
|
|
268
|
+
execute: async (args, { session, log }) => {
|
|
269
|
+
try {
|
|
270
|
+
// Extract taskId and optional parameters
|
|
271
|
+
const { taskId, limitResponsePayload, wasTaskMerged } = args;
|
|
272
|
+
log.info("Deleting task", {
|
|
273
|
+
taskId: taskId,
|
|
274
|
+
limitResponsePayload: limitResponsePayload,
|
|
275
|
+
wasTaskMerged: wasTaskMerged
|
|
276
|
+
});
|
|
277
|
+
// Get the appropriate client based on transport type
|
|
278
|
+
const sunsamaClient = getSunsamaClient(session);
|
|
279
|
+
// Call sunsamaClient.deleteTask(taskId, limitResponsePayload, wasTaskMerged)
|
|
280
|
+
const result = await sunsamaClient.deleteTask(taskId, limitResponsePayload, wasTaskMerged);
|
|
281
|
+
log.info("Successfully deleted task", {
|
|
282
|
+
taskId: taskId,
|
|
283
|
+
success: result.success
|
|
284
|
+
});
|
|
285
|
+
return {
|
|
286
|
+
content: [
|
|
287
|
+
{
|
|
288
|
+
type: "text",
|
|
289
|
+
text: JSON.stringify({
|
|
290
|
+
success: result.success,
|
|
291
|
+
taskId: taskId,
|
|
292
|
+
deleted: true,
|
|
293
|
+
updatedFields: result.updatedFields
|
|
294
|
+
})
|
|
295
|
+
}
|
|
296
|
+
]
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
log.error("Failed to delete task", {
|
|
301
|
+
taskId: args.taskId,
|
|
302
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
303
|
+
});
|
|
304
|
+
throw new Error(`Failed to delete task: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
// Stream Operations
|
|
309
|
+
server.addTool({
|
|
310
|
+
name: "get-streams",
|
|
311
|
+
description: "Get streams for the user's group (streams are called 'channels' in the Sunsama UI)",
|
|
312
|
+
parameters: getStreamsSchema,
|
|
313
|
+
execute: async (_args, { session, log }) => {
|
|
314
|
+
try {
|
|
315
|
+
log.info("Getting streams for user's group");
|
|
316
|
+
// Get the appropriate client based on transport type
|
|
317
|
+
const sunsamaClient = getSunsamaClient(session);
|
|
318
|
+
// Get streams for the user's group
|
|
319
|
+
const streams = await sunsamaClient.getStreamsByGroupId();
|
|
320
|
+
log.info("Successfully retrieved streams", { count: streams.length });
|
|
321
|
+
return {
|
|
322
|
+
content: [
|
|
323
|
+
{
|
|
324
|
+
type: "text",
|
|
325
|
+
text: toTsv(streams)
|
|
326
|
+
}
|
|
327
|
+
]
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
catch (error) {
|
|
331
|
+
log.error("Failed to get streams", { error: error instanceof Error ? error.message : 'Unknown error' });
|
|
332
|
+
throw new Error(`Failed to get streams: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
server.addResource({
|
|
337
|
+
uri: "sunsama://api/docs",
|
|
338
|
+
name: "Sunsama API Documentation",
|
|
339
|
+
description: "Documentation for the Sunsama API endpoints and data structures",
|
|
340
|
+
mimeType: "text/markdown",
|
|
341
|
+
load: async () => {
|
|
342
|
+
return {
|
|
343
|
+
text: `# Sunsama MCP Server Documentation
|
|
344
|
+
|
|
345
|
+
## Overview
|
|
346
|
+
This MCP server provides access to the Sunsama API for task and project management.
|
|
347
|
+
Authentication is handled server-side using environment variables.
|
|
348
|
+
|
|
349
|
+
## Authentication
|
|
350
|
+
The server authenticates to Sunsama using environment variables:
|
|
351
|
+
- \`SUNSAMA_EMAIL\`: Your Sunsama account email
|
|
352
|
+
- \`SUNSAMA_PASSWORD\`: Your Sunsama account password
|
|
353
|
+
|
|
354
|
+
Authentication happens automatically on server startup. No client-side authentication is required.
|
|
355
|
+
|
|
356
|
+
## Available Tools
|
|
357
|
+
|
|
358
|
+
### User Operations
|
|
359
|
+
- **get-user**: Get current user information
|
|
360
|
+
- Parameters: none
|
|
361
|
+
- Returns: User object with profile, timezone, and primary group details
|
|
362
|
+
|
|
363
|
+
### Task Operations
|
|
364
|
+
- **get-tasks-by-day**: Get tasks for a specific day with optional filtering
|
|
365
|
+
- Parameters:
|
|
366
|
+
- \`day\` (required): Date in YYYY-MM-DD format
|
|
367
|
+
- \`timezone\` (optional): Timezone string (e.g., "America/New_York")
|
|
368
|
+
- \`completionFilter\` (optional): Filter by completion status
|
|
369
|
+
- \`"all"\` (default): Return all tasks
|
|
370
|
+
- \`"incomplete"\`: Return only incomplete tasks
|
|
371
|
+
- \`"completed"\`: Return only completed tasks
|
|
372
|
+
- Returns: Array of filtered Task objects for the specified day
|
|
373
|
+
|
|
374
|
+
- **get-tasks-backlog**: Get tasks from the backlog
|
|
375
|
+
- Parameters: none
|
|
376
|
+
- Returns: Array of Task objects from the backlog
|
|
377
|
+
|
|
378
|
+
### Stream Operations
|
|
379
|
+
- **get-streams**: Get streams for the user's group
|
|
380
|
+
- Parameters: none
|
|
381
|
+
- Returns: Array of Stream objects
|
|
382
|
+
- Note: Streams are called "channels" in the Sunsama UI. If a user requests channels, use this tool.
|
|
383
|
+
|
|
384
|
+
## Data Types
|
|
385
|
+
|
|
386
|
+
### User Object
|
|
387
|
+
\`\`\`typescript
|
|
388
|
+
{
|
|
389
|
+
_id: string;
|
|
390
|
+
email: string;
|
|
391
|
+
profile: {
|
|
392
|
+
_id: string;
|
|
393
|
+
email: string;
|
|
394
|
+
firstName: string;
|
|
395
|
+
lastName: string;
|
|
396
|
+
timezone: string;
|
|
397
|
+
avatarUrl?: string;
|
|
398
|
+
};
|
|
399
|
+
primaryGroup?: {
|
|
400
|
+
groupId: string;
|
|
401
|
+
name: string;
|
|
402
|
+
role?: string;
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
\`\`\`
|
|
406
|
+
|
|
407
|
+
### Task Object
|
|
408
|
+
\`\`\`typescript
|
|
409
|
+
{
|
|
410
|
+
_id: string;
|
|
411
|
+
title: string;
|
|
412
|
+
description?: string;
|
|
413
|
+
status: string;
|
|
414
|
+
createdAt: string;
|
|
415
|
+
updatedAt: string;
|
|
416
|
+
scheduledDate?: string;
|
|
417
|
+
completedAt?: string;
|
|
418
|
+
streamId?: string;
|
|
419
|
+
userId: string;
|
|
420
|
+
groupId: string;
|
|
421
|
+
}
|
|
422
|
+
\`\`\`
|
|
423
|
+
|
|
424
|
+
### Stream Object
|
|
425
|
+
Note: Streams are called "channels" in the Sunsama UI.
|
|
426
|
+
\`\`\`typescript
|
|
427
|
+
{
|
|
428
|
+
_id: string;
|
|
429
|
+
name: string;
|
|
430
|
+
color?: string;
|
|
431
|
+
groupId: string;
|
|
432
|
+
isActive: boolean;
|
|
433
|
+
createdAt: string;
|
|
434
|
+
updatedAt: string;
|
|
435
|
+
}
|
|
436
|
+
\`\`\`
|
|
437
|
+
|
|
438
|
+
## Error Handling
|
|
439
|
+
- All operations require valid Sunsama authentication
|
|
440
|
+
- Invalid dates will return validation errors
|
|
441
|
+
- Network errors are handled gracefully with descriptive messages
|
|
442
|
+
- Server maintains session state across tool calls
|
|
443
|
+
|
|
444
|
+
## Environment Setup
|
|
445
|
+
Required environment variables:
|
|
446
|
+
- \`API_KEY\`: MCP server authentication key
|
|
447
|
+
- \`SUNSAMA_EMAIL\`: Sunsama account email
|
|
448
|
+
- \`SUNSAMA_PASSWORD\`: Sunsama account password
|
|
449
|
+
- \`PORT\`: Server port (default: 3000)
|
|
450
|
+
`.trim()
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
// Start server with dynamic transport configuration
|
|
455
|
+
if (transportConfig.transportType === "httpStream") {
|
|
456
|
+
// Log startup information
|
|
457
|
+
console.log(`HTTP Stream configuration: port=${transportConfig.httpStream?.port}, endpoint=${transportConfig.httpStream?.endpoint}`);
|
|
458
|
+
server.start({
|
|
459
|
+
transportType: "httpStream",
|
|
460
|
+
httpStream: {
|
|
461
|
+
port: transportConfig.httpStream.port
|
|
462
|
+
}
|
|
463
|
+
}).then(() => {
|
|
464
|
+
console.log(`Sunsama MCP Server running on port ${transportConfig.httpStream.port}`);
|
|
465
|
+
console.log(`HTTP endpoint: ${transportConfig.httpStream.endpoint}`);
|
|
466
|
+
console.log("Authentication: HTTP Basic Auth with Sunsama credentials");
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
server.start({
|
|
471
|
+
transportType: "stdio"
|
|
472
|
+
});
|
|
473
|
+
}
|