@ateam-ai/mcp 0.3.0 → 0.3.2

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.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/src/api.js +65 -41
  3. package/src/tools.js +52 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ateam-ai/mcp",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "mcpName": "io.github.ariekogan/ateam-mcp",
5
5
  "description": "A-Team MCP Server — build, validate, and deploy multi-agent solutions from any AI environment",
6
6
  "type": "module",
package/src/api.js CHANGED
@@ -353,50 +353,74 @@ function formatError(method, path, status, body) {
353
353
  */
354
354
  async function request(method, path, body, sessionId, opts = {}) {
355
355
  const timeoutMs = opts.timeoutMs || REQUEST_TIMEOUT_MS;
356
- const controller = new AbortController();
357
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
356
+ const maxRetries = opts.retries ?? 0;
358
357
  const baseUrl = getBaseUrl(sessionId);
359
358
 
360
- try {
361
- const fetchOpts = {
362
- method,
363
- headers: headers(sessionId),
364
- signal: controller.signal,
365
- };
366
- if (body !== undefined) {
367
- fetchOpts.body = JSON.stringify(body);
368
- }
369
-
370
- const res = await fetch(`${baseUrl}${path}`, fetchOpts);
371
-
372
- if (!res.ok) {
373
- const text = await res.text().catch(() => "");
374
- throw new Error(formatError(method, path, res.status, text));
375
- }
376
-
377
- return res.json();
378
- } catch (err) {
379
- if (err.name === "AbortError") {
380
- throw new Error(
381
- `A-Team API timeout: ${method} ${path} did not respond within ${timeoutMs / 1000}s.\n` +
382
- `Hint: The A-Team API at ${baseUrl} may be down. Check ${baseUrl}/health`
383
- );
384
- }
385
- if (err.cause?.code === "ECONNREFUSED") {
386
- throw new Error(
387
- `Cannot connect to A-Team API at ${baseUrl}.\n` +
388
- `Hint: The service may be down. Check ${baseUrl}/health`
389
- );
359
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
360
+ const controller = new AbortController();
361
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
362
+
363
+ try {
364
+ const fetchOpts = {
365
+ method,
366
+ headers: headers(sessionId),
367
+ signal: controller.signal,
368
+ };
369
+ if (body !== undefined) {
370
+ fetchOpts.body = JSON.stringify(body);
371
+ }
372
+
373
+ const res = await fetch(`${baseUrl}${path}`, fetchOpts);
374
+
375
+ // Auto-retry on 502/504 (proxy timeout during long deploys)
376
+ if ((res.status === 502 || res.status === 504) && attempt < maxRetries) {
377
+ const wait = Math.min(5000 * (attempt + 1), 15000);
378
+ console.error(`[MCP] ${method} ${path} returned ${res.status}, retrying in ${wait / 1000}s (attempt ${attempt + 1}/${maxRetries})...`);
379
+ await new Promise(r => setTimeout(r, wait));
380
+ continue;
381
+ }
382
+
383
+ if (!res.ok) {
384
+ const text = await res.text().catch(() => "");
385
+ throw new Error(formatError(method, path, res.status, text));
386
+ }
387
+
388
+ return res.json();
389
+ } catch (err) {
390
+ if (err.name === "AbortError") {
391
+ if (attempt < maxRetries) {
392
+ const wait = Math.min(5000 * (attempt + 1), 15000);
393
+ console.error(`[MCP] ${method} ${path} timed out, retrying in ${wait / 1000}s (attempt ${attempt + 1}/${maxRetries})...`);
394
+ await new Promise(r => setTimeout(r, wait));
395
+ continue;
396
+ }
397
+ throw new Error(
398
+ `A-Team API timeout: ${method} ${path} did not respond within ${timeoutMs / 1000}s.\n` +
399
+ `Hint: The A-Team API at ${baseUrl} may be down. Check ${baseUrl}/health`
400
+ );
401
+ }
402
+ if (err.cause?.code === "ECONNREFUSED") {
403
+ if (attempt < maxRetries) {
404
+ const wait = Math.min(5000 * (attempt + 1), 15000);
405
+ console.error(`[MCP] ${method} ${path} connection refused, retrying in ${wait / 1000}s (attempt ${attempt + 1}/${maxRetries})...`);
406
+ await new Promise(r => setTimeout(r, wait));
407
+ continue;
408
+ }
409
+ throw new Error(
410
+ `Cannot connect to A-Team API at ${baseUrl}.\n` +
411
+ `Hint: The service may be down. Check ${baseUrl}/health`
412
+ );
413
+ }
414
+ if (err.cause?.code === "ENOTFOUND") {
415
+ throw new Error(
416
+ `Cannot resolve A-Team API host: ${baseUrl}.\n` +
417
+ `Hint: Check your internet connection and ADAS_API_URL setting.`
418
+ );
419
+ }
420
+ throw err;
421
+ } finally {
422
+ clearTimeout(timeout);
390
423
  }
391
- if (err.cause?.code === "ENOTFOUND") {
392
- throw new Error(
393
- `Cannot resolve A-Team API host: ${baseUrl}.\n` +
394
- `Hint: Check your internet connection and ADAS_API_URL setting.`
395
- );
396
- }
397
- throw err;
398
- } finally {
399
- clearTimeout(timeout);
400
424
  }
401
425
  }
402
426
 
package/src/tools.js CHANGED
@@ -367,6 +367,46 @@ export const tools = [
367
367
  },
368
368
  },
369
369
 
370
+ {
371
+ name: "ateam_upload_connector",
372
+ core: true,
373
+ description:
374
+ "Upload connector code to Core and restart — WITHOUT redeploying skills. " +
375
+ "Use this to update connector source code (server.js, UI assets, plugins) quickly. " +
376
+ "Set github=true to pull files from the solution's GitHub repo, or pass files directly. " +
377
+ "Much faster than ateam_build_and_run for connector-only changes.",
378
+ inputSchema: {
379
+ type: "object",
380
+ properties: {
381
+ solution_id: {
382
+ type: "string",
383
+ description: "The solution ID",
384
+ },
385
+ connector_id: {
386
+ type: "string",
387
+ description: "The connector ID to upload (e.g. 'personal-assistant-ui-mcp')",
388
+ },
389
+ github: {
390
+ type: "boolean",
391
+ description: "If true, pull connector files from GitHub repo. Default: false.",
392
+ },
393
+ files: {
394
+ type: "array",
395
+ items: {
396
+ type: "object",
397
+ properties: {
398
+ path: { type: "string", description: "Relative file path (e.g. 'server.js', 'ui-dist/panel/1.0.0/index.html')" },
399
+ content: { type: "string", description: "File content" },
400
+ },
401
+ required: ["path", "content"],
402
+ },
403
+ description: "Files to upload. Alternative to github=true.",
404
+ },
405
+ },
406
+ required: ["solution_id", "connector_id"],
407
+ },
408
+ },
409
+
370
410
  // ═══════════════════════════════════════════════════════════════════
371
411
  // ADVANCED TOOLS — hidden from tools/list, still callable by name
372
412
  // Use these for manual lifecycle control, debugging, and diagnostics
@@ -987,6 +1027,7 @@ const TENANT_TOOLS = new Set([
987
1027
  "ateam_redeploy",
988
1028
  "ateam_delete_solution",
989
1029
  "ateam_delete_connector",
1030
+ "ateam_upload_connector",
990
1031
  "ateam_solution_chat",
991
1032
  // Read operations (tenant-specific data)
992
1033
  "ateam_list_solutions",
@@ -1268,7 +1309,7 @@ const handlers = {
1268
1309
  // Phase 2: Deploy
1269
1310
  let deploy;
1270
1311
  try {
1271
- deploy = await post("/deploy/solution", { solution, skills, connectors, mcp_store: effectiveMcpStore }, sid, { timeoutMs: 300_000 });
1312
+ deploy = await post("/deploy/solution", { solution, skills, connectors, mcp_store: effectiveMcpStore }, sid, { timeoutMs: 300_000, retries: 2 });
1272
1313
  phases.push({ phase: "deploy", status: deploy.ok ? "done" : "failed" });
1273
1314
  } catch (err) {
1274
1315
  return {
@@ -1558,7 +1599,7 @@ const handlers = {
1558
1599
  post(`/deploy/solutions/${solution_id}/github/push`, { message }, sid, { timeoutMs: 60_000 }),
1559
1600
 
1560
1601
  ateam_github_pull: async ({ solution_id }, sid) =>
1561
- post(`/deploy/solutions/${solution_id}/github/pull`, {}, sid, { timeoutMs: 300_000 }),
1602
+ post(`/deploy/solutions/${solution_id}/github/pull`, {}, sid, { timeoutMs: 300_000, retries: 2 }),
1562
1603
 
1563
1604
  ateam_github_status: async ({ solution_id }, sid) =>
1564
1605
  get(`/deploy/solutions/${solution_id}/github/status`, sid),
@@ -1589,11 +1630,19 @@ const handlers = {
1589
1630
  ateam_delete_connector: async ({ solution_id, connector_id }, sid) =>
1590
1631
  del(`/deploy/solutions/${solution_id}/connectors/${connector_id}`, sid),
1591
1632
 
1633
+ ateam_upload_connector: async ({ solution_id, connector_id, github, files }, sid) =>
1634
+ post(
1635
+ `/deploy/solutions/${solution_id}/connectors/${connector_id}/upload`,
1636
+ { github, files },
1637
+ sid,
1638
+ { timeoutMs: 300_000, retries: 1 },
1639
+ ),
1640
+
1592
1641
  ateam_redeploy: async ({ solution_id, skill_id }, sid) => {
1593
1642
  const endpoint = skill_id
1594
1643
  ? `/deploy/solutions/${solution_id}/skills/${skill_id}/redeploy`
1595
1644
  : `/deploy/solutions/${solution_id}/redeploy`;
1596
- const result = await post(endpoint, {}, sid, { timeoutMs: 300_000 });
1645
+ const result = await post(endpoint, {}, sid, { timeoutMs: 300_000, retries: 2 });
1597
1646
  return {
1598
1647
  ok: result.ok,
1599
1648
  solution_id,