@aiconnect/agentjobs-mcp 1.0.9 → 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 CHANGED
@@ -9,3 +9,10 @@ AICONNECT_API_KEY=your-api-key-here
9
9
 
10
10
  # Optional: Default organization ID (defaults to 'aiconnect' if not set)
11
11
  # DEFAULT_ORG_ID=your-organization-id
12
+
13
+ # Debug Settings
14
+ MCP_DEBUG=false
15
+ NODE_ENV=development
16
+
17
+ # HTTP Debug (opcional - para ver requisições HTTP detalhadas)
18
+ DEBUG=axios
package/README.md CHANGED
@@ -185,8 +185,39 @@ To use this MCP server with Claude Desktop, add the following configuration to y
185
185
  }
186
186
  ```
187
187
 
188
+ ### Local Development with Claude Code
189
+
190
+ For development and testing, you can add this MCP server directly to your Claude Code project:
191
+
192
+ ```bash
193
+ # Prerequisites: build the project first
194
+ npm install
195
+ npm run build
196
+
197
+ # Configure your .env file
198
+ cp .env.example .env
199
+ # Edit .env with your API credentials
200
+
201
+ # Add MCP server to Claude Code (project scope)
202
+ claude mcp add --scope project agentjobs -- ./mcp-agentjobs.sh
203
+ ```
204
+
205
+ This allows you to test and use the AgentJobs tools directly within Claude Code during development, providing immediate feedback and easier debugging.
206
+
188
207
  ## Available Tools
189
208
 
209
+ ### 📊 `get_jobs_stats`
210
+ Get aggregated statistics for agent jobs without retrieving individual job data. Optimized for dashboards and monitoring with minimal network overhead.
211
+
212
+ **Parameters:**
213
+ - `scheduled_at_gte`: Start of period (ISO 8601)
214
+ - `scheduled_at_lte`: End of period (ISO 8601)
215
+ - `org_id`: Organization filter
216
+ - `job_type_id`: Job type filter
217
+ - `tags`: Tags filter (comma-separated)
218
+ - `status`: Status filter
219
+ - `channel_code`: Channel filter
220
+
190
221
  ### 🔧 `list_jobs`
191
222
  Lists all jobs with filtering and pagination options.
192
223
 
@@ -263,6 +294,7 @@ agentjobs-mcp/
263
294
  │ ├── index.ts # Main MCP server entry point
264
295
  │ ├── config.ts # Configuration loader
265
296
  │ └── tools/ # Directory for all MCP tools
297
+ │ ├── get_jobs_stats.ts # Tool for getting job statistics
266
298
  │ ├── list_jobs.ts # Tool for listing jobs
267
299
  │ ├── get_job.ts # Tool for getting a job
268
300
  │ ├── create_job.ts # Tool for creating a job
@@ -282,6 +314,40 @@ agentjobs-mcp/
282
314
 
283
315
  - `npm run build`: Compiles TypeScript
284
316
  - `npm start`: Runs the compiled server
317
+ - `npm run debug`: Runs server in debug mode with detailed logging
318
+ - `npm run test:tools`: Tests tool loading without starting server
319
+ - `npm run cli:config`: Shows current configuration
320
+ - `npm run cli:version`: Shows version information
321
+ - `npm run cli:help`: Shows help information
322
+
323
+ ### Debugging
324
+
325
+ For detailed debugging information, see [Debug Guide](docs/debug-guide.md).
326
+
327
+ **Quick Debug Commands:**
328
+ ```bash
329
+ # Test configuration
330
+ npm run cli:config
331
+
332
+ # Test tool loading
333
+ npm run test:tools
334
+
335
+ # Run in debug mode
336
+ MCP_DEBUG=true npm run debug
337
+
338
+ # Use debug helper script (Fish shell)
339
+ ./debug.fish help
340
+ ./debug.fish quick
341
+ ```
342
+
343
+ **Debug Environment:**
344
+ ```bash
345
+ # Copy debug environment template
346
+ cp .env.debug .env
347
+ # Edit .env with your API credentials
348
+ # Run with debug environment
349
+ ./debug.fish debug-with-env
350
+ ```
285
351
 
286
352
  ### Adding new tools
287
353
 
@@ -290,6 +356,7 @@ Adding a new tool is simple:
290
356
  1. Create a new TypeScript file inside the `src/tools/` directory (e.g., `my_new_tool.ts`).
291
357
  2. Implement your tool logic following the existing pattern. The server will automatically detect and register it on startup.
292
358
  3. Recompile the project with `npm run build`.
359
+ 4. Test with `npm run test:tools` to verify loading.
293
360
 
294
361
  ## Contributing
295
362
 
package/build/config.js CHANGED
@@ -1,6 +1,11 @@
1
- // Configuration with fallback to .env.example defaults
1
+ // Configuration for the MCP server
2
2
  export const config = {
3
- DEFAULT_ORG_ID: process.env.DEFAULT_ORG_ID || 'aiconnect',
3
+ apiUrl: process.env.AICONNECT_API_URL || 'https://api.aiconnect.cloud/api/v0',
4
+ apiKey: process.env.AICONNECT_API_KEY || '',
5
+ defaultOrgId: process.env.DEFAULT_ORG_ID || 'aiconnect',
6
+ debugMode: process.env.DEBUG === 'true',
7
+ // Legacy compatibility
8
+ AICONNECT_API_URL: process.env.AICONNECT_API_URL || 'https://api.aiconnect.cloud/api/v0',
4
9
  AICONNECT_API_KEY: process.env.AICONNECT_API_KEY || '',
5
- AICONNECT_API_URL: process.env.AICONNECT_API_URL || 'https://api.aiconnect.cloud/api/v0'
10
+ DEFAULT_ORG_ID: process.env.DEFAULT_ORG_ID || 'aiconnect'
6
11
  };
package/build/debug.js ADDED
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+ import * as dotenv from 'dotenv';
3
+ dotenv.config();
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import fs from "fs/promises";
7
+ import path from "path";
8
+ import { fileURLToPath } from "url";
9
+ import { config } from './config.js';
10
+ // Enable debug mode
11
+ const DEBUG = true;
12
+ function debugLog(message, data) {
13
+ if (DEBUG) {
14
+ const timestamp = new Date().toISOString();
15
+ console.error(`[DEBUG ${timestamp}] ${message}`);
16
+ if (data) {
17
+ console.error('[DEBUG DATA]', JSON.stringify(data, null, 2));
18
+ }
19
+ }
20
+ }
21
+ // Get package version
22
+ const packageJson = JSON.parse(await import('fs').then(fs => fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8')));
23
+ debugLog('Starting MCP Server in DEBUG mode');
24
+ debugLog('Configuration loaded', {
25
+ DEFAULT_ORG_ID: config.DEFAULT_ORG_ID,
26
+ AICONNECT_API_URL: config.AICONNECT_API_URL,
27
+ AICONNECT_API_KEY: config.AICONNECT_API_KEY ? '[SET]' : '[NOT SET]',
28
+ NODE_ENV: process.env.NODE_ENV,
29
+ version: packageJson.version
30
+ });
31
+ // Initialize server with debug logging
32
+ const server = new McpServer({
33
+ name: "agentjobs-mcp-debug",
34
+ version: packageJson.version
35
+ });
36
+ debugLog('MCP Server initialized');
37
+ // Dynamically load and register tools with debug info
38
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
39
+ const toolsDir = path.join(__dirname, 'tools');
40
+ try {
41
+ debugLog(`Loading tools from: ${toolsDir}`);
42
+ const toolFiles = await fs.readdir(toolsDir);
43
+ debugLog(`Found tool files:`, toolFiles);
44
+ for (const file of toolFiles) {
45
+ if (file.endsWith('.js')) {
46
+ debugLog(`Loading tool: ${file}`);
47
+ try {
48
+ const toolModule = await import(`./tools/${file}`);
49
+ if (typeof toolModule.default === 'function') {
50
+ toolModule.default(server);
51
+ debugLog(`✅ Successfully registered tool: ${file}`);
52
+ }
53
+ else {
54
+ debugLog(`❌ Tool ${file} does not export a default function`);
55
+ }
56
+ }
57
+ catch (error) {
58
+ debugLog(`❌ Error loading tool ${file}:`, error);
59
+ }
60
+ }
61
+ }
62
+ }
63
+ catch (error) {
64
+ debugLog("❌ Error loading tools directory:", error);
65
+ process.exit(1);
66
+ }
67
+ // Start server with debug transport
68
+ const transport = new StdioServerTransport();
69
+ debugLog('Starting transport connection...');
70
+ // Add connection event handling
71
+ transport.onclose = () => {
72
+ debugLog('Transport connection closed');
73
+ };
74
+ transport.onerror = (error) => {
75
+ debugLog('Transport error:', error);
76
+ };
77
+ console.error(`🔍 AI Connect Agent Jobs MCP Server v${packageJson.version} (DEBUG MODE)`);
78
+ console.error('🚀 Server ready and listening for MCP connections');
79
+ try {
80
+ await server.connect(transport);
81
+ debugLog('✅ Server connected successfully');
82
+ }
83
+ catch (error) {
84
+ debugLog('❌ Error connecting MCP server:', error);
85
+ process.exit(1);
86
+ }
package/build/index.js CHANGED
@@ -3,6 +3,7 @@ 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 { InitializeRequestSchema } from "@modelcontextprotocol/sdk/types.js";
6
7
  import fs from "fs/promises";
7
8
  import path from "path";
8
9
  import { fileURLToPath } from "url";
@@ -54,10 +55,48 @@ if (args.includes('--config') || args.includes('-c')) {
54
55
  console.log(` MCP Server Version: ${packageJson.version}`);
55
56
  process.exit(0);
56
57
  }
57
- // Initialize server
58
+ // Protocol version tracking
59
+ let clientProtocolVersion = "2024-11-05"; // Default fallback
60
+ let serverCapabilities = {
61
+ tools: { listChanged: true },
62
+ resources: {},
63
+ prompts: {}
64
+ };
65
+ // Initialize server with dynamic capabilities based on protocol version
58
66
  const server = new McpServer({
59
67
  name: "agentjobs-mcp",
60
68
  version: packageJson.version
69
+ }, {
70
+ capabilities: serverCapabilities
71
+ });
72
+ console.error(`[DEBUG] MCP Server initialized with name: agentjobs-mcp`);
73
+ console.error(`[DEBUG] Server version: ${packageJson.version}`);
74
+ console.error(`[DEBUG] Default capabilities: tools, resources, prompts`);
75
+ // Intercept initialization to detect protocol version
76
+ const originalSetRequestHandler = server.server.setRequestHandler.bind(server.server);
77
+ // Override initialization handler to capture protocol version
78
+ server.server.setRequestHandler(InitializeRequestSchema, async (request) => {
79
+ const initParams = request.params;
80
+ clientProtocolVersion = initParams.protocolVersion || "2024-11-05";
81
+ console.error(`[VERSION-NEGOTIATION] Client requested protocol: ${clientProtocolVersion}`);
82
+ // Always use minimal capabilities to prevent loops
83
+ console.error(`[VERSION-NEGOTIATION] Client requested: ${clientProtocolVersion}`);
84
+ console.error(`[VERSION-NEGOTIATION] Using minimal capabilities to prevent infinite loops`);
85
+ // Use only tools capability - no resources/prompts to avoid repeated calls
86
+ serverCapabilities = {
87
+ tools: {}
88
+ };
89
+ console.error(`[VERSION-NEGOTIATION] Responding with our supported version: 2025-03-26`);
90
+ console.error(`[VERSION-NEGOTIATION] Updated capabilities:`, JSON.stringify(serverCapabilities, null, 2));
91
+ // Return initialization response with our supported version
92
+ return {
93
+ protocolVersion: "2025-03-26", // Our SDK version
94
+ capabilities: serverCapabilities,
95
+ serverInfo: {
96
+ name: "agentjobs-mcp",
97
+ version: packageJson.version
98
+ }
99
+ };
61
100
  });
62
101
  // Dynamically load and register tools
63
102
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -66,10 +105,16 @@ try {
66
105
  const toolFiles = await fs.readdir(toolsDir);
67
106
  for (const file of toolFiles) {
68
107
  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}`);
