sequentum-mcp 1.1.4 → 1.2.2

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 (60) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/README.md +104 -3
  3. package/dist/{api-client.d.ts → api/api-client.d.ts} +106 -4
  4. package/dist/api/api-client.d.ts.map +1 -0
  5. package/dist/{api-client.js → api/api-client.js} +345 -99
  6. package/dist/api/api-client.js.map +1 -0
  7. package/dist/{types.d.ts → api/types.d.ts} +122 -12
  8. package/dist/api/types.d.ts.map +1 -0
  9. package/dist/{types.js → api/types.js} +57 -0
  10. package/dist/api/types.js.map +1 -0
  11. package/dist/index.js +11 -1737
  12. package/dist/index.js.map +1 -1
  13. package/dist/server/cors.d.ts +33 -0
  14. package/dist/server/cors.d.ts.map +1 -0
  15. package/dist/server/cors.js +72 -0
  16. package/dist/server/cors.js.map +1 -0
  17. package/dist/server/handlers.d.ts +49 -0
  18. package/dist/server/handlers.d.ts.map +1 -0
  19. package/dist/server/handlers.js +1031 -0
  20. package/dist/server/handlers.js.map +1 -0
  21. package/dist/server/http-server.d.ts +13 -0
  22. package/dist/server/http-server.d.ts.map +1 -0
  23. package/dist/server/http-server.js +393 -0
  24. package/dist/server/http-server.js.map +1 -0
  25. package/dist/server/policies.d.ts +19 -0
  26. package/dist/server/policies.d.ts.map +1 -0
  27. package/dist/server/policies.js +32 -0
  28. package/dist/server/policies.js.map +1 -0
  29. package/dist/server/prompts.d.ts +19 -0
  30. package/dist/server/prompts.d.ts.map +1 -0
  31. package/dist/server/prompts.js +464 -0
  32. package/dist/server/prompts.js.map +1 -0
  33. package/dist/server/resources.d.ts +26 -0
  34. package/dist/server/resources.d.ts.map +1 -0
  35. package/dist/server/resources.js +348 -0
  36. package/dist/server/resources.js.map +1 -0
  37. package/dist/server/tools.d.ts +9 -0
  38. package/dist/server/tools.d.ts.map +1 -0
  39. package/dist/server/tools.js +977 -0
  40. package/dist/server/tools.js.map +1 -0
  41. package/dist/utils/oauth-metadata.d.ts.map +1 -0
  42. package/dist/utils/oauth-metadata.js.map +1 -0
  43. package/dist/{validation.d.ts → utils/validation.d.ts} +25 -2
  44. package/dist/utils/validation.d.ts.map +1 -0
  45. package/dist/{validation.js → utils/validation.js} +43 -3
  46. package/dist/utils/validation.js.map +1 -0
  47. package/docs/prompts-reference.md +370 -0
  48. package/docs/resources-reference.md +300 -0
  49. package/docs/tool-reference.md +244 -2
  50. package/package.json +4 -3
  51. package/dist/api-client.d.ts.map +0 -1
  52. package/dist/api-client.js.map +0 -1
  53. package/dist/oauth-metadata.d.ts.map +0 -1
  54. package/dist/oauth-metadata.js.map +0 -1
  55. package/dist/types.d.ts.map +0 -1
  56. package/dist/types.js.map +0 -1
  57. package/dist/validation.d.ts.map +0 -1
  58. package/dist/validation.js.map +0 -1
  59. /package/dist/{oauth-metadata.d.ts → utils/oauth-metadata.d.ts} +0 -0
  60. /package/dist/{oauth-metadata.js → utils/oauth-metadata.js} +0 -0
