@cloudgrid-io/mcp 0.20.34 → 0.20.36
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/deploy.js +80 -4
- package/src/tools/register.js +22 -52
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudgrid-io/mcp",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.36",
|
|
4
4
|
"description": "MCP access to CloudGrid — the live runtime environment where the user's apps run WITH the infrastructure they need out of the box: managed MongoDB, Redis (cache/queues/pub-sub), a pgvector store, persistent disk, and built-in AI with no API keys, in any language or stack, no Docker or Kubernetes to manage. Brainstorm, build, plug, and improve apps, games, dashboards, and tools; instant single-page publishes and full runtime-app deploys with a live URL — for developers and non-technical users. Three editions: local stdio (full toolset), hosted web (light, CLI-free), and a public docs-search server.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/tools/deploy.js
CHANGED
|
@@ -1438,7 +1438,8 @@ export async function runPlug(ctx, input, deps = {}) {
|
|
|
1438
1438
|
} else if (verdict.status === "failed") {
|
|
1439
1439
|
const msg = verdict.error || "The build failed.";
|
|
1440
1440
|
throw new Error(
|
|
1441
|
-
`Deploy failed (trace ${data.trace_id ?? "n/a"}): ${msg} The URL is NOT live — do not give it to the user as working
|
|
1441
|
+
`Deploy failed (trace ${data.trace_id ?? "n/a"}): ${msg} The URL is NOT live — do not give it to the user as working.` +
|
|
1442
|
+
formatFailureDetail(verdict),
|
|
1442
1443
|
);
|
|
1443
1444
|
}
|
|
1444
1445
|
// Anything else (still building / poll unreachable): fall through to the
|
|
@@ -1452,6 +1453,7 @@ export async function runPlug(ctx, input, deps = {}) {
|
|
|
1452
1453
|
...(url ? { url } : {}),
|
|
1453
1454
|
...(data.poll_url ? { poll_url: data.poll_url } : {}),
|
|
1454
1455
|
status: data.status ?? (isEdit ? "updated" : "created"),
|
|
1456
|
+
source: hasPath ? "path" : hasHtml && !hasArtifacts ? "html" : "artifact_files",
|
|
1455
1457
|
...(data.claim_url ? { claim_url: data.claim_url } : {}),
|
|
1456
1458
|
...(data.claim_message ? { claim_message: data.claim_message } : {}),
|
|
1457
1459
|
// Spec v2 omits owner_token from the output block — a spec bug (the anon
|
|
@@ -1480,6 +1482,39 @@ export async function runPlug(ctx, input, deps = {}) {
|
|
|
1480
1482
|
detectedKind !== "agent" &&
|
|
1481
1483
|
(inlineHtmlBytes != null || detectedKind === "inspiration");
|
|
1482
1484
|
|
|
1485
|
+
// ── Source-transport disclosure (always) + CLI steer for multi-file apps ─────
|
|
1486
|
+
// Two transports with very different reliability: INLINE (`html`/`artifact_files`
|
|
1487
|
+
// — the source is copied THROUGH the tool call, so a large file like a lockfile
|
|
1488
|
+
// or a binary can silently truncate and fail the build) vs DISK (`path`, or the
|
|
1489
|
+
// CLI's folder plug — read straight from disk, no transcription risk). State
|
|
1490
|
+
// which one ran so a watching user can see it, and for a multi-file inline
|
|
1491
|
+
// deploy steer to the CLI, which reads from disk and cannot truncate.
|
|
1492
|
+
const artifactCount = Array.isArray(artifacts) ? artifacts.length : 0;
|
|
1493
|
+
let sourceLine;
|
|
1494
|
+
let cliSteer = null;
|
|
1495
|
+
if (hasPath) {
|
|
1496
|
+
sourceLine = `Source: local folder \`${effectivePath}\`, read from disk (no truncation risk).`;
|
|
1497
|
+
} else if (hasHtml && !hasArtifacts) {
|
|
1498
|
+
sourceLine = "Source: inline `html` — a single self-contained document sent through the tool call.";
|
|
1499
|
+
} else {
|
|
1500
|
+
const kb = Math.round(artifacts.reduce((n, a) => n + (a.buffer?.byteLength ?? 0), 0) / 1024);
|
|
1501
|
+
sourceLine = `Source: inline \`artifact_files\` — ${artifactCount} file${artifactCount === 1 ? "" : "s"}, ~${kb} KB copied through the tool call.`;
|
|
1502
|
+
// A multi-file app (a real framework, a lockfile, or binary assets) is exactly
|
|
1503
|
+
// where inline copying truncates. Steer to the CLI's disk-based plug.
|
|
1504
|
+
const looksRuntime =
|
|
1505
|
+
artifactCount > 1 ||
|
|
1506
|
+
artifacts.some((a) => /(^|\/)(cloudgrid\.ya?ml|package-lock\.json|pnpm-lock\.yaml|yarn\.lock)$/i.test(a.path || ""));
|
|
1507
|
+
if (looksRuntime) {
|
|
1508
|
+
const gridSlug = data.grid ?? grid ?? null;
|
|
1509
|
+
const eid = data.entity_id ?? targetEntityId ?? null;
|
|
1510
|
+
cliSteer =
|
|
1511
|
+
"For a multi-file app the local CLI is more reliable — it reads from disk, so lockfiles and binaries can't truncate the way inline copying can. " +
|
|
1512
|
+
(eid
|
|
1513
|
+
? `If you have the folder: \`cd <app> && npx -y @cloudgrid-io/cli plug\`. If not, pick it up first: \`npx -y @cloudgrid-io/cli pickup ${gridSlug ? `${gridSlug}/` : ""}<slug> --force\` then \`cd <slug> && npx -y @cloudgrid-io/cli plug\`. Or re-plug directly: \`npx -y @cloudgrid-io/cli plug --existing ${eid}${gridSlug ? ` --grid ${gridSlug}` : ""} --verbose\`.`
|
|
1514
|
+
: "Prefer `npx -y @cloudgrid-io/cli plug` from the app folder over inlining files.");
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1483
1518
|
const lines = [];
|
|
1484
1519
|
if (isBuilding) {
|
|
1485
1520
|
lines.push(
|
|
@@ -1507,6 +1542,10 @@ export async function runPlug(ctx, input, deps = {}) {
|
|
|
1507
1542
|
} else {
|
|
1508
1543
|
lines.push(`Live: ${url}`);
|
|
1509
1544
|
}
|
|
1545
|
+
// Suggestion 3: always disclose the transport; Suggestion 1: steer multi-file
|
|
1546
|
+
// inline deploys to the CLI's disk-based plug.
|
|
1547
|
+
lines.push(sourceLine);
|
|
1548
|
+
if (cliSteer) lines.push(cliSteer);
|
|
1510
1549
|
if (data.entity_id) {
|
|
1511
1550
|
lines.push(
|
|
1512
1551
|
`Re-plug handle: entity_id=${data.entity_id} — persist it (with the url) and pass it back as target_entity_id to update this entity later.`,
|
|
@@ -1854,12 +1893,41 @@ async function fetchDeployTrace(ctx, { pollUrl, grid }) {
|
|
|
1854
1893
|
if (!res.ok) {
|
|
1855
1894
|
throw new Error(`Deploy status check failed (HTTP ${res.status}): ${data?.error?.message || data?.error || raw || res.status}`);
|
|
1856
1895
|
}
|
|
1896
|
+
// Surface the real build failure, not just the generic floor message. A failed
|
|
1897
|
+
// deploy trace carries a structured DeployEventError: message_user (the floor),
|
|
1898
|
+
// build_log_excerpt.text (a sanitized ~50-line Cloud Build log tail — what the
|
|
1899
|
+
// CLI's --verbose prints), a Cloud Build console_url, and suggested_fixes. Pull
|
|
1900
|
+
// the log tail + first fix so grid_check_deploy stops dead-ending on "check the
|
|
1901
|
+
// build logs" with no logs.
|
|
1902
|
+
const e = data?.error && typeof data.error === "object" ? data.error : null;
|
|
1903
|
+
const excerpt = e?.build_log_excerpt && typeof e.build_log_excerpt === "object" ? e.build_log_excerpt : null;
|
|
1904
|
+
const logTail = (typeof excerpt?.text === "string" && excerpt.text.trim()) || (typeof excerpt?.summary === "string" && excerpt.summary.trim()) || null;
|
|
1905
|
+
const fix = Array.isArray(e?.suggested_fixes) && typeof e.suggested_fixes[0]?.summary === "string"
|
|
1906
|
+
? e.suggested_fixes[0].summary
|
|
1907
|
+
: (typeof e?.ai_explanation?.message_user === "string" ? e.ai_explanation.message_user : null);
|
|
1857
1908
|
return {
|
|
1858
1909
|
status: data?.status ?? "unknown",
|
|
1859
|
-
error:
|
|
1910
|
+
error: e?.message_user || e?.message || null,
|
|
1911
|
+
logTail: logTail || null,
|
|
1912
|
+
consoleUrl: (typeof excerpt?.console_url === "string" && excerpt.console_url) || null,
|
|
1913
|
+
fix: fix || null,
|
|
1860
1914
|
};
|
|
1861
1915
|
}
|
|
1862
1916
|
|
|
1917
|
+
// Cap the log tail we echo into a tool result — enough to see the real error
|
|
1918
|
+
// (a few dozen lines) without flooding the model's context.
|
|
1919
|
+
const BUILD_LOG_TAIL_MAX = 1600;
|
|
1920
|
+
function formatFailureDetail({ logTail, consoleUrl, fix } = {}) {
|
|
1921
|
+
const parts = [];
|
|
1922
|
+
if (logTail) {
|
|
1923
|
+
const t = logTail.length > BUILD_LOG_TAIL_MAX ? "…\n" + logTail.slice(-BUILD_LOG_TAIL_MAX) : logTail;
|
|
1924
|
+
parts.push(`Build log (tail):\n${t}`);
|
|
1925
|
+
}
|
|
1926
|
+
if (fix) parts.push(`Suggested fix: ${fix}`);
|
|
1927
|
+
if (consoleUrl) parts.push(`Full log: ${consoleUrl}`);
|
|
1928
|
+
return parts.length ? "\n" + parts.join("\n") : "";
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1863
1931
|
// Poll until terminal or the budget runs out. Never throws — an unreachable
|
|
1864
1932
|
// poll endpoint degrades to { status: "unknown" } and the caller keeps the
|
|
1865
1933
|
// do-not-claim-live wording (the safe direction).
|
|
@@ -1911,8 +1979,16 @@ export async function runCheckDeploy(ctx, { poll_url, grid } = {}) {
|
|
|
1911
1979
|
}
|
|
1912
1980
|
if (verdict.status === "failed") {
|
|
1913
1981
|
return {
|
|
1914
|
-
text: `The build FAILED: ${verdict.error || "no reason reported"}. The URL is not live — do not give it to the user as working. Fix the app (or re-deploy) and try again
|
|
1915
|
-
|
|
1982
|
+
text: `The build FAILED: ${verdict.error || "no reason reported"}. The URL is not live — do not give it to the user as working. Fix the app (or re-deploy) and try again.` +
|
|
1983
|
+
formatFailureDetail(verdict),
|
|
1984
|
+
structured: {
|
|
1985
|
+
status: "failed",
|
|
1986
|
+
live: false,
|
|
1987
|
+
...(verdict.error ? { error: verdict.error } : {}),
|
|
1988
|
+
...(verdict.logTail ? { build_log_tail: verdict.logTail } : {}),
|
|
1989
|
+
...(verdict.fix ? { suggested_fix: verdict.fix } : {}),
|
|
1990
|
+
...(verdict.consoleUrl ? { build_log_url: verdict.consoleUrl } : {}),
|
|
1991
|
+
},
|
|
1916
1992
|
};
|
|
1917
1993
|
}
|
|
1918
1994
|
return {
|
package/src/tools/register.js
CHANGED
|
@@ -77,35 +77,20 @@ export function registerTools(server, ctx) {
|
|
|
77
77
|
}
|
|
78
78
|
};
|
|
79
79
|
|
|
80
|
-
// Every
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
|
|
80
|
+
// Every tool registers under its canonical grid_* name ONLY. Deprecated
|
|
81
|
+
// aliases were removed entirely (founder directive, 2026-07-22): each alias
|
|
82
|
+
// doubled the ListTools surface for pure clutter, clients enumerate tools
|
|
83
|
+
// dynamically, and the renames' old names (grid_deploy, grid_set_sharing,
|
|
84
|
+
// grid_copy_app, grid_claim_anonymous_deploy, grid_download_source,
|
|
85
|
+
// grid_fetch, grid_logs) are gone rather than advertised as redirects.
|
|
84
86
|
const reg = (name, config, handler) => {
|
|
85
|
-
registered[name] = { kind: "reg", config, handler };
|
|
86
87
|
server.registerTool(name, config, withCapture(name, handler));
|
|
87
88
|
};
|
|
88
89
|
|
|
89
90
|
const regTool = (name, description, schema, annotations, handler) => {
|
|
90
|
-
registered[name] = { kind: "tool", description, schema, annotations, handler };
|
|
91
91
|
server.tool(name, description, schema, annotations, withCapture(name, handler));
|
|
92
92
|
};
|
|
93
93
|
|
|
94
|
-
// Register OLD tool names as deprecated aliases of their new (clearer) names.
|
|
95
|
-
// Same handler/schema; description is redirect-only so it never wins selection.
|
|
96
|
-
// Skips silently if the primary isn't registered in this edition (e.g. a
|
|
97
|
-
// CLI-only tool on the web edition).
|
|
98
|
-
const registerAlias = (oldName, newName) => {
|
|
99
|
-
const r = registered[newName];
|
|
100
|
-
if (!r) return;
|
|
101
|
-
const redirect = `Deprecated alias of ${newName}. Always call ${newName} instead; this name is kept for backward compatibility.`;
|
|
102
|
-
if (r.kind === "reg") {
|
|
103
|
-
server.registerTool(oldName, { ...r.config, description: redirect }, withCapture(oldName, r.handler));
|
|
104
|
-
} else {
|
|
105
|
-
server.tool(oldName, redirect, r.schema, r.annotations, withCapture(oldName, r.handler));
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
|
|
109
94
|
// ── Widget resources (web edition, ChatGPT Apps SDK) ──────────────────────
|
|
110
95
|
if (ctx.edition === "web") {
|
|
111
96
|
server.registerResource("cloudgrid-live-result", LIVE_RESULT_URI, {
|
|
@@ -289,7 +274,15 @@ export function registerTools(server, ctx) {
|
|
|
289
274
|
? " or `artifact_files` — a multi-file app inline. "
|
|
290
275
|
: ", `path` — a local folder/file, or `artifact_files` — inline files. ") +
|
|
291
276
|
"If you need to edit a page but don't have its HTML, call grid_get_app_source first, then deploy with " +
|
|
292
|
-
"target_entity_id.
|
|
277
|
+
"target_entity_id. " +
|
|
278
|
+
// Suggestion 1: steer multi-file runtime apps to the CLI up front — inline
|
|
279
|
+
// copying truncates large lockfiles/binaries and silently fails the build.
|
|
280
|
+
"RELIABILITY: `html` and `artifact_files` are sent INLINE (copied through this call), which can " +
|
|
281
|
+
"truncate large files (lockfiles, binaries) on a multi-file app. For a real framework (Next.js, etc.), " +
|
|
282
|
+
"a lockfile, or binary assets — and for re-plugging an existing runtime app — prefer the local CLI, " +
|
|
283
|
+
"which reads from disk: `npx -y @cloudgrid-io/cli plug` in the app folder (pick it up first with " +
|
|
284
|
+
"`npx -y @cloudgrid-io/cli pickup <grid>/<slug> --force` if you don't have it locally). Reserve inline " +
|
|
285
|
+
"for a single page or a few small text files. (CloudGrid calls this operation 'plug'.)",
|
|
293
286
|
inputSchema: plugInputSchema,
|
|
294
287
|
outputSchema: {
|
|
295
288
|
entity_id: z.string().optional().describe("Globally unique — pass back as target_entity_id to re-plug."),
|
|
@@ -298,6 +291,7 @@ export function registerTools(server, ctx) {
|
|
|
298
291
|
url: z.string().optional().describe("Canonical serving URL (stable across re-plugs; server-composed, flat-arch-aware)."),
|
|
299
292
|
poll_url: z.string().optional().describe("Deploy status path while building (runtimes only)."),
|
|
300
293
|
status: z.string().optional().describe("live | building | created | updated …"),
|
|
294
|
+
source: z.string().optional().describe("Transport used for this deploy: `path` (disk), `html`, or `artifact_files` (inline). Inline copies risk truncation on large files."),
|
|
301
295
|
claim_url: z.string().optional().describe("Anon create only: sign-in link to claim ownership."),
|
|
302
296
|
claim_message: z.string().optional().describe("Anon create only: the claim nudge to relay."),
|
|
303
297
|
owner_token: z.string().optional().describe("Anonymous drops: the bearer owner token (re-plug + claim). Re-minted on every anonymous edit — persist the newest."),
|
|
@@ -430,11 +424,9 @@ export function registerTools(server, ctx) {
|
|
|
430
424
|
}
|
|
431
425
|
};
|
|
432
426
|
reg("grid_plug", plugConfig, plugHandler);
|
|
433
|
-
// `grid_deploy`
|
|
434
|
-
//
|
|
435
|
-
//
|
|
436
|
-
// grid_plug's own description, so deploy-intent routing lands on grid_plug.
|
|
437
|
-
registerAlias("grid_deploy", "grid_plug");
|
|
427
|
+
// NOTE: no `grid_deploy` alias — the tool is grid_plug only. "deploy"/
|
|
428
|
+
// "publish"/"ship"/"make live" live in grid_plug's description, so
|
|
429
|
+
// deploy-intent routing lands there without a second listed name.
|
|
438
430
|
|
|
439
431
|
// ── grid_fork / grid_remix / grid_download — direct-API copy/source verbs ──────
|
|
440
432
|
// fork = same-grid byte-carrying copy; remix = cross-grid copy with secrets
|
|
@@ -718,6 +710,9 @@ export function registerTools(server, ctx) {
|
|
|
718
710
|
live: z.boolean().describe("True only when the build finished successfully and the URL serves."),
|
|
719
711
|
url: z.string().optional().describe("The live URL, when known."),
|
|
720
712
|
error: z.string().optional().describe("User-language failure reason, when the build failed."),
|
|
713
|
+
build_log_tail: z.string().optional().describe("Sanitized tail of the Cloud Build log (the real error), when the build failed."),
|
|
714
|
+
suggested_fix: z.string().optional().describe("A suggested fix for the failure, when the platform provides one."),
|
|
715
|
+
build_log_url: z.string().optional().describe("Cloud Build console URL for the full log, when available."),
|
|
721
716
|
},
|
|
722
717
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
|
|
723
718
|
},
|
|
@@ -972,26 +967,6 @@ export function registerTools(server, ctx) {
|
|
|
972
967
|
},
|
|
973
968
|
);
|
|
974
969
|
|
|
975
|
-
// ── Kept alias (both editions): grid_fetch has real muscle memory in saved
|
|
976
|
-
// prompts and older docs. Every other legacy alias was dropped in 0.20.8 -
|
|
977
|
-
// 16 alias schemas were pure ListTools context weight on every session.
|
|
978
|
-
registerAlias("grid_fetch", "grid_get_template");
|
|
979
|
-
// grid_set_sharing → grid_visibility (2026-07-20, founder rename). Same handler
|
|
980
|
-
// and schema; old name kept as a redirect-only deprecated alias so any client
|
|
981
|
-
// still calling it works while the model prefers the new. MUST live here, above
|
|
982
|
-
// the local cutoff: grid_visibility is a both-editions tool, so the alias has
|
|
983
|
-
// to register on the web edition too (hosted callers keep back-compat).
|
|
984
|
-
registerAlias("grid_set_sharing", "grid_visibility");
|
|
985
|
-
// Copy/adopt verbs renamed to match the CLI (fork / remix / pickup / download).
|
|
986
|
-
// Old names kept as redirect-only deprecated aliases; all both-editions tools,
|
|
987
|
-
// so they must register above the local cutoff for hosted back-compat.
|
|
988
|
-
// grid_claim_anonymous_deploy → grid_pickup drops the "claim" naming; claiming
|
|
989
|
-
// an anonymous drop is now grid_pickup with a claim_token (mirrors the CLI's
|
|
990
|
-
// `grid pickup --claim-token`).
|
|
991
|
-
registerAlias("grid_copy_app", "grid_fork");
|
|
992
|
-
registerAlias("grid_claim_anonymous_deploy", "grid_pickup");
|
|
993
|
-
registerAlias("grid_download_source", "grid_download");
|
|
994
|
-
|
|
995
970
|
if (ctx.edition !== "local") return; // web edition stops here — no CLI tools
|
|
996
971
|
|
|
997
972
|
// ── CLI-wrapping tools (local edition only) ───────────────────────────────
|
|
@@ -1310,9 +1285,4 @@ export function registerTools(server, ctx) {
|
|
|
1310
1285
|
}),
|
|
1311
1286
|
);
|
|
1312
1287
|
|
|
1313
|
-
// ── Deprecated aliases (tool-name cleanup): OLD CLI-tool names kept callable
|
|
1314
|
-
// (local edition) as redirect-only aliases of their new, clearer names.
|
|
1315
|
-
// Kept alias (local): grid_logs - muscle memory. The other legacy CLI-tool
|
|
1316
|
-
// aliases were dropped in 0.20.8 (see CHANGELOG).
|
|
1317
|
-
registerAlias("grid_logs", "grid_view_logs");
|
|
1318
1288
|
}
|