108
+ try {
109
+ const toolModule = await import(`./tools/${file}`);
110
+ if (typeof toolModule.default === 'function') {
111
+ toolModule.default(server);
112
+ console.error(`-> Registered tool: ${file}`);
113
+ }
114
+ }
115
+ catch (error) {
116
+ console.error(`-> Failed to register tool ${file}:`, error);
117
+ // Continue loading other tools
73
118
  }
74
119
  }
75
120
  }
@@ -78,8 +123,20 @@ catch (error) {
78
123
  console.error("Error loading tools:", error);
79
124
  process.exit(1);
80
125
  }
126
+ console.error(`[DEBUG] Only tools capability enabled - no resources/prompts handlers needed`);
81
127
  // Start server with stdio transport
82
128
  const transport = new StdioServerTransport();
83
129
  console.error(`Starting AI Connect Agent Jobs MCP Server v${packageJson.version}...`);
130
+ console.error('Configuration:');
131
+ console.error(` API URL: ${process.env.AICONNECT_API_URL || 'Using default'}`);
132
+ console.error(` API Key: ${process.env.AICONNECT_API_KEY ? '[SET]' : '[NOT SET]'}`);
133
+ console.error(` Default Org: ${process.env.DEFAULT_ORG_ID || 'aiconnect'}`);
84
134
  console.error('Server ready and listening for MCP connections');
