@aiconnect/agentjobs-mcp 1.0.8 → 1.0.9
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/README.md +15 -12
- package/build/index.js +22 -20
- package/build/{cancel_job.js → tools/cancel_job.js} +13 -8
- package/build/tools/create_job.js +96 -0
- package/build/tools/get_job.js +83 -0
- package/build/tools/get_job_type.js +82 -0
- package/build/{list_jobs.js → tools/list_jobs.js} +24 -18
- package/build/utils/formatters.js +101 -0
- package/package.json +1 -1
- package/build/create_job.js +0 -151
- package/build/get_job.js +0 -62
package/README.md
CHANGED
|
@@ -153,9 +153,9 @@ This MCP server is designed to work out-of-the-box with minimal configuration. I
|
|
|
153
153
|
- `AICONNECT_API_KEY`: `""` (empty - you must provide this)
|
|
154
154
|
|
|
155
155
|
**Error Handling:**
|
|
156
|
-
-
|
|
157
|
-
- If `AICONNECT_API_URL`
|
|
158
|
-
- If `DEFAULT_ORG_ID` is not set, it defaults to "aiconnect"
|
|
156
|
+
- The server will always start, even if environment variables are missing.
|
|
157
|
+
- If `AICONNECT_API_KEY` or `AICONNECT_API_URL` are not provided, each tool will return a clear error message upon execution, guiding the user to configure the environment correctly.
|
|
158
|
+
- If `DEFAULT_ORG_ID` is not set, it defaults to "aiconnect".
|
|
159
159
|
|
|
160
160
|
### Running the MCP server
|
|
161
161
|
|
|
@@ -260,11 +260,13 @@ Agent: "Cancel job job-456 because it's no longer needed"
|
|
|
260
260
|
```
|
|
261
261
|
agentjobs-mcp/
|
|
262
262
|
├── src/ # TypeScript source code
|
|
263
|
-
│ ├── index.ts # Main MCP server
|
|
264
|
-
│ ├──
|
|
265
|
-
│
|
|
266
|
-
│
|
|
267
|
-
│
|
|
263
|
+
│ ├── index.ts # Main MCP server entry point
|
|
264
|
+
│ ├── config.ts # Configuration loader
|
|
265
|
+
│ └── tools/ # Directory for all MCP tools
|
|
266
|
+
│ ├── list_jobs.ts # Tool for listing jobs
|
|
267
|
+
│ ├── get_job.ts # Tool for getting a job
|
|
268
|
+
│ ├── create_job.ts # Tool for creating a job
|
|
269
|
+
│ └── cancel_job.ts # Tool for canceling a job
|
|
268
270
|
├── build/ # Compiled JavaScript code
|
|
269
271
|
├── docs/ # Documentation
|
|
270
272
|
│ └── agent-jobs-api.md # API documentation
|
|
@@ -283,10 +285,11 @@ agentjobs-mcp/
|
|
|
283
285
|
|
|
284
286
|
### Adding new tools
|
|
285
287
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
288
|
+
Adding a new tool is simple:
|
|
289
|
+
|
|
290
|
+
1. Create a new TypeScript file inside the `src/tools/` directory (e.g., `my_new_tool.ts`).
|
|
291
|
+
2. Implement your tool logic following the existing pattern. The server will automatically detect and register it on startup.
|
|
292
|
+
3. Recompile the project with `npm run build`.
|
|
290
293
|
|
|
291
294
|
## Contributing
|
|
292
295
|
|
package/build/index.js
CHANGED
|
@@ -3,10 +3,9 @@ import * as dotenv from 'dotenv';
|
|
|
3
3
|
dotenv.config();
|
|
4
4
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import create_job from "./create_job.js";
|
|
6
|
+
import fs from "fs/promises";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
10
9
|
// Get package version
|
|
11
10
|
const packageJson = JSON.parse(await import('fs').then(fs => fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8')));
|
|
12
11
|
// CLI argument parsing
|
|
@@ -55,27 +54,30 @@ if (args.includes('--config') || args.includes('-c')) {
|
|
|
55
54
|
console.log(` MCP Server Version: ${packageJson.version}`);
|
|
56
55
|
process.exit(0);
|
|
57
56
|
}
|
|
58
|
-
// Validate required environment variables
|
|
59
|
-
if (!process.env.AICONNECT_API_URL) {
|
|
60
|
-
console.error('Error: AICONNECT_API_URL environment variable is required');
|
|
61
|
-
console.error('Use --help for more information');
|
|
62
|
-
process.exit(1);
|
|
63
|
-
}
|
|
64
|
-
if (!process.env.AICONNECT_API_KEY) {
|
|
65
|
-
console.error('Error: AICONNECT_API_KEY environment variable is required');
|
|
66
|
-
console.error('Use --help for more information');
|
|
67
|
-
process.exit(1);
|
|
68
|
-
}
|
|
69
57
|
// Initialize server
|
|
70
58
|
const server = new McpServer({
|
|
71
59
|
name: "agentjobs-mcp",
|
|
72
60
|
version: packageJson.version
|
|
73
61
|
});
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
62
|
+
// Dynamically load and register tools
|
|
63
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
64
|
+
const toolsDir = path.join(__dirname, 'tools');
|
|
65
|
+
try {
|
|
66
|
+
const toolFiles = await fs.readdir(toolsDir);
|
|
67
|
+
for (const file of toolFiles) {
|
|
68
|
+
if (file.endsWith('.js')) { // In production, files will be .js
|
|
69
|
+
const toolModule = await import(`./tools/${file}`);
|
|
70
|
+
if (typeof toolModule.default === 'function') {
|
|
71
|
+
toolModule.default(server);
|
|
72
|
+
console.error(`-> Registered tool: ${file}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
console.error("Error loading tools:", error);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
79
81
|
// Start server with stdio transport
|
|
80
82
|
const transport = new StdioServerTransport();
|
|
81
83
|
console.error(`Starting AI Connect Agent Jobs MCP Server v${packageJson.version}...`);
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import axios from 'axios';
|
|
3
|
-
import { config } from '
|
|
3
|
+
import { config } from '../config.js';
|
|
4
|
+
import { formatJobSummary } from '../utils/formatters.js';
|
|
4
5
|
export default (server) => {
|
|
5
6
|
server.tool("cancel_job", "Cancels an agent job by its ID.", {
|
|
6
7
|
job_id: z.string({
|
|
7
|
-
description: "The
|
|
8
|
+
description: "The unique identifier of the job to be canceled. Example: 'job-12345'.",
|
|
8
9
|
}),
|
|
9
|
-
reason: z.string().optional().describe("
|
|
10
|
+
reason: z.string().optional().describe("An optional reason explaining why the job is being canceled."),
|
|
10
11
|
}, async (params) => {
|
|
11
12
|
const { job_id, reason } = params;
|
|
12
13
|
const apiUrl = config.AICONNECT_API_URL;
|
|
@@ -41,21 +42,25 @@ export default (server) => {
|
|
|
41
42
|
headers,
|
|
42
43
|
data: requestBody, // axios uses 'data' for DELETE request body
|
|
43
44
|
});
|
|
44
|
-
|
|
45
|
-
const
|
|
45
|
+
const canceledJob = response.data?.data || { job_id, job_status: 'canceled' };
|
|
46
|
+
const summary = formatJobSummary(canceledJob);
|
|
46
47
|
return {
|
|
47
48
|
content: [{
|
|
48
49
|
type: "text",
|
|
49
|
-
text:
|
|
50
|
+
text: `Successfully canceled job:\n\n${summary}`,
|
|
50
51
|
}]
|
|
51
52
|
};
|
|
52
53
|
}
|
|
53
54
|
catch (error) {
|
|
54
55
|
let errorMessage = `Failed to cancel job ${job_id}.`;
|
|
56
|
+
let errorDetails = {};
|
|
55
57
|
if (axios.isAxiosError(error) && error.response) {
|
|
56
|
-
// Try to get a more specific error message from the API response
|
|
57
58
|
const apiError = error.response.data?.message || error.response.data?.error || JSON.stringify(error.response.data);
|
|
58
59
|
errorMessage = `API Error (${error.response.status}): ${apiError || error.message}`;
|
|
60
|
+
errorDetails = {
|
|
61
|
+
status: error.response.status,
|
|
62
|
+
data: error.response.data
|
|
63
|
+
};
|
|
59
64
|
}
|
|
60
65
|
else if (error instanceof Error) {
|
|
61
66
|
errorMessage = `Error: ${error.message}`;
|
|
@@ -64,7 +69,7 @@ export default (server) => {
|
|
|
64
69
|
content: [{
|
|
65
70
|
type: "text",
|
|
66
71
|
text: errorMessage,
|
|
67
|
-
}]
|
|
72
|
+
}],
|
|
68
73
|
};
|
|
69
74
|
}
|
|
70
75
|
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
import { config } from '../config.js';
|
|
4
|
+
/**
|
|
5
|
+
* Lightweight Agent‑Jobs creator.
|
|
6
|
+
* Only the essentials are required to keep the contract LLM‑friendly.
|
|
7
|
+
*/
|
|
8
|
+
export default (server) => {
|
|
9
|
+
server.tool('create_job', 'Create a new Agent Job with the minimal set of fields.', {
|
|
10
|
+
// ─────────────────────────────────────────────────────────────────────────┐
|
|
11
|
+
// Required
|
|
12
|
+
// ─────────────────────────────────────────────────────────────────────────┘
|
|
13
|
+
job_type_id: z
|
|
14
|
+
.string()
|
|
15
|
+
.describe('ID of the job type (e.g. "mood-monitor")'),
|
|
16
|
+
target_channel: z
|
|
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
|
|
25
|
+
.string()
|
|
26
|
+
.optional()
|
|
27
|
+
.describe('Org ID – defaults to config.DEFAULT_ORG_ID')
|
|
28
|
+
})
|
|
29
|
+
.describe('Where the agent will communicate.'),
|
|
30
|
+
params: z
|
|
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
|
+
};
|
|
60
|
+
}
|
|
61
|
+
// Fall back to default org if none supplied.
|
|
62
|
+
params.target_channel.org_id ??= DEFAULT_ORG_ID;
|
|
63
|
+
try {
|
|
64
|
+
const res = await axios.post(`${apiUrl}/services/agent-jobs`, params, {
|
|
65
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
66
|
+
});
|
|
67
|
+
const jobId = res.data?.data?.id ?? res.data?.id ?? 'unknown';
|
|
68
|
+
return {
|
|
69
|
+
content: [
|
|
70
|
+
{
|
|
71
|
+
type: 'text',
|
|
72
|
+
text: `✅ Job created (id: ${jobId}).`
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
let errorMessage = `Failed to create job.`;
|
|
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
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
content: [
|
|
88
|
+
{
|
|
89
|
+
type: 'text',
|
|
90
|
+
text: errorMessage
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
import { config } from '../config.js';
|
|
4
|
+
import { formatJobDetails } from '../utils/formatters.js';
|
|
5
|
+
export default (server) => {
|
|
6
|
+
server.tool('get_job', 'Retrieves an agent job by its ID.', {
|
|
7
|
+
job_id: z.string({
|
|
8
|
+
description: "The unique identifier of the job you want to retrieve. Example: 'job-12345'."
|
|
9
|
+
}),
|
|
10
|
+
org_id: z
|
|
11
|
+
.string({
|
|
12
|
+
description: "The organization ID. Example: 'aiconnect'."
|
|
13
|
+
})
|
|
14
|
+
.optional()
|
|
15
|
+
}, async (params) => {
|
|
16
|
+
const { job_id } = params;
|
|
17
|
+
const apiUrl = config.AICONNECT_API_URL;
|
|
18
|
+
const apiKey = config.AICONNECT_API_KEY;
|
|
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
|
+
};
|
|
43
|
+
try {
|
|
44
|
+
const response = await axios.get(endpoint, {
|
|
45
|
+
headers
|
|
46
|
+
});
|
|
47
|
+
const job = response.data?.data || response.data;
|
|
48
|
+
return {
|
|
49
|
+
content: [
|
|
50
|
+
{
|
|
51
|
+
type: 'text',
|
|
52
|
+
text: formatJobDetails(job)
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
let errorMessage = `Failed to retrieve job ${job_id}.`;
|
|
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
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
content: [
|
|
75
|
+
{
|
|
76
|
+
type: 'text',
|
|
77
|
+
text: errorMessage
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
import { config } from '../config.js';
|
|
4
|
+
import { formatJobTypeDetails } from '../utils/formatters.js';
|
|
5
|
+
export default (server) => {
|
|
6
|
+
server.tool('get_job_type', 'Retrieves an agent job type by its ID.', {
|
|
7
|
+
job_type_id: z.string({
|
|
8
|
+
description: "The unique identifier of the job type you want to retrieve. Example: 'mood-monitor'."
|
|
9
|
+
}),
|
|
10
|
+
org_id: z.string({
|
|
11
|
+
description: "The organization ID. Example: 'aiconnect'."
|
|
12
|
+
}).optional()
|
|
13
|
+
}, async (params) => {
|
|
14
|
+
const { job_type_id } = params;
|
|
15
|
+
const org_id = params.org_id || config.DEFAULT_ORG_ID;
|
|
16
|
+
const apiUrl = config.AICONNECT_API_URL;
|
|
17
|
+
const apiKey = config.AICONNECT_API_KEY;
|
|
18
|
+
if (!apiUrl) {
|
|
19
|
+
return {
|
|
20
|
+
content: [
|
|
21
|
+
{
|
|
22
|
+
type: 'text',
|
|
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
|
+
};
|
|
42
|
+
try {
|
|
43
|
+
const response = await axios.get(endpoint, {
|
|
44
|
+
headers
|
|
45
|
+
});
|
|
46
|
+
const jobType = response.data?.data || response.data;
|
|
47
|
+
return {
|
|
48
|
+
content: [
|
|
49
|
+
{
|
|
50
|
+
type: 'text',
|
|
51
|
+
text: formatJobTypeDetails(jobType)
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
let errorMessage = `Failed to retrieve job type ${job_type_id}.`;
|
|
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
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
content: [
|
|
74
|
+
{
|
|
75
|
+
type: 'text',
|
|
76
|
+
text: errorMessage
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import axios from 'axios';
|
|
3
|
-
import { config } from '
|
|
3
|
+
import { config } from '../config.js';
|
|
4
|
+
import { formatJobList } from "../utils/formatters.js";
|
|
4
5
|
// Define the schema for job status based on docs/agent-jobs-api.md:246-251
|
|
5
6
|
const jobStatusSchema = z.enum([
|
|
6
7
|
"waiting",
|
|
@@ -12,18 +13,18 @@ const jobStatusSchema = z.enum([
|
|
|
12
13
|
]);
|
|
13
14
|
export default (server) => {
|
|
14
15
|
server.tool("list_jobs", "Retrieves a list of agent jobs, with optional filters and pagination.", {
|
|
15
|
-
org_id: z.string().optional().describe("Filter by organization ID. If not provided,
|
|
16
|
-
status: jobStatusSchema.optional().describe("Filter by job status."),
|
|
17
|
-
scheduled_at: z.string().datetime().optional().describe("Filter by exact scheduled time
|
|
18
|
-
scheduled_at_gte: z.string().datetime().optional().describe("Filter
|
|
19
|
-
scheduled_at_lte: z.string().datetime().optional().describe("Filter
|
|
20
|
-
created_at_gte: z.string().datetime().optional().describe("Filter
|
|
21
|
-
created_at_lte: z.string().datetime().optional().describe("Filter
|
|
22
|
-
job_type_id: z.string().optional().describe("Filter by job type ID."),
|
|
23
|
-
channel_code: z.string().optional().describe("Filter by channel code."),
|
|
24
|
-
limit: z.number().int().positive().optional().describe("Maximum number of jobs to return."),
|
|
25
|
-
offset: z.number().int().nonnegative().optional().describe("Number of jobs to skip
|
|
26
|
-
sort: z.string().optional().describe("
|
|
16
|
+
org_id: z.string().optional().describe("Filter by organization ID. If not provided, the default from the environment is used."),
|
|
17
|
+
status: jobStatusSchema.optional().describe("Filter by job status. Possible values are: 'waiting', 'scheduled', 'running', 'completed', 'failed', 'canceled'."),
|
|
18
|
+
scheduled_at: z.string().datetime().optional().describe("Filter by the exact scheduled time in ISO 8601 format (e.g., '2024-07-23T10:00:00Z')."),
|
|
19
|
+
scheduled_at_gte: z.string().datetime().optional().describe("Filter for jobs scheduled at or after a specific time (ISO 8601)."),
|
|
20
|
+
scheduled_at_lte: z.string().datetime().optional().describe("Filter for jobs scheduled at or before a specific time (ISO 8601)."),
|
|
21
|
+
created_at_gte: z.string().datetime().optional().describe("Filter for jobs created at or after a specific time (ISO 8601)."),
|
|
22
|
+
created_at_lte: z.string().datetime().optional().describe("Filter for jobs created at or before a specific time (ISO 8601)."),
|
|
23
|
+
job_type_id: z.string().optional().describe("Filter by the specific job type ID (e.g., 'daily-report')."),
|
|
24
|
+
channel_code: z.string().optional().describe("Filter by the channel code (e.g., 'C123456' for a Slack channel)."),
|
|
25
|
+
limit: z.number().int().positive().optional().describe("Maximum number of jobs to return (e.g.,20). Default is 20."),
|
|
26
|
+
offset: z.number().int().nonnegative().optional().describe("Number of jobs to skip, used for pagination. Default is 0."),
|
|
27
|
+
sort: z.string().optional().describe("Field to sort by and direction. Format is 'field:direction'. Example: 'created_at:desc'."),
|
|
27
28
|
}, async (params) => {
|
|
28
29
|
const apiUrl = config.AICONNECT_API_URL;
|
|
29
30
|
const apiKey = config.AICONNECT_API_KEY;
|
|
@@ -57,22 +58,27 @@ export default (server) => {
|
|
|
57
58
|
try {
|
|
58
59
|
const response = await axios.get(endpoint, {
|
|
59
60
|
headers,
|
|
60
|
-
params: queryParams,
|
|
61
|
+
params: queryParams,
|
|
61
62
|
});
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
const jobs = response.data?.data || [];
|
|
64
|
+
const meta = response.data?.meta || {};
|
|
64
65
|
return {
|
|
65
66
|
content: [{
|
|
66
67
|
type: "text",
|
|
67
|
-
text:
|
|
68
|
+
text: formatJobList(jobs, meta),
|
|
68
69
|
}]
|
|
69
70
|
};
|
|
70
71
|
}
|
|
71
72
|
catch (error) {
|
|
72
73
|
let errorMessage = `Failed to list jobs.`;
|
|
74
|
+
let errorDetails = {};
|
|
73
75
|
if (axios.isAxiosError(error) && error.response) {
|
|
74
76
|
const apiError = error.response.data?.message || error.response.data?.error || JSON.stringify(error.response.data);
|
|
75
77
|
errorMessage = `API Error (${error.response.status}): ${apiError || error.message}`;
|
|
78
|
+
errorDetails = {
|
|
79
|
+
status: error.response.status,
|
|
80
|
+
data: error.response.data
|
|
81
|
+
};
|
|
76
82
|
}
|
|
77
83
|
else if (error instanceof Error) {
|
|
78
84
|
errorMessage = `Error: ${error.message}`;
|
|
@@ -81,7 +87,7 @@ export default (server) => {
|
|
|
81
87
|
content: [{
|
|
82
88
|
type: "text",
|
|
83
89
|
text: errorMessage,
|
|
84
|
-
}]
|
|
90
|
+
}],
|
|
85
91
|
};
|
|
86
92
|
}
|
|
87
93
|
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// Schema para um job individual, baseado no exemplo fornecido.
|
|
3
|
+
const jobSchema = z.object({
|
|
4
|
+
job_id: z.string(),
|
|
5
|
+
channel_code: z.string(),
|
|
6
|
+
created_at: z.string().datetime(),
|
|
7
|
+
updated_at: z.string().datetime(),
|
|
8
|
+
scheduled_at: z.string().datetime(),
|
|
9
|
+
job_status: z.string(),
|
|
10
|
+
result: z.string().nullable(),
|
|
11
|
+
job_type_id: z.string(),
|
|
12
|
+
}).passthrough(); // .passthrough() permite outros campos não definidos no schema.
|
|
13
|
+
/**
|
|
14
|
+
* Formata a resposta completa de um job, ideal para o get_job.
|
|
15
|
+
* @param job - O objeto do job.
|
|
16
|
+
* @returns Uma string formatada com os detalhes completos do job.
|
|
17
|
+
*/
|
|
18
|
+
export function formatJobDetails(job) {
|
|
19
|
+
const fullJobDetails = JSON.stringify(job, null, 2);
|
|
20
|
+
return `Job Details:\n\n${fullJobDetails}`;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Formata um resumo de um job, com os campos principais.
|
|
24
|
+
* @param job - O objeto do job.
|
|
25
|
+
* @returns Uma string formatada com o resumo do job.
|
|
26
|
+
*/
|
|
27
|
+
export function formatJobSummary(job) {
|
|
28
|
+
try {
|
|
29
|
+
const parsedJob = jobSchema.parse(job);
|
|
30
|
+
return `
|
|
31
|
+
- Job ID: ${parsedJob.job_id}
|
|
32
|
+
- Status: ${parsedJob.job_status}
|
|
33
|
+
- Type: ${parsedJob.job_type_id}
|
|
34
|
+
- Channel: ${parsedJob.channel_code}
|
|
35
|
+
- Scheduled At: ${parsedJob.scheduled_at}
|
|
36
|
+
- Updated At: ${parsedJob.updated_at}
|
|
37
|
+
- Result: ${parsedJob.result || 'N/A'}
|
|
38
|
+
`.trim();
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
// Se a validação falhar, retorna o objeto como string.
|
|
42
|
+
return JSON.stringify(job, null, 2);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Formata a resposta para a lista de jobs.
|
|
47
|
+
* @param jobs - Um array de jobs.
|
|
48
|
+
* @param pagination - O objeto de paginação.
|
|
49
|
+
* @returns Uma string formatada com a lista de resumos de jobs.
|
|
50
|
+
*/
|
|
51
|
+
export function formatJobList(jobs, pagination) {
|
|
52
|
+
if (!jobs || jobs.length === 0) {
|
|
53
|
+
return "No jobs found for the given criteria.";
|
|
54
|
+
}
|
|
55
|
+
const jobSummaries = jobs.map(job => formatJobSummary(job)).join('\n\n');
|
|
56
|
+
const paginationSummary = `Page: ${Math.floor((pagination.offset || 0) / (pagination.limit || 20)) + 1} | Total Jobs: ${pagination.total}`;
|
|
57
|
+
return `Found ${jobs.length} jobs.\n\n${jobSummaries}\n\n${paginationSummary}`;
|
|
58
|
+
}
|
|
59
|
+
// Schema for job type details
|
|
60
|
+
const jobTypeSchema = z.object({
|
|
61
|
+
id: z.string(),
|
|
62
|
+
org_id: z.string(),
|
|
63
|
+
name: z.string(),
|
|
64
|
+
description: z.string(),
|
|
65
|
+
default_config: z.object({
|
|
66
|
+
profile_id: z.string(),
|
|
67
|
+
max_follow_ups: z.number(),
|
|
68
|
+
max_task_retries: z.number(),
|
|
69
|
+
task_retry_interval: z.number().describe("The interval in minutes to wait before retrying a task."),
|
|
70
|
+
max_time_to_complete: z.number().describe("The maximum time in minutes to complete a task."),
|
|
71
|
+
start_prompt: z.string(),
|
|
72
|
+
}),
|
|
73
|
+
}).passthrough();
|
|
74
|
+
/**
|
|
75
|
+
* Formats the response for job type details.
|
|
76
|
+
* @param jobType - The job type object.
|
|
77
|
+
* @returns A formatted string with the job type details.
|
|
78
|
+
*/
|
|
79
|
+
export function formatJobTypeDetails(jobType) {
|
|
80
|
+
try {
|
|
81
|
+
const parsedJobType = jobTypeSchema.parse(jobType);
|
|
82
|
+
return `
|
|
83
|
+
- ID: ${parsedJobType.id}
|
|
84
|
+
- Name: ${parsedJobType.name}
|
|
85
|
+
- Description: ${parsedJobType.description}
|
|
86
|
+
- Organization ID: ${parsedJobType.org_id}
|
|
87
|
+
|
|
88
|
+
Default Configuration:
|
|
89
|
+
- Profile ID: ${parsedJobType.default_config.profile_id}
|
|
90
|
+
- Max Follow-ups: ${parsedJobType.default_config.max_follow_ups}
|
|
91
|
+
- Max Task Retries: ${parsedJobType.default_config.max_task_retries}
|
|
92
|
+
- Task Retry Interval: ${parsedJobType.default_config.task_retry_interval} minutes
|
|
93
|
+
- Max Time to Complete: ${parsedJobType.default_config.max_time_to_complete} minutes
|
|
94
|
+
- Start Prompt: ${parsedJobType.default_config.start_prompt}
|
|
95
|
+
`.trim();
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
// If validation fails, return the object as a string.
|
|
99
|
+
return `Invalid job type details format: ${JSON.stringify(jobType, null, 2)}`;
|
|
100
|
+
}
|
|
101
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiconnect/agentjobs-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
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",
|
package/build/create_job.js
DELETED
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import axios from 'axios';
|
|
3
|
-
import { config } from './config.js';
|
|
4
|
-
// Schema for the target_channel object
|
|
5
|
-
const targetChannelSchema = z
|
|
6
|
-
.object({
|
|
7
|
-
org_id: z.string().optional().describe('Organization ID for the target channel. If not provided, uses the default organization.'),
|
|
8
|
-
platform: z
|
|
9
|
-
.enum(['whatsapp', 'slack', 'web'])
|
|
10
|
-
.describe('Platform of the target channel.'),
|
|
11
|
-
type: z.string().describe('Type of the target channel (e.g., channel).'),
|
|
12
|
-
code: z.string().describe('Code/identifier for the target channel.'),
|
|
13
|
-
data: z
|
|
14
|
-
.record(z.any())
|
|
15
|
-
.optional()
|
|
16
|
-
.describe('Additional platform-specific data for the channel.')
|
|
17
|
-
})
|
|
18
|
-
.describe('Defines the target channel for the job.');
|
|
19
|
-
// Schema for the config object
|
|
20
|
-
const configSchema = z
|
|
21
|
-
.object({
|
|
22
|
-
max_follow_ups: z
|
|
23
|
-
.number()
|
|
24
|
-
.int()
|
|
25
|
-
.optional()
|
|
26
|
-
.describe('Maximum number of follow-ups allowed.'),
|
|
27
|
-
max_task_retries: z
|
|
28
|
-
.number()
|
|
29
|
-
.int()
|
|
30
|
-
.optional()
|
|
31
|
-
.describe('Maximum number of retries for a task.'),
|
|
32
|
-
task_retry_interval: z
|
|
33
|
-
.number()
|
|
34
|
-
.int()
|
|
35
|
-
.optional()
|
|
36
|
-
.describe('Interval in minutes between task retries.'),
|
|
37
|
-
start_prompt: z.string().describe('The initial prompt to start the job.'),
|
|
38
|
-
max_time_to_complete: z
|
|
39
|
-
.number()
|
|
40
|
-
.int()
|
|
41
|
-
.optional()
|
|
42
|
-
.describe('Maximum time in minutes for the job to complete.'),
|
|
43
|
-
profile_id: z.string().describe('Profile ID to be used for the job.')
|
|
44
|
-
})
|
|
45
|
-
.describe('Configuration settings for the job.');
|
|
46
|
-
export default (server) => {
|
|
47
|
-
server.tool('create_job', 'Creates a new agent job.', {
|
|
48
|
-
target_channel: targetChannelSchema,
|
|
49
|
-
job_type_id: z.string().describe('The ID of the job type.'),
|
|
50
|
-
config: configSchema.optional(),
|
|
51
|
-
params: z
|
|
52
|
-
.record(z.any())
|
|
53
|
-
.optional()
|
|
54
|
-
.describe('Arbitrary parameters for the job.'),
|
|
55
|
-
scheduled_at: z
|
|
56
|
-
.string()
|
|
57
|
-
.datetime({ message: 'Invalid datetime string. Must be ISO 8601' })
|
|
58
|
-
.optional()
|
|
59
|
-
.describe('Optional ISO 8601 date string for scheduling the job.'),
|
|
60
|
-
delay: z
|
|
61
|
-
.number()
|
|
62
|
-
.int()
|
|
63
|
-
.nonnegative()
|
|
64
|
-
.optional()
|
|
65
|
-
.describe('Optional maximum random delay in minutes to add to the scheduled time (query parameter).')
|
|
66
|
-
}, async (toolParams) => {
|
|
67
|
-
const apiUrl = config.AICONNECT_API_URL;
|
|
68
|
-
const apiKey = config.AICONNECT_API_KEY;
|
|
69
|
-
const defaultOrgId = config.DEFAULT_ORG_ID;
|
|
70
|
-
if (!apiUrl) {
|
|
71
|
-
return {
|
|
72
|
-
content: [
|
|
73
|
-
{
|
|
74
|
-
type: 'text',
|
|
75
|
-
text: 'Error: API URL is not configured. Please set AICONNECT_API_URL environment variable.'
|
|
76
|
-
}
|
|
77
|
-
]
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
if (!apiKey) {
|
|
81
|
-
return {
|
|
82
|
-
content: [
|
|
83
|
-
{
|
|
84
|
-
type: 'text',
|
|
85
|
-
text: 'Error: API Key is not configured. Please set AICONNECT_API_KEY environment variable.'
|
|
86
|
-
}
|
|
87
|
-
]
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
// Use default org_id if not provided
|
|
91
|
-
if (!toolParams.target_channel.org_id && defaultOrgId) {
|
|
92
|
-
toolParams.target_channel.org_id = defaultOrgId;
|
|
93
|
-
}
|
|
94
|
-
else if (!toolParams.target_channel.org_id && !defaultOrgId) {
|
|
95
|
-
return {
|
|
96
|
-
content: [
|
|
97
|
-
{
|
|
98
|
-
type: 'text',
|
|
99
|
-
text: 'Error: Organization ID is required. Please provide org_id or set DEFAULT_ORG_ID environment variable.'
|
|
100
|
-
}
|
|
101
|
-
]
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
const endpoint = `${apiUrl}/services/agent-jobs`;
|
|
105
|
-
const headers = {
|
|
106
|
-
Authorization: `Bearer ${apiKey}`,
|
|
107
|
-
'Content-Type': 'application/json'
|
|
108
|
-
};
|
|
109
|
-
// Separate delay as it's a query parameter
|
|
110
|
-
const { delay, ...bodyPayload } = toolParams;
|
|
111
|
-
const queryParams = {};
|
|
112
|
-
if (delay !== undefined) {
|
|
113
|
-
queryParams.delay = delay;
|
|
114
|
-
}
|
|
115
|
-
try {
|
|
116
|
-
const response = await axios.post(endpoint, bodyPayload, {
|
|
117
|
-
headers,
|
|
118
|
-
params: queryParams
|
|
119
|
-
});
|
|
120
|
-
// API returns job details under 'data' key
|
|
121
|
-
return {
|
|
122
|
-
content: [
|
|
123
|
-
{
|
|
124
|
-
type: 'text',
|
|
125
|
-
text: JSON.stringify(response.data, null, 2)
|
|
126
|
-
}
|
|
127
|
-
]
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
catch (error) {
|
|
131
|
-
let errorMessage = `Failed to create job.`;
|
|
132
|
-
if (axios.isAxiosError(error) && error.response) {
|
|
133
|
-
const apiError = error.response.data?.message ||
|
|
134
|
-
error.response.data?.error ||
|
|
135
|
-
JSON.stringify(error.response.data);
|
|
136
|
-
errorMessage = `API Error (${error.response.status}): ${apiError || error.message}`;
|
|
137
|
-
}
|
|
138
|
-
else if (error instanceof Error) {
|
|
139
|
-
errorMessage = `Error: ${error.message}`;
|
|
140
|
-
}
|
|
141
|
-
return {
|
|
142
|
-
content: [
|
|
143
|
-
{
|
|
144
|
-
type: 'text',
|
|
145
|
-
text: errorMessage
|
|
146
|
-
}
|
|
147
|
-
]
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
};
|
package/build/get_job.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import axios from 'axios';
|
|
3
|
-
import { config } from './config.js';
|
|
4
|
-
export default (server) => {
|
|
5
|
-
server.tool("get_job", "Retrieves an agent job by its ID.", {
|
|
6
|
-
job_id: z.string({
|
|
7
|
-
description: "The ID of the job to retrieve.",
|
|
8
|
-
}),
|
|
9
|
-
}, async (params) => {
|
|
10
|
-
const { job_id } = params;
|
|
11
|
-
const apiUrl = config.AICONNECT_API_URL;
|
|
12
|
-
const apiKey = config.AICONNECT_API_KEY;
|
|
13
|
-
if (!apiUrl) {
|
|
14
|
-
return {
|
|
15
|
-
content: [{
|
|
16
|
-
type: "text",
|
|
17
|
-
text: "Error: API URL is not configured. Please set AICONNECT_API_URL environment variable."
|
|
18
|
-
}]
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
if (!apiKey) {
|
|
22
|
-
return {
|
|
23
|
-
content: [{
|
|
24
|
-
type: "text",
|
|
25
|
-
text: "Error: API Key is not configured. Please set AICONNECT_API_KEY environment variable."
|
|
26
|
-
}]
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
const endpoint = `${apiUrl}/services/agent-jobs/${job_id}`;
|
|
30
|
-
const headers = {
|
|
31
|
-
"Authorization": `Bearer ${apiKey}`,
|
|
32
|
-
};
|
|
33
|
-
try {
|
|
34
|
-
const response = await axios.get(endpoint, {
|
|
35
|
-
headers,
|
|
36
|
-
});
|
|
37
|
-
// Return the data part of the response, stringified as JSON text
|
|
38
|
-
return {
|
|
39
|
-
content: [{
|
|
40
|
-
type: "text",
|
|
41
|
-
text: JSON.stringify(response.data?.data || response.data, null, 2),
|
|
42
|
-
}]
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
catch (error) {
|
|
46
|
-
let errorMessage = `Failed to retrieve job ${job_id}.`;
|
|
47
|
-
if (axios.isAxiosError(error) && error.response) {
|
|
48
|
-
const apiError = error.response.data?.message || error.response.data?.error || JSON.stringify(error.response.data);
|
|
49
|
-
errorMessage = `API Error (${error.response.status}): ${apiError || error.message}`;
|
|
50
|
-
}
|
|
51
|
-
else if (error instanceof Error) {
|
|
52
|
-
errorMessage = `Error: ${error.message}`;
|
|
53
|
-
}
|
|
54
|
-
return {
|
|
55
|
-
content: [{
|
|
56
|
-
type: "text",
|
|
57
|
-
text: errorMessage,
|
|
58
|
-
}]
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
};
|