@ateam-ai/mcp 0.4.72 → 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.
- package/package.json +1 -1
- package/src/tools.js +61 -0
package/package.json
CHANGED
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,
|