85
- await server.connect(transport);
135
+ // Add error handling for the server connection
136
+ try {
137
+ await server.connect(transport);
138
+ }
139
+ catch (error) {
140
+ console.error('Error connecting MCP server:', error);
141
+ process.exit(1);
142
+ }
@@ -0,0 +1,79 @@
1
+ import axios from 'axios';
2
+ import { config } from '../config.js';
3
+ class AgentJobsClient {
4
+ client = null;
5
+ getClient() {
6
+ if (!this.client) {
7
+ const { AICONNECT_API_URL, AICONNECT_API_KEY } = config;
8
+ if (!AICONNECT_API_URL || !AICONNECT_API_KEY) {
9
+ throw new Error('API URL or Key is not configured. Please set AICONNECT_API_URL and AICONNECT_API_KEY environment variables.');
10
+ }
11
+ this.client = axios.create({
12
+ baseURL: AICONNECT_API_URL,
13
+ headers: {
14
+ 'Authorization': `Bearer ${AICONNECT_API_KEY}`,
15
+ 'X-Client-Type': 'mcp',
16
+ 'Content-Type': 'application/json'
17
+ }
18
+ });
19
+ }
20
+ return this.client;
21
+ }
22
+ async get(endpoint, params) {
23
+ return this.request(() => this.getClient().get(endpoint, { params }), params);
24
+ }
25
+ async getWithMeta(endpoint, params) {
26
+ try {
27
+ const response = await this.getClient().get(endpoint, { params });
28
+ return response.data; // Return the full API response including data and meta
29
+ }
30
+ catch (error) {
31
+ this.handleError(error);
32
+ }
33
+ }
34
+ async getStats(filters = {}) {
35
+ const params = {
36
+ ...filters,
37
+ include: 'stats',
38
+ limit: 1
39
+ };
40
+ return this.get('/services/agent-jobs', params);
41
+ }
42
+ async post(endpoint, data) {
43
+ return this.request(() => this.getClient().post(endpoint, data));
44
+ }
45
+ async patch(endpoint, data) {
46
+ return this.request(() => this.getClient().patch(endpoint, data));
47
+ }
48
+ async delete(endpoint, data) {
49
+ return this.request(() => this.getClient().delete(endpoint, { data }));
50
+ }
51
+ async request(requestFn, params) {
52
+ try {
53
+ const response = await requestFn();
54
+ if (params?.include === 'stats') {
55
+ // @ts-expect-error axios response typing varies per call in this client
56
+ return response.data;
57
+ }
58
+ // @ts-expect-error axios response typing varies per call in this client
59
+ return response.data?.data || response.data;
60
+ }
61
+ catch (error) {
62
+ this.handleError(error);
63
+ }
64
+ }
65
+ handleError(error) {
66
+ if (axios.isAxiosError(error)) {
67
+ const axiosError = error;
68
+ const apiError = axiosError.response?.data?.message || axiosError.response?.data?.error || JSON.stringify(axiosError.response?.data);
69
+ throw new Error(`API Error (${axiosError.response?.status}): ${apiError || axiosError.message}`);
70
+ }
71
+ else if (error instanceof Error) {
72
+ throw new Error(`Error: ${error.message}`);
73
+ }
74
+ else {
75
+ throw new Error('An unknown error occurred.');
76
+ }
77
+ }
78
+ }
79
+ export default new AgentJobsClient();
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+ import * as dotenv from 'dotenv';
3
+ // Load debug environment
4
+ dotenv.config({ path: '.env.debug' });
5
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
6
+ import fs from 'fs/promises';
7
+ import path from 'path';
8
+ import { fileURLToPath } from 'url';
9
+ // Test specific tools
10
+ async function testTools() {
11
+ console.log('🧪 Testing MCP Tools...\n');
12
+ // Initialize server
13
+ const server = new McpServer({
14
+ name: 'agentjobs-mcp-test',
15
+ version: 'test'
16
+ });
17
+ // Load tools
18
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
19
+ const toolsDir = path.join(__dirname, 'tools');
20
+ try {
21
+ const toolFiles = await fs.readdir(toolsDir);
22
+ console.log(`📁 Found ${toolFiles.length} tool files`);
23
+ for (const file of toolFiles) {
24
+ if (file.endsWith('.js')) {
25
+ try {
26
+ console.log(`⚙️ Loading tool: ${file}`);
27
+ const toolModule = await import(`./tools/${file}`);
28
+ if (typeof toolModule.default === 'function') {
29
+ toolModule.default(server);
30
+ console.log(`✅ Successfully registered: ${file}`);
31
+ }
32
+ else {
33
+ console.log(`❌ Invalid tool format: ${file}`);
34
+ }
35
+ }
36
+ catch (error) {
37
+ console.log(`❌ Error loading ${file}:`, error?.message || error);
38
+ }
39
+ }
40
+ }
41
+ // Show success message
42
+ console.log('\n✅ Tool testing completed!');
43
+ console.log('💡 To run in debug mode: MCP_DEBUG=true npm run debug');
44
+ }
45
+ catch (error) {
46
+ console.error('❌ Error during tool testing:', error);
47
+ }
48
+ }
49
+ // Run if this script is executed directly
50
+ if (import.meta.url === `file://${process.argv[1]}`) {
51
+ testTools().catch(console.error);
52
+ }
53
+ export { testTools };
@@ -1,74 +1,56 @@
1
1
  import { z } from "zod";
