@aiconnect/agentjobs-mcp 1.0.8 → 1.1.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/.env.example +7 -0
- package/README.md +82 -12
- package/build/config.js +8 -3
- package/build/debug.js +86 -0
- package/build/index.js +81 -22
- package/build/lib/agentJobsClient.js +79 -0
- package/build/test-tools.js +53 -0
- package/build/tools/cancel_job.js +58 -0
- package/build/tools/create_job.js +76 -0
- package/build/tools/get_job.js +55 -0
- package/build/tools/get_job_type.js +66 -0
- package/build/tools/get_jobs_stats.js +57 -0
- package/build/tools/list_jobs.js +79 -0
- package/build/utils/debugger.js +74 -0
- package/build/utils/formatters.js +498 -0
- package/build/utils/formatters.test.js +387 -0
- package/build/utils/schemas.js +20 -0
- package/build/utils/schemas.test.js +40 -0
- package/docs/debug-guide.md +203 -0
- package/package.json +12 -4
- package/build/cancel_job.js +0 -71
- package/build/create_job.js +0 -151
- package/build/get_job.js +0 -62
- package/build/list_jobs.js +0 -88
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { formatJobDetails, formatJobStats, formatJobTypeDetails, formatJobTypeSummary } from './formatters.js';
|
|
3
|
+
describe('formatJobDetails', () => {
|
|
4
|
+
const fullJob = {
|
|
5
|
+
job_id: 'job_123',
|
|
6
|
+
job_type_id: 'type_abc',
|
|
7
|
+
org_id: 'org_xyz',
|
|
8
|
+
channel_code: 'ch_def',
|
|
9
|
+
chat_id: 'chat_456',
|
|
10
|
+
job_status: 'completed',
|
|
11
|
+
result: 'Success',
|
|
12
|
+
created_at: '2023-01-01T10:00:00.000Z',
|
|
13
|
+
updated_at: '2023-01-01T10:15:00.000Z',
|
|
14
|
+
scheduled_at: '2023-01-01T09:55:00.000Z',
|
|
15
|
+
last_task_created_at: '2023-01-01T10:10:00.000Z',
|
|
16
|
+
tags: 'tag1, tag2',
|
|
17
|
+
execution_log: ['Log entry 1', 'Log entry 2'],
|
|
18
|
+
tasks: [{ task_id: 'task_789', created_at: '2023-01-01T10:05:00.000Z' }],
|
|
19
|
+
flags: {
|
|
20
|
+
is_new_channel: true,
|
|
21
|
+
has_human_reply: false,
|
|
22
|
+
first_reply_at: '2023-01-01T10:02:00.000Z',
|
|
23
|
+
ignore_cooldown: true,
|
|
24
|
+
},
|
|
25
|
+
channel_data: {
|
|
26
|
+
platform: 'test_platform',
|
|
27
|
+
name: 'Test Channel',
|
|
28
|
+
},
|
|
29
|
+
job_config: {
|
|
30
|
+
max_task_retries: 3,
|
|
31
|
+
start_prompt: 'Initial prompt',
|
|
32
|
+
},
|
|
33
|
+
params: { key: 'value' },
|
|
34
|
+
};
|
|
35
|
+
it('should format a full job object correctly', () => {
|
|
36
|
+
const result = formatJobDetails(fullJob);
|
|
37
|
+
expect(result).toContain('Job ID: job_123');
|
|
38
|
+
expect(result).toContain('Status: completed');
|
|
39
|
+
expect(result).toContain('Result: Success');
|
|
40
|
+
expect(result).toContain('Tags: tag1, tag2');
|
|
41
|
+
expect(result).toContain('Total Tasks: 1');
|
|
42
|
+
expect(result).toContain('Retries Used: 0 / 3 (remaining: 3)');
|
|
43
|
+
expect(result).toContain('Duration: 20m 0s');
|
|
44
|
+
});
|
|
45
|
+
it('should handle minimal job object', () => {
|
|
46
|
+
const minimalJob = {
|
|
47
|
+
job_id: 'job_min',
|
|
48
|
+
job_type_id: 'type_min',
|
|
49
|
+
org_id: 'org_min',
|
|
50
|
+
channel_code: 'ch_min',
|
|
51
|
+
job_status: 'running',
|
|
52
|
+
created_at: new Date().toISOString(),
|
|
53
|
+
updated_at: new Date().toISOString(),
|
|
54
|
+
};
|
|
55
|
+
const result = formatJobDetails(minimalJob);
|
|
56
|
+
expect(result).toContain('Job ID: job_min');
|
|
57
|
+
expect(result).toContain('Status: running');
|
|
58
|
+
expect(result).toContain('Result: n/a');
|
|
59
|
+
expect(result).toContain('Total Tasks: 0');
|
|
60
|
+
});
|
|
61
|
+
it('should handle dates as numbers (timestamps)', () => {
|
|
62
|
+
const jobWithTimestamps = {
|
|
63
|
+
...fullJob,
|
|
64
|
+
created_at: new Date(fullJob.created_at).getTime(),
|
|
65
|
+
updated_at: new Date(fullJob.updated_at).getTime(),
|
|
66
|
+
};
|
|
67
|
+
const result = formatJobDetails(jobWithTimestamps);
|
|
68
|
+
expect(result).toContain(`Created At: ${new Date(jobWithTimestamps.created_at).toISOString()}`);
|
|
69
|
+
expect(result).toContain(`Updated At: ${new Date(jobWithTimestamps.updated_at).toISOString()}`);
|
|
70
|
+
});
|
|
71
|
+
it('should fall back to JSON.stringify for invalid job object', () => {
|
|
72
|
+
const invalidJob = { job_id: '123' }; // Missing required fields
|
|
73
|
+
const result = formatJobDetails(invalidJob);
|
|
74
|
+
expect(result).toContain('"job_id": "123"');
|
|
75
|
+
expect(result).toContain('Job Details (raw):');
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
describe('formatJobStats', () => {
|
|
79
|
+
it('should return correct percentages when total is greater than 0', () => {
|
|
80
|
+
const stats = {
|
|
81
|
+
status: {
|
|
82
|
+
completed: 10,
|
|
83
|
+
running: 5,
|
|
84
|
+
failed: 2,
|
|
85
|
+
canceled: 1,
|
|
86
|
+
waiting: 1,
|
|
87
|
+
scheduled: 1,
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
const filters = {};
|
|
91
|
+
const result = formatJobStats(stats, filters);
|
|
92
|
+
expect(result).toContain('Completed: 10 jobs (50.0%)');
|
|
93
|
+
expect(result).toContain('Running: 5 jobs (25.0%)');
|
|
94
|
+
expect(result).toContain('Failed: 2 jobs (10.0%)');
|
|
95
|
+
expect(result).toContain('Canceled: 1 jobs (5.0%)');
|
|
96
|
+
expect(result).toContain('Waiting: 1 jobs (5.0%)');
|
|
97
|
+
expect(result).toContain('Scheduled: 1 jobs (5.0%)');
|
|
98
|
+
expect(result).toContain('Total Jobs: 20');
|
|
99
|
+
});
|
|
100
|
+
it('should return 0.0% for all statuses when there are no jobs', () => {
|
|
101
|
+
const stats = {
|
|
102
|
+
status: {
|
|
103
|
+
completed: 0,
|
|
104
|
+
running: 0,
|
|
105
|
+
failed: 0,
|
|
106
|
+
canceled: 0,
|
|
107
|
+
waiting: 0,
|
|
108
|
+
scheduled: 0,
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
const filters = {};
|
|
112
|
+
const result = formatJobStats(stats, filters);
|
|
113
|
+
expect(result).toContain('Completed: 0 jobs (0.0%)');
|
|
114
|
+
expect(result).toContain('Running: 0 jobs (0.0%)');
|
|
115
|
+
expect(result).toContain('Failed: 0 jobs (0.0%)');
|
|
116
|
+
expect(result).toContain('Canceled: 0 jobs (0.0%)');
|
|
117
|
+
expect(result).toContain('Waiting: 0 jobs (0.0%)');
|
|
118
|
+
expect(result).toContain('Scheduled: 0 jobs (0.0%)');
|
|
119
|
+
expect(result).toContain('Total Jobs: 0');
|
|
120
|
+
});
|
|
121
|
+
it('should handle null filters gracefully', () => {
|
|
122
|
+
const stats = {
|
|
123
|
+
status: {
|
|
124
|
+
completed: 10,
|
|
125
|
+
running: 5,
|
|
126
|
+
failed: 2,
|
|
127
|
+
canceled: 1,
|
|
128
|
+
waiting: 1,
|
|
129
|
+
scheduled: 1,
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
const filters = null;
|
|
133
|
+
const result = formatJobStats(stats, filters);
|
|
134
|
+
expect(result).toContain('Period: All time');
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
describe('formatJobTypeDetails', () => {
|
|
138
|
+
const fullJobType = {
|
|
139
|
+
id: 'follow-up-v2',
|
|
140
|
+
name: 'Follow Up by DM',
|
|
141
|
+
org_id: 'acme-co',
|
|
142
|
+
version: 2,
|
|
143
|
+
visibility: 'private',
|
|
144
|
+
active: true,
|
|
145
|
+
description: 'Automated follow-up to contacts who haven\'t replied within 24h. Supports templated prompts and throttling.',
|
|
146
|
+
default_config: {
|
|
147
|
+
profile_id: 'default-bot',
|
|
148
|
+
max_follow_ups: 3,
|
|
149
|
+
max_task_retries: 2,
|
|
150
|
+
task_retry_interval: 30,
|
|
151
|
+
max_time_to_complete: 180,
|
|
152
|
+
failure_cooldown_minutes: 120,
|
|
153
|
+
start_prompt: 'Hello! Just circling back on our last conversation...'
|
|
154
|
+
},
|
|
155
|
+
params_schema: {
|
|
156
|
+
type: 'object',
|
|
157
|
+
required: ['recipient_id', 'initial_message'],
|
|
158
|
+
properties: {
|
|
159
|
+
recipient_id: { type: 'string', description: 'User ID of the recipient' },
|
|
160
|
+
initial_message: { type: 'string', description: 'Initial text to send to the recipient' },
|
|
161
|
+
locale: { type: 'string', default: 'en-US', description: 'Locale for message formatting' },
|
|
162
|
+
throttle_minutes: { type: 'number', description: 'Min minutes between attempts' },
|
|
163
|
+
metadata: { type: 'object', description: 'Free-form context data' }
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
tags: 'outreach, dm, v2',
|
|
167
|
+
created_at: '2025-08-02T14:03:28.120Z',
|
|
168
|
+
updated_at: '2025-08-12T09:22:01.553Z'
|
|
169
|
+
};
|
|
170
|
+
it('should format a full job type object correctly', () => {
|
|
171
|
+
const result = formatJobTypeDetails(fullJobType);
|
|
172
|
+
expect(result).toContain('ID: follow-up-v2');
|
|
173
|
+
expect(result).toContain('Name: Follow Up by DM');
|
|
174
|
+
expect(result).toContain('Version: 2');
|
|
175
|
+
expect(result).toContain('Visibility: private');
|
|
176
|
+
expect(result).toContain('Active: yes');
|
|
177
|
+
expect(result).toContain('Profile ID: default-bot');
|
|
178
|
+
expect(result).toContain('Max Follow-ups: 3');
|
|
179
|
+
expect(result).toContain('Max Task Retries: 2');
|
|
180
|
+
expect(result).toContain('Task Retry Interval: 30 min');
|
|
181
|
+
expect(result).toContain('retries up to 2 every 30 min');
|
|
182
|
+
expect(result).toContain('window 180 min');
|
|
183
|
+
expect(result).toContain('cooldown 120 min');
|
|
184
|
+
expect(result).toContain('Type: object | Required: 2 | Properties: 5');
|
|
185
|
+
expect(result).toContain('recipient_id: string (required)');
|
|
186
|
+
expect(result).toContain('locale: string — Locale for message formatting — Defaults to "en-US"');
|
|
187
|
+
expect(result).toContain('Tags: outreach, dm, v2');
|
|
188
|
+
});
|
|
189
|
+
it('should handle minimal job type object', () => {
|
|
190
|
+
const minimalJobType = {
|
|
191
|
+
id: 'minimal',
|
|
192
|
+
name: 'Minimal Type',
|
|
193
|
+
org_id: 'org-min',
|
|
194
|
+
created_at: new Date().toISOString(),
|
|
195
|
+
updated_at: new Date().toISOString()
|
|
196
|
+
};
|
|
197
|
+
const result = formatJobTypeDetails(minimalJobType);
|
|
198
|
+
expect(result).toContain('ID: minimal');
|
|
199
|
+
expect(result).toContain('Name: Minimal Type');
|
|
200
|
+
expect(result).toContain('Org ID: org-min');
|
|
201
|
+
expect(result).not.toContain('Version:');
|
|
202
|
+
expect(result).not.toContain('Description:');
|
|
203
|
+
});
|
|
204
|
+
it('should handle missing optional fields gracefully', () => {
|
|
205
|
+
const jobTypeNoConfig = {
|
|
206
|
+
id: 'no-config',
|
|
207
|
+
name: 'No Config Type',
|
|
208
|
+
org_id: 'org-test',
|
|
209
|
+
created_at: '2025-01-01T00:00:00Z',
|
|
210
|
+
updated_at: '2025-01-01T00:00:00Z'
|
|
211
|
+
};
|
|
212
|
+
const result = formatJobTypeDetails(jobTypeNoConfig, { showEmptySections: true });
|
|
213
|
+
expect(result).toContain('Default Config:');
|
|
214
|
+
expect(result).toContain('n/a');
|
|
215
|
+
expect(result).toContain('Params Schema:');
|
|
216
|
+
});
|
|
217
|
+
it('should truncate long strings with ellipsis', () => {
|
|
218
|
+
const longDescription = 'A'.repeat(500);
|
|
219
|
+
const longPrompt = 'B'.repeat(600);
|
|
220
|
+
const jobTypeWithLongText = {
|
|
221
|
+
id: 'long-text',
|
|
222
|
+
name: 'Long Text Type',
|
|
223
|
+
org_id: 'org-test',
|
|
224
|
+
description: longDescription,
|
|
225
|
+
default_config: {
|
|
226
|
+
start_prompt: longPrompt
|
|
227
|
+
},
|
|
228
|
+
created_at: '2025-01-01T00:00:00Z',
|
|
229
|
+
updated_at: '2025-01-01T00:00:00Z'
|
|
230
|
+
};
|
|
231
|
+
const result = formatJobTypeDetails(jobTypeWithLongText);
|
|
232
|
+
expect(result).toContain('A'.repeat(400) + '…');
|
|
233
|
+
expect(result).toContain('B'.repeat(500) + '…');
|
|
234
|
+
});
|
|
235
|
+
it('should normalize date fields (ms to ISO)', () => {
|
|
236
|
+
const jobTypeWithTimestamps = {
|
|
237
|
+
id: 'timestamps',
|
|
238
|
+
name: 'Timestamp Type',
|
|
239
|
+
org_id: 'org-test',
|
|
240
|
+
created_at: 1704067200000, // 2024-01-01T00:00:00.000Z
|
|
241
|
+
updated_at: 1704153600000 // 2024-01-02T00:00:00.000Z
|
|
242
|
+
};
|
|
243
|
+
const result = formatJobTypeDetails(jobTypeWithTimestamps);
|
|
244
|
+
expect(result).toContain('Created At: 2024-01-01T00:00:00.000Z');
|
|
245
|
+
expect(result).toContain('Updated At: 2024-01-02T00:00:00.000Z');
|
|
246
|
+
});
|
|
247
|
+
it('should handle tags as CSV string', () => {
|
|
248
|
+
const jobTypeWithCSVTags = {
|
|
249
|
+
id: 'csv-tags',
|
|
250
|
+
name: 'CSV Tags Type',
|
|
251
|
+
org_id: 'org-test',
|
|
252
|
+
tags: 'tag1, tag2 , tag3',
|
|
253
|
+
created_at: '2025-01-01T00:00:00Z',
|
|
254
|
+
updated_at: '2025-01-01T00:00:00Z'
|
|
255
|
+
};
|
|
256
|
+
const result = formatJobTypeDetails(jobTypeWithCSVTags);
|
|
257
|
+
expect(result).toContain('Tags: tag1, tag2, tag3');
|
|
258
|
+
});
|
|
259
|
+
it('should handle tags as array', () => {
|
|
260
|
+
const jobTypeWithArrayTags = {
|
|
261
|
+
id: 'array-tags',
|
|
262
|
+
name: 'Array Tags Type',
|
|
263
|
+
org_id: 'org-test',
|
|
264
|
+
tags: ['tag1', 'tag2', 'tag3'],
|
|
265
|
+
created_at: '2025-01-01T00:00:00Z',
|
|
266
|
+
updated_at: '2025-01-01T00:00:00Z'
|
|
267
|
+
};
|
|
268
|
+
const result = formatJobTypeDetails(jobTypeWithArrayTags);
|
|
269
|
+
expect(result).toContain('Tags: tag1, tag2, tag3');
|
|
270
|
+
});
|
|
271
|
+
it('should summarize large schemas with "+N more" indication', () => {
|
|
272
|
+
const properties = {};
|
|
273
|
+
for (let i = 1; i <= 30; i++) {
|
|
274
|
+
properties[`prop${i}`] = { type: 'string', description: `Property ${i}` };
|
|
275
|
+
}
|
|
276
|
+
const jobTypeWithLargeSchema = {
|
|
277
|
+
id: 'large-schema',
|
|
278
|
+
name: 'Large Schema Type',
|
|
279
|
+
org_id: 'org-test',
|
|
280
|
+
params_schema: {
|
|
281
|
+
type: 'object',
|
|
282
|
+
required: ['prop1', 'prop2'],
|
|
283
|
+
properties
|
|
284
|
+
},
|
|
285
|
+
created_at: '2025-01-01T00:00:00Z',
|
|
286
|
+
updated_at: '2025-01-01T00:00:00Z'
|
|
287
|
+
};
|
|
288
|
+
const result = formatJobTypeDetails(jobTypeWithLargeSchema);
|
|
289
|
+
expect(result).toContain('Properties: 30');
|
|
290
|
+
expect(result).toContain('+18 more…');
|
|
291
|
+
expect(result).toContain('prop1: string (required)');
|
|
292
|
+
expect(result).toContain('prop12: string');
|
|
293
|
+
});
|
|
294
|
+
it('should return raw JSON on parse failure', () => {
|
|
295
|
+
const invalidJobType = { notAValidField: 123 };
|
|
296
|
+
const result = formatJobTypeDetails(invalidJobType);
|
|
297
|
+
expect(result).toContain('Job Type Details (raw):');
|
|
298
|
+
expect(result).toContain('"notAValidField": 123');
|
|
299
|
+
});
|
|
300
|
+
it('should respect formatter options', () => {
|
|
301
|
+
const result = formatJobTypeDetails(fullJobType, {
|
|
302
|
+
includeSchema: false,
|
|
303
|
+
renderAsMarkdown: false
|
|
304
|
+
});
|
|
305
|
+
expect(result).not.toContain('## Job Type Details');
|
|
306
|
+
expect(result).toContain('Job Type Details\n===========');
|
|
307
|
+
expect(result).not.toContain('Params Schema:');
|
|
308
|
+
});
|
|
309
|
+
it('should handle custom truncation limits', () => {
|
|
310
|
+
const longJobType = {
|
|
311
|
+
id: 'custom-truncate',
|
|
312
|
+
name: 'Custom Truncate',
|
|
313
|
+
org_id: 'org-test',
|
|
314
|
+
description: 'X'.repeat(100),
|
|
315
|
+
default_config: {
|
|
316
|
+
start_prompt: 'Y'.repeat(100)
|
|
317
|
+
},
|
|
318
|
+
created_at: '2025-01-01T00:00:00Z',
|
|
319
|
+
updated_at: '2025-01-01T00:00:00Z'
|
|
320
|
+
};
|
|
321
|
+
const result = formatJobTypeDetails(longJobType, {
|
|
322
|
+
truncate: {
|
|
323
|
+
description: 50,
|
|
324
|
+
startPrompt: 30
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
expect(result).toContain('X'.repeat(50) + '…');
|
|
328
|
+
expect(result).toContain('Y'.repeat(30) + '…');
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
describe('formatJobTypeSummary', () => {
|
|
332
|
+
it('should format a complete job type summary', () => {
|
|
333
|
+
const jobType = {
|
|
334
|
+
id: 'summary-test',
|
|
335
|
+
name: 'Summary Test Type',
|
|
336
|
+
org_id: 'org-test',
|
|
337
|
+
active: true,
|
|
338
|
+
default_config: {
|
|
339
|
+
max_task_retries: 3,
|
|
340
|
+
task_retry_interval: 15,
|
|
341
|
+
max_time_to_complete: 60,
|
|
342
|
+
failure_cooldown_minutes: 30
|
|
343
|
+
},
|
|
344
|
+
params_schema: {
|
|
345
|
+
type: 'object',
|
|
346
|
+
required: ['field1', 'field2'],
|
|
347
|
+
properties: {
|
|
348
|
+
field1: { type: 'string' },
|
|
349
|
+
field2: { type: 'number' },
|
|
350
|
+
field3: { type: 'boolean' }
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
created_at: '2025-01-01T00:00:00Z',
|
|
354
|
+
updated_at: '2025-01-01T00:00:00Z'
|
|
355
|
+
};
|
|
356
|
+
const result = formatJobTypeSummary(jobType);
|
|
357
|
+
expect(result).toContain('ID: summary-test');
|
|
358
|
+
expect(result).toContain('Name: Summary Test Type');
|
|
359
|
+
expect(result).toContain('Active: yes');
|
|
360
|
+
expect(result).toContain('Retries: 3 every 15 min');
|
|
361
|
+
expect(result).toContain('Max Time: 60 min');
|
|
362
|
+
expect(result).toContain('Cooldown: 30 min');
|
|
363
|
+
expect(result).toContain('Params: required=2, props=3');
|
|
364
|
+
});
|
|
365
|
+
it('should handle minimal job type summary', () => {
|
|
366
|
+
const minimalJobType = {
|
|
367
|
+
id: 'minimal',
|
|
368
|
+
name: 'Minimal',
|
|
369
|
+
org_id: 'org-test',
|
|
370
|
+
created_at: '2025-01-01T00:00:00Z',
|
|
371
|
+
updated_at: '2025-01-01T00:00:00Z'
|
|
372
|
+
};
|
|
373
|
+
const result = formatJobTypeSummary(minimalJobType);
|
|
374
|
+
expect(result).toContain('ID: minimal');
|
|
375
|
+
expect(result).toContain('Name: Minimal');
|
|
376
|
+
expect(result).toContain('Active: n/a');
|
|
377
|
+
expect(result).toContain('Retries: n/a');
|
|
378
|
+
expect(result).toContain('Max Time: n/a');
|
|
379
|
+
expect(result).toContain('Cooldown: n/a');
|
|
380
|
+
expect(result).toContain('Params: n/a');
|
|
381
|
+
});
|
|
382
|
+
it('should return raw JSON for invalid job type', () => {
|
|
383
|
+
const invalidJobType = { invalid: true };
|
|
384
|
+
const result = formatJobTypeSummary(invalidJobType);
|
|
385
|
+
expect(result).toContain('"invalid": true');
|
|
386
|
+
});
|
|
387
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* A flexible Zod schema for validating ISO 8601 date-time strings.
|
|
4
|
+
*
|
|
5
|
+
* This schema accepts any string that can be successfully parsed by the `Date` constructor,
|
|
6
|
+
* which includes formats with 'Z' (UTC) and timezone offsets (e.g., '+01:00').
|
|
7
|
+
* It refines a base string schema, providing a more specific error message if the
|
|
8
|
+
* date-time string is invalid.
|
|
9
|
+
*/
|
|
10
|
+
export const flexibleDateTimeSchema = z.string().refine((value) => {
|
|
11
|
+
// Try to parse the date string.
|
|
12
|
+
// The Date constructor is quite flexible with ISO 8601 formats.
|
|
13
|
+
const date = new Date(value);
|
|
14
|
+
// Check if the parsed date is valid.
|
|
15
|
+
// `isNaN(date.getTime())` is a reliable way to check for invalid dates.
|
|
16
|
+
return !isNaN(date.getTime());
|
|
17
|
+
}, {
|
|
18
|
+
// Custom error message for invalid date-time strings.
|
|
19
|
+
message: "Invalid date-time string. Please use a valid ISO 8601 format.",
|
|
20
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { flexibleDateTimeSchema } from './schemas.js';
|
|
3
|
+
describe('flexibleDateTimeSchema', () => {
|
|
4
|
+
// Test case 1: Valid ISO 8601 with UTC 'Z'
|
|
5
|
+
it('should validate a correct ISO 8601 string with Zulu time', () => {
|
|
6
|
+
const validDate = '2025-07-23T21:00:00Z';
|
|
7
|
+
const result = flexibleDateTimeSchema.safeParse(validDate);
|
|
8
|
+
expect(result.success).toBe(true);
|
|
9
|
+
});
|
|
10
|
+
// Test case 2: Valid ISO 8601 with a positive timezone offset
|
|
11
|
+
it('should validate a correct ISO 8601 string with a positive offset', () => {
|
|
12
|
+
const validDate = '2025-07-23T22:00:00+01:00';
|
|
13
|
+
const result = flexibleDateTimeSchema.safeParse(validDate);
|
|
14
|
+
expect(result.success).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
// Test case 3: Valid ISO 8601 with a negative timezone offset
|
|
17
|
+
it('should validate a correct ISO 8601 string with a negative offset', () => {
|
|
18
|
+
const validDate = '2025-07-23T16:00:00-05:00';
|
|
19
|
+
const result = flexibleDateTimeSchema.safeParse(validDate);
|
|
20
|
+
expect(result.success).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
// Test case 4: Invalid date format (not ISO 8601)
|
|
23
|
+
it('should not validate an incorrect date format', () => {
|
|
24
|
+
const invalidDate = '23/07/2025 21:00:00';
|
|
25
|
+
const result = flexibleDateTimeSchema.safeParse(invalidDate);
|
|
26
|
+
expect(result.success).toBe(false);
|
|
27
|
+
});
|
|
28
|
+
// Test case 5: Invalid date string (gibberish)
|
|
29
|
+
it('should not validate a gibberish string', () => {
|
|
30
|
+
const gibberish = 'not-a-date';
|
|
31
|
+
const result = flexibleDateTimeSchema.safeParse(gibberish);
|
|
32
|
+
expect(result.success).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
// Test case 6: Empty string
|
|
35
|
+
it('should not validate an empty string', () => {
|
|
36
|
+
const emptyString = '';
|
|
37
|
+
const result = flexibleDateTimeSchema.safeParse(emptyString);
|
|
38
|
+
expect(result.success).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# 🔍 Guia de Debug do MCP Server
|
|
2
|
+
|
|
3
|
+
Este guia fornece várias maneiras de depurar e diagnosticar problemas no seu MCP Server.
|
|
4
|
+
|
|
5
|
+
## 🚀 Comandos de Debug Disponíveis
|
|
6
|
+
|
|
7
|
+
### 1. Debug Básico
|
|
8
|
+
```bash
|
|
9
|
+
# Compilar e rodar em modo debug
|
|
10
|
+
npm run debug
|
|
11
|
+
|
|
12
|
+
# Debug com variáveis de ambiente específicas
|
|
13
|
+
MCP_DEBUG=true npm run debug
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### 2. Testar Tools
|
|
17
|
+
```bash
|
|
18
|
+
# Verificar se todas as tools estão carregando
|
|
19
|
+
npm run test:tools
|
|
20
|
+
|
|
21
|
+
# Verificar configuração atual
|
|
22
|
+
npm run cli:config
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### 3. Debug com Logs Detalhados
|
|
26
|
+
```bash
|
|
27
|
+
# Usar arquivo de ambiente de debug
|
|
28
|
+
cp .env.debug .env
|
|
29
|
+
# Editar .env com suas credenciais
|
|
30
|
+
npm run debug
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 🛠️ Tipos de Debug
|
|
34
|
+
|
|
35
|
+
### 1. **Debug de Configuração**
|
|
36
|
+
```bash
|
|
37
|
+
npm run cli:config
|
|
38
|
+
```
|
|
39
|
+
Este comando mostra:
|
|
40
|
+
- URL da API
|
|
41
|
+
- Status da chave de API
|
|
42
|
+
- Versão do Node.js
|
|
43
|
+
- Versão do MCP Server
|
|
44
|
+
|
|
45
|
+
### 2. **Debug de Carregamento de Tools**
|
|
46
|
+
```bash
|
|
47
|
+
npm run test:tools
|
|
48
|
+
```
|
|
49
|
+
Este comando:
|
|
50
|
+
- Lista todas as tools encontradas
|
|
51
|
+
- Testa o carregamento de cada tool
|
|
52
|
+
- Mostra erros de carregamento se houver
|
|
53
|
+
|
|
54
|
+
### 3. **Debug Completo em Runtime**
|
|
55
|
+
```bash
|
|
56
|
+
MCP_DEBUG=true npm run debug
|
|
57
|
+
```
|
|
58
|
+
Com `MCP_DEBUG=true`, você verá:
|
|
59
|
+
- Logs de inicialização detalhados
|
|
60
|
+
- Configuração carregada
|
|
61
|
+
- Chamadas de tools em tempo real
|
|
62
|
+
- Respostas das APIs
|
|
63
|
+
- Erros detalhados
|
|
64
|
+
|
|
65
|
+
## 📊 Interpretando os Logs
|
|
66
|
+
|
|
67
|
+
### Logs de Tool
|
|
68
|
+
```
|
|
69
|
+
[MCP-DEBUG 2025-08-13T...] [INFO] Tool called: get_jobs_stats
|
|
70
|
+
[MCP-DEBUG 2025-08-13T...] [DEBUG] Data: {"org_id":"aiconnect"}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Logs de API
|
|
74
|
+
```
|
|
75
|
+
[MCP-DEBUG 2025-08-13T...] [DEBUG] HTTP GET https://api.aiconnect.cloud/api/v0/jobs/stats
|
|
76
|
+
[MCP-DEBUG 2025-08-13T...] [DEBUG] HTTP Response GET [200]
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Logs de Erro
|
|
80
|
+
```
|
|
81
|
+
[MCP-DEBUG 2025-08-13T...] [ERROR] Tool error: get_jobs_stats
|
|
82
|
+
[MCP-DEBUG 2025-08-13T...] [ERROR] Data: {"error": "API key not provided"}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## 🔧 Problemas Comuns e Soluções
|
|
86
|
+
|
|
87
|
+
### 1. **API Key não configurada**
|
|
88
|
+
**Erro:** `API key not provided`
|
|
89
|
+
**Solução:**
|
|
90
|
+
```bash
|
|
91
|
+
# Definir a chave de API
|
|
92
|
+
export AICONNECT_API_KEY="sua-chave-aqui"
|
|
93
|
+
npm run debug
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### 2. **URL da API incorreta**
|
|
97
|
+
**Erro:** `Request failed with status 404`
|
|
98
|
+
**Solução:**
|
|
99
|
+
```bash
|
|
100
|
+
# Verificar/definir URL da API
|
|
101
|
+
export AICONNECT_API_URL="https://api.aiconnect.cloud/api/v0"
|
|
102
|
+
npm run debug
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 3. **Tools não carregando**
|
|
106
|
+
**Erro:** `Error loading tools`
|
|
107
|
+
**Solução:**
|
|
108
|
+
```bash
|
|
109
|
+
# Verificar se o build está atualizado
|
|
110
|
+
npm run build
|
|
111
|
+
npm run test:tools
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 4. **Problemas de Conexão MCP**
|
|
115
|
+
**Erro:** `Transport connection closed`
|
|
116
|
+
**Solução:**
|
|
117
|
+
- Verificar se o cliente MCP está conectado corretamente
|
|
118
|
+
- Usar logs de debug para ver detalhes da conexão
|
|
119
|
+
|
|
120
|
+
## 📝 Configuração para Claude Desktop
|
|
121
|
+
|
|
122
|
+
Para debug no Claude Desktop, use esta configuração no `claude_desktop_config.json`:
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"mcpServers": {
|
|
127
|
+
"agentjobs-debug": {
|
|
128
|
+
"command": "node",
|
|
129
|
+
"args": ["/caminho/para/agentjobs-mcp/build/debug.js"],
|
|
130
|
+
"env": {
|
|
131
|
+
"MCP_DEBUG": "true",
|
|
132
|
+
"AICONNECT_API_KEY": "sua-chave-aqui",
|
|
133
|
+
"AICONNECT_API_URL": "https://api.aiconnect.cloud/api/v0",
|
|
134
|
+
"DEFAULT_ORG_ID": "aiconnect"
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## 🧪 Testes de Funcionalidade
|
|
142
|
+
|
|
143
|
+
### Teste Manual das Tools
|
|
144
|
+
|
|
145
|
+
1. **Listar Jobs:**
|
|
146
|
+
- Comando: "List all jobs"
|
|
147
|
+
- Debug: Verificar se a API é chamada e se os dados são formatados
|
|
148
|
+
|
|
149
|
+
2. **Buscar Job Específico:**
|
|
150
|
+
- Comando: "Get job details for job-123"
|
|
151
|
+
- Debug: Verificar parâmetros enviados e resposta recebida
|
|
152
|
+
|
|
153
|
+
3. **Criar Job:**
|
|
154
|
+
- Comando: "Create a new job"
|
|
155
|
+
- Debug: Verificar payload da criação e resposta
|
|
156
|
+
|
|
157
|
+
### Teste de Configuração
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
# Verificar todas as configurações
|
|
161
|
+
npm run cli:config
|
|
162
|
+
|
|
163
|
+
# Verificar carregamento das tools
|
|
164
|
+
npm run test:tools
|
|
165
|
+
|
|
166
|
+
# Verificar versão
|
|
167
|
+
npm run cli:version
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## 🎯 Dicas de Debug
|
|
171
|
+
|
|
172
|
+
1. **Use logs incrementais:** Comece com debug básico e aumente verbosidade conforme necessário
|
|
173
|
+
|
|
174
|
+
2. **Teste isoladamente:** Use `npm run test:tools` para verificar carregamento antes de testar funcionalidade
|
|
175
|
+
|
|
176
|
+
3. **Monitore a API:** Se possível, monitore logs da API AI Connect em paralelo
|
|
177
|
+
|
|
178
|
+
4. **Verifique configuração:** Sempre rode `npm run cli:config` primeiro
|
|
179
|
+
|
|
180
|
+
5. **Use ambiente controlado:** Use `.env.debug` para configuração consistente
|
|
181
|
+
|
|
182
|
+
## 📋 Checklist de Debug
|
|
183
|
+
|
|
184
|
+
- [ ] Configuração carregada corretamente (`npm run cli:config`)
|
|
185
|
+
- [ ] Tools carregando sem erro (`npm run test:tools`)
|
|
186
|
+
- [ ] API key configurada
|
|
187
|
+
- [ ] URL da API acessível
|
|
188
|
+
- [ ] Logs de debug habilitados (`MCP_DEBUG=true`)
|
|
189
|
+
- [ ] Conexão MCP funcionando
|
|
190
|
+
- [ ] Tools respondendo conforme esperado
|
|
191
|
+
|
|
192
|
+
## 🆘 Debugging Avançado
|
|
193
|
+
|
|
194
|
+
Para problemas complexos, você pode:
|
|
195
|
+
|
|
196
|
+
1. **Adicionar mais logs** nas tools específicas
|
|
197
|
+
2. **Usar Node.js debugger** com `--inspect`
|
|
198
|
+
3. **Monitorar requisições HTTP** com `DEBUG=axios`
|
|
199
|
+
4. **Verificar memória e performance** com `node --trace-warnings`
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
💡 **Lembre-se:** O debug é mais eficiente quando feito de forma incremental, testando cada componente isoladamente antes de testar o sistema completo.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiconnect/agentjobs-mcp",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "MCP (Model Context Protocol) server for managing Agent Jobs in the AI Connect platform. Developed by AI Connect - Advanced AI automation and integration solutions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "build/index.js",
|
|
@@ -28,10 +28,14 @@
|
|
|
28
28
|
"build": "tsc && chmod +x build/index.js",
|
|
29
29
|
"start": "node build/index.js",
|
|
30
30
|
"dev": "tsc && node build/index.js",
|
|
31
|
+
"debug": "tsc && node build/debug.js",
|
|
32
|
+
"debug:watch": "tsc --watch & sleep 2 && node build/debug.js",
|
|
31
33
|
"clean": "rm -rf build",
|
|
32
34
|
"typecheck": "tsc --noEmit",
|
|
33
|
-
"lint": "
|
|
34
|
-
"
|
|
35
|
+
"lint": "eslint . --ext .ts,.js",
|
|
36
|
+
"lint:fix": "eslint . --ext .ts,.js --fix",
|
|
37
|
+
"test": "vitest",
|
|
38
|
+
"test:tools": "npm run build && node build/test-tools.js",
|
|
35
39
|
"prepare": "npm run build",
|
|
36
40
|
"version:patch": "npm version patch",
|
|
37
41
|
"version:minor": "npm version minor",
|
|
@@ -53,7 +57,11 @@
|
|
|
53
57
|
},
|
|
54
58
|
"devDependencies": {
|
|
55
59
|
"@types/node": "^22.10.5",
|
|
56
|
-
"
|
|
60
|
+
"@eslint/js": "^9.14.0",
|
|
61
|
+
"eslint": "^9.14.0",
|
|
62
|
+
"typescript-eslint": "^8.12.2",
|
|
63
|
+
"typescript": "^5.7.2",
|
|
64
|
+
"vitest": "^3.2.4"
|
|
57
65
|
},
|
|
58
66
|
"engines": {
|
|
59
67
|
"node": ">=18.0.0"
|