@ateam-ai/mcp 0.4.35 → 0.4.37
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 +87 -10
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
|
@@ -116,6 +116,32 @@ function _summarizeDef(def) {
|
|
|
116
116
|
};
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
// OPEN-8: byte offset/limit paging for reads that can exceed the ~50KB output
|
|
120
|
+
// cap. Serializes the result to pretty JSON and returns a [offset, offset+limit)
|
|
121
|
+
// slice plus a cursor so an agent can page the rest (like Read offset/limit).
|
|
122
|
+
function _pageJson(data, offset = 0, limit) {
|
|
123
|
+
const full = typeof data === "string" ? data : JSON.stringify(data, null, 2);
|
|
124
|
+
const total = full.length;
|
|
125
|
+
const start = Math.min(Math.max(0, Math.trunc(offset) || 0), total);
|
|
126
|
+
const size = (limit != null) ? Math.max(1, Math.trunc(limit)) : (total - start);
|
|
127
|
+
const chunk = full.slice(start, start + size);
|
|
128
|
+
const end = start + chunk.length;
|
|
129
|
+
return {
|
|
130
|
+
ok: true,
|
|
131
|
+
_paging: {
|
|
132
|
+
offset: start,
|
|
133
|
+
limit: (limit != null) ? size : null,
|
|
134
|
+
returned_bytes: chunk.length,
|
|
135
|
+
total_bytes: total,
|
|
136
|
+
next_offset: end < total ? end : null,
|
|
137
|
+
has_more: end < total,
|
|
138
|
+
note: end < total ? `Truncated to bytes ${start}-${end} of ${total}. Fetch the next page with offset:${end}.` : `Complete (bytes ${start}-${end} of ${total}).`,
|
|
139
|
+
},
|
|
140
|
+
// Raw JSON text slice — parse only once you've concatenated all pages.
|
|
141
|
+
content: chunk,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
119
145
|
function _widgetHasRender(r) {
|
|
120
146
|
if (!r || typeof r !== "object" || !r.mode) return false;
|
|
121
147
|
const hasIframe = !!(r.iframeUrl || r.iframe?.iframeUrl);
|
|
@@ -783,6 +809,14 @@ export const tools = [
|
|
|
783
809
|
type: "string",
|
|
784
810
|
description: "Optional (with skill_id): return ONLY this section of the skill instead of the whole definition — avoids the ~50KB output truncation on big skills. Dotted paths work (e.g. 'role', 'tools', 'intents.supported', 'policy', 'engine'). Omit for the full skill; use ateam_show_skill_minimal for the slim authoring view.",
|
|
785
811
|
},
|
|
812
|
+
offset: {
|
|
813
|
+
type: "number",
|
|
814
|
+
description: "Optional byte-paging: start returning the serialized result from this byte offset. Use with 'limit' to page a result larger than the ~50KB output cap; the response's _paging.next_offset gives the next page (null when done). Concatenate the `content` slices across pages, then JSON.parse.",
|
|
815
|
+
},
|
|
816
|
+
limit: {
|
|
817
|
+
type: "number",
|
|
818
|
+
description: "Optional byte-paging: max bytes of the serialized result to return in this page (pair with 'offset'). Omit both for the whole result (may truncate at the output cap).",
|
|
819
|
+
},
|
|
786
820
|
},
|
|
787
821
|
required: ["solution_id", "view"],
|
|
788
822
|
},
|
|
@@ -3227,11 +3261,51 @@ const handlers = {
|
|
|
3227
3261
|
current = JSON.parse(readResult.content);
|
|
3228
3262
|
}
|
|
3229
3263
|
} catch (err) {
|
|
3230
|
-
//
|
|
3231
|
-
//
|
|
3232
|
-
//
|
|
3264
|
+
// OPEN-31 guard: only scaffold-create when the skill is GENUINELY ABSENT.
|
|
3265
|
+
// The old code scaffolded on ANY read error, so a transient github/read
|
|
3266
|
+
// failure (fetch failed, 5xx, parse error) silently OVERWROTE a full
|
|
3267
|
+
// deployed skill with a bare scaffold on the next write — total data loss.
|
|
3268
|
+
//
|
|
3269
|
+
// A genuine "doesn't exist" is a 404 (or the local empty-definition throw).
|
|
3270
|
+
// Anything else = the store is unreachable/broken → FAIL LOUD, never write.
|
|
3271
|
+
const notFound = err.status === 404 || /not found \(empty definition\)/i.test(err.message || "");
|
|
3272
|
+
if (target === "skill" && skill_id && !notFound) {
|
|
3273
|
+
return {
|
|
3274
|
+
ok: false, phase: "read",
|
|
3275
|
+
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).`,
|
|
3276
|
+
phases,
|
|
3277
|
+
};
|
|
3278
|
+
}
|
|
3279
|
+
// Even on a real 404, the skill may exist in the OTHER source (deployed to
|
|
3280
|
+
// the Builder store but not pushed to GitHub, or vice versa). Scaffolding
|
|
3281
|
+
// then would destroy/diverge that real def — cross-check before creating.
|
|
3282
|
+
if (target === "skill" && skill_id) {
|
|
3283
|
+
let otherDef = null;
|
|
3284
|
+
try {
|
|
3285
|
+
const other = isLocal
|
|
3286
|
+
? JSON.parse((await get(`/deploy/solutions/${solution_id}/github/read?path=${encodeURIComponent(filePath)}`, sid)).content)
|
|
3287
|
+
: await get(`/deploy/solutions/${solution_id}/skills/${encodeURIComponent(skill_id)}`, sid);
|
|
3288
|
+
otherDef = other?.skill || other?.definition || other;
|
|
3289
|
+
} catch { otherDef = null; /* absent in the other source too → truly new */ }
|
|
3290
|
+
const otherIsReal = otherDef && typeof otherDef === "object" && (
|
|
3291
|
+
(Array.isArray(otherDef.tools) && otherDef.tools.length > 0) ||
|
|
3292
|
+
(Array.isArray(otherDef.connectors) && otherDef.connectors.length > 0) ||
|
|
3293
|
+
otherDef.voice_native || otherDef.ui_plugins ||
|
|
3294
|
+
(otherDef.role && otherDef.role.persona)
|
|
3295
|
+
);
|
|
3296
|
+
if (otherIsReal) {
|
|
3297
|
+
return {
|
|
3298
|
+
ok: false, phase: "read",
|
|
3299
|
+
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.`,
|
|
3300
|
+
phases,
|
|
3301
|
+
};
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
// If it's a skill that GENUINELY doesn't exist (404 in the primary source
|
|
3305
|
+
// AND absent from the other), create a default scaffold. This lets agents
|
|
3306
|
+
// use ateam_patch to both CREATE and UPDATE skills — no separate step.
|
|
3233
3307
|
if (target === "skill" && skill_id) {
|
|
3234
|
-
console.log(`[ateam_patch] Skill "${skill_id}"
|
|
3308
|
+
console.log(`[ateam_patch] Skill "${skill_id}" genuinely absent (404, both sources) — creating new skill scaffold`);
|
|
3235
3309
|
isNewSkill = true;
|
|
3236
3310
|
current = {
|
|
3237
3311
|
id: skill_id,
|
|
@@ -3672,22 +3746,24 @@ const handlers = {
|
|
|
3672
3746
|
return { ...raw, solutions: enriched };
|
|
3673
3747
|
},
|
|
3674
3748
|
|
|
3675
|
-
ateam_get_solution: async ({ solution_id, view, skill_id, section }, sid) => {
|
|
3749
|
+
ateam_get_solution: async ({ solution_id, view, skill_id, section, offset, limit }, sid) => {
|
|
3676
3750
|
const base = `/deploy/solutions/${solution_id}`;
|
|
3751
|
+
const paged = (offset != null || limit != null);
|
|
3677
3752
|
if (skill_id) {
|
|
3678
3753
|
const r = await get(`${base}/skills/${skill_id}`, sid);
|
|
3679
3754
|
// OPEN-8: a single skill def can be 50KB+ and truncate at the output cap.
|
|
3680
|
-
// `section` slices it to one field (dotted paths ok, e.g. intents.supported)
|
|
3681
|
-
//
|
|
3755
|
+
// `section` slices it to one field (dotted paths ok, e.g. intents.supported);
|
|
3756
|
+
// offset/limit page the raw bytes — so a big skill is always readable.
|
|
3757
|
+
let result = r;
|
|
3682
3758
|
if (section) {
|
|
3683
3759
|
const skill = r?.skill || r?.definition || r || {};
|
|
3684
3760
|
const val = String(section).split(".").reduce((o, k) => (o == null ? undefined : o[k]), skill);
|
|
3685
|
-
|
|
3761
|
+
result = {
|
|
3686
3762
|
ok: true, solution_id, skill_id, section, [section]: val,
|
|
3687
3763
|
_note: `Sliced to '${section}'. Omit 'section' for the full skill; ateam_show_skill_minimal gives the slim authoring view.`,
|
|
3688
3764
|
};
|
|
3689
3765
|
}
|
|
3690
|
-
return
|
|
3766
|
+
return paged ? _pageJson(result, offset, limit) : result;
|
|
3691
3767
|
}
|
|
3692
3768
|
const paths = {
|
|
3693
3769
|
definition: `${base}/definition`,
|
|
@@ -3698,7 +3774,8 @@ const handlers = {
|
|
|
3698
3774
|
validate: `${base}/validate`,
|
|
3699
3775
|
connectors_health: `${base}/connectors/health`,
|
|
3700
3776
|
};
|
|
3701
|
-
|
|
3777
|
+
const viewResult = await get(paths[view], sid);
|
|
3778
|
+
return paged ? _pageJson(viewResult, offset, limit) : viewResult;
|
|
3702
3779
|
},
|
|
3703
3780
|
|
|
3704
3781
|
ateam_update: async ({ solution_id, target, skill_id, updates }, sid) => {
|