@ateam-ai/mcp 0.3.9 → 0.3.11

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/http.js +18 -11
  3. package/src/tools.js +128 -23
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ateam-ai/mcp",
3
- "version": "0.3.9",
3
+ "version": "0.3.11",
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/http.js CHANGED
@@ -60,21 +60,28 @@ export function startHttpServer(port = 3100) {
60
60
  app.use(express.json());
61
61
 
62
62
  // ─── Fix Accept header for MCP endpoints ──────────────────────────
63
- // Claude.ai may not send the required Accept header with text/event-stream.
64
- // The MCP SDK requires it per spec, so we inject it if missing.
63
+ // The MCP SDK requires Accept to include BOTH application/json and
64
+ // text/event-stream. Different clients send different combinations:
65
+ // - Claude.ai web: may omit text/event-stream
66
+ // - Claude.ai mobile: may send only text/event-stream
67
+ // - ChatGPT: may send only application/json
68
+ // We normalize to always include both to satisfy the SDK.
65
69
  // Must patch both parsed headers AND rawHeaders since @hono/node-server
66
70
  // reads from rawHeaders when converting to Web Standard Request.
67
71
  for (const path of MCP_PATHS) {
68
72
  app.use(path, (req, _res, next) => {
69
- const accept = req.headers.accept || "";
70
- if (req.method === "POST" && !accept.includes("text/event-stream")) {
71
- const fixed = "application/json, text/event-stream";
72
- req.headers.accept = fixed;
73
- const idx = req.rawHeaders.findIndex((h) => h.toLowerCase() === "accept");
74
- if (idx !== -1) {
75
- req.rawHeaders[idx + 1] = fixed;
76
- } else {
77
- req.rawHeaders.push("Accept", fixed);
73
+ if (req.method === "POST") {
74
+ const accept = req.headers.accept || "";
75
+ const needsFix = !accept.includes("text/event-stream") || !accept.includes("application/json");
76
+ if (needsFix) {
77
+ const fixed = "application/json, text/event-stream";
78
+ req.headers.accept = fixed;
79
+ const idx = req.rawHeaders.findIndex((h) => h.toLowerCase() === "accept");
80
+ if (idx !== -1) {
81
+ req.rawHeaders[idx + 1] = fixed;
82
+ } else {
83
+ req.rawHeaders.push("Accept", fixed);
84
+ }
78
85
  }
79
86
  }
80
87
  next();
package/src/tools.js CHANGED
@@ -187,10 +187,47 @@ export const tools = [
187
187
  description:
188
188
  "If true (default), wait for completion. If false, return job_id immediately for polling via ateam_test_status.",
189
189
  },
190
+ actor_id: {
191
+ type: "string",
192
+ description:
193
+ "Optional actor ID for conversation continuity. Pass the actor_id from a previous test response to continue the conversation. Omit to auto-generate a test actor (test_<timestamp>_<random>, auto-expires in 24h).",
194
+ },
190
195
  },
191
196
  required: ["solution_id", "skill_id", "message"],
192
197
  },
193
198
  },
199
+ {
200
+ name: "ateam_conversation",
201
+ core: true,
202
+ description:
203
+ "Send a message to a deployed solution and get the result. No skill_id needed — the system auto-routes to the right skill. Supports multi-turn conversations: pass the actor_id from a previous response to continue the thread (e.g., reply to a confirmation prompt). Each call creates a new job but the same actor_id maintains conversation context.",
204
+ inputSchema: {
205
+ type: "object",
206
+ properties: {
207
+ solution_id: {
208
+ type: "string",
209
+ description: "The solution ID",
210
+ },
211
+ message: {
212
+ type: "string",
213
+ description: "The message to send (e.g., 'send email to X' or 'I confirm')",
214
+ },
215
+ actor_id: {
216
+ type: "string",
217
+ description: "Optional: actor ID from a previous response to continue the conversation. Omit for a new conversation.",
218
+ },
219
+ wait: {
220
+ type: "boolean",
221
+ description: "If true (default), wait for completion. If false, return job_id immediately for polling.",
222
+ },
223
+ timeout_ms: {
224
+ type: "number",
225
+ description: "Optional: max wait time in ms (default: 60000, max: 300000).",
226
+ },
227
+ },
228
+ required: ["solution_id", "message"],
229
+ },
230
+ },
194
231
  {
195
232
  name: "ateam_test_pipeline",
196
233
  core: true,
@@ -628,9 +665,9 @@ export const tools = [
628
665
  },
629
666
  {
630
667
  name: "ateam_test_status",
631
- core: false,
668
+ core: true,
632
669
  description:
633
- "Poll the progress of an async skill test. Returns iteration count, tool call steps, status, pending questions, and result when done. (Advanced — use ateam_test_skill with wait=true for synchronous testing.)",
670
+ "Poll the progress of an async skill test. Returns iteration count, tool call steps, status (running/completed/failed), and result when done. (Advanced — use ateam_test_skill with wait=true for synchronous testing.)",
634
671
  inputSchema: {
635
672
  type: "object",
636
673
  properties: {
@@ -652,7 +689,7 @@ export const tools = [
652
689
  },
653
690
  {
654
691
  name: "ateam_test_abort",
655
- core: false,
692
+ core: true,
656
693
  description:
657
694
  "Abort a running skill test. Stops the job execution at the next iteration boundary. (Advanced.)",
658
695
  inputSchema: {
@@ -1095,6 +1132,7 @@ const TENANT_TOOLS = new Set([
1095
1132
  "ateam_list_solutions",
1096
1133
  "ateam_get_solution",
1097
1134
  "ateam_get_execution_logs",
1135
+ "ateam_conversation",
1098
1136
  "ateam_test_skill",
1099
1137
  "ateam_test_pipeline",
1100
1138
  "ateam_test_voice",
@@ -1147,7 +1185,7 @@ const handlers = {
1147
1185
  { step: 2, action: "Build & Run", description: "Define your solution + skills + connector code, then validate, deploy, and health-check in one call. Include mcp_store with connector source code on the first deploy.", tools: ["ateam_build_and_run"] },
1148
1186
  { step: 3, action: "Version", description: "Every deploy auto-pushes to main on GitHub. The repo (tenant--solution-id) is the source of truth for connector code.", tools: ["ateam_github_status", "ateam_github_log"] },
1149
1187
  { step: 4, action: "Iterate", description: "Edit connector code ONE FILE AT A TIME via ateam_github_patch, then redeploy with ateam_build_and_run (auto-pulls from GitHub). NEVER re-pass all connector code inline after first deploy. For skill definitions, use ateam_patch.", tools: ["ateam_github_patch", "ateam_build_and_run", "ateam_patch"] },
1150
- { step: 5, action: "Test & Debug", description: "Test the decision pipeline or full execution, then diagnose with logs and metrics. For voice-enabled solutions, use ateam_test_voice to simulate phone conversations.", tools: ["ateam_test_pipeline", "ateam_test_skill", "ateam_test_voice", "ateam_get_execution_logs", "ateam_get_metrics"] },
1188
+ { step: 5, action: "Test & Debug", description: "Test with ateam_conversation (auto-routes, supports multi-turn with actor_id for confirmations). Use ateam_test_pipeline for intent debugging, ateam_test_voice for voice. Diagnose with logs and metrics.", tools: ["ateam_conversation", "ateam_test_pipeline", "ateam_test_skill", "ateam_test_voice", "ateam_get_execution_logs", "ateam_get_metrics"] },
1151
1189
  { step: 6, action: "Checkpoint", description: "When solution is in a good state, create a checkpoint (safe point). You can rollback to any checkpoint if something breaks.", tools: ["ateam_github_promote", "ateam_github_list_versions"] },
1152
1190
  ],
1153
1191
  },
@@ -1455,17 +1493,53 @@ const handlers = {
1455
1493
  // Phase 2: Deploy
1456
1494
  let deploy;
1457
1495
  try {
1458
- deploy = await post("/deploy/solution", { solution, skills: effectiveSkills, connectors, mcp_store: effectiveMcpStore }, sid, { timeoutMs: 300_000, retries: 2 });
1496
+ // Try sync first (fast for small solutions)
1497
+ deploy = await post("/deploy/solution", {
1498
+ solution, skills: effectiveSkills, connectors, mcp_store: effectiveMcpStore,
1499
+ ...(github && { skip_github_push: true }),
1500
+ }, sid, { timeoutMs: 120_000 });
1459
1501
  phases.push({ phase: "deploy", status: deploy.ok ? "done" : "failed" });
1460
1502
  } catch (err) {
1461
- return {
1462
- ok: false,
1463
- phase: "deployment",
1464
- phases,
1465
- error: err.message,
1466
- validation_warnings: validation.warnings || [],
1467
- message: "Deployment failed. See error details above.",
1468
- };
1503
+ const isTimeout = /524|502|503|timeout|ETIMEDOUT/i.test(err.message);
1504
+ if (!isTimeout) {
1505
+ return { ok: false, phase: "deployment", phases, error: err.message, validation_warnings: validation.warnings || [] };
1506
+ }
1507
+
1508
+ // Timeout retry with async mode + polling
1509
+ phases.push({ phase: "deploy", status: "async_retry" });
1510
+ try {
1511
+ const asyncResult = await post("/deploy/solution", {
1512
+ solution, skills: effectiveSkills, connectors, mcp_store: effectiveMcpStore,
1513
+ ...(github && { skip_github_push: true }),
1514
+ async: true,
1515
+ }, sid, { timeoutMs: 15_000 });
1516
+
1517
+ if (asyncResult.job_id) {
1518
+ // Poll for completion (up to 10 min)
1519
+ const jobId = asyncResult.job_id;
1520
+ const maxWait = 600_000;
1521
+ const pollInterval = 5_000;
1522
+ const start = Date.now();
1523
+ while (Date.now() - start < maxWait) {
1524
+ await new Promise(r => setTimeout(r, pollInterval));
1525
+ try {
1526
+ const job = await get(`/deploy/jobs/${jobId}`, sid);
1527
+ if (job.status === 'done' || job.status === 'failed') {
1528
+ deploy = job;
1529
+ phases.push({ phase: "deploy", status: job.status });
1530
+ break;
1531
+ }
1532
+ } catch { /* keep polling */ }
1533
+ }
1534
+ if (!deploy) {
1535
+ return { ok: false, phase: "deployment", phases, error: "Async deploy timed out after 10 minutes", validation_warnings: validation.warnings || [],
1536
+ hint: "Deploy is too large even for async mode. Use incremental tools instead: ateam_patch(solution_id, target:'skill', skill_id, updates) for skill changes, ateam_upload_connector(solution_id, connector_id, github:true) for connector code changes." };
1537
+ }
1538
+ }
1539
+ } catch (asyncErr) {
1540
+ return { ok: false, phase: "deployment", phases, error: `Sync timed out, async fallback failed: ${asyncErr.message}`, validation_warnings: validation.warnings || [],
1541
+ hint: "Deploy timed out. Use incremental tools: ateam_patch for skill changes, ateam_upload_connector for connector changes. These deploy one component at a time and never timeout." };
1542
+ }
1469
1543
  }
1470
1544
 
1471
1545
  if (!deploy.ok) {
@@ -1534,17 +1608,16 @@ const handlers = {
1534
1608
  }
1535
1609
  }
1536
1610
 
1537
- // Phase 5: GitHub push (skip if we just pulled from GitHub — don't overwrite source of truth)
1611
+ // Phase 5: GitHub push only when NOT deployed from GitHub
1538
1612
  let github_result;
1539
1613
  if (github) {
1540
- // We pulled from GitHub GitHub IS the source of truth. Don't push stale Core state back.
1541
- github_result = { skipped: true, reason: 'Deployed from GitHub — skipping push-back to avoid overwriting source of truth.' };
1614
+ github_result = { skipped: true, reason: 'Deployed from GitHub push-back skipped.' };
1542
1615
  phases.push({ phase: "github", status: "skipped", reason: "pulled_from_github" });
1543
1616
  } else {
1544
1617
  try {
1545
1618
  github_result = await post(
1546
1619
  `/deploy/solutions/${solutionId}/github/push`,
1547
- { message: `Deploy: ${solution.name || solutionId}` },
1620
+ { push_to_github: true, message: `Deploy: ${solution.name || solutionId}` },
1548
1621
  sid,
1549
1622
  { timeoutMs: 60_000 },
1550
1623
  );
@@ -1596,11 +1669,15 @@ const handlers = {
1596
1669
  }
1597
1670
  phases.push({ phase: "update", status: "done" });
1598
1671
  } catch (err) {
1672
+ const notFound = /not found|404|ENOENT/i.test(err.message);
1599
1673
  return {
1600
1674
  ok: false,
1601
1675
  phase: "update",
1602
1676
  error: err.message,
1603
1677
  message: "Patch failed. Check your updates payload format.",
1678
+ ...(notFound && {
1679
+ hint: "Skill not found in Builder storage. Use ateam_github_patch to edit the skill JSON directly on GitHub (path: skills/<skill-id>/skill.json), then ateam_redeploy(solution_id, skill_id) to deploy the updated skill.",
1680
+ }),
1604
1681
  };
1605
1682
  }
1606
1683
 
@@ -1647,7 +1724,7 @@ const handlers = {
1647
1724
  try {
1648
1725
  github_result = await post(
1649
1726
  `/deploy/solutions/${solution_id}/github/push`,
1650
- { message: `Patch: ${target}${skill_id ? ` ${skill_id}` : ''} — ${Object.keys(updates || {}).join(', ')}` },
1727
+ { push_to_github: true, message: `Patch: ${target}${skill_id ? ` ${skill_id}` : ''} — ${Object.keys(updates || {}).join(', ')}` },
1651
1728
  sid,
1652
1729
  { timeoutMs: 60_000 },
1653
1730
  );
@@ -1751,9 +1828,21 @@ const handlers = {
1751
1828
  return get(`/deploy/solutions/${solution_id}/logs${qsStr}`, sid);
1752
1829
  },
1753
1830
 
1754
- ateam_test_skill: async ({ solution_id, skill_id, message, wait }, sid) => {
1831
+ ateam_conversation: async ({ solution_id, message, actor_id, wait, timeout_ms }, sid) => {
1832
+ const asyncMode = wait === false;
1833
+ const body = {
1834
+ message,
1835
+ ...(actor_id ? { actor_id } : {}),
1836
+ ...(asyncMode ? { async: true } : {}),
1837
+ ...(timeout_ms ? { timeout_ms } : {}),
1838
+ };
1839
+ const timeoutMs = asyncMode ? 15_000 : Math.min((timeout_ms || 60_000) + 30_000, 330_000);
1840
+ return post(`/deploy/solutions/${solution_id}/test`, body, sid, { timeoutMs });
1841
+ },
1842
+
1843
+ ateam_test_skill: async ({ solution_id, skill_id, message, wait, actor_id }, sid) => {
1755
1844
  const asyncMode = wait === false;
1756
- const body = { message, ...(asyncMode ? { async: true } : {}) };
1845
+ const body = { message, ...(asyncMode ? { async: true } : {}), ...(actor_id ? { actor_id } : {}) };
1757
1846
  const timeoutMs = asyncMode ? 15_000 : 90_000;
1758
1847
  return post(`/deploy/solutions/${solution_id}/skills/${skill_id}/test`, body, sid, { timeoutMs });
1759
1848
  },
@@ -1797,7 +1886,7 @@ const handlers = {
1797
1886
  // ─── GitHub tools ──────────────────────────────────────────────────
1798
1887
 
1799
1888
  ateam_github_push: async ({ solution_id, message }, sid) =>
1800
- post(`/deploy/solutions/${solution_id}/github/push`, { message }, sid, { timeoutMs: 60_000 }),
1889
+ post(`/deploy/solutions/${solution_id}/github/push`, { push_to_github: true, message }, sid, { timeoutMs: 60_000 }),
1801
1890
 
1802
1891
  ateam_github_pull: async ({ solution_id }, sid) =>
1803
1892
  post(`/deploy/solutions/${solution_id}/github/pull`, {}, sid, { timeoutMs: 300_000, retries: 2 }),
@@ -1846,7 +1935,23 @@ const handlers = {
1846
1935
  const endpoint = skill_id
1847
1936
  ? `/deploy/solutions/${solution_id}/skills/${skill_id}/redeploy`
1848
1937
  : `/deploy/solutions/${solution_id}/redeploy`;
1849
- const result = await post(endpoint, {}, sid, { timeoutMs: 300_000, retries: 2 });
1938
+ let result;
1939
+ try {
1940
+ result = await post(endpoint, {}, sid, { timeoutMs: 300_000, retries: 2 });
1941
+ } catch (err) {
1942
+ const notFound = /not found|404|ENOENT/i.test(err.message);
1943
+ const isTimeout = /524|502|503|timeout|ETIMEDOUT/i.test(err.message);
1944
+ return {
1945
+ ok: false,
1946
+ error: err.message,
1947
+ ...(notFound && {
1948
+ hint: "Skill not found in Builder storage. Edit the skill on GitHub with ateam_github_patch(solution_id, path: 'skills/<skill-id>/skill.json', search: '...', replace: '...'), then use ateam_build_and_run(solution_id, github: true) or ask the platform operator to deploy the single skill.",
1949
+ }),
1950
+ ...(isTimeout && {
1951
+ hint: "Redeploy timed out. For large solutions, redeploy one skill at a time: ateam_redeploy(solution_id, skill_id: '<specific-skill>').",
1952
+ }),
1953
+ };
1954
+ }
1850
1955
  return {
1851
1956
  ok: result.ok,
1852
1957
  solution_id,
@@ -1914,7 +2019,7 @@ const handlers = {
1914
2019
  // Push: Builder FS → GitHub
1915
2020
  if (!pull_only) {
1916
2021
  try {
1917
- const pushResult = await post(`/deploy/solutions/${sol.id}/github/push`, {}, sid);
2022
+ const pushResult = await post(`/deploy/solutions/${sol.id}/github/push`, { push_to_github: true }, sid);
1918
2023
  entry.push = { ok: true, commit: pushResult.commitSha?.slice(0, 8), files: pushResult.filesCommitted };
1919
2024
  } catch (err) {
1920
2025
  entry.push = { ok: false, error: err.message.slice(0, 100) };