@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.
- package/package.json +1 -1
- package/src/http.js +18 -11
- package/src/tools.js +128 -23
package/package.json
CHANGED
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
|
-
//
|
|
64
|
-
//
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
req.rawHeaders
|
|
76
|
-
|
|
77
|
-
|
|
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:
|
|
668
|
+
core: true,
|
|
632
669
|
description:
|
|
633
|
-
"Poll the progress of an async skill test. Returns iteration count, tool call steps, status
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1462
|
-
|
|
1463
|
-
phase: "deployment",
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
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
|
|
1611
|
+
// Phase 5: GitHub push — only when NOT deployed from GitHub
|
|
1538
1612
|
let github_result;
|
|
1539
1613
|
if (github) {
|
|
1540
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) };
|