@ateam-ai/mcp 0.4.71 → 0.4.73

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 +131 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ateam-ai/mcp",
3
- "version": "0.4.71",
3
+ "version": "0.4.73",
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
@@ -223,6 +223,37 @@ function _widgetHasRender(r) {
223
223
  return true; // unknown mode — don't false-positive on a custom render
224
224
  }
225
225
 
226
+
227
+ // ── WIDGET POSTMESSAGE PROTOCOL: the failure that RENDERS FINE ────────────────
228
+ //
229
+ // A widget using the wrong postMessage shape draws its chrome, calls the host,
230
+ // and hangs until its own timeout. Nothing errors: the connector is healthy,
231
+ // tools/list is correct, the plugin "renders", and only the DATA is missing.
232
+ // A clinic dashboard shipped that way and was reported as working (2026-08-29).
233
+ //
234
+ // The host matches action === "mcp-call" with payload.requestId. Three shapes
235
+ // are fatal on their own, and a widget can get two right and still hang:
236
+ // type:"tool.call" — never existed in the host, at any version
237
+ // correlationId — the host echoes requestId; the pending map misses
238
+ // type:"mcp-call" on SEND — send is matched on message.action, not .type
239
+ //
240
+ // Detected here rather than left to a person noticing an empty panel, and
241
+ // reported with the repair attached — a signal the reasoning engine can act on.
242
+ function _widgetProtocolProblems(html) {
243
+ if (typeof html !== "string" || !html.includes("postMessage")) return [];
244
+ const problems = [];
245
+ if (/["']tool\.call["']/.test(html)) {
246
+ problems.push('sends type:"tool.call" — the host has NEVER accepted it (silent 15s timeout, no error). Send message:{action:"mcp-call",payload:{requestId,connectorId,tool,args}}.');
247
+ }
248
+ if (/correlationId/.test(html)) {
249
+ problems.push('keys responses on correlationId — the host echoes payload.requestId, so the pending request is never matched and every call times out even when the host answered.');
250
+ }
251
+ if (/postMessage/.test(html) && !/["']mcp-call["']/.test(html)) {
252
+ problems.push('never sends action:"mcp-call" — the host ignores the message entirely.');
253
+ }
254
+ return problems;
255
+ }
256
+
226
257
  async function verifyWidgetHealth(solution_id, sid) {
227
258
  // 1. Declared plugins — solution.ui_plugins[]
228
259
  let declared = [];
@@ -290,6 +321,36 @@ async function verifyWidgetHealth(solution_id, sid) {
290
321
  return { id: id || "(missing)", discovered: !!found, render_ok, problems };
291
322
  });
292
323
 
324
+ // 4. PROTOCOL CHECK on the served HTML. Steps 1-3 prove a widget is declared,
325
+ // discovered and renderable — none of which catches a widget that renders
326
+ // and then silently times out on every call.
327
+ // Read the SOURCE we serve rather than fetching the rendered iframe: the
328
+ // source is what a fix would edit, and it needs no browser or signed URL.
329
+ // Plugin ids are `mcp:<connector>:<name>`, so one source read per connector
330
+ // covers all of its widgets.
331
+ const connectorsSeen = new Map(); // connectorId -> [{path, content}]
332
+ for (const p of plugins) {
333
+ const connectorId = String(p.id || "").startsWith("mcp:") ? String(p.id).split(":")[1] : null;
334
+ if (!connectorId) continue;
335
+ if (!connectorsSeen.has(connectorId)) {
336
+ try {
337
+ const src = await get(`/deploy/solutions/${solution_id}/connectors/${connectorId}/source`, sid);
338
+ connectorsSeen.set(connectorId, Array.isArray(src?.files) ? src.files : []);
339
+ } catch {
340
+ connectorsSeen.set(connectorId, []); // unreadable source is not a verdict
341
+ }
342
+ }
343
+ const html = (connectorsSeen.get(connectorId) || [])
344
+ .filter((f) => /ui-dist\/.*\.html$/.test(f?.path || ""))
345
+ .map((f) => f?.content || "")
346
+ .join("\n");
347
+ const protoProblems = _widgetProtocolProblems(html);
348
+ if (protoProblems.length) {
349
+ p.problems.push(...protoProblems);
350
+ p.fix_with = `Fix the widget's postMessage code and redeploy: ateam_github_patch(solution_id, "connectors/${connectorId}/ui-dist/<widget>/index.html", ...) then ateam_upload_connector(solution_id, "${connectorId}", github:true). The exact working shape is in ateam_get_examples(type:"ui-plugin-iframe").`;
351
+ }
352
+ }
353
+
293
354
  const unhealthy = plugins.filter((p) => p.problems.length);
294
355
  return {
295
356
  ok: unhealthy.length === 0,
@@ -1031,6 +1092,57 @@ export const tools = [
1031
1092
  },
1032
1093
  },
1033
1094
 
1095
+ {
1096
+ name: "ateam_log_progress",
1097
+ core: true,
1098
+ description:
1099
+ "Record that a build STEP is done, so a later run does not redo it. Write it AS IT HAPPENS, never at the end — the runs that most need a journal are the ones the clock kills.\n\n" +
1100
+ "WHY: a continuation cannot otherwise tell what the previous run achieved, so it re-runs the whole orientation (bootstrap, get_workflows, list_solutions, get_solution, get_spec, get_examples, github_read) to re-derive from documents what was already established — and re-inflates its prompt into the region where provider latency collapses. Measured across 13 runs: stalls track PROMPT SIZE (~60k), not turn count. ateam_get_progress is ONE call instead of nine.\n\n" +
1101
+ "status — three values, and the third is the point:\n" +
1102
+ " built — the artefact exists (files written, committed)\n" +
1103
+ " deployed — the platform accepted it\n" +
1104
+ " verified — YOU CALLED IT AND GOT REAL DATA BACK\n\n" +
1105
+ "`verified` REQUIRES verified_by, and a deploy response is not verification. `connected` and `tools > 0` are tools/list facts: a clinic connector showed 9 tools while every storage call returned 401. A journal that stops at `deployed` records that build as finished.\n\n" +
1106
+ "Re-log the same step as it advances (built → deployed → verified) — latest wins, no update path. If a step REGRESSES, re-log it at the lower status: silence must not read as \"still fine\".",
1107
+ inputSchema: {
1108
+ type: "object",
1109
+ properties: {
1110
+ solution_id: { type: "string", description: "The solution ID" },
1111
+ step: {
1112
+ type: "string",
1113
+ description: "STABLE slug a later run can MATCH rather than enumerate: 'connector:<id>', 'skill:<id>', 'widget:<name>', 'seed:<what>'. Keep it identical across runs — a renamed step reads as a new one.",
1114
+ },
1115
+ status: {
1116
+ type: "string",
1117
+ enum: ["built", "deployed", "verified"],
1118
+ description: "built = artefact exists · deployed = platform accepted it · verified = you called it and got real data back",
1119
+ },
1120
+ detail: { type: "string", description: "One line of what exists — e.g. '10 tools, 8 seeded appointments'." },
1121
+ verified_by: {
1122
+ type: "string",
1123
+ description: "REQUIRED when status is 'verified': the literal call that proved it and what came back, e.g. 'ateam_test_connector(clinic-data-mcp, appointments.list_all) → 23 rows'. Storing the evidence beside the claim is what makes the journal auditable instead of self-reported.",
1124
+ },
1125
+ },
1126
+ required: ["solution_id", "step", "status"],
1127
+ },
1128
+ },
1129
+ {
1130
+ name: "ateam_get_progress",
1131
+ core: true,
1132
+ description:
1133
+ "What this build has already ACHIEVED — read it FIRST when continuing a run, before any orientation call. Returns the CURRENT status per step (latest entry wins) plus recent history.\n\n" +
1134
+ "Then build only the steps that are ABSENT or not yet `verified`.\n\n" +
1135
+ "ABSENT IS NOT \"NOTHING WAS DONE\" — the journal may predate a step, or a run may have died before writing. It is a fast path, not a new source of truth: check before rebuilding something expensive.\n\n" +
1136
+ "Complements ateam_get_lessons, which records what BROKE. This records what WORKS.",
1137
+ inputSchema: {
1138
+ type: "object",
1139
+ properties: {
1140
+ solution_id: { type: "string", description: "The solution ID" },
1141
+ limit: { type: "number", description: "History entries to return (default 60). `steps` is always complete." },
1142
+ },
1143
+ required: ["solution_id"],
1144
+ },
1145
+ },
1034
1146
  {
1035
1147
  name: "ateam_log_lesson",
1036
1148
  core: true,
@@ -2321,6 +2433,8 @@ const TENANT_TOOLS = new Set([
2321
2433
  // tenant's lessons. Tenant isolation is the one boundary this platform
2322
2434
  // treats as absolute.
2323
2435
  "ateam_log_lesson",
2436
+ "ateam_log_progress",
2437
+ "ateam_get_progress",
2324
2438
  "ateam_get_lessons",
2325
2439
 
2326
2440
  // Write operations
@@ -5376,6 +5490,23 @@ const handlers = {
5376
5490
  );
5377
5491
  },
5378
5492
 
5493
+ ateam_log_progress: async ({ solution_id, step, status, detail, verified_by }, sid) => {
5494
+ if (!solution_id) throw new Error("solution_id required");
5495
+ if (!step) throw new Error("step required — a STABLE slug a later run can match on, e.g. \"connector:clinic-data-mcp\"");
5496
+ if (!status) throw new Error("status required — built | deployed | verified");
5497
+ return await post(
5498
+ `/deploy/solutions/${solution_id}/progress`,
5499
+ { step, status, detail, verified_by },
5500
+ sid,
5501
+ );
5502
+ },
5503
+
5504
+ ateam_get_progress: async ({ solution_id, limit }, sid) => {
5505
+ if (!solution_id) throw new Error("solution_id required");
5506
+ const qs = Number.isFinite(limit) ? `?limit=${limit}` : "";
5507
+ return await get(`/deploy/solutions/${solution_id}/progress${qs}`, sid);
5508
+ },
5509
+
5379
5510
  ateam_get_lessons: async ({ solution_id, limit }, sid) => {
5380
5511
  if (!solution_id) throw new Error("solution_id required");
5381
5512
  const qs = Number.isFinite(limit) ? `?limit=${limit}` : "";