@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 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
- - If `AICONNECT_API_KEY` is not provided, tools will return helpful error messages
157
- - If `AICONNECT_API_URL` is not set, it defaults to the production API
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
- │ ├── cancel_job.ts # Tool for canceling jobs
265
- ├── create_job.ts # Tool for creating jobs
266
- ├── get_job.ts # Tool for querying job
267
- └── list_jobs.ts # Tool for listing jobs
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
- 1. Create a new file in the `src/` folder (e.g., `new_tool.ts`)
287
- 2. Implement the tool following the pattern of existing files
288
- 3. Register the tool in `src/index.ts`
289
- 4. Recompile with `npm run build`
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 cancel_job from "./cancel_job.js";
7
- import get_job from "./get_job.js";
8
- import list_jobs from "./list_jobs.js";
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
- // Initialize components
75
- cancel_job(server);
76
- get_job(server);
77
- list_jobs(server);
78
- create_job(server);
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 './config.js';
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 ID of the job to cancel.",
8
+ description: "The unique identifier of the job to be canceled. Example: 'job-12345'.",
8
9
  }),
9
- reason: z.string().optional().describe("Optional reason for cancellation."),
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
- // Assuming the API returns a message field on success as per docs/agent-jobs-api.md:229
45
- const responseMessage = response.data?.message || `Job with ID '${job_id}' successfully canceled.`;
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: responseMessage,
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 './config.js';
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, uses the default organization."),
16
- status: jobStatusSchema.optional().describe("Filter by job status."),
17
- scheduled_at: z.string().datetime().optional().describe("Filter by exact scheduled time (ISO 8601)."),
18
- scheduled_at_gte: z.string().datetime().optional().describe("Filter by scheduled time greater than or equal to (ISO 8601)."),
19
- scheduled_at_lte: z.string().datetime().optional().describe("Filter by scheduled time less than or equal to (ISO 8601)."),
20
- created_at_gte: z.string().datetime().optional().describe("Filter by creation time greater than or equal to (ISO 8601)."),
21
- created_at_lte: z.string().datetime().optional().describe("Filter by creation time less than or equal to (ISO 8601)."),
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 (for pagination)."),
26
- sort: z.string().optional().describe("Sorting field and direction (e.g., created_at:desc)."),
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, // Axios uses 'params' for query parameters in GET requests
61
+ params: queryParams,
61
62
  });
62
- // Return the data part of the response, stringified as JSON text
63
- // API docs show jobs under 'data' key, and meta for pagination
63
+ const jobs = response.data?.data || [];
64
+ const meta = response.data?.meta || {};
64
65
  return {
65
66
  content: [{
66
67
  type: "text",
67
- text: JSON.stringify(response.data, null, 2),
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.8",
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",
@@ -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
- };