@aiconnect/agentjobs-mcp 1.0.9 → 1.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/.env.example +7 -0
- package/README.md +67 -0
- package/build/config.js +10 -3
- package/build/config.test.js +22 -0
- package/build/debug.js +86 -0
- package/build/index.js +71 -7
- package/build/lib/agentJobsClient.js +90 -0
- package/build/test-tools.js +53 -0
- package/build/tools/cancel_job.js +35 -50
- package/build/tools/create_job.js +49 -69
- package/build/tools/get_context.js +61 -0
- package/build/tools/get_job.js +30 -58
- package/build/tools/get_job_type.js +40 -56
- package/build/tools/get_jobs_stats.js +47 -0
- package/build/tools/list_jobs.js +44 -58
- package/build/utils/debugger.js +74 -0
- package/build/utils/formatters.js +516 -44
- package/build/utils/formatters.test.js +629 -0
- package/build/utils/schemas.js +24 -0
- package/build/utils/schemas.test.js +95 -0
- package/build/utils/version.js +12 -0
- package/build/utils/version.test.js +9 -0
- package/docs/agent-jobs-api.md +41 -0
- package/docs/debug-guide.md +203 -0
- package/package.json +13 -4
|
@@ -1,71 +1,56 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import
|
|
2
|
+
import agentJobsClient from '../lib/agentJobsClient.js';
|
|
3
3
|
import { config } from '../config.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* Only the essentials are required to keep the contract LLM‑friendly.
|
|
7
|
-
*/
|
|
4
|
+
import { flexibleDateTimeSchema } from '../utils/schemas.js';
|
|
5
|
+
import { mcpDebugger, withTiming } from '../utils/debugger.js';
|
|
8
6
|
export default (server) => {
|
|
9
|
-
server.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
.object({
|
|
18
|
-
platform: z
|
|
19
|
-
.enum(['whatsapp', 'slack', 'web'])
|
|
20
|
-
.describe('Destination platform.'),
|
|
21
|
-
code: z
|
|
22
|
-
.string()
|
|
23
|
-
.describe('Channel identifier, phone number, or user ID.'),
|
|
24
|
-
org_id: z
|
|
7
|
+
server.registerTool('create_job', {
|
|
8
|
+
description: 'Create a new Agent Job with the minimal set of fields.',
|
|
9
|
+
annotations: {
|
|
10
|
+
title: 'Create Agent Job'
|
|
11
|
+
},
|
|
12
|
+
inputSchema: {
|
|
13
|
+
// Required fields
|
|
14
|
+
job_type_id: z
|
|
25
15
|
.string()
|
|
16
|
+
.describe('ID of the job type (e.g. "mood-monitor")'),
|
|
17
|
+
target_channel: z
|
|
18
|
+
.object({
|
|
19
|
+
platform: z
|
|
20
|
+
.enum(['whatsapp', 'slack', 'web'])
|
|
21
|
+
.describe('Destination platform.'),
|
|
22
|
+
code: z
|
|
23
|
+
.string()
|
|
24
|
+
.describe('Channel identifier, phone number, or user ID.'),
|
|
25
|
+
org_id: z
|
|
26
|
+
.string()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe('Org ID – defaults to config.DEFAULT_ORG_ID')
|
|
29
|
+
})
|
|
30
|
+
.describe('Where the agent will communicate.'),
|
|
31
|
+
params: z
|
|
32
|
+
.record(z.any())
|
|
26
33
|
.optional()
|
|
27
|
-
.describe('
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
.record(z.any())
|
|
32
|
-
.optional()
|
|
33
|
-
.describe('Free‑form params passed to the agent'),
|
|
34
|
-
scheduled_at: z
|
|
35
|
-
.string()
|
|
36
|
-
.datetime({ message: 'Use ISO‑8601' })
|
|
37
|
-
.optional()
|
|
38
|
-
.describe('Schedule the job to run later')
|
|
39
|
-
},
|
|
40
|
-
// ───────────────────────────────────────────────────────────────────────────┐
|
|
41
|
-
// Implementation │
|
|
42
|
-
// ───────────────────────────────────────────────────────────────────────────┘
|
|
43
|
-
async (params) => {
|
|
44
|
-
const { AICONNECT_API_URL: apiUrl, AICONNECT_API_KEY: apiKey, DEFAULT_ORG_ID } = config;
|
|
45
|
-
if (!apiUrl) {
|
|
46
|
-
return {
|
|
47
|
-
content: [{
|
|
48
|
-
type: "text",
|
|
49
|
-
text: "Error: API URL is not configured. Please set AICONNECT_API_URL environment variable."
|
|
50
|
-
}]
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
if (!apiKey) {
|
|
54
|
-
return {
|
|
55
|
-
content: [{
|
|
56
|
-
type: "text",
|
|
57
|
-
text: "Error: API Key is not configured. Please set AICONNECT_API_KEY environment variable."
|
|
58
|
-
}]
|
|
59
|
-
};
|
|
34
|
+
.describe('Free‑form params passed to the agent'),
|
|
35
|
+
scheduled_at: flexibleDateTimeSchema()
|
|
36
|
+
.optional()
|
|
37
|
+
.describe('Schedule the job to run later')
|
|
60
38
|
}
|
|
39
|
+
}, async (params) => {
|
|
40
|
+
mcpDebugger.toolCall("create_job", params);
|
|
61
41
|
// Fall back to default org if none supplied.
|
|
62
|
-
params.target_channel.org_id ??= DEFAULT_ORG_ID;
|
|
42
|
+
params.target_channel.org_id ??= config.DEFAULT_ORG_ID;
|
|
43
|
+
mcpDebugger.debug("Job creation payload", {
|
|
44
|
+
job_type_id: params.job_type_id,
|
|
45
|
+
target_channel: params.target_channel,
|
|
46
|
+
scheduled_at: params.scheduled_at,
|
|
47
|
+
paramsCount: params.params ? Object.keys(params.params).length : 0
|
|
48
|
+
});
|
|
63
49
|
try {
|
|
64
|
-
const res = await
|
|
65
|
-
|
|
66
|
-
});
|
|
67
|
-
const
|
|
68
|
-
return {
|
|
50
|
+
const res = await withTiming(() => agentJobsClient.post('/services/agent-jobs', params), "create_job API call");
|
|
51
|
+
const jobId = res.id ?? 'unknown';
|
|
52
|
+
mcpDebugger.debug("Job creation response", { jobId, fullResponse: res });
|
|
53
|
+
const result = {
|
|
69
54
|
content: [
|
|
70
55
|
{
|
|
71
56
|
type: 'text',
|
|
@@ -73,21 +58,16 @@ export default (server) => {
|
|
|
73
58
|
}
|
|
74
59
|
]
|
|
75
60
|
};
|
|
61
|
+
mcpDebugger.toolResponse("create_job", { jobId });
|
|
62
|
+
return result;
|
|
76
63
|
}
|
|
77
64
|
catch (error) {
|
|
78
|
-
|
|
79
|
-
if (axios.isAxiosError(error) && error.response) {
|
|
80
|
-
const apiError = error.response.data?.message || error.response.data?.error || JSON.stringify(error.response.data);
|
|
81
|
-
errorMessage = `API Error (${error.response.status}): ${apiError || error.message}`;
|
|
82
|
-
}
|
|
83
|
-
else if (error instanceof Error) {
|
|
84
|
-
errorMessage = `Error: ${error.message}`;
|
|
85
|
-
}
|
|
65
|
+
mcpDebugger.toolError("create_job", error);
|
|
86
66
|
return {
|
|
87
67
|
content: [
|
|
88
68
|
{
|
|
89
69
|
type: 'text',
|
|
90
|
-
text:
|
|
70
|
+
text: `Error creating job: ${error.message}`
|
|
91
71
|
}
|
|
92
72
|
]
|
|
93
73
|
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import agentJobsClient from '../lib/agentJobsClient.js';
|
|
3
|
+
import { config } from '../config.js';
|
|
4
|
+
import { formatContext } from '../utils/formatters.js';
|
|
5
|
+
import { mcpServerVersion } from '../utils/version.js';
|
|
6
|
+
import { mcpDebugger, withTiming } from '../utils/debugger.js';
|
|
7
|
+
export default (server) => {
|
|
8
|
+
server.registerTool('get_context', {
|
|
9
|
+
description: 'Returns the MCP server runtime context: local defaults (org_id, timezone, API URL, server version) plus the list of agent job types available in the effective organization. Designed to be the first call an LLM client makes — surfaces what would otherwise be invisible env vars and avoids guessing valid job type IDs before calling create_job.',
|
|
10
|
+
annotations: {
|
|
11
|
+
title: 'Get MCP Server Runtime Context'
|
|
12
|
+
},
|
|
13
|
+
inputSchema: {
|
|
14
|
+
org_id: z
|
|
15
|
+
.string()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe('Override DEFAULT_ORG_ID for this call (introspect a different org without restarting the server)')
|
|
18
|
+
}
|
|
19
|
+
}, async (params) => {
|
|
20
|
+
mcpDebugger.toolCall('get_context', params);
|
|
21
|
+
const effectiveOrgId = params.org_id || config.DEFAULT_ORG_ID;
|
|
22
|
+
const localConfig = {
|
|
23
|
+
org_id: effectiveOrgId,
|
|
24
|
+
timezone: config.DEFAULT_TIMEZONE,
|
|
25
|
+
api_url: config.AICONNECT_API_URL,
|
|
26
|
+
server_version: mcpServerVersion
|
|
27
|
+
};
|
|
28
|
+
let jobTypes;
|
|
29
|
+
let total;
|
|
30
|
+
let jobTypesError;
|
|
31
|
+
try {
|
|
32
|
+
const response = await withTiming(() => agentJobsClient.listJobTypes(effectiveOrgId), 'get_context.listJobTypes API call');
|
|
33
|
+
jobTypes = (response?.data ?? []).map((jt) => ({
|
|
34
|
+
id: jt.id,
|
|
35
|
+
name: jt.name,
|
|
36
|
+
description: jt.description,
|
|
37
|
+
emoji: jt.emoji
|
|
38
|
+
}));
|
|
39
|
+
total = response?.meta?.total ?? jobTypes.length;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
jobTypesError = error?.message ?? String(error);
|
|
43
|
+
mcpDebugger.toolError('get_context.listJobTypes', error);
|
|
44
|
+
}
|
|
45
|
+
const text = formatContext({ localConfig, jobTypes, total, jobTypesError });
|
|
46
|
+
mcpDebugger.toolResponse('get_context', {
|
|
47
|
+
org_id: effectiveOrgId,
|
|
48
|
+
jobTypesCount: jobTypes?.length,
|
|
49
|
+
hasError: !!jobTypesError,
|
|
50
|
+
resultLength: text.length
|
|
51
|
+
});
|
|
52
|
+
return {
|
|
53
|
+
content: [
|
|
54
|
+
{
|
|
55
|
+
type: 'text',
|
|
56
|
+
text
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
};
|
package/build/tools/get_job.js
CHANGED
|
@@ -1,51 +1,32 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import
|
|
3
|
-
import { config } from '../config.js';
|
|
2
|
+
import agentJobsClient from '../lib/agentJobsClient.js';
|
|
4
3
|
import { formatJobDetails } from '../utils/formatters.js';
|
|
4
|
+
import { mcpDebugger, withTiming } from '../utils/debugger.js';
|
|
5
5
|
export default (server) => {
|
|
6
|
-
server.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
server.registerTool('get_job', {
|
|
7
|
+
description: 'Retrieves an agent job by its ID.',
|
|
8
|
+
annotations: {
|
|
9
|
+
title: 'Get Agent Job'
|
|
10
|
+
},
|
|
11
|
+
inputSchema: {
|
|
12
|
+
job_id: z.string({
|
|
13
|
+
description: "The unique identifier of the job you want to retrieve. Example: 'job-12345'."
|
|
14
|
+
}),
|
|
15
|
+
org_id: z
|
|
16
|
+
.string({
|
|
17
|
+
description: "The organization ID. Example: 'aiconnect'."
|
|
18
|
+
})
|
|
19
|
+
.optional()
|
|
20
|
+
}
|
|
15
21
|
}, async (params) => {
|
|
22
|
+
mcpDebugger.toolCall("get_job", params);
|
|
16
23
|
const { job_id } = params;
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
if (!apiUrl) {
|
|
20
|
-
return {
|
|
21
|
-
content: [
|
|
22
|
-
{
|
|
23
|
-
type: 'text',
|
|
24
|
-
text: 'Error: API URL is not configured. Please set AICONNECT_API_URL environment variable.'
|
|
25
|
-
}
|
|
26
|
-
]
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
if (!apiKey) {
|
|
30
|
-
return {
|
|
31
|
-
content: [
|
|
32
|
-
{
|
|
33
|
-
type: 'text',
|
|
34
|
-
text: 'Error: API Key is not configured. Please set AICONNECT_API_KEY environment variable.'
|
|
35
|
-
}
|
|
36
|
-
]
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
const endpoint = `${apiUrl}/services/agent-jobs/${job_id}${params.org_id ? `?org_id=${params.org_id}` : ''}`;
|
|
40
|
-
const headers = {
|
|
41
|
-
Authorization: `Bearer ${apiKey}`
|
|
42
|
-
};
|
|
24
|
+
const endpoint = `/services/agent-jobs/${job_id}${params.org_id ? `?org_id=${params.org_id}` : ''}`;
|
|
25
|
+
mcpDebugger.debug("Built endpoint", { endpoint, job_id, org_id: params.org_id });
|
|
43
26
|
try {
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const job = response.data?.data || response.data;
|
|
48
|
-
return {
|
|
27
|
+
const job = await withTiming(() => agentJobsClient.get(endpoint), "get_job API call");
|
|
28
|
+
mcpDebugger.debug("Raw API response", { job });
|
|
29
|
+
const result = {
|
|
49
30
|
content: [
|
|
50
31
|
{
|
|
51
32
|
type: 'text',
|
|
@@ -53,28 +34,19 @@ export default (server) => {
|
|
|
53
34
|
}
|
|
54
35
|
]
|
|
55
36
|
};
|
|
37
|
+
mcpDebugger.toolResponse("get_job", {
|
|
38
|
+
jobId: job_id,
|
|
39
|
+
resultLength: result.content[0].text.length
|
|
40
|
+
});
|
|
41
|
+
return result;
|
|
56
42
|
}
|
|
57
43
|
catch (error) {
|
|
58
|
-
|
|
59
|
-
let errorDetails = {};
|
|
60
|
-
if (axios.isAxiosError(error) && error.response) {
|
|
61
|
-
const apiError = error.response.data?.message ||
|
|
62
|
-
error.response.data?.error ||
|
|
63
|
-
JSON.stringify(error.response.data);
|
|
64
|
-
errorMessage = `API Error (${error.response.status}): ${apiError || error.message}`;
|
|
65
|
-
errorDetails = {
|
|
66
|
-
status: error.response.status,
|
|
67
|
-
data: error.response.data
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
else if (error instanceof Error) {
|
|
71
|
-
errorMessage = `Error: ${error.message}`;
|
|
72
|
-
}
|
|
44
|
+
mcpDebugger.toolError("get_job", error);
|
|
73
45
|
return {
|
|
74
46
|
content: [
|
|
75
47
|
{
|
|
76
48
|
type: 'text',
|
|
77
|
-
text:
|
|
49
|
+
text: `Error getting job: ${error.message}`
|
|
78
50
|
}
|
|
79
51
|
]
|
|
80
52
|
};
|
|
@@ -1,79 +1,63 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import
|
|
2
|
+
import agentJobsClient from '../lib/agentJobsClient.js';
|
|
3
3
|
import { config } from '../config.js';
|
|
4
4
|
import { formatJobTypeDetails } from '../utils/formatters.js';
|
|
5
|
+
import { mcpDebugger, withTiming } from '../utils/debugger.js';
|
|
5
6
|
export default (server) => {
|
|
6
|
-
server.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
server.registerTool('get_job_type', {
|
|
8
|
+
description: 'Retrieves an agent job type by its ID.',
|
|
9
|
+
annotations: {
|
|
10
|
+
title: 'Get Job Type Configuration'
|
|
11
|
+
},
|
|
12
|
+
inputSchema: {
|
|
13
|
+
job_type_id: z.string({
|
|
14
|
+
description: "The unique identifier of the job type you want to retrieve. Example: 'mood-monitor'."
|
|
15
|
+
}),
|
|
16
|
+
org_id: z.string({
|
|
17
|
+
description: "The organization ID. Example: 'aiconnect'."
|
|
18
|
+
}).optional()
|
|
19
|
+
}
|
|
13
20
|
}, async (params) => {
|
|
21
|
+
mcpDebugger.toolCall("get_job_type", params);
|
|
14
22
|
const { job_type_id } = params;
|
|
15
23
|
const org_id = params.org_id || config.DEFAULT_ORG_ID;
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
text: 'Error: API URL is not configured. Please set AICONNECT_API_URL environment variable.'
|
|
24
|
-
}
|
|
25
|
-
]
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
if (!apiKey) {
|
|
29
|
-
return {
|
|
30
|
-
content: [
|
|
31
|
-
{
|
|
32
|
-
type: 'text',
|
|
33
|
-
text: 'Error: API Key is not configured. Please set AICONNECT_API_KEY environment variable.'
|
|
34
|
-
}
|
|
35
|
-
]
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
const endpoint = `${apiUrl}/organizations/${org_id}/agent-jobs-type/${job_type_id}`;
|
|
39
|
-
const headers = {
|
|
40
|
-
Authorization: `Bearer ${apiKey}`
|
|
41
|
-
};
|
|
24
|
+
const endpoint = `/organizations/${org_id}/agent-jobs-type/${job_type_id}`;
|
|
25
|
+
mcpDebugger.debug("Job type request", {
|
|
26
|
+
endpoint,
|
|
27
|
+
job_type_id,
|
|
28
|
+
org_id,
|
|
29
|
+
usingDefaultOrg: !params.org_id
|
|
30
|
+
});
|
|
42
31
|
try {
|
|
43
|
-
const response = await
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
32
|
+
const response = await withTiming(() => agentJobsClient.get(endpoint), "get_job_type API call");
|
|
33
|
+
mcpDebugger.debug("Job type raw response", { response });
|
|
34
|
+
// The API returns { data: {...}, meta: {...} } but agentJobsClient should extract data
|
|
35
|
+
// However, let's be defensive and handle both cases
|
|
36
|
+
const jobTypeData = response?.data ? response.data : response;
|
|
37
|
+
mcpDebugger.debug("Job type extracted data", { jobTypeData });
|
|
38
|
+
const result = {
|
|
48
39
|
content: [
|
|
49
40
|
{
|
|
50
41
|
type: 'text',
|
|
51
|
-
text: formatJobTypeDetails(
|
|
42
|
+
text: formatJobTypeDetails(jobTypeData)
|
|
52
43
|
}
|
|
53
44
|
]
|
|
54
45
|
};
|
|
46
|
+
mcpDebugger.toolResponse("get_job_type", {
|
|
47
|
+
job_type_id,
|
|
48
|
+
org_id,
|
|
49
|
+
resultLength: result.content[0].text.length,
|
|
50
|
+
hasData: !!jobTypeData
|
|
51
|
+
});
|
|
52
|
+
return result;
|
|
55
53
|
}
|
|
56
54
|
catch (error) {
|
|
57
|
-
|
|
58
|
-
let errorDetails = {};
|
|
59
|
-
if (axios.isAxiosError(error) && error.response) {
|
|
60
|
-
const apiError = error.response.data?.message ||
|
|
61
|
-
error.response.data?.error ||
|
|
62
|
-
JSON.stringify(error.response.data);
|
|
63
|
-
errorMessage = `API Error (${error.response.status}): ${apiError || error.message}`;
|
|
64
|
-
errorDetails = {
|
|
65
|
-
status: error.response.status,
|
|
66
|
-
data: error.response.data
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
else if (error instanceof Error) {
|
|
70
|
-
errorMessage = `Error: ${error.message}`;
|
|
71
|
-
}
|
|
55
|
+
mcpDebugger.toolError("get_job_type", error);
|
|
72
56
|
return {
|
|
73
57
|
content: [
|
|
74
58
|
{
|
|
75
59
|
type: 'text',
|
|
76
|
-
text:
|
|
60
|
+
text: `Error getting job type: ${error.message}`
|
|
77
61
|
}
|
|
78
62
|
]
|
|
79
63
|
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import agentJobsClient from "../lib/agentJobsClient.js";
|
|
3
|
+
import { formatJobStats } from "../utils/formatters.js";
|
|
4
|
+
import { flexibleDateTimeSchema } from "../utils/schemas.js";
|
|
5
|
+
import { mcpDebugger, withTiming } from "../utils/debugger.js";
|
|
6
|
+
export default (server) => {
|
|
7
|
+
server.registerTool("get_jobs_stats", {
|
|
8
|
+
description: "Returns aggregated job counts broken down by status (waiting/scheduled/running/completed/failed/canceled) plus a summary (total, success rate, active, completion rate). To filter to a single status, use `list_jobs` with `status=` instead — this tool intentionally does not expose a `status` filter because the upstream stats endpoint ignores it (the breakdown is itself by status). Filters available here narrow the universe along orthogonal dimensions: `job_type_id`, `channel_code`, `tags`, `scheduled_at_*`, `created_at_*`. Result-code and duration aggregates are not yet available; for those today, fall back to `list_jobs` and aggregate client-side. Optimized for dashboards and monitoring with minimal network overhead.",
|
|
9
|
+
annotations: {
|
|
10
|
+
title: "Get Job Statistics"
|
|
11
|
+
},
|
|
12
|
+
inputSchema: {
|
|
13
|
+
org_id: z.string().optional().describe("Filter by organization ID."),
|
|
14
|
+
scheduled_at_gte: flexibleDateTimeSchema().optional().describe("Start of period (ISO 8601)"),
|
|
15
|
+
scheduled_at_lte: flexibleDateTimeSchema().optional().describe("End of period (ISO 8601)"),
|
|
16
|
+
created_at_gte: flexibleDateTimeSchema().optional().describe("Filter for jobs created at or after a specific time (ISO 8601)."),
|
|
17
|
+
created_at_lte: flexibleDateTimeSchema().optional().describe("Filter for jobs created at or before a specific time (ISO 8601)."),
|
|
18
|
+
job_type_id: z.string().optional().describe("Job type filter"),
|
|
19
|
+
channel_code: z.string().optional().describe("Channel filter"),
|
|
20
|
+
tags: z.string().optional().describe("Tags filter (comma-separated)"),
|
|
21
|
+
}
|
|
22
|
+
}, async (params) => {
|
|
23
|
+
mcpDebugger.toolCall("get_jobs_stats", params);
|
|
24
|
+
try {
|
|
25
|
+
const response = await withTiming(() => agentJobsClient.getStats(params), "get_jobs_stats API call");
|
|
26
|
+
const stats = response.meta?.stats || {};
|
|
27
|
+
mcpDebugger.debug("Raw API response", { stats, appliedFilters: params });
|
|
28
|
+
const result = {
|
|
29
|
+
content: [{
|
|
30
|
+
type: "text",
|
|
31
|
+
text: formatJobStats(stats, params),
|
|
32
|
+
}]
|
|
33
|
+
};
|
|
34
|
+
mcpDebugger.toolResponse("get_jobs_stats", result);
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
mcpDebugger.toolError("get_jobs_stats", error);
|
|
39
|
+
return {
|
|
40
|
+
content: [{
|
|
41
|
+
type: "text",
|
|
42
|
+
text: `Error getting job stats: ${error.message}`,
|
|
43
|
+
}],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
};
|
package/build/tools/list_jobs.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import
|
|
3
|
-
import { config } from '../config.js';
|
|
2
|
+
import agentJobsClient from "../lib/agentJobsClient.js";
|
|
4
3
|
import { formatJobList } from "../utils/formatters.js";
|
|
4
|
+
import { flexibleDateTimeSchema } from "../utils/schemas.js";
|
|
5
|
+
import { mcpDebugger, withTiming } from "../utils/debugger.js";
|
|
5
6
|
// Define the schema for job status based on docs/agent-jobs-api.md:246-251
|
|
6
7
|
const jobStatusSchema = z.enum([
|
|
7
8
|
"waiting",
|
|
@@ -12,42 +13,28 @@ const jobStatusSchema = z.enum([
|
|
|
12
13
|
"canceled"
|
|
13
14
|
]);
|
|
14
15
|
export default (server) => {
|
|
15
|
-
server.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
content: [{
|
|
34
|
-
type: "text",
|
|
35
|
-
text: "Error: API URL is not configured. Please set AICONNECT_API_URL environment variable."
|
|
36
|
-
}]
|
|
37
|
-
};
|
|
16
|
+
server.registerTool("list_jobs", {
|
|
17
|
+
description: "Retrieves a list of agent jobs, with optional filters and pagination.",
|
|
18
|
+
annotations: {
|
|
19
|
+
title: "List Agent Jobs"
|
|
20
|
+
},
|
|
21
|
+
inputSchema: {
|
|
22
|
+
org_id: z.string().optional().describe("Filter by organization ID. If not provided, the default from the environment is used."),
|
|
23
|
+
status: jobStatusSchema.optional().describe("Filter by job status. Possible values are: 'waiting', 'scheduled', 'running', 'completed', 'failed', 'canceled'."),
|
|
24
|
+
scheduled_at: flexibleDateTimeSchema().optional().describe("Filter by the exact scheduled time in ISO 8601 format (e.g., '2024-07-23T10:00:00Z')."),
|
|
25
|
+
scheduled_at_gte: flexibleDateTimeSchema().optional().describe("Filter for jobs scheduled at or after a specific time (ISO 8601)."),
|
|
26
|
+
scheduled_at_lte: flexibleDateTimeSchema().optional().describe("Filter for jobs scheduled at or before a specific time (ISO 8601)."),
|
|
27
|
+
created_at_gte: flexibleDateTimeSchema().optional().describe("Filter for jobs created at or after a specific time (ISO 8601)."),
|
|
28
|
+
created_at_lte: flexibleDateTimeSchema().optional().describe("Filter for jobs created at or before a specific time (ISO 8601)."),
|
|
29
|
+
job_type_id: z.string().optional().describe("Filter by the specific job type ID (e.g., 'daily-report')."),
|
|
30
|
+
channel_code: z.string().optional().describe("Filter by the channel code (e.g., 'C123456' for a Slack channel)."),
|
|
31
|
+
limit: z.number().int().positive().optional().describe("Maximum number of jobs to return (e.g.,20). Default is 20."),
|
|
32
|
+
offset: z.number().int().nonnegative().optional().describe("Number of jobs to skip, used for pagination. Default is 0."),
|
|
33
|
+
sort: z.string().optional().describe("Field to sort by and direction. Format is 'field:direction'. Example: 'created_at:desc'.")
|
|
38
34
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
type: "text",
|
|
43
|
-
text: "Error: API Key is not configured. Please set AICONNECT_API_KEY environment variable."
|
|
44
|
-
}]
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
const endpoint = `${apiUrl}/services/agent-jobs`;
|
|
48
|
-
const headers = {
|
|
49
|
-
"Authorization": `Bearer ${apiKey}`,
|
|
50
|
-
};
|
|
35
|
+
}, async (params) => {
|
|
36
|
+
mcpDebugger.toolCall("list_jobs", params);
|
|
37
|
+
const endpoint = `/services/agent-jobs`;
|
|
51
38
|
// Build query parameters object from provided params
|
|
52
39
|
const queryParams = {};
|
|
53
40
|
for (const [key, value] of Object.entries(params)) {
|
|
@@ -55,38 +42,37 @@ export default (server) => {
|
|
|
55
42
|
queryParams[key] = value;
|
|
56
43
|
}
|
|
57
44
|
}
|
|
45
|
+
mcpDebugger.debug("Built query parameters", { endpoint, queryParams });
|
|
58
46
|
try {
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
47
|
+
const apiResponse = await withTiming(() => agentJobsClient.getWithMeta(endpoint, queryParams), "list_jobs API call");
|
|
48
|
+
// Access the full API response structure
|
|
49
|
+
const jobs = apiResponse.data || [];
|
|
50
|
+
const meta = apiResponse.meta || {};
|
|
51
|
+
mcpDebugger.debug("Raw API response", {
|
|
52
|
+
fullResponse: apiResponse,
|
|
53
|
+
jobsCount: jobs.length,
|
|
54
|
+
meta,
|
|
55
|
+
firstJob: jobs[0] || null
|
|
62
56
|
});
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
return {
|
|
57
|
+
const offset = typeof params.offset === 'number' ? params.offset : 0;
|
|
58
|
+
const result = {
|
|
66
59
|
content: [{
|
|
67
60
|
type: "text",
|
|
68
|
-
text: formatJobList(jobs, meta),
|
|
61
|
+
text: formatJobList(jobs, meta, offset),
|
|
69
62
|
}]
|
|
70
63
|
};
|
|
64
|
+
mcpDebugger.toolResponse("list_jobs", {
|
|
65
|
+
jobsReturned: jobs.length,
|
|
66
|
+
resultLength: result.content[0].text.length
|
|
67
|
+
});
|
|
68
|
+
return result;
|
|
71
69
|
}
|
|
72
70
|
catch (error) {
|
|
73
|
-
|
|
74
|
-
let errorDetails = {};
|
|
75
|
-
if (axios.isAxiosError(error) && error.response) {
|
|
76
|
-
const apiError = error.response.data?.message || error.response.data?.error || JSON.stringify(error.response.data);
|
|
77
|
-
errorMessage = `API Error (${error.response.status}): ${apiError || error.message}`;
|
|
78
|
-
errorDetails = {
|
|
79
|
-
status: error.response.status,
|
|
80
|
-
data: error.response.data
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
else if (error instanceof Error) {
|
|
84
|
-
errorMessage = `Error: ${error.message}`;
|
|
85
|
-
}
|
|
71
|
+
mcpDebugger.toolError("list_jobs", error);
|
|
86
72
|
return {
|
|
87
73
|
content: [{
|
|
88
74
|
type: "text",
|
|
89
|
-
text:
|
|
75
|
+
text: `Error listing jobs: ${error.message}`,
|
|
90
76
|
}],
|
|
91
77
|
};
|
|
92
78
|
}
|