@ateam-ai/mcp 0.4.36 → 0.4.38
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/api.js +6 -1
- package/src/tools.js +78 -8
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -520,7 +520,12 @@ async function request(method, path, body, sessionId, opts = {}) {
|
|
|
520
520
|
|
|
521
521
|
if (!res.ok) {
|
|
522
522
|
const text = await res.text().catch(() => "");
|
|
523
|
-
|
|
523
|
+
// Attach the HTTP status so callers can distinguish a genuine 404
|
|
524
|
+
// (resource absent) from a transient/5xx failure. ateam_patch relies
|
|
525
|
+
// on this to NOT scaffold-clobber an existing skill on a read error.
|
|
526
|
+
const e = new Error(formatError(method, path, res.status, text, baseUrl));
|
|
527
|
+
e.status = res.status;
|
|
528
|
+
throw e;
|
|
524
529
|
}
|
|
525
530
|
|
|
526
531
|
return res.json();
|
package/src/tools.js
CHANGED
|
@@ -1026,6 +1026,7 @@ export const tools = [
|
|
|
1026
1026
|
" • github:true + files:[] — GitHub state at `ref` as BASE, your files overlay on top (incoming wins).\n" +
|
|
1027
1027
|
" • files:[] (no github) — default MERGE with GitHub state at `ref`. Refuses if no GitHub base exists (no silent nuke).\n" +
|
|
1028
1028
|
" • files:[] + replace:true — full replace. Wipes connector dir + writes only the provided files. Use deliberately.\n\n" +
|
|
1029
|
+
"Multi-file connectors (server.js + dashboard HTML + RN bundle + package/manifest): pass each file with content_base64 (a single-line, escape-safe base64 string) instead of content — so you don't hand-escape ~90KB of HTML/JS/JSON inside one tool call. This is the CANONICAL agent path for a full connector; do NOT hand-roll `curl` against the raw endpoint (that skips connector registration / PAT provisioning).\n\n" +
|
|
1029
1030
|
"Common traps this design prevents:\n" +
|
|
1030
1031
|
" • Pre-fix bug (2026-06-06): sending just ui-dist HTML wiped server.js + node_modules — connector broke until a full re-upload. Now: those files merge with the GitHub base.\n" +
|
|
1031
1032
|
" • Pre-fix bug: github:true silently read from `main` even when patches were on `dev`. Now: defaults to dev; pass ref:'main' to opt into the legacy path.",
|
|
@@ -1054,11 +1055,12 @@ export const tools = [
|
|
|
1054
1055
|
type: "object",
|
|
1055
1056
|
properties: {
|
|
1056
1057
|
path: { type: "string", description: "Relative file path (e.g. 'server.js', 'ui-dist/panel/index.html')" },
|
|
1057
|
-
content: { type: "string", description: "File content" },
|
|
1058
|
+
content: { type: "string", description: "File content as an inline string. Prefer content_base64 when the content has complex escaping (HTML/JS/JSON)." },
|
|
1059
|
+
content_base64: { type: "string", description: "File content as a single-line base64 string — escape-safe. PREFERRED for a multi-file connector so large HTML/JS/bundles don't need hand-escaping in the tool call. Provide exactly ONE of content / content_base64 per file." },
|
|
1058
1060
|
},
|
|
1059
|
-
required: ["path"
|
|
1061
|
+
required: ["path"],
|
|
1060
1062
|
},
|
|
1061
|
-
description: "Files to upload. By default merges with the GitHub state at `ref`. Set replace:true to wipe the connector dir and write only these files.",
|
|
1063
|
+
description: "Files to upload — each needs 'path' plus ONE of content (inline string) or content_base64 (escape-safe base64; preferred for multi-file connectors). By default merges with the GitHub state at `ref`. Set replace:true to wipe the connector dir and write only these files.",
|
|
1062
1064
|
},
|
|
1063
1065
|
replace: {
|
|
1064
1066
|
type: "boolean",
|
|
@@ -3261,11 +3263,51 @@ const handlers = {
|
|
|
3261
3263
|
current = JSON.parse(readResult.content);
|
|
3262
3264
|
}
|
|
3263
3265
|
} catch (err) {
|
|
3264
|
-
//
|
|
3265
|
-
//
|
|
3266
|
-
//
|
|
3266
|
+
// OPEN-31 guard: only scaffold-create when the skill is GENUINELY ABSENT.
|
|
3267
|
+
// The old code scaffolded on ANY read error, so a transient github/read
|
|
3268
|
+
// failure (fetch failed, 5xx, parse error) silently OVERWROTE a full
|
|
3269
|
+
// deployed skill with a bare scaffold on the next write — total data loss.
|
|
3270
|
+
//
|
|
3271
|
+
// A genuine "doesn't exist" is a 404 (or the local empty-definition throw).
|
|
3272
|
+
// Anything else = the store is unreachable/broken → FAIL LOUD, never write.
|
|
3273
|
+
const notFound = err.status === 404 || /not found \(empty definition\)/i.test(err.message || "");
|
|
3274
|
+
if (target === "skill" && skill_id && !notFound) {
|
|
3275
|
+
return {
|
|
3276
|
+
ok: false, phase: "read",
|
|
3277
|
+
error: `Refusing to patch "${skill_id}": could not read its current definition from ${isLocal ? "the Builder store" : "GitHub"} (${err.message}). This is NOT a "skill doesn't exist" error (that would be a 404) — scaffolding now could DESTROY the existing definition. Retry once the store is reachable, or check ateam_get_solution(solution_id, skill_id).`,
|
|
3278
|
+
phases,
|
|
3279
|
+
};
|
|
3280
|
+
}
|
|
3281
|
+
// Even on a real 404, the skill may exist in the OTHER source (deployed to
|
|
3282
|
+
// the Builder store but not pushed to GitHub, or vice versa). Scaffolding
|
|
3283
|
+
// then would destroy/diverge that real def — cross-check before creating.
|
|
3284
|
+
if (target === "skill" && skill_id) {
|
|
3285
|
+
let otherDef = null;
|
|
3286
|
+
try {
|
|
3287
|
+
const other = isLocal
|
|
3288
|
+
? JSON.parse((await get(`/deploy/solutions/${solution_id}/github/read?path=${encodeURIComponent(filePath)}`, sid)).content)
|
|
3289
|
+
: await get(`/deploy/solutions/${solution_id}/skills/${encodeURIComponent(skill_id)}`, sid);
|
|
3290
|
+
otherDef = other?.skill || other?.definition || other;
|
|
3291
|
+
} catch { otherDef = null; /* absent in the other source too → truly new */ }
|
|
3292
|
+
const otherIsReal = otherDef && typeof otherDef === "object" && (
|
|
3293
|
+
(Array.isArray(otherDef.tools) && otherDef.tools.length > 0) ||
|
|
3294
|
+
(Array.isArray(otherDef.connectors) && otherDef.connectors.length > 0) ||
|
|
3295
|
+
otherDef.voice_native || otherDef.ui_plugins ||
|
|
3296
|
+
(otherDef.role && otherDef.role.persona)
|
|
3297
|
+
);
|
|
3298
|
+
if (otherIsReal) {
|
|
3299
|
+
return {
|
|
3300
|
+
ok: false, phase: "read",
|
|
3301
|
+
error: `Refusing to patch "${skill_id}": it was not found in ${isLocal ? "the Builder store" : "GitHub"}, but a full definition EXISTS in ${isLocal ? "GitHub" : "the Builder store"}. Scaffold-creating here would destroy/diverge it. Sync the two first (ateam_redeploy / ateam_verify_consistency), then retry — do NOT patch-create over an existing skill.`,
|
|
3302
|
+
phases,
|
|
3303
|
+
};
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
// If it's a skill that GENUINELY doesn't exist (404 in the primary source
|
|
3307
|
+
// AND absent from the other), create a default scaffold. This lets agents
|
|
3308
|
+
// use ateam_patch to both CREATE and UPDATE skills — no separate step.
|
|
3267
3309
|
if (target === "skill" && skill_id) {
|
|
3268
|
-
console.log(`[ateam_patch] Skill "${skill_id}"
|
|
3310
|
+
console.log(`[ateam_patch] Skill "${skill_id}" genuinely absent (404, both sources) — creating new skill scaffold`);
|
|
3269
3311
|
isNewSkill = true;
|
|
3270
3312
|
current = {
|
|
3271
3313
|
id: skill_id,
|
|
@@ -4423,12 +4465,40 @@ const handlers = {
|
|
|
4423
4465
|
},
|
|
4424
4466
|
|
|
4425
4467
|
ateam_upload_connector: async ({ solution_id, connector_id, github, files, ref, replace }, sid) => {
|
|
4468
|
+
// OPEN-32: accept content_base64 per file (single-line, escape-safe) and
|
|
4469
|
+
// decode it to plain content here, so an agent can upload a multi-file
|
|
4470
|
+
// connector without hand-escaping ~90KB of HTML/JS/JSON in one tool call —
|
|
4471
|
+
// and via THIS registered path (not a raw curl that skips PAT provisioning).
|
|
4472
|
+
let normFiles = files;
|
|
4473
|
+
if (Array.isArray(files)) {
|
|
4474
|
+
normFiles = files.map((f, i) => {
|
|
4475
|
+
if (!f || typeof f !== "object" || !f.path) {
|
|
4476
|
+
throw new Error(`ateam_upload_connector: files[${i}] must be an object with a 'path'.`);
|
|
4477
|
+
}
|
|
4478
|
+
const hasContent = typeof f.content === "string";
|
|
4479
|
+
const hasB64 = typeof f.content_base64 === "string";
|
|
4480
|
+
if (hasContent && hasB64) {
|
|
4481
|
+
throw new Error(`ateam_upload_connector: files[${i}] ("${f.path}") has BOTH content and content_base64 — provide exactly one.`);
|
|
4482
|
+
}
|
|
4483
|
+
if (!hasContent && !hasB64) {
|
|
4484
|
+
throw new Error(`ateam_upload_connector: files[${i}] ("${f.path}") needs 'content' (inline string) or 'content_base64' (escape-safe base64).`);
|
|
4485
|
+
}
|
|
4486
|
+
if (hasB64) {
|
|
4487
|
+
const decoded = Buffer.from(f.content_base64, "base64").toString("utf8");
|
|
4488
|
+
if (!decoded && f.content_base64.trim()) {
|
|
4489
|
+
throw new Error(`ateam_upload_connector: files[${i}] ("${f.path}") content_base64 did not decode to any content — check the encoding.`);
|
|
4490
|
+
}
|
|
4491
|
+
return { path: f.path, content: decoded };
|
|
4492
|
+
}
|
|
4493
|
+
return { path: f.path, content: f.content };
|
|
4494
|
+
});
|
|
4495
|
+
}
|
|
4426
4496
|
// Async-first: this runs npm install + build in Core (up to ~7min) and is a
|
|
4427
4497
|
// prime Cloudflare-524 culprit. Kick async → poll /deploy/jobs; fall back to
|
|
4428
4498
|
// sync for older backends that don't honor async. Mirrors ateam_github_pull.
|
|
4429
4499
|
const body = {
|
|
4430
4500
|
github,
|
|
4431
|
-
files,
|
|
4501
|
+
files: normFiles,
|
|
4432
4502
|
...(ref ? { ref } : {}),
|
|
4433
4503
|
...(replace === true ? { replace: true } : {}),
|
|
4434
4504
|
};
|