@ateam-ai/mcp 0.4.20 → 0.4.21

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/tools.js +156 -39
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ateam-ai/mcp",
3
- "version": "0.4.20",
3
+ "version": "0.4.21",
4
4
  "mcpName": "io.github.ariekogan/ateam-mcp",
5
5
  "description": "A-Team MCP Server — build, validate, and deploy multi-agent solutions from any AI environment",
6
6
  "type": "module",
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
- const apiKey = getCredentials(sid)?.apiKey;
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 coreUrl = process.env.ADAS_CORE_URL || "http://adas-backend:4000";
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,
@@ -2977,7 +3015,7 @@ const handlers = {
2977
3015
  // Updates → Redeploys → Optionally tests
2978
3016
  // One call replaces: ateam_update + ateam_redeploy
2979
3017
 
2980
- ateam_patch: async ({ solution_id, target, skill_id, updates, test_message, dry_run, source }, sid) => {
3018
+ ateam_patch: async ({ solution_id, target, skill_id, updates, test_message, dry_run, source, include_definition }, sid) => {
2981
3019
  const phases = [];
2982
3020
  let isNewSkill = false;
2983
3021
  const _diff = { arrays_merged: [], arrays_replaced: [], scalars_changed: [], sections_replaced: [] };
@@ -3330,7 +3368,13 @@ const handlers = {
3330
3368
  source: isLocal ? "local" : "github",
3331
3369
  ...(isLocal ? {} : { branch: 'main' }),
3332
3370
  phases,
3333
- patched: patched,
3371
+ // The full patched definition can be 10s of KB and pushes the rest of the
3372
+ // result (redeploy status, widget_health) past the ~50KB output ceiling,
3373
+ // truncating it. Return a compact summary by default; pass
3374
+ // include_definition:true for the whole thing.
3375
+ ...(include_definition
3376
+ ? { patched }
3377
+ : { patched_summary: _summarizeDef(patched) }),
3334
3378
  ...(isNewSkill && { created_skill: skill_id }),
3335
3379
  ...(redeployResult && { redeploy: redeployResult }),
3336
3380
  ...(widget_health && { widget_health }),
@@ -3763,25 +3807,18 @@ const handlers = {
3763
3807
  };
3764
3808
  },
3765
3809
 
3766
- ateam_get_widget_catalog: async ({ origin, format }, sid) => {
3767
- // Wraps Core's existing GET /api/ui-plugins (merged tenant plugin list)
3768
- // and enriches each entry with the documentation/how-to-use layer.
3769
- // Filtering by origin and the summary/full format projection happen
3770
- // client-side here — Core just returns the raw merged plugins[].
3771
- const creds = getCredentials(sid);
3772
- const apiKey = creds?.apiKey;
3773
- if (!apiKey) throw new Error("No api_key in session — call ateam_auth(api_key) first.");
3774
- const coreUrl = process.env.ADAS_CORE_URL || "http://adas-backend:4000";
3775
- const res = await fetch(`${coreUrl}/api/ui-plugins`, {
3776
- method: "GET",
3777
- headers: { "x-api-key": apiKey, "X-ADAS-SERVICE": "ateam-mcp.get_widget_catalog" },
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)}`);
3810
+ ateam_get_widget_catalog: async ({ origin, format, solution_id }, sid) => {
3811
+ // Wraps Core's GET /api/ui-plugins (merged tenant plugin list) and enriches
3812
+ // each entry with the documentation/how-to-use layer. Filtering by origin
3813
+ // and the summary/full projection happen client-side here.
3814
+ //
3815
+ // Reaches the catalog through the Builder proxy (/deploy/.../ui-plugins) on
3816
+ // the normal base URL — reliable from any connection. (The old direct
3817
+ // ADAS_CORE_URL fetch "fetch failed" from remote/desktop MCP connections.)
3818
+ if (!solution_id) throw new Error("solution_id required (used to route the catalog request through the Builder).");
3819
+ const data = await get(`/deploy/solutions/${solution_id}/ui-plugins`, sid);
3820
+ if (data?.ok === false) {
3821
+ throw new Error(`widget catalog unavailable: ${data.error || "unknown"}`);
3785
3822
  }
3786
3823
 
3787
3824
  // Project each plugin into the catalog shape with how_to_use guidance.
@@ -3844,8 +3881,29 @@ const handlers = {
3844
3881
  ateam_test_abort: async ({ solution_id, skill_id, job_id }, sid) =>
3845
3882
  del(`/deploy/solutions/${solution_id}/skills/${skill_id}/test/${job_id}`, sid),
3846
3883
 
3847
- ateam_get_connector_source: async ({ solution_id, connector_id }, sid) =>
3848
- get(`/deploy/solutions/${solution_id}/connectors/${connector_id}/source`, sid),
3884
+ ateam_get_connector_source: async ({ solution_id, connector_id, path }, sid) => {
3885
+ const data = await get(`/deploy/solutions/${solution_id}/connectors/${connector_id}/source`, sid);
3886
+ const files = Array.isArray(data?.files) ? data.files : [];
3887
+ // A whole connector's source easily exceeds the ~50KB tool-output ceiling and
3888
+ // truncates (you couldn't read the file you needed). So: no `path` → return a
3889
+ // FILE MANIFEST (paths + sizes, no content — small); with `path` → return just
3890
+ // that ONE file's content. Targeted, never truncated.
3891
+ if (!path) {
3892
+ return {
3893
+ ok: true,
3894
+ connector_id,
3895
+ files: files.map((f) => ({ path: f.path, bytes: (f.content || "").length, encoding: f.encoding || "utf8" })),
3896
+ total_bytes: files.reduce((n, f) => n + (f.content || "").length, 0),
3897
+ hint: "Large source is not returned inline. Call again with path:'<file>' to read one file (e.g. path:'server.js').",
3898
+ };
3899
+ }
3900
+ const norm = String(path).replace(/^\.?\//, "");
3901
+ const file = files.find((f) => f.path === path || f.path === norm || f.path.replace(/^\.?\//, "") === norm);
3902
+ if (!file) {
3903
+ return { ok: false, connector_id, error: `file '${path}' not found`, available: files.map((f) => f.path) };
3904
+ }
3905
+ return { ok: true, connector_id, path: file.path, encoding: file.encoding || "utf8", content: file.content };
3906
+ },
3849
3907
 
3850
3908
  // Render + write CLAUDE.md into the solution's GitHub repo.
3851
3909
  // Preserves content below the sentinel unless overwrite=true.
@@ -3920,6 +3978,70 @@ const handlers = {
3920
3978
  ateam_verify_consistency: async ({ solution_id }, sid) =>
3921
3979
  get(`/deploy/solutions/${solution_id}/verify`, sid),
3922
3980
 
3981
+ // OPEN-7: one call that returns the REAL runtime end-state — connectors
3982
+ // connected + tools discovered, declared widgets actually rendering, skills
3983
+ // deployed — with the exact failing gaps, so you never guess-and-check.
3984
+ // All sub-checks go through the Builder base (reliable from any connection).
3985
+ ateam_verify: async ({ solution_id }, sid) => {
3986
+ if (!solution_id) throw new Error("solution_id required");
3987
+ const gaps = [];
3988
+ const out = { ok: true, solution_id };
3989
+
3990
+ // 1. Connectors — connected + tools discovered.
3991
+ try {
3992
+ const ch = await get(`/deploy/solutions/${solution_id}/connectors/health`, sid);
3993
+ const raw = ch?.connectors || ch?.results || (Array.isArray(ch) ? ch : []);
3994
+ out.connectors = (raw || []).map((c) => {
3995
+ const id = c.id || c.connector_id || c.name;
3996
+ const connected = c.status === "connected" || c.connected === true || c.ok === true || c.healthy === true;
3997
+ const tools = Array.isArray(c.tools) ? c.tools.length : (typeof c.tools === "number" ? c.tools : (c.toolCount ?? c.tool_count));
3998
+ return { id, connected, tools };
3999
+ });
4000
+ for (const c of out.connectors) {
4001
+ if (!c.connected) gaps.push(`connector '${c.id}' not connected`);
4002
+ else if (c.tools === 0) gaps.push(`connector '${c.id}' connected but discovered 0 tools`);
4003
+ }
4004
+ } catch (e) {
4005
+ out.connectors = { error: e.message };
4006
+ gaps.push(`connectors health unavailable: ${e.message}`);
4007
+ }
4008
+
4009
+ // 2. Widgets — every declared ui_plugin actually renders (reliable proxy).
4010
+ try {
4011
+ const wh = await verifyWidgetHealth(solution_id, sid);
4012
+ out.widgets = wh || { checked: 0, note: "no widgets declared" };
4013
+ if (wh && !wh.ok) for (const i of (wh.issues || [])) gaps.push(`widget: ${i}`);
4014
+ } catch (e) {
4015
+ out.widgets = { error: e.message };
4016
+ gaps.push(`widget health unavailable: ${e.message}`);
4017
+ }
4018
+
4019
+ // 3. Skills — deployed + registered (from the solution health check).
4020
+ try {
4021
+ const h = await get(`/deploy/solutions/${solution_id}/health`, sid);
4022
+ const skills = h?.skills || h?.verification?.skills || [];
4023
+ out.skills = (Array.isArray(skills) ? skills : []).map((s) => ({
4024
+ id: s.skill_id || s.id || s.skillSlug,
4025
+ deployed: s.ok !== false && s.status !== "failed",
4026
+ }));
4027
+ for (const s of out.skills) if (!s.deployed) gaps.push(`skill '${s.id}' not deployed`);
4028
+ if (h?.needs_attention && Array.isArray(h.issues)) {
4029
+ // Surface Core's own attention flags that aren't already captured.
4030
+ for (const iss of h.issues.slice(0, 10)) gaps.push(`health: ${typeof iss === "string" ? iss : JSON.stringify(iss)}`);
4031
+ }
4032
+ } catch (e) {
4033
+ out.skills = { error: e.message };
4034
+ gaps.push(`solution health unavailable: ${e.message}`);
4035
+ }
4036
+
4037
+ out.gaps = gaps;
4038
+ out.ok = gaps.length === 0;
4039
+ out._status = out.ok
4040
+ ? "✅ Verified live — connectors connected, widgets render, skills deployed."
4041
+ : `⚠️ ${gaps.length} gap(s): ${gaps.slice(0, 5).join("; ")}${gaps.length > 5 ? " …" : ""}`;
4042
+ return out;
4043
+ },
4044
+
3923
4045
  ateam_diff: async ({ solution_id, skill_id }, sid) => {
3924
4046
  const qs = skill_id ? `?skill_id=${encodeURIComponent(skill_id)}` : "";
3925
4047
  return get(`/deploy/solutions/${solution_id}/diff${qs}`, sid);
@@ -4169,16 +4291,11 @@ const handlers = {
4169
4291
  const pluginId = `mcp:${connector_id}:${plugin_name}`;
4170
4292
  let verified = { renders: false, note: "not yet discovered by Core after upload" };
4171
4293
  try {
4172
- const apiKey = getCredentials(sid)?.apiKey;
4173
- const coreUrl = process.env.ADAS_CORE_URL || "http://adas-backend:4000";
4174
4294
  for (let attempt = 0; attempt < 4; attempt++) {
4175
4295
  await new Promise((r) => setTimeout(r, attempt === 0 ? 1500 : 2500));
4176
- const res = await fetch(`${coreUrl}/api/ui-plugins`, {
4177
- headers: { "x-api-key": apiKey, "X-ADAS-SERVICE": "ateam-mcp.create_plugin_verify" },
4178
- signal: AbortSignal.timeout(15_000),
4179
- }).catch(() => null);
4180
- if (!res || !res.ok) continue;
4181
- const data = await res.json().catch(() => ({}));
4296
+ // Reliable catalog via the Builder proxy (not direct ADAS_CORE_URL).
4297
+ const data = await get(`/deploy/solutions/${solution_id}/ui-plugins`, sid).catch(() => null);
4298
+ if (!data || data.ok === false) continue;
4182
4299
  const found = (data?.plugins || []).find((p) => p?.id === pluginId);
4183
4300
  if (found) {
4184
4301
  verified = _widgetHasRender(found.render)