@@ -0,0 +1,1031 @@
1
+ /**
2
+ * MCP Server Factory and Tool Handlers
3
+ *
4
+ * Contains input validation helpers, response helpers, and the
5
+ * createMcpServer factory that wires tool definitions to their handlers.
6
+ */
7
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
8
+ import { SUFFICIENCY_POLICY } from "./policies.js";
9
+ import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
10
+ import { ApiRequestError, RateLimitError, AuthenticationError } from "../api/types.js";
11
+ import { getDefaultDateRange, validateBoolean, validateDateRange, validateEnum, validateISODate, validateJsonString, validateNumber, validateStartTimeInFuture, validateString, } from "../utils/validation.js";
12
+ import { tools } from "./tools.js";
13
+ import { resources, resourceTemplates, readResource } from "./resources.js";
14
+ import { prompts, getPromptMessages } from "./prompts.js";
15
+ // ==========================================
16
+ // Response Helpers
17
+ // ==========================================
18
+ /**
19
+ * Map RunStatus numeric value to human-readable string
20
+ */
21
+ function getRunStatusLabel(status) {
22
+ const statusMap = {
23
+ 0: "Invalid",
24
+ 1: "Running",
25
+ 2: "Exporting",
26
+ 3: "Starting",
27
+ 4: "Queuing",
28
+ 5: "Stopping",
29
+ 6: "Failure",
30
+ 7: "Failed",
31
+ 8: "Stopped",
32
+ 9: "Completed",
33
+ 10: "Success",
34
+ 11: "Skipped",
35
+ 12: "Waiting",
36
+ };
37
+ if (status === undefined || status === null) {
38
+ return "Never Run";
39
+ }
40
+ return statusMap[status] ?? `Unknown (${status})`;
41
+ }
42
+ /**
43
+ * Transform agent list to summary format for display
44
+ */
45
+ function summarizeAgents(agents) {
46
+ return agents.map((a) => ({
47
+ id: a.id,
48
+ name: a.name,
49
+ description: a.description,
50
+ status: getRunStatusLabel(a.status),
51
+ configType: a.configType,
52
+ version: a.version,
53
+ lastActivity: a.lastActivity,
54
+ }));
55
+ }
56
+ /**
57
+ * Type guard for paginated agent responses from the API.
58
+ */
59
+ export function isPaginatedResponse(r) {
60
+ return r !== null && typeof r === 'object' && 'agents' in r && Array.isArray(r.agents);
61
+ }
62
+ export function parseScheduleParams(params) {
63
+ return {
64
+ scheduleType: validateNumber(params, "scheduleType", { required: false, min: 1, max: 3, integer: true }),
65
+ startTime: validateString(params, "startTime", false),
66
+ cronExpression: validateString(params, "cronExpression", false),
67
+ runEveryCount: validateNumber(params, "runEveryCount", { required: false, min: 1, integer: true }),
68
+ runEveryPeriod: validateNumber(params, "runEveryPeriod", { required: false, min: 1, max: 5, integer: true }),
69
+ timezone: validateString(params, "timezone", false),
70
+ inputParameters: validateJsonString(params, "inputParameters", false),
71
+ isEnabled: validateBoolean(params, "isEnabled", false),
72
+ parallelism: validateNumber(params, "parallelism", { required: false, min: 1, max: 50, integer: true }),
73
+ parallelMaxConcurrency: validateNumber(params, "parallelMaxConcurrency", { required: false, min: 1, integer: true }),
74
+ parallelExport: validateString(params, "parallelExport", false),
75
+ logLevel: validateString(params, "logLevel", false),
76
+ logMode: validateString(params, "logMode", false),
77
+ isExclusive: validateBoolean(params, "isExclusive", false),
78
+ isWaitOnFailure: validateBoolean(params, "isWaitOnFailure", false),
79
+ };
80
+ }
81
+ export function validateScheduleStartTime(effectiveScheduleType, startTime) {
82
+ if (effectiveScheduleType === 1 && startTime) {
83
+ validateStartTimeInFuture(startTime, 1);
84
+ }
85
+ if (effectiveScheduleType === 2 && startTime) {
86
+ validateStartTimeInFuture(startTime, 0);
87
+ }
88
+ }
89
+ export function formatToolError(error) {
90
+ let errorMessage;
91
+ let errorPrefix = "Error";
92
+ if (error instanceof RateLimitError) {
93
+ errorPrefix = "Rate Limited";
94
+ const retryHint = error.retryAfterSeconds
95
+ ? ` Try again in ${error.retryAfterSeconds} seconds.`
96
+ : " Please wait a moment before retrying.";
97
+ errorMessage = `The Sequentum API rate limit has been reached.${retryHint}`;
98
+ }
99
+ else if (error instanceof AuthenticationError) {
100
+ errorPrefix = "Authentication Error";
101
+ errorMessage = error.message;
102
+ }
103
+ else if (error instanceof ApiRequestError) {
104
+ if (error.isUnauthorized) {
105
+ errorPrefix = "Authentication Failed";
106
+ errorMessage = "Your API key or OAuth token is invalid or has expired. Please check your credentials.";
107
+ }
108
+ else if (error.isForbidden) {
109
+ errorPrefix = "Access Denied";
110
+ errorMessage = "You don't have permission to perform this action. Check your API key permissions.";
111
+ }
112
+ else if (error.isNotFound) {
113
+ errorPrefix = "Not Found";
114
+ errorMessage = error.message;
115
+ }
116
+ else if (error.isServerError) {
117
+ errorPrefix = "Server Error";
118
+ errorMessage = `The Sequentum API encountered an internal error (${error.statusCode}). This is a server-side issue — please try again later.`;
119
+ }
120
+ else {
121
+ errorPrefix = `API Error (${error.statusCode})`;
122
+ errorMessage = error.message;
123
+ }
124
+ }
125
+ else if (error instanceof Error) {
126
+ errorMessage = error.message;
127
+ }
128
+ else {
129
+ errorMessage = "An unknown error occurred";
130
+ }
131
+ return {
132
+ content: [
133
+ {
134
+ type: "text",
135
+ text: `${errorPrefix}: ${errorMessage}`,
136
+ },
137
+ ],
138
+ isError: true,
139
+ };
140
+ }
141
+ // ==========================================
142
+ // Server Factory
143
+ // ==========================================
144
+ const DEBUG = process.env.DEBUG === '1';
145
+ function redactDebugArgs(args) {
146
+ if (args === null || typeof args !== "object" || Array.isArray(args)) {
147
+ return args;
148
+ }
149
+ const safeArgs = { ...args };
150
+ const sensitiveFields = [
151
+ "inputParameters",
152
+ "prompt",
153
+ "comments",
154
+ "apiKey",
155
+ "token",
156
+ "accessToken",
157
+ "refreshToken",
158
+ "password",
159
+ "secret",
160
+ "clientSecret",
161
+ ];
162
+ for (const field of sensitiveFields) {
163
+ if (field in safeArgs) {
164
+ safeArgs[field] = "[REDACTED]";
165
+ }
166
+ }
167
+ return safeArgs;
168
+ }
169
+ /**
170
+ * Create a new MCP Server instance with all handlers registered.
171
+ * Each session in HTTP mode needs its own Server instance.
172
+ *
173
+ * @param apiClient - The API client to use for this server instance
174
+ * @param version - The server version string from package.json
175
+ * @returns Configured MCP Server instance
176
+ */
177
+ export function createMcpServer(apiClient, version) {
178
+ const server = new Server({
179
+ name: "sequentum-mcp-server",
180
+ version,
181
+ }, {
182
+ capabilities: {
183
+ tools: {},
184
+ resources: {},
185
+ prompts: {},
186
+ },
187
+ // Sent to clients on `initialize`. Canonical text + JSDoc live in
188
+ // policies.ts; reinforced by the start_agent_build PRE-CALL CHECK in
189
+ // tools.ts. Keep these in sync if the policy's name or scope changes.
190
+ instructions: SUFFICIENCY_POLICY,
191
+ });
192
+ // Handle tool listing
193
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
194
+ tools,
195
+ }));
196
+ // Handle tool execution
197
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
198
+ const { name, arguments: args } = request.params;
199
+ if (DEBUG) {
200
+ console.error(`[DEBUG] Tool called: ${name}`);
201
+ console.error(`[DEBUG] Args: ${JSON.stringify(redactDebugArgs(args))}`);
202
+ }
203
+ try {
204
+ // TODO: Consider replacing this switch with a dispatch map (Record<string, HandlerFn>)
205
+ // to improve readability and testability as the tool count grows.
206
+ switch (name) {
207
+ // Agent Tools
208
+ case "list_agents": {
209
+ const params = args;
210
+ const statusNum = validateNumber(params, "status", { required: false, min: 0, max: 12, integer: true });
211
+ const spaceId = validateNumber(params, "spaceId", { required: false, min: 1, integer: true });
212
+ const search = validateString(params, "search", false);
213
+ const configTypeStr = validateString(params, "configType", false);
214
+ const sortColumn = validateString(params, "sortColumn", false);
215
+ const sortOrderStr = validateString(params, "sortOrder", false);
216
+ const pageIndex = validateNumber(params, "pageIndex", { required: false, min: 1, integer: true });
217
+ const recordsPerPage = validateNumber(params, "recordsPerPage", { required: false, min: 1, max: 100, integer: true });
218
+ // Build filters object - ALWAYS include pagination to ensure resource-efficient API calls
219
+ const filters = {
220
+ // Always enforce pagination with defaults (pageIndex is 1-based per API spec)
221
+ pageIndex: pageIndex ?? 1,
222
+ recordsPerPage: recordsPerPage ?? 50,
223
+ };
224
+ // Add other optional filters
225
+ // Status is now the RunStatus enum value (1=Running, 7=Failed, 9=Completed, etc.)
226
+ if (statusNum !== undefined) {
227
+ filters.status = statusNum;
228
+ }
229
+ if (spaceId !== undefined) {
230
+ filters.spaceId = spaceId;
231
+ }
232
+ if (search) {
233
+ filters.search = search;
234
+ }
235
+ if (configTypeStr) {
236
+ filters.configType = configTypeStr;
237
+ }
238
+ if (sortColumn) {
239
+ filters.sortColumn = sortColumn;
240
+ }
241
+ if (sortOrderStr) {
242
+ if (sortOrderStr !== "asc" && sortOrderStr !== "desc") {
243
+ throw new Error(`Invalid parameter 'sortOrder': must be "asc" or "desc", got "${sortOrderStr}"`);
244
+ }
245
+ // Convert "asc"/"desc" to 0/1 as the API expects
246
+ filters.sortOrder = sortOrderStr === "desc" ? 1 : 0;
247
+ }
248
+ const response = await apiClient.getAllAgents(filters);
249
+ // Parse response — either a plain array (no pagination) or a PaginatedAgentsResponse
250
+ let agents;
251
+ let paginationInfo = null;
252
+ if (Array.isArray(response)) {
253
+ agents = response;
254
+ }
255
+ else if (isPaginatedResponse(response)) {
256
+ agents = response.agents;
257
+ paginationInfo = {
258
+ totalRecordCount: response.totalRecordCount,
259
+ pageIndex: filters.pageIndex ?? 1,
260
+ recordsPerPage: filters.recordsPerPage ?? 50,
261
+ };
262
+ }
263
+ else {
264
+ throw new Error(`Unexpected response type: ${typeof response}`);
265
+ }
266
+ const summary = summarizeAgents(agents);
267
+ // Include pagination info if available
268
+ const result = paginationInfo ? {
269
+ agents: summary,
270
+ pagination: paginationInfo,
271
+ } : summary;
272
+ return {
273
+ content: [
274
+ {
275
+ type: "text",
276
+ text: JSON.stringify(result, null, 2),
277
+ },
278
+ ],
279
+ };
280
+ }
281
+ case "get_agent": {
282
+ const params = args;
283
+ const agentId = validateNumber(params, "agentId", { min: 1, integer: true });
284
+ const agent = await apiClient.getAgent(agentId);
285
+ return {
286
+ content: [
287
+ {
288
+ type: "text",
289
+ text: JSON.stringify(agent, null, 2),
290
+ },
291
+ ],
292
+ };
293
+ }
294
+ case "search_agents": {
295
+ const params = args;
296
+ const query = validateString(params, "query");
297
+ if (!query.trim()) {
298
+ throw new Error("Search query cannot be empty");
299
+ }
300
+ const maxRecords = validateNumber(params, "maxRecords", { required: false, min: 1, max: 1000, integer: true });
301
+ const agents = await apiClient.searchAgents(query, maxRecords);
302
+ return {
303
+ content: [
304
+ {
305
+ type: "text",
306
+ text: JSON.stringify(summarizeAgents(agents), null, 2),
307
+ },
308
+ ],
309
+ };
310
+ }
311
+ // Run Tools
312
+ case "get_agent_runs": {
313
+ const params = args;
314
+ const agentId = validateNumber(params, "agentId", { min: 1, integer: true });
315
+ const maxRecords = validateNumber(params, "maxRecords", { required: false, min: 1, max: 1000, integer: true });
316
+ const runs = await apiClient.getAgentRuns(agentId, maxRecords);
317
+ return {
318
+ content: [
319
+ {
320
+ type: "text",
321
+ text: JSON.stringify(runs, null, 2),
322
+ },
323
+ ],
324
+ };
325
+ }
326
+ case "get_run_status": {
327
+ const params = args;
328
+ const agentId = validateNumber(params, "agentId", { min: 1, integer: true });
329
+ const runId = validateNumber(params, "runId", { min: 1, integer: true });
330
+ const status = await apiClient.getRunStatus(agentId, runId);
331
+ return {
332
+ content: [
333
+ {
334
+ type: "text",
335
+ text: JSON.stringify(status, null, 2),
336
+ },
337
+ ],
338
+ };
339
+ }
340
+ case "start_agent": {
341
+ const params = args;
342
+ const agentId = validateNumber(params, "agentId", { min: 1, integer: true });
343
+ const inputParameters = validateJsonString(params, "inputParameters", false);
344
+ const isRunSynchronously = validateBoolean(params, "isRunSynchronously", false);
345
+ const timeout = validateNumber(params, "timeout", { required: false, min: 1, max: 3600, integer: true });
346
+ const parallelism = validateNumber(params, "parallelism", { required: false, min: 1, max: 50, integer: true });
347
+ const result = await apiClient.startAgent(agentId, {
348
+ inputParameters,
349
+ isRunSynchronously: isRunSynchronously ?? false,
350
+ timeout: timeout ?? 60,
351
+ parallelism: parallelism ?? 1,
352
+ });
353
+ if (typeof result === "string") {
354
+ // Synchronous run returned data directly
355
+ return {
356
+ content: [
357
+ {
358
+ type: "text",
359
+ text: result,
360
+ },
361
+ ],
362
+ };
363
+ }
364
+ else {
365
+ // Asynchronous run returned run info
366
+ return {
367
+ content: [
368
+ {
369
+ type: "text",
370
+ text: `Agent started successfully.\n\n${JSON.stringify(result, null, 2)}`,
371
+ },
372
+ ],
373
+ };
374
+ }
375
+ }
376
+ case "stop_agent": {
377
+ const params = args;
378
+ const agentId = validateNumber(params, "agentId", { min: 1, integer: true });
379
+ const runId = validateNumber(params, "runId", { min: 1, integer: true });
380
+ await apiClient.stopAgent(agentId, runId);
381
+ return {
382
+ content: [
383
+ {
384
+ type: "text",
385
+ text: `Successfully stopped run ${runId} for agent ${agentId}`,
386
+ },
387
+ ],
388
+ };
389
+ }
390
+ case "kill_agent": {
391
+ const params = args;
392
+ const agentId = validateNumber(params, "agentId", { min: 1, integer: true });
393
+ const runId = validateNumber(params, "runId", { min: 1, integer: true });
394
+ await apiClient.killAgent(agentId, runId);
395
+ return {
396
+ content: [
397
+ {
398
+ type: "text",
399
+ text: `Kill command sent for run ${runId} of agent ${agentId}. If the agent was running, it will initiate graceful stop. If already stopping, it will force immediate termination.`,
400
+ },
401
+ ],
402
+ };
403
+ }
404
+ // Destructive Operations
405
+ case "delete_run": {
406
+ const params = args;
407
+ const agentId = validateNumber(params, "agentId", { min: 1, integer: true });
408
+ const runId = validateNumber(params, "runId", { min: 1, integer: true });
409
+ const removeMethod = validateEnum(params, "removeMethod", ["RemoveEntireRun", "RemoveAllFiles", "RemoveAllFilesAndAgentInput"], false);
410
+ await apiClient.deleteRun(agentId, runId, removeMethod);
411
+ const methodDescriptions = {
412
+ RemoveEntireRun: `Successfully deleted run ${runId} and all associated files from agent ${agentId}.`,
413
+ RemoveAllFiles: `Successfully removed all files for run ${runId} from agent ${agentId}. The run record has been preserved.`,
414
+ RemoveAllFilesAndAgentInput: `Successfully removed all files and agent input for run ${runId} from agent ${agentId}. The run record has been preserved.`,
415
+ };
416
+ const description = (removeMethod ? methodDescriptions[removeMethod] : undefined) ??
417
+ `Successfully deleted run ${runId} and all associated files from agent ${agentId}.`;
418
+ return {
419
+ content: [
420
+ {
421
+ type: "text",
422
+ text: description,
423
+ },
424
+ ],
425
+ };
426
+ }
427
+ // File Tools
428
+ case "get_run_files": {
429
+ const params = args;
430
+ const agentId = validateNumber(params, "agentId", { min: 1, integer: true });
431
+ const runId = validateNumber(params, "runId", { min: 1, integer: true });
432
+ const files = await apiClient.getRunFiles(agentId, runId);
433
+ if (files.length === 0) {
434
+ return {
435
+ content: [
436
+ {
437
+ type: "text",
438
+ text: "No files found for this run.",
439
+ },
440
+ ],
441
+ };
442
+ }
443
+ const summary = files.map((f) => ({
444
+ id: f.id,
445
+ name: f.name,
446
+ fileType: f.fileType,
447
+ fileSize: `${((f.fileSize ?? 0) / 1024).toFixed(2)} KB`,
448
+ created: f.created,
449
+ }));
450
+ return {
451
+ content: [
452
+ {
453
+ type: "text",
454
+ text: JSON.stringify(summary, null, 2),
455
+ },
456
+ ],
457
+ };
458
+ }
459
+ case "get_file_download_url": {
460
+ const params = args;
461
+ const agentId = validateNumber(params, "agentId", { min: 1, integer: true });
462
+ const runId = validateNumber(params, "runId", { min: 1, integer: true });
463
+ const fileId = validateNumber(params, "fileId", { min: 1, integer: true });
464
+ const result = await apiClient.downloadRunFile(agentId, runId, fileId);
465
+ return {
466
+ content: [
467
+ {
468
+ type: "text",
469
+ text: `Download URL:\n${result.redirectUrl}\n\nNote: This URL is temporary and will expire.`,
470
+ },
471
+ ],
472
+ };
473
+ }
474
+ // Version Tools
475
+ case "get_agent_versions": {
476
+ const params = args;
477
+ const agentId = validateNumber(params, "agentId", { min: 1, integer: true });
478
+ const versions = await apiClient.getAgentVersions(agentId);
479
+ return {
480
+ content: [
481
+ {
482
+ type: "text",
483
+ text: JSON.stringify(versions, null, 2),
484
+ },
485
+ ],
486
+ };
487
+ }
488
+ case "restore_agent_version": {
489
+ const params = args;
490
+ const agentId = validateNumber(params, "agentId", { min: 1, integer: true });
491
+ const versionNumber = validateNumber(params, "versionNumber", { min: 1, integer: true });
492
+ const comments = validateString(params, "comments");
493
+ await apiClient.restoreAgentVersion(agentId, versionNumber, comments);
494
+ return {
495
+ content: [
496
+ {
497
+ type: "text",
498
+ text: `Successfully restored agent ${agentId} to version ${versionNumber}.\n\nA new version has been created based on version ${versionNumber}.`,
499
+ },
500
+ ],
501
+ };
502
+ }
503
+ // Schedule Tools
504
+ case "list_agent_schedules": {
505
+ const params = args;
506
+ const agentId = validateNumber(params, "agentId", { min: 1, integer: true });
507
+ const schedules = await apiClient.getAgentSchedules(agentId);
508
+ return {
509
+ content: [
510
+ {
511
+ type: "text",
512
+ text: JSON.stringify(schedules, null, 2),
513
+ },
514
+ ],
515
+ };
516
+ }
517
+ case "create_agent_schedule": {
518
+ const params = args;
519
+ const agentId = validateNumber(params, "agentId", { min: 1, integer: true });
520
+ const name = validateString(params, "name");
521
+ const { scheduleType, startTime, cronExpression, runEveryCount, runEveryPeriod, timezone, inputParameters, isEnabled, parallelism, parallelMaxConcurrency, parallelExport, logLevel, logMode, isExclusive, isWaitOnFailure, } = parseScheduleParams(params);
522
+ // Validate schedule type specific parameters
523
+ const effectiveScheduleType = scheduleType ?? 3; // Default to CRON
524
+ // RunOnce (1): startTime is required and must be at least 1 minute in the future
525
+ if (effectiveScheduleType === 1) {
526
+ if (!startTime) {
527
+ throw new Error("startTime is required when scheduleType is 1 (RunOnce)");
528
+ }
529
+ }
530
+ // RunEvery (2): runEveryCount and runEveryPeriod are required, startTime is optional but must be in the future if provided
531
+ if (effectiveScheduleType === 2) {
532
+ if (runEveryCount === undefined || runEveryPeriod === undefined) {
533
+ throw new Error("runEveryCount and runEveryPeriod are required when scheduleType is 2 (RunEvery)");
534
+ }
535
+ }
536
+ // CRON (3): cronExpression is required, startTime is not used
537
+ if (effectiveScheduleType === 3 && !cronExpression) {
538
+ throw new Error("cronExpression is required when scheduleType is 3 (CRON)");
539
+ }
540
+ validateScheduleStartTime(effectiveScheduleType, startTime);
541
+ const schedule = await apiClient.createAgentSchedule(agentId, {
542
+ name,
543
+ scheduleType: effectiveScheduleType,
544
+ startTime,
545
+ cronExpression,
546
+ runEveryCount,
547
+ runEveryPeriod,
548
+ timezone,
549
+ inputParameters,
550
+ isEnabled: isEnabled ?? true,
551
+ parallelism: parallelism ?? 1,
552
+ parallelMaxConcurrency,
553
+ parallelExport,
554
+ logLevel,
555
+ logMode,
556
+ isExclusive,
557
+ isWaitOnFailure,
558
+ });
559
+ return {
560
+ content: [
561
+ {
562
+ type: "text",
563
+ text: `Schedule created successfully.\n\n${JSON.stringify(schedule, null, 2)}`,
564
+ },
565
+ ],
566
+ };
567
+ }
568
+ case "delete_agent_schedule": {
569
+ const params = args;
570
+ const agentId = validateNumber(params, "agentId", { min: 1, integer: true });
571
+ const scheduleId = validateNumber(params, "scheduleId", { min: 1, integer: true });
572
+ await apiClient.deleteAgentSchedule(agentId, scheduleId);
573
+ return {
574
+ content: [
575
+ {
576
+ type: "text",
577
+ text: `Successfully deleted schedule ${scheduleId} from agent ${agentId}`,
578
+ },
579
+ ],
580
+ };
581
+ }
582
+ case "get_agent_schedule": {
583
+ const params = args;
584
+ const agentId = validateNumber(params, "agentId", { min: 1, integer: true });
585
+ const scheduleId = validateNumber(params, "scheduleId", { min: 1, integer: true });
586
+ const schedule = await apiClient.getAgentSchedule(agentId, scheduleId);
587
+ return {
588
+ content: [
589
+ {
590
+ type: "text",
591
+ text: JSON.stringify(schedule, null, 2),
592
+ },
593
+ ],
594
+ };
595
+ }
596
+ case "update_agent_schedule": {
597
+ const params = args;
598
+ const agentId = validateNumber(params, "agentId", { min: 1, integer: true });
599
+ const scheduleId = validateNumber(params, "scheduleId", { min: 1, integer: true });
600
+ const name = validateString(params, "name");
601
+ const { scheduleType, startTime, cronExpression, runEveryCount, runEveryPeriod, timezone, inputParameters, isEnabled, parallelism, parallelMaxConcurrency, parallelExport, logLevel, logMode, isExclusive, isWaitOnFailure, } = parseScheduleParams(params);
602
+ const hasCronFields = cronExpression !== undefined;
603
+ const hasRunEveryFields = runEveryCount !== undefined || runEveryPeriod !== undefined;
604
+ if (hasCronFields && hasRunEveryFields && scheduleType === undefined) {
605
+ throw new Error("Conflicting schedule fields: both cronExpression and runEveryCount/runEveryPeriod were provided without an explicit scheduleType. " +
606
+ "Specify scheduleType to clarify intent (2=RunEvery, 3=CRON).");
607
+ }
608
+ // Infer scheduleType from provided fields when not explicitly set,
609
+ // so the user doesn't have to redundantly specify it on every update.
610
+ let effectiveScheduleType = scheduleType;
611
+ if (effectiveScheduleType === undefined) {
612
+ if (hasCronFields)
613
+ effectiveScheduleType = 3;
614
+ else if (hasRunEveryFields)
615
+ effectiveScheduleType = 2;
616
+ }
617
+ validateScheduleStartTime(effectiveScheduleType, startTime);
618
+ const updated = await apiClient.updateAgentSchedule(agentId, scheduleId, {
619
+ name,
620
+ scheduleType: effectiveScheduleType,
621
+ startTime,
622
+ cronExpression,
623
+ runEveryCount,
624
+ runEveryPeriod,
625
+ timezone,
626
+ inputParameters,
627
+ isEnabled,
628
+ parallelism,
629
+ parallelMaxConcurrency,
630
+ parallelExport,
631
+ logLevel,
632
+ logMode,
633
+ isExclusive,
634
+ isWaitOnFailure,
635
+ });
636
+ return {
637
+ content: [
638
+ {
639
+ type: "text",
640
+ text: `Schedule updated successfully.\n\n${JSON.stringify(updated, null, 2)}`,
641
+ },
642
+ ],
643
+ };
644
+ }
645
+ case "enable_agent_schedule": {
646
+ const params = args;
647
+ const agentId = validateNumber(params, "agentId", { min: 1, integer: true });
648
+ const scheduleId = validateNumber(params, "scheduleId", { min: 1, integer: true });
649
+ await apiClient.enableAgentSchedule(agentId, scheduleId);
650
+ return {
651
+ content: [
652
+ {
653
+ type: "text",
654
+ text: `Successfully enabled schedule ${scheduleId} for agent ${agentId}. The schedule will now run according to its configuration.`,
655
+ },
656
+ ],
657
+ };
658
+ }
659
+ case "disable_agent_schedule": {
660
+ const params = args;
661
+ const agentId = validateNumber(params, "agentId", { min: 1, integer: true });
662
+ const scheduleId = validateNumber(params, "scheduleId", { min: 1, integer: true });
663
+ await apiClient.disableAgentSchedule(agentId, scheduleId);
664
+ return {
665
+ content: [
666
+ {
667
+ type: "text",
668
+ text: `Successfully disabled schedule ${scheduleId} for agent ${agentId}. The schedule will not run until re-enabled.`,
669
+ },
670
+ ],
671
+ };
672
+ }
673
+ case "get_scheduled_runs": {
674
+ const params = args;
675
+ const startDate = validateString(params, "startDate", false);
676
+ const endDate = validateString(params, "endDate", false);
677
+ const schedules = await apiClient.getUpcomingSchedules(startDate, endDate);
678
+ return {
679
+ content: [
680
+ {
681
+ type: "text",
682
+ text: JSON.stringify(schedules, null, 2),
683
+ },
684
+ ],
685
+ };
686
+ }
687
+ // Billing/Credits Tools
688
+ case "get_credits_balance": {
689
+ const balance = await apiClient.getCreditsBalance();
690
+ return {
691
+ content: [
692
+ {
693
+ type: "text",
694
+ text: JSON.stringify(balance, null, 2),
695
+ },
696
+ ],
697
+ };
698
+ }
699
+ case "get_spending_summary": {
700
+ const params = args;
701
+ const startDate = validateString(params, "startDate", false);
702
+ const endDate = validateString(params, "endDate", false);
703
+ const spending = await apiClient.getSpendingSummary(startDate, endDate);
704
+ return {
705
+ content: [
706
+ {
707
+ type: "text",
708
+ text: JSON.stringify(spending, null, 2),
709
+ },
710
+ ],
711
+ };
712
+ }
713
+ case "get_credit_history": {
714
+ const params = args;
715
+ const pageIndex = validateNumber(params, "pageIndex", { required: false, min: 1, integer: true });
716
+ const recordsPerPage = validateNumber(params, "recordsPerPage", { required: false, min: 1, max: 100, integer: true });
717
+ const history = await apiClient.getCreditHistory(pageIndex, recordsPerPage);
718
+ return {
719
+ content: [
720
+ {
721
+ type: "text",
722
+ text: JSON.stringify(history, null, 2),
723
+ },
724
+ ],
725
+ };
726
+ }
727
+ case "get_agents_usage": {
728
+ const params = args;
729
+ const defaults = getDefaultDateRange();
730
+ const startDate = validateString(params, "startDate", false) ?? defaults.startDate;
731
+ const endDate = validateString(params, "endDate", false) ?? defaults.endDate;
732
+ validateISODate(startDate, "startDate");
733
+ validateISODate(endDate, "endDate");
734
+ validateDateRange(startDate, endDate);
735
+ const pageIndex = validateNumber(params, "pageIndex", { required: false, min: 1, integer: true });
736
+ const recordsPerPage = validateNumber(params, "recordsPerPage", { required: false, min: 1, max: 1000, integer: true });
737
+ const sortColumn = validateString(params, "sortColumn", false);
738
+ const sortOrder = validateNumber(params, "sortOrder", { required: false, min: 0, max: 1, integer: true });
739
+ const name = validateString(params, "name", false);
740
+ const usageTypes = validateString(params, "usageTypes", false);
741
+ const result = await apiClient.getAgentsUsage(startDate, endDate, pageIndex, recordsPerPage, sortColumn, sortOrder, name, usageTypes);
742
+ return {
743
+ content: [
744
+ {
745
+ type: "text",
746
+ text: JSON.stringify(result, null, 2),
747
+ },
748
+ ],
749
+ };
750
+ }
751
+ case "get_agent_cost_breakdown": {
752
+ const params = args;
753
+ const agentId = validateNumber(params, "agentId", { min: 1, integer: true });
754
+ const defaults = getDefaultDateRange();
755
+ const startDate = validateString(params, "startDate", false) ?? defaults.startDate;
756
+ const endDate = validateString(params, "endDate", false) ?? defaults.endDate;
757
+ validateISODate(startDate, "startDate");
758
+ validateISODate(endDate, "endDate");
759
+ validateDateRange(startDate, endDate);
760
+ const timeUnit = validateEnum(params, "timeUnit", ["day", "month"], false);
761
+ const usageTypes = validateString(params, "usageTypes", false);
762
+ const result = await apiClient.getAgentCostBreakdown(agentId, startDate, endDate, timeUnit, usageTypes);
763
+ return {
764
+ content: [
765
+ {
766
+ type: "text",
767
+ text: JSON.stringify(result, null, 2),
768
+ },
769
+ ],
770
+ };
771
+ }
772
+ case "get_agent_runs_cost": {
773
+ const params = args;
774
+ const agentId = validateNumber(params, "agentId", { min: 1, integer: true });
775
+ const defaults = getDefaultDateRange();
776
+ const startDate = validateString(params, "startDate", false) ?? defaults.startDate;
777
+ const endDate = validateString(params, "endDate", false) ?? defaults.endDate;
778
+ validateISODate(startDate, "startDate");
779
+ validateISODate(endDate, "endDate");
780
+ validateDateRange(startDate, endDate);
781
+ const pageIndex = validateNumber(params, "pageIndex", { required: false, min: 1, integer: true });
782
+ const recordsPerPage = validateNumber(params, "recordsPerPage", { required: false, min: 1, max: 1000, integer: true });
783
+ const sortColumn = validateEnum(params, "sortColumn", ["date", "cost", "duration"], false);
784
+ const sortOrder = validateNumber(params, "sortOrder", { required: false, min: 0, max: 1, integer: true });
785
+ const usageTypes = validateString(params, "usageTypes", false);
786
+ const result = await apiClient.getAgentRunsCost(agentId, startDate, endDate, pageIndex, recordsPerPage, sortColumn, sortOrder, usageTypes);
787
+ return {
788
+ content: [
789
+ {
790
+ type: "text",
791
+ text: JSON.stringify(result, null, 2),
792
+ },
793
+ ],
794
+ };
795
+ }
796
+ // Space Tools
797
+ case "list_spaces": {
798
+ const spaces = await apiClient.getAllSpaces();
799
+ return {
800
+ content: [
801
+ {
802
+ type: "text",
803
+ text: JSON.stringify(spaces, null, 2),
804
+ },
805
+ ],
806
+ };
807
+ }
808
+ case "get_space": {
809
+ const params = args;
810
+ const spaceId = validateNumber(params, "spaceId", { min: 1, integer: true });
811
+ const space = await apiClient.getSpace(spaceId);
812
+ return {
813
+ content: [
814
+ {
815
+ type: "text",
816
+ text: JSON.stringify(space, null, 2),
817
+ },
818
+ ],
819
+ };
820
+ }
821
+ case "get_space_agents": {
822
+ const params = args;
823
+ const spaceId = validateNumber(params, "spaceId", { min: 1, integer: true });
824
+ const agents = await apiClient.getSpaceAgents(spaceId);
825
+ return {
826
+ content: [
827
+ {
828
+ type: "text",
829
+ text: JSON.stringify(agents, null, 2),
830
+ },
831
+ ],
832
+ };
833
+ }
834
+ case "search_space_by_name": {
835
+ const params = args;
836
+ const name = validateString(params, "name");
837
+ const space = await apiClient.searchSpaceByName(name);
838
+ return {
839
+ content: [
840
+ {
841
+ type: "text",
842
+ text: JSON.stringify(space, null, 2),
843
+ },
844
+ ],
845
+ };
846
+ }
847
+ case "run_space_agents": {
848
+ const params = args;
849
+ const spaceId = validateNumber(params, "spaceId", { min: 1, integer: true });
850
+ const inputParameters = validateJsonString(params, "inputParameters", false);
851
+ const result = await apiClient.runSpaceAgents(spaceId, inputParameters);
852
+ return {
853
+ content: [
854
+ {
855
+ type: "text",
856
+ text: `Started agents in space.\n\n${JSON.stringify(result, null, 2)}`,
857
+ },
858
+ ],
859
+ };
860
+ }
861
+ // Analytics Tools
862
+ case "get_runs_summary": {
863
+ const params = args;
864
+ const startDate = validateString(params, "startDate", false);
865
+ const endDate = validateString(params, "endDate", false);
866
+ const status = validateString(params, "status", false);
867
+ const includeDetails = validateBoolean(params, "includeDetails", false);
868
+ const summary = await apiClient.getRunsSummary(startDate, endDate, status, includeDetails);
869
+ return {
870
+ content: [
871
+ {
872
+ type: "text",
873
+ text: JSON.stringify(summary, null, 2),
874
+ },
875
+ ],
876
+ };
877
+ }
878
+ case "get_records_summary": {
879
+ const params = args;
880
+ const startDate = validateString(params, "startDate", false);
881
+ const endDate = validateString(params, "endDate", false);
882
+ const agentId = validateNumber(params, "agentId", { required: false, min: 1, integer: true });
883
+ const summary = await apiClient.getRecordsSummary(startDate, endDate, agentId);
884
+ return {
885
+ content: [
886
+ {
887
+ type: "text",
888
+ text: JSON.stringify(summary, null, 2),
889
+ },
890
+ ],
891
+ };
892
+ }
893
+ case "get_run_diagnostics": {
894
+ const params = args;
895
+ const agentId = validateNumber(params, "agentId", { min: 1, integer: true });
896
+ const runId = validateNumber(params, "runId", { min: 1, integer: true });
897
+ const diagnostics = await apiClient.getRunDiagnostics(agentId, runId);
898
+ return {
899
+ content: [
900
+ {
901
+ type: "text",
902
+ text: JSON.stringify(diagnostics, null, 2),
903
+ },
904
+ ],
905
+ };
906
+ }
907
+ case "get_latest_failure": {
908
+ const params = args;
909
+ const agentId = validateNumber(params, "agentId", { min: 1, integer: true });
910
+ const diagnostics = await apiClient.getLatestFailure(agentId);
911
+ return {
912
+ content: [
913
+ {
914
+ type: "text",
915
+ text: JSON.stringify(diagnostics, null, 2),
916
+ },
917
+ ],
918
+ };
919
+ }
920
+ // Agent Builder Tools
921
+ case "start_agent_build": {
922
+ const params = args;
923
+ const prompt = validateString(params, "prompt", {
924
+ required: true,
925
+ minLength: 10,
926
+ maxLength: 5000,
927
+ trim: true,
928
+ });
929
+ const spaceId = validateNumber(params, "spaceId", { required: false, min: 1, integer: true });
930
+ const response = await apiClient.startAgentBuild({ prompt, spaceId });
931
+ return {
932
+ content: [
933
+ {
934
+ type: "text",
935
+ text: JSON.stringify(response, null, 2),
936
+ },
937
+ ],
938
+ };
939
+ }
940
+ case "get_agent_build_status": {
941
+ const params = args;
942
+ // maxLength:256 guards against oversized session IDs being forwarded to the API (#7)
943
+ const sessionId = validateString(params, "sessionId", { required: true, maxLength: 256 });
944
+ const status = await apiClient.getAgentBuildStatus(sessionId);
945
+ // The backend's `error` field is a raw ex.Message passthrough that can contain
946
+ // stack traces, internal endpoint paths, upstream LLM URLs, and similar. Replace
947
+ // it with a fixed user-facing string; log the raw value at DEBUG only (#6).
948
+ if (DEBUG && status.status === "error" && status.error) {
949
+ console.error(`[DEBUG] Agent build session ${sessionId} failed: ${status.error}`);
950
+ }
951
+ const sanitized = {
952
+ ...status,
953
+ error: status.status === "error"
954
+ ? "Build failed. Please review your prompt and try again."
955
+ : undefined,
956
+ };
957
+ return {
958
+ content: [
959
+ {
960
+ type: "text",
961
+ text: JSON.stringify(sanitized, null, 2),
962
+ },
963
+ ],
964
+ };
965
+ }
966
+ case "stop_agent_build": {
967
+ const params = args;
968
+ // maxLength:256 guards against oversized session IDs (#7)
969
+ const sessionId = validateString(params, "sessionId", { required: true, maxLength: 256 });
970
+ await apiClient.stopAgentBuild(sessionId);
971
+ // Return structured JSON consistent with every other tool handler (#8)
972
+ return {
973
+ content: [
974
+ {
975
+ type: "text",
976
+ text: JSON.stringify({ stopped: true, sessionId }, null, 2),
977
+ },
978
+ ],
979
+ };
980
+ }
981
+ default:
982
+ throw new Error(`Unknown tool: ${name}`);
983
+ }
984
+ }
985
+ catch (error) {
986
+ return formatToolError(error);
987
+ }
988
+ });
989
+ // ==========================================
990
+ // Resource Handlers
991
+ // ==========================================
992
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
993
+ resources,
994
+ }));
995
+ server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({
996
+ resourceTemplates,
997
+ }));
998
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
999
+ const { uri } = request.params;
1000
+ if (DEBUG) {
1001
+ console.error(`[DEBUG] Resource read: ${uri}`);
1002
+ }
1003
+ try {
1004
+ const content = await readResource(uri, apiClient);
1005
+ return {
1006
+ contents: [content],
1007
+ };
1008
+ }
1009
+ catch (error) {
1010
+ const errorMessage = error instanceof Error ? error.message : "An unknown error occurred";
1011
+ throw new Error(`Failed to read resource ${uri}: ${errorMessage}`);
1012
+ }
1013
+ });
1014
+ // ==========================================
1015
+ // Prompt Handlers
1016
+ // ==========================================
1017
+ server.setRequestHandler(ListPromptsRequestSchema, async () => ({
1018
+ prompts,
1019
+ }));
1020
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
1021
+ const { name, arguments: args } = request.params;
1022
+ if (DEBUG) {
1023
+ console.error(`[DEBUG] Prompt requested: ${name}`);
1024
+ console.error(`[DEBUG] Args: ${JSON.stringify(redactDebugArgs(args))}`);
1025
+ }
1026
+ const messages = getPromptMessages(name, args);
1027
+ return { messages };
1028
+ });
1029
+ return server;
1030
+ }
1031
+ //# sourceMappingURL=handlers.js.map