2
- import axios from 'axios';
3
- import { config } from '../config.js';
2
+ import agentJobsClient from "../lib/agentJobsClient.js";
4
3
  import { formatJobSummary } from '../utils/formatters.js';
4
+ import { mcpDebugger, withTiming } from '../utils/debugger.js';
5
5
  export default (server) => {
6
- server.tool("cancel_job", "Cancels an agent job by its ID.", {
7
- job_id: z.string({
8
- description: "The unique identifier of the job to be canceled. Example: 'job-12345'.",
9
- }),
10
- reason: z.string().optional().describe("An optional reason explaining why the job is being canceled."),
6
+ server.registerTool("cancel_job", {
7
+ description: "Cancels an agent job by its ID.",
8
+ annotations: {
9
+ title: "Cancel Agent Job"
10
+ },
11
+ inputSchema: {
12
+ job_id: z.string({
13
+ description: "The unique identifier of the job to be canceled. Example: 'job-12345'.",
14
+ }),
15
+ reason: z.string().optional().describe("An optional reason explaining why the job is being canceled."),
16
+ }
11
17
  }, async (params) => {
18
+ mcpDebugger.toolCall("cancel_job", params);
12
19
  const { job_id, reason } = params;
13
- const apiUrl = config.AICONNECT_API_URL;
14
- const apiKey = config.AICONNECT_API_KEY;
15
- if (!apiUrl) {
16
- return {
17
- content: [{
18
- type: "text",
19
- text: "Error: API URL is not configured. Please set AICONNECT_API_URL environment variable."
20
- }]
21
- };
22
- }
23
- if (!apiKey) {
24
- return {
25
- content: [{
26
- type: "text",
27
- text: "Error: API Key is not configured. Please set AICONNECT_API_KEY environment variable."
28
- }]
29
- };
30
- }
31
- const endpoint = `${apiUrl}/services/agent-jobs/${job_id}`;
32
- const headers = {
33
- "Authorization": `Bearer ${apiKey}`,
34
- };
20
+ const endpoint = `/services/agent-jobs/${job_id}`;
35
21
  let requestBody;
36
22
  if (reason) {
37
- headers["Content-Type"] = "application/json";
38
23
  requestBody = { reason };
39
24
  }
25
+ mcpDebugger.debug("Job cancellation request", {
26
+ endpoint,
27
+ job_id,
28
+ reason,
29
+ hasRequestBody: !!requestBody
30
+ });
40
31
  try {
41
- const response = await axios.delete(endpoint, {
42
- headers,
43
- data: requestBody, // axios uses 'data' for DELETE request body
44
- });
45
- const canceledJob = response.data?.data || { job_id, job_status: 'canceled' };
32
+ const canceledJob = await withTiming(() => agentJobsClient.delete(endpoint, requestBody), "cancel_job API call");
33
+ mcpDebugger.debug("Job cancellation response", { canceledJob });
46
34
  const summary = formatJobSummary(canceledJob);
47
- return {
35
+ const result = {
48
36
  content: [{
49
37
  type: "text",
50
38
  text: `Successfully canceled job:\n\n${summary}`,
51
39
  }]
52
40
  };
41
+ mcpDebugger.toolResponse("cancel_job", {
42
+ jobId: job_id,
43
+ reason,
44
+ resultLength: result.content[0].text.length
45
+ });
46
+ return result;
53
47
  }
54
48
  catch (error) {
55
- let errorMessage = `Failed to cancel job ${job_id}.`;
56
- let errorDetails = {};
57
- if (axios.isAxiosError(error) && error.response) {
58
- const apiError = error.response.data?.message || error.response.data?.error || JSON.stringify(error.response.data);
59
- errorMessage = `API Error (${error.response.status}): ${apiError || error.message}`;
60
- errorDetails = {
61
- status: error.response.status,
62
- data: error.response.data
63
- };
64
- }
65
- else if (error instanceof Error) {
66
- errorMessage = `Error: ${error.message}`;
67
- }
49
+ mcpDebugger.toolError("cancel_job", error);
68
50
  return {
69
51
  content: [{
70
52
  type: "text",
71
- text: errorMessage,
53
+ text: `Error canceling job: ${error.message}`,
72
54
  }],
73
55
  };
74
56
  }