@ateam-ai/mcp 0.4.0 → 0.4.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.
- package/package.json +1 -1
- package/src/http.js +9 -2
- package/src/tools.js +91 -37
package/package.json
CHANGED
package/src/http.js
CHANGED
|
@@ -417,9 +417,16 @@ function seedCredentials(req, sessionId) {
|
|
|
417
417
|
return;
|
|
418
418
|
}
|
|
419
419
|
|
|
420
|
-
// Default: use the bearer token itself as credentials
|
|
420
|
+
// Default: use the bearer token itself as credentials.
|
|
421
|
+
// `explicit: true` — a Bearer is a per-connection credential the user
|
|
422
|
+
// deliberately configured (OAuth authorize page, or the plugin's userConfig
|
|
423
|
+
// key injected as `Authorization: Bearer`). Unlike an ambient ADAS_API_KEY
|
|
424
|
+
// env var baked into shared MCP config, it carries explicit tenant intent, so
|
|
425
|
+
// it satisfies isExplicitlyAuthenticated and tenant tools work without a
|
|
426
|
+
// redundant ateam_auth call. (The env-var guard in tools.js is unaffected —
|
|
427
|
+
// env creds never flow through here; this path only fires for a real Bearer.)
|
|
421
428
|
const parsed = parseApiKey(token);
|
|
422
429
|
if (parsed.isValid) {
|
|
423
|
-
setSessionCredentials(sessionId, { tenant: parsed.tenant, apiKey: token });
|
|
430
|
+
setSessionCredentials(sessionId, { tenant: parsed.tenant, apiKey: token, explicit: true });
|
|
424
431
|
}
|
|
425
432
|
}
|
package/src/tools.js
CHANGED
|
@@ -563,6 +563,12 @@ export const tools = [
|
|
|
563
563
|
type: "boolean",
|
|
564
564
|
description: "If true, apply the patch in memory and return the diff (arrays_merged, arrays_replaced, dropped_ids, added_ids, would_write_bytes) WITHOUT writing to GitHub or redeploying. Preview a change before committing to it.",
|
|
565
565
|
},
|
|
566
|
+
source: {
|
|
567
|
+
type: "string",
|
|
568
|
+
enum: ["github", "local"],
|
|
569
|
+
description:
|
|
570
|
+
"Where the solution/skill definition lives. 'github' (DEFAULT) — read from and write to the tenant's GitHub repo (GitHub is master; the normal path). 'local' — read from and write to the Builder FS store (no GitHub repo required). Use 'local' ONLY for a repo-less bootstrap tenant (e.g. freshly onboarded from a template, before GitHub is connected). This is a DEDICATED, EXPLICIT switch — never a fallback. Redeploy is local in both modes.",
|
|
571
|
+
},
|
|
566
572
|
},
|
|
567
573
|
required: ["solution_id", "target", "updates"],
|
|
568
574
|
},
|
|
@@ -2647,22 +2653,45 @@ const handlers = {
|
|
|
2647
2653
|
// Updates → Redeploys → Optionally tests
|
|
2648
2654
|
// One call replaces: ateam_update + ateam_redeploy
|
|
2649
2655
|
|
|
2650
|
-
ateam_patch: async ({ solution_id, target, skill_id, updates, test_message, dry_run }, sid) => {
|
|
2656
|
+
ateam_patch: async ({ solution_id, target, skill_id, updates, test_message, dry_run, source }, sid) => {
|
|
2651
2657
|
const phases = [];
|
|
2652
2658
|
let isNewSkill = false;
|
|
2653
2659
|
const _diff = { arrays_merged: [], arrays_replaced: [], scalars_changed: [], sections_replaced: [] };
|
|
2654
2660
|
|
|
2655
|
-
//
|
|
2656
|
-
//
|
|
2657
|
-
|
|
2658
|
-
//
|
|
2661
|
+
// Two backing stores, chosen EXPLICITLY by `source` (never inferred):
|
|
2662
|
+
// 'github' (default) — GitHub-first: read from GitHub → apply patch → write
|
|
2663
|
+
// back → redeploy. GitHub stays the single source of truth.
|
|
2664
|
+
// 'local' — Builder-FS-first: read from and write to the Builder store for a
|
|
2665
|
+
// repo-less bootstrap tenant (freshly onboarded from a template, GitHub not
|
|
2666
|
+
// yet connected). GitHub is still master overall; local is a temporary
|
|
2667
|
+
// bootstrap until the tenant connects a repo (then local is pushed → GitHub).
|
|
2668
|
+
// Redeploy (Phase 4) is local (Builder FS → Core) in BOTH modes.
|
|
2669
|
+
const isLocal = source === "local";
|
|
2670
|
+
|
|
2671
|
+
// Phase 1: Read current state (or create scaffold if new skill)
|
|
2659
2672
|
let current;
|
|
2660
2673
|
const filePath = target === "skill" && skill_id
|
|
2661
2674
|
? `skills/${skill_id}/skill.json`
|
|
2662
2675
|
: `solution.json`;
|
|
2663
2676
|
try {
|
|
2664
|
-
|
|
2665
|
-
|
|
2677
|
+
if (isLocal) {
|
|
2678
|
+
// Read the raw definition from the Builder store — no GitHub repo needed.
|
|
2679
|
+
if (target === "skill" && skill_id) {
|
|
2680
|
+
const r = await get(`/deploy/solutions/${solution_id}/skills/${encodeURIComponent(skill_id)}`, sid);
|
|
2681
|
+
current = r.skill || r.definition || r;
|
|
2682
|
+
} else {
|
|
2683
|
+
// ?raw=1 → the agent-api returns the UNSTRIPPED solution (keeps
|
|
2684
|
+
// linked_skills/conversation) so _delete/_push operate on the real arrays.
|
|
2685
|
+
const r = await get(`/deploy/solutions/${solution_id}/definition?raw=1`, sid);
|
|
2686
|
+
current = r.solution || r;
|
|
2687
|
+
}
|
|
2688
|
+
if (!current || typeof current !== "object") {
|
|
2689
|
+
throw new Error(`Local ${filePath} not found (empty definition)`);
|
|
2690
|
+
}
|
|
2691
|
+
} else {
|
|
2692
|
+
const readResult = await get(`/deploy/solutions/${solution_id}/github/read?path=${encodeURIComponent(filePath)}`, sid);
|
|
2693
|
+
current = JSON.parse(readResult.content);
|
|
2694
|
+
}
|
|
2666
2695
|
} catch (err) {
|
|
2667
2696
|
// If it's a skill that doesn't exist yet, create a default scaffold.
|
|
2668
2697
|
// This lets agents use ateam_patch to both CREATE and UPDATE skills —
|
|
@@ -2694,7 +2723,7 @@ const handlers = {
|
|
|
2694
2723
|
};
|
|
2695
2724
|
phases.push({ phase: "read", status: "created_scaffold", skill_id });
|
|
2696
2725
|
} else {
|
|
2697
|
-
return { ok: false, phase: "read", error: `Failed to read ${filePath} from GitHub: ${err.message}` };
|
|
2726
|
+
return { ok: false, phase: "read", error: `Failed to read ${filePath} from ${isLocal ? "Builder store (local)" : "GitHub"}: ${err.message}` };
|
|
2698
2727
|
}
|
|
2699
2728
|
}
|
|
2700
2729
|
|
|
@@ -2833,42 +2862,63 @@ const handlers = {
|
|
|
2833
2862
|
};
|
|
2834
2863
|
}
|
|
2835
2864
|
|
|
2836
|
-
// Phase 3: Write patched version back to
|
|
2865
|
+
// Phase 3: Write patched version back to the chosen store.
|
|
2837
2866
|
try {
|
|
2838
2867
|
const patchKeys = Object.keys(updates || {});
|
|
2839
2868
|
const message = `Patch: ${target}${skill_id ? ` ${skill_id}` : ""} — ${patchKeys.join(", ")}`;
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2869
|
+
if (isLocal) {
|
|
2870
|
+
// Write the FULL patched object to the Builder store. Top-level keys are
|
|
2871
|
+
// replaced (removals honored) — the client-side merge above already
|
|
2872
|
+
// resolved _push/_delete/_update, so we send the resolved object.
|
|
2873
|
+
const endpoint = (target === "skill" && skill_id)
|
|
2874
|
+
? `/deploy/solutions/${solution_id}/skills/${encodeURIComponent(skill_id)}`
|
|
2875
|
+
: `/deploy/solutions/${solution_id}`;
|
|
2876
|
+
await patch(endpoint, { state_update: patched }, sid, { timeoutMs: 30_000 });
|
|
2877
|
+
phases.push({ phase: "local_write", status: "done" });
|
|
2878
|
+
} else {
|
|
2879
|
+
await post(`/deploy/solutions/${solution_id}/github/patch`, {
|
|
2880
|
+
path: filePath,
|
|
2881
|
+
content: JSON.stringify(patched, null, 2),
|
|
2882
|
+
message,
|
|
2883
|
+
}, sid, { timeoutMs: 30_000 });
|
|
2884
|
+
phases.push({ phase: "github_write", status: "done" });
|
|
2885
|
+
}
|
|
2846
2886
|
} catch (err) {
|
|
2847
|
-
|
|
2887
|
+
const store = isLocal ? "Builder store (local)" : "GitHub";
|
|
2888
|
+
return { ok: false, phase: isLocal ? "local_write" : "github_write", error: `Patch applied but failed to write to ${store}: ${err.message}`, phases };
|
|
2848
2889
|
}
|
|
2849
2890
|
|
|
2850
2891
|
// Phase 3b: If new skill, add it to solution.json topology (skills[], linked_skills)
|
|
2851
2892
|
if (isNewSkill && skill_id) {
|
|
2852
2893
|
try {
|
|
2853
|
-
const solRead = await get(`/deploy/solutions/${solution_id}/github/read?path=solution.json`, sid);
|
|
2854
|
-
const sol = JSON.parse(solRead.content);
|
|
2855
2894
|
const skillEntry = { id: skill_id, name: patched.name || skill_id, role: "worker", description: patched.description || "", connectors: patched.connectors || [] };
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2895
|
+
if (isLocal) {
|
|
2896
|
+
// Local: _push the entries via the Builder store (dedup handled by the
|
|
2897
|
+
// store's _push — it updates in place if the id already exists).
|
|
2898
|
+
await patch(`/deploy/solutions/${solution_id}`, {
|
|
2899
|
+
state_update: { skills_push: [skillEntry], linked_skills_push: [skill_id] },
|
|
2900
|
+
}, sid, { timeoutMs: 30_000 });
|
|
2901
|
+
phases.push({ phase: "solution_topology", status: "done", added: skill_id });
|
|
2902
|
+
} else {
|
|
2903
|
+
const solRead = await get(`/deploy/solutions/${solution_id}/github/read?path=solution.json`, sid);
|
|
2904
|
+
const sol = JSON.parse(solRead.content);
|
|
2905
|
+
// Add to skills[] if not already present
|
|
2906
|
+
if (!sol.skills) sol.skills = [];
|
|
2907
|
+
if (!sol.skills.find(s => s.id === skill_id)) {
|
|
2908
|
+
sol.skills.push(skillEntry);
|
|
2909
|
+
}
|
|
2910
|
+
// Add to linked_skills if not already present
|
|
2911
|
+
if (!sol.linked_skills) sol.linked_skills = [];
|
|
2912
|
+
if (!sol.linked_skills.includes(skill_id)) {
|
|
2913
|
+
sol.linked_skills.push(skill_id);
|
|
2914
|
+
}
|
|
2915
|
+
await post(`/deploy/solutions/${solution_id}/github/patch`, {
|
|
2916
|
+
path: "solution.json",
|
|
2917
|
+
content: JSON.stringify(sol, null, 2),
|
|
2918
|
+
message: `Add skill "${skill_id}" to solution topology`,
|
|
2919
|
+
}, sid, { timeoutMs: 30_000 });
|
|
2920
|
+
phases.push({ phase: "solution_topology", status: "done", added: skill_id });
|
|
2865
2921
|
}
|
|
2866
|
-
await post(`/deploy/solutions/${solution_id}/github/patch`, {
|
|
2867
|
-
path: "solution.json",
|
|
2868
|
-
content: JSON.stringify(sol, null, 2),
|
|
2869
|
-
message: `Add skill "${skill_id}" to solution topology`,
|
|
2870
|
-
}, sid, { timeoutMs: 30_000 });
|
|
2871
|
-
phases.push({ phase: "solution_topology", status: "done", added: skill_id });
|
|
2872
2922
|
} catch (err) {
|
|
2873
2923
|
// Non-fatal: skill.json was written, topology can be fixed manually
|
|
2874
2924
|
phases.push({ phase: "solution_topology", status: "warning", error: err.message });
|
|
@@ -2914,19 +2964,23 @@ const handlers = {
|
|
|
2914
2964
|
}
|
|
2915
2965
|
|
|
2916
2966
|
const redeployOk = phases.some(p => p.phase === "redeploy" && p.status === "done");
|
|
2967
|
+
const store = isLocal ? "Builder store (local)" : "GitHub";
|
|
2917
2968
|
return {
|
|
2918
2969
|
ok: true,
|
|
2919
2970
|
solution_id,
|
|
2920
|
-
|
|
2971
|
+
source: isLocal ? "local" : "github",
|
|
2972
|
+
...(isLocal ? {} : { branch: 'main' }),
|
|
2921
2973
|
phases,
|
|
2922
2974
|
patched: patched,
|
|
2923
2975
|
...(isNewSkill && { created_skill: skill_id }),
|
|
2924
2976
|
...(redeployResult && { redeploy: redeployResult }),
|
|
2925
2977
|
...(test_result && { test_result }),
|
|
2926
2978
|
_status: redeployOk
|
|
2927
|
-
?
|
|
2928
|
-
:
|
|
2929
|
-
_next:
|
|
2979
|
+
? `✅ Patched on ${store} + redeployed.`
|
|
2980
|
+
: `⚠️ Patched on ${store} ✅ but redeploy timed out. Run: ateam_redeploy(solution_id` + (skill_id ? `, skill_id: "${skill_id}"` : '') + ')',
|
|
2981
|
+
_next: isLocal
|
|
2982
|
+
? 'Local edit saved + redeployed. When the tenant connects a GitHub repo, the local state is pushed → GitHub (which then becomes master).'
|
|
2983
|
+
: 'Create a checkpoint before making more changes: ateam_github_promote(solution_id)',
|
|
2930
2984
|
};
|
|
2931
2985
|
},
|
|
2932
2986
|
|