@ateam-ai/mcp 0.4.20 → 0.4.22
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/tools.js +189 -45
package/package.json
CHANGED
package/src/tools.js
CHANGED
|
@@ -87,6 +87,27 @@ async function pollDeployJob(jobId, sid, { label = 'deploy', maxMs = 15 * 60_000
|
|
|
87
87
|
//
|
|
88
88
|
// Returns null when the solution declares no widgets (nothing to check), else
|
|
89
89
|
// { ok, checked, healthy, plugins[], issues[]?, hint? }.
|
|
90
|
+
// Compress a skill/solution definition to a small, non-truncating summary for
|
|
91
|
+
// tool results — enough to confirm the shape without the 10s-of-KB full doc.
|
|
92
|
+
function _summarizeDef(def) {
|
|
93
|
+
if (!def || typeof def !== "object") return def;
|
|
94
|
+
const pick = (arr, key) => Array.isArray(arr) ? arr.map((x) => (typeof x === "string" ? x : x?.[key] || x?.id)).filter(Boolean) : undefined;
|
|
95
|
+
return {
|
|
96
|
+
id: def.id,
|
|
97
|
+
name: def.name,
|
|
98
|
+
version: def.version,
|
|
99
|
+
phase: def.phase,
|
|
100
|
+
...(def.linked_skills && { linked_skills: pick(def.linked_skills, "id") }),
|
|
101
|
+
...(def.skills && { skills: pick(def.skills, "id") }),
|
|
102
|
+
...(def.connectors && { connectors: pick(def.connectors, "id") }),
|
|
103
|
+
...(def.platform_connectors && { platform_connectors: pick(def.platform_connectors, "id") }),
|
|
104
|
+
...(def.ui_plugins && { ui_plugins: pick(def.ui_plugins, "id") }),
|
|
105
|
+
...(def.tools && { tools: pick(def.tools, "name") }),
|
|
106
|
+
_fields: Object.keys(def),
|
|
107
|
+
_note: "compact summary — pass include_definition:true to ateam_patch for the full definition.",
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
90
111
|
function _widgetHasRender(r) {
|
|
91
112
|
if (!r || typeof r !== "object" || !r.mode) return false;
|
|
92
113
|
const hasIframe = !!(r.iframeUrl || r.iframe?.iframeUrl);
|
|
@@ -109,16 +130,12 @@ async function verifyWidgetHealth(solution_id, sid) {
|
|
|
109
130
|
}
|
|
110
131
|
if (declared.length === 0) return null; // no widgets → nothing to verify
|
|
111
132
|
|
|
112
|
-
// 2. Live catalog — what Core actually discovered/serves right now
|
|
113
|
-
|
|
133
|
+
// 2. Live catalog — what Core actually discovered/serves right now. Go through
|
|
134
|
+
// the Builder proxy (reliable from any connection), NOT ADAS_CORE_URL directly
|
|
135
|
+
// (unreachable from remote/desktop MCP — the old "fetch failed" flakiness).
|
|
114
136
|
let live = [];
|
|
115
137
|
try {
|
|
116
|
-
const
|
|
117
|
-
const res = await fetch(`${coreUrl}/api/ui-plugins`, {
|
|
118
|
-
headers: { "x-api-key": apiKey, "X-ADAS-SERVICE": "ateam-mcp.verify_widget_health" },
|
|
119
|
-
signal: AbortSignal.timeout(15_000),
|
|
120
|
-
});
|
|
121
|
-
const data = await res.json().catch(() => ({}));
|
|
138
|
+
const data = await get(`/deploy/solutions/${solution_id}/ui-plugins`, sid);
|
|
122
139
|
live = Array.isArray(data?.plugins) ? data.plugins : [];
|
|
123
140
|
} catch (e) {
|
|
124
141
|
return { ok: false, error: `widget health: could not read live plugin catalog — ${e.message}` };
|
|
@@ -699,6 +716,10 @@ export const tools = [
|
|
|
699
716
|
description:
|
|
700
717
|
"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.",
|
|
701
718
|
},
|
|
719
|
+
include_definition: {
|
|
720
|
+
type: "boolean",
|
|
721
|
+
description: "If true, return the FULL patched definition. Default false — the result returns a compact patched_summary instead, because the full definition can exceed the ~50KB output limit and truncate the rest of the result (redeploy status, widget_health).",
|
|
722
|
+
},
|
|
702
723
|
},
|
|
703
724
|
required: ["solution_id", "target", "updates"],
|
|
704
725
|
},
|
|
@@ -1372,6 +1393,10 @@ export const tools = [
|
|
|
1372
1393
|
type: "string",
|
|
1373
1394
|
description: "The connector ID to read (e.g. 'home-assistant-mcp')",
|
|
1374
1395
|
},
|
|
1396
|
+
path: {
|
|
1397
|
+
type: "string",
|
|
1398
|
+
description: "Optional. Read ONE file (e.g. 'server.js', 'ui-dist/panel/index.html'). Omit to get a file manifest (paths + sizes, no content) — a whole connector's source exceeds the ~50KB output limit and truncates, so read files one at a time.",
|
|
1399
|
+
},
|
|
1375
1400
|
},
|
|
1376
1401
|
required: ["solution_id", "connector_id"],
|
|
1377
1402
|
},
|
|
@@ -1400,6 +1425,19 @@ export const tools = [
|
|
|
1400
1425
|
required: ["solution_id"],
|
|
1401
1426
|
},
|
|
1402
1427
|
},
|
|
1428
|
+
{
|
|
1429
|
+
name: "ateam_verify",
|
|
1430
|
+
core: true,
|
|
1431
|
+
description:
|
|
1432
|
+
"ONE call that returns the REAL runtime end-state of a solution — connectors connected + tools discovered, every declared widget actually rendering, skills deployed — with the EXACT failing gaps. Use this instead of guess-and-check after a deploy/patch: it tells you the truth (what's actually live) and names precisely what's broken, not a generic warning. Reliable from any connection (routes through the Builder, not a direct Core call).",
|
|
1433
|
+
inputSchema: {
|
|
1434
|
+
type: "object",
|
|
1435
|
+
properties: {
|
|
1436
|
+
solution_id: { type: "string", description: "The solution ID to verify." },
|
|
1437
|
+
},
|
|
1438
|
+
required: ["solution_id"],
|
|
1439
|
+
},
|
|
1440
|
+
},
|
|
1403
1441
|
{
|
|
1404
1442
|
name: "ateam_diff",
|
|
1405
1443
|
core: false,
|
|
@@ -2615,16 +2653,43 @@ const handlers = {
|
|
|
2615
2653
|
// Design-time capability advisor. Proxies to the Builder's /spec/advisor
|
|
2616
2654
|
// (LLM over the curated capability catalog). Public endpoint (auth-exempt),
|
|
2617
2655
|
// but we forward the session so a base override is honored.
|
|
2618
|
-
ateam_design_advisor: async ({ goal, design_state }, sid) => {
|
|
2656
|
+
ateam_design_advisor: async ({ goal, design_state, solution_id }, sid) => {
|
|
2619
2657
|
if (!goal || typeof goal !== "string") throw new Error("goal required (a string describing what you're building)");
|
|
2620
|
-
|
|
2658
|
+
// Reach the advisor through the sysSpecSearch-mcp platform connector via the
|
|
2659
|
+
// proven connector-call path (same as ateam_spec_search) — works on prod with
|
|
2660
|
+
// no bespoke /spec route. The connector's advise tool proxies the Builder's
|
|
2661
|
+
// LLM+catalog internally.
|
|
2662
|
+
const sol = solution_id || "_";
|
|
2663
|
+
const r = await post(
|
|
2664
|
+
`/deploy/solutions/${encodeURIComponent(sol)}/connectors/sysSpecSearch-mcp/call`,
|
|
2665
|
+
{ tool: "sysSpecSearch.advise", args: { goal, design_state: design_state || {} } },
|
|
2666
|
+
sid,
|
|
2667
|
+
{ timeoutMs: 90_000, retries: 1 },
|
|
2668
|
+
);
|
|
2669
|
+
const text = r?.result?.content?.[0]?.text;
|
|
2670
|
+
if (text) { try { return JSON.parse(text); } catch { return { ok: true, raw: text }; } }
|
|
2671
|
+
return r?.result ?? r;
|
|
2621
2672
|
},
|
|
2622
2673
|
|
|
2623
|
-
// Semantic search over the full /spec corpus
|
|
2624
|
-
//
|
|
2625
|
-
|
|
2674
|
+
// Semantic search over the full /spec corpus. Reaches the sysSpecSearch-mcp
|
|
2675
|
+
// PLATFORM connector through the proven connector-call path (the same route
|
|
2676
|
+
// ateam_test_connector uses) — so it works wherever the existing tools do, with
|
|
2677
|
+
// no bespoke /spec route to route on prod. Auth: the agent's api-key gates the
|
|
2678
|
+
// Builder route; the Builder calls Core with the internal secret; tenant is
|
|
2679
|
+
// forwarded from the session. solution_id is only for the route path (any).
|
|
2680
|
+
ateam_spec_search: async ({ query, top_k, solution_id }, sid) => {
|
|
2626
2681
|
if (!query || typeof query !== "string") throw new Error("query required (a string question)");
|
|
2627
|
-
|
|
2682
|
+
const sol = solution_id || "_";
|
|
2683
|
+
const r = await post(
|
|
2684
|
+
`/deploy/solutions/${encodeURIComponent(sol)}/connectors/sysSpecSearch-mcp/call`,
|
|
2685
|
+
{ tool: "sysSpecSearch.search", args: { query, ...(top_k ? { top_k } : {}) } },
|
|
2686
|
+
sid,
|
|
2687
|
+
{ timeoutMs: 30_000, retries: 1 },
|
|
2688
|
+
);
|
|
2689
|
+
// Unwrap the MCP tool result: { result: { content: [{ type:"text", text }] } }.
|
|
2690
|
+
const text = r?.result?.content?.[0]?.text;
|
|
2691
|
+
if (text) { try { return JSON.parse(text); } catch { return { ok: true, raw: text }; } }
|
|
2692
|
+
return r?.result ?? r;
|
|
2628
2693
|
},
|
|
2629
2694
|
|
|
2630
2695
|
// ─── Composite: Build & Run ────────────────────────────────────────
|
|
@@ -2977,7 +3042,7 @@ const handlers = {
|
|
|
2977
3042
|
// Updates → Redeploys → Optionally tests
|
|
2978
3043
|
// One call replaces: ateam_update + ateam_redeploy
|
|
2979
3044
|
|
|
2980
|
-
ateam_patch: async ({ solution_id, target, skill_id, updates, test_message, dry_run, source }, sid) => {
|
|
3045
|
+
ateam_patch: async ({ solution_id, target, skill_id, updates, test_message, dry_run, source, include_definition }, sid) => {
|
|
2981
3046
|
const phases = [];
|
|
2982
3047
|
let isNewSkill = false;
|
|
2983
3048
|
const _diff = { arrays_merged: [], arrays_replaced: [], scalars_changed: [], sections_replaced: [] };
|
|
@@ -3330,7 +3395,13 @@ const handlers = {
|
|
|
3330
3395
|
source: isLocal ? "local" : "github",
|
|
3331
3396
|
...(isLocal ? {} : { branch: 'main' }),
|
|
3332
3397
|
phases,
|
|
3333
|
-
patched
|
|
3398
|
+
// The full patched definition can be 10s of KB and pushes the rest of the
|
|
3399
|
+
// result (redeploy status, widget_health) past the ~50KB output ceiling,
|
|
3400
|
+
// truncating it. Return a compact summary by default; pass
|
|
3401
|
+
// include_definition:true for the whole thing.
|
|
3402
|
+
...(include_definition
|
|
3403
|
+
? { patched }
|
|
3404
|
+
: { patched_summary: _summarizeDef(patched) }),
|
|
3334
3405
|
...(isNewSkill && { created_skill: skill_id }),
|
|
3335
3406
|
...(redeployResult && { redeploy: redeployResult }),
|
|
3336
3407
|
...(widget_health && { widget_health }),
|
|
@@ -3763,25 +3834,18 @@ const handlers = {
|
|
|
3763
3834
|
};
|
|
3764
3835
|
},
|
|
3765
3836
|
|
|
3766
|
-
ateam_get_widget_catalog: async ({ origin, format }, sid) => {
|
|
3767
|
-
// Wraps Core's
|
|
3768
|
-
//
|
|
3769
|
-
//
|
|
3770
|
-
//
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
const
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
signal: AbortSignal.timeout(15_000),
|
|
3779
|
-
});
|
|
3780
|
-
const text = await res.text();
|
|
3781
|
-
let data;
|
|
3782
|
-
try { data = JSON.parse(text); } catch { data = { ok: false, error: text.slice(0, 400) }; }
|
|
3783
|
-
if (!res.ok) {
|
|
3784
|
-
throw new Error(`Core /api/ui-plugins returned ${res.status}: ${data.error || JSON.stringify(data).slice(0, 200)}`);
|
|
3837
|
+
ateam_get_widget_catalog: async ({ origin, format, solution_id }, sid) => {
|
|
3838
|
+
// Wraps Core's GET /api/ui-plugins (merged tenant plugin list) and enriches
|
|
3839
|
+
// each entry with the documentation/how-to-use layer. Filtering by origin
|
|
3840
|
+
// and the summary/full projection happen client-side here.
|
|
3841
|
+
//
|
|
3842
|
+
// Reaches the catalog through the Builder proxy (/deploy/.../ui-plugins) on
|
|
3843
|
+
// the normal base URL — reliable from any connection. (The old direct
|
|
3844
|
+
// ADAS_CORE_URL fetch "fetch failed" from remote/desktop MCP connections.)
|
|
3845
|
+
if (!solution_id) throw new Error("solution_id required (used to route the catalog request through the Builder).");
|
|
3846
|
+
const data = await get(`/deploy/solutions/${solution_id}/ui-plugins`, sid);
|
|
3847
|
+
if (data?.ok === false) {
|
|
3848
|
+
throw new Error(`widget catalog unavailable: ${data.error || "unknown"}`);
|
|
3785
3849
|
}
|
|
3786
3850
|
|
|
3787
3851
|
// Project each plugin into the catalog shape with how_to_use guidance.
|
|
@@ -3844,8 +3908,29 @@ const handlers = {
|
|
|
3844
3908
|
ateam_test_abort: async ({ solution_id, skill_id, job_id }, sid) =>
|
|
3845
3909
|
del(`/deploy/solutions/${solution_id}/skills/${skill_id}/test/${job_id}`, sid),
|
|
3846
3910
|
|
|
3847
|
-
ateam_get_connector_source: async ({ solution_id, connector_id }, sid) =>
|
|
3848
|
-
get(`/deploy/solutions/${solution_id}/connectors/${connector_id}/source`, sid)
|
|
3911
|
+
ateam_get_connector_source: async ({ solution_id, connector_id, path }, sid) => {
|
|
3912
|
+
const data = await get(`/deploy/solutions/${solution_id}/connectors/${connector_id}/source`, sid);
|
|
3913
|
+
const files = Array.isArray(data?.files) ? data.files : [];
|
|
3914
|
+
// A whole connector's source easily exceeds the ~50KB tool-output ceiling and
|
|
3915
|
+
// truncates (you couldn't read the file you needed). So: no `path` → return a
|
|
3916
|
+
// FILE MANIFEST (paths + sizes, no content — small); with `path` → return just
|
|
3917
|
+
// that ONE file's content. Targeted, never truncated.
|
|
3918
|
+
if (!path) {
|
|
3919
|
+
return {
|
|
3920
|
+
ok: true,
|
|
3921
|
+
connector_id,
|
|
3922
|
+
files: files.map((f) => ({ path: f.path, bytes: (f.content || "").length, encoding: f.encoding || "utf8" })),
|
|
3923
|
+
total_bytes: files.reduce((n, f) => n + (f.content || "").length, 0),
|
|
3924
|
+
hint: "Large source is not returned inline. Call again with path:'<file>' to read one file (e.g. path:'server.js').",
|
|
3925
|
+
};
|
|
3926
|
+
}
|
|
3927
|
+
const norm = String(path).replace(/^\.?\//, "");
|
|
3928
|
+
const file = files.find((f) => f.path === path || f.path === norm || f.path.replace(/^\.?\//, "") === norm);
|
|
3929
|
+
if (!file) {
|
|
3930
|
+
return { ok: false, connector_id, error: `file '${path}' not found`, available: files.map((f) => f.path) };
|
|
3931
|
+
}
|
|
3932
|
+
return { ok: true, connector_id, path: file.path, encoding: file.encoding || "utf8", content: file.content };
|
|
3933
|
+
},
|
|
3849
3934
|
|
|
3850
3935
|
// Render + write CLAUDE.md into the solution's GitHub repo.
|
|
3851
3936
|
// Preserves content below the sentinel unless overwrite=true.
|
|
@@ -3920,6 +4005,70 @@ const handlers = {
|
|
|
3920
4005
|
ateam_verify_consistency: async ({ solution_id }, sid) =>
|
|
3921
4006
|
get(`/deploy/solutions/${solution_id}/verify`, sid),
|
|
3922
4007
|
|
|
4008
|
+
// OPEN-7: one call that returns the REAL runtime end-state — connectors
|
|
4009
|
+
// connected + tools discovered, declared widgets actually rendering, skills
|
|
4010
|
+
// deployed — with the exact failing gaps, so you never guess-and-check.
|
|
4011
|
+
// All sub-checks go through the Builder base (reliable from any connection).
|
|
4012
|
+
ateam_verify: async ({ solution_id }, sid) => {
|
|
4013
|
+
if (!solution_id) throw new Error("solution_id required");
|
|
4014
|
+
const gaps = [];
|
|
4015
|
+
const out = { ok: true, solution_id };
|
|
4016
|
+
|
|
4017
|
+
// 1. Connectors — connected + tools discovered.
|
|
4018
|
+
try {
|
|
4019
|
+
const ch = await get(`/deploy/solutions/${solution_id}/connectors/health`, sid);
|
|
4020
|
+
const raw = ch?.connectors || ch?.results || (Array.isArray(ch) ? ch : []);
|
|
4021
|
+
out.connectors = (raw || []).map((c) => {
|
|
4022
|
+
const id = c.id || c.connector_id || c.name;
|
|
4023
|
+
const connected = c.status === "connected" || c.connected === true || c.ok === true || c.healthy === true;
|
|
4024
|
+
const tools = Array.isArray(c.tools) ? c.tools.length : (typeof c.tools === "number" ? c.tools : (c.toolCount ?? c.tool_count));
|
|
4025
|
+
return { id, connected, tools };
|
|
4026
|
+
});
|
|
4027
|
+
for (const c of out.connectors) {
|
|
4028
|
+
if (!c.connected) gaps.push(`connector '${c.id}' not connected`);
|
|
4029
|
+
else if (c.tools === 0) gaps.push(`connector '${c.id}' connected but discovered 0 tools`);
|
|
4030
|
+
}
|
|
4031
|
+
} catch (e) {
|
|
4032
|
+
out.connectors = { error: e.message };
|
|
4033
|
+
gaps.push(`connectors health unavailable: ${e.message}`);
|
|
4034
|
+
}
|
|
4035
|
+
|
|
4036
|
+
// 2. Widgets — every declared ui_plugin actually renders (reliable proxy).
|
|
4037
|
+
try {
|
|
4038
|
+
const wh = await verifyWidgetHealth(solution_id, sid);
|
|
4039
|
+
out.widgets = wh || { checked: 0, note: "no widgets declared" };
|
|
4040
|
+
if (wh && !wh.ok) for (const i of (wh.issues || [])) gaps.push(`widget: ${i}`);
|
|
4041
|
+
} catch (e) {
|
|
4042
|
+
out.widgets = { error: e.message };
|
|
4043
|
+
gaps.push(`widget health unavailable: ${e.message}`);
|
|
4044
|
+
}
|
|
4045
|
+
|
|
4046
|
+
// 3. Skills — deployed + registered (from the solution health check).
|
|
4047
|
+
try {
|
|
4048
|
+
const h = await get(`/deploy/solutions/${solution_id}/health`, sid);
|
|
4049
|
+
const skills = h?.skills || h?.verification?.skills || [];
|
|
4050
|
+
out.skills = (Array.isArray(skills) ? skills : []).map((s) => ({
|
|
4051
|
+
id: s.skill_id || s.id || s.skillSlug,
|
|
4052
|
+
deployed: s.ok !== false && s.status !== "failed",
|
|
4053
|
+
}));
|
|
4054
|
+
for (const s of out.skills) if (!s.deployed) gaps.push(`skill '${s.id}' not deployed`);
|
|
4055
|
+
if (h?.needs_attention && Array.isArray(h.issues)) {
|
|
4056
|
+
// Surface Core's own attention flags that aren't already captured.
|
|
4057
|
+
for (const iss of h.issues.slice(0, 10)) gaps.push(`health: ${typeof iss === "string" ? iss : JSON.stringify(iss)}`);
|
|
4058
|
+
}
|
|
4059
|
+
} catch (e) {
|
|
4060
|
+
out.skills = { error: e.message };
|
|
4061
|
+
gaps.push(`solution health unavailable: ${e.message}`);
|
|
4062
|
+
}
|
|
4063
|
+
|
|
4064
|
+
out.gaps = gaps;
|
|
4065
|
+
out.ok = gaps.length === 0;
|
|
4066
|
+
out._status = out.ok
|
|
4067
|
+
? "✅ Verified live — connectors connected, widgets render, skills deployed."
|
|
4068
|
+
: `⚠️ ${gaps.length} gap(s): ${gaps.slice(0, 5).join("; ")}${gaps.length > 5 ? " …" : ""}`;
|
|
4069
|
+
return out;
|
|
4070
|
+
},
|
|
4071
|
+
|
|
3923
4072
|
ateam_diff: async ({ solution_id, skill_id }, sid) => {
|
|
3924
4073
|
const qs = skill_id ? `?skill_id=${encodeURIComponent(skill_id)}` : "";
|
|
3925
4074
|
return get(`/deploy/solutions/${solution_id}/diff${qs}`, sid);
|
|
@@ -4169,16 +4318,11 @@ const handlers = {
|
|
|
4169
4318
|
const pluginId = `mcp:${connector_id}:${plugin_name}`;
|
|
4170
4319
|
let verified = { renders: false, note: "not yet discovered by Core after upload" };
|
|
4171
4320
|
try {
|
|
4172
|
-
const apiKey = getCredentials(sid)?.apiKey;
|
|
4173
|
-
const coreUrl = process.env.ADAS_CORE_URL || "http://adas-backend:4000";
|
|
4174
4321
|
for (let attempt = 0; attempt < 4; attempt++) {
|
|
4175
4322
|
await new Promise((r) => setTimeout(r, attempt === 0 ? 1500 : 2500));
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
}).catch(() => null);
|
|
4180
|
-
if (!res || !res.ok) continue;
|
|
4181
|
-
const data = await res.json().catch(() => ({}));
|
|
4323
|
+
// Reliable catalog via the Builder proxy (not direct ADAS_CORE_URL).
|
|
4324
|
+
const data = await get(`/deploy/solutions/${solution_id}/ui-plugins`, sid).catch(() => null);
|
|
4325
|
+
if (!data || data.ok === false) continue;
|
|
4182
4326
|
const found = (data?.plugins || []).find((p) => p?.id === pluginId);
|
|
4183
4327
|
if (found) {
|
|
4184
4328
|
verified = _widgetHasRender(found.render)
|