@cloudgrid-io/mcp 0.20.35 → 0.20.37
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 +89 -4
- package/src/tools/register.js +17 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudgrid-io/mcp",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.37",
|
|
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,48 @@ 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
|
+
// The disk CLI is the reliable route for a multi-file app — BUT it only
|
|
1511
|
+
// works where the CLI is already SIGNED IN: a terminal or Claude Code
|
|
1512
|
+
// where `grid login` was run once. Do NOT tell the agent to `grid login`
|
|
1513
|
+
// inside an ephemeral chat sandbox — that login is a long-lived poll (up
|
|
1514
|
+
// to 5 min) and the sandbox is time-bounded + non-persistent, so it won't
|
|
1515
|
+
// complete or won't survive to the next command (observed live). If this
|
|
1516
|
+
// was inline-only, the fix is to move to a signed-in shell OR ask the
|
|
1517
|
+
// user — never silently drop large files.
|
|
1518
|
+
const replug = eid
|
|
1519
|
+
? `npx -y @cloudgrid-io/cli plug --existing ${eid}${gridSlug ? ` --grid ${gridSlug}` : ""} --verbose`
|
|
1520
|
+
: "npx -y @cloudgrid-io/cli plug";
|
|
1521
|
+
cliSteer =
|
|
1522
|
+
"Heads-up: this went in INLINE. For a multi-file app the disk-based CLI is more reliable (lockfiles/binaries can't truncate), but it must run where the CLI is already signed in — a terminal or Claude Code that has done `grid login` once. Do NOT try `grid login` inside a chat sandbox: its login poll is long-lived and the sandbox is ephemeral, so it won't stick. " +
|
|
1523
|
+
`If you had to leave any files out to make this inline deploy safe, tell the user — offer the complete deploy from Claude Code or a terminal (\`${replug}\`), or confirm they accept the reduced version. Do not drop files silently.`;
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1483
1527
|
const lines = [];
|
|
1484
1528
|
if (isBuilding) {
|
|
1485
1529
|
lines.push(
|
|
@@ -1507,6 +1551,10 @@ export async function runPlug(ctx, input, deps = {}) {
|
|
|
1507
1551
|
} else {
|
|
1508
1552
|
lines.push(`Live: ${url}`);
|
|
1509
1553
|
}
|
|
1554
|
+
// Suggestion 3: always disclose the transport; Suggestion 1: steer multi-file
|
|
1555
|
+
// inline deploys to the CLI's disk-based plug.
|
|
1556
|
+
lines.push(sourceLine);
|
|
1557
|
+
if (cliSteer) lines.push(cliSteer);
|
|
1510
1558
|
if (data.entity_id) {
|
|
1511
1559
|
lines.push(
|
|
1512
1560
|
`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 +1902,41 @@ async function fetchDeployTrace(ctx, { pollUrl, grid }) {
|
|
|
1854
1902
|
if (!res.ok) {
|
|
1855
1903
|
throw new Error(`Deploy status check failed (HTTP ${res.status}): ${data?.error?.message || data?.error || raw || res.status}`);
|
|
1856
1904
|
}
|
|
1905
|
+
// Surface the real build failure, not just the generic floor message. A failed
|
|
1906
|
+
// deploy trace carries a structured DeployEventError: message_user (the floor),
|
|
1907
|
+
// build_log_excerpt.text (a sanitized ~50-line Cloud Build log tail — what the
|
|
1908
|
+
// CLI's --verbose prints), a Cloud Build console_url, and suggested_fixes. Pull
|
|
1909
|
+
// the log tail + first fix so grid_check_deploy stops dead-ending on "check the
|
|
1910
|
+
// build logs" with no logs.
|
|
1911
|
+
const e = data?.error && typeof data.error === "object" ? data.error : null;
|
|
1912
|
+
const excerpt = e?.build_log_excerpt && typeof e.build_log_excerpt === "object" ? e.build_log_excerpt : null;
|
|
1913
|
+
const logTail = (typeof excerpt?.text === "string" && excerpt.text.trim()) || (typeof excerpt?.summary === "string" && excerpt.summary.trim()) || null;
|
|
1914
|
+
const fix = Array.isArray(e?.suggested_fixes) && typeof e.suggested_fixes[0]?.summary === "string"
|
|
1915
|
+
? e.suggested_fixes[0].summary
|
|
1916
|
+
: (typeof e?.ai_explanation?.message_user === "string" ? e.ai_explanation.message_user : null);
|
|
1857
1917
|
return {
|
|
1858
1918
|
status: data?.status ?? "unknown",
|
|
1859
|
-
error:
|
|
1919
|
+
error: e?.message_user || e?.message || null,
|
|
1920
|
+
logTail: logTail || null,
|
|
1921
|
+
consoleUrl: (typeof excerpt?.console_url === "string" && excerpt.console_url) || null,
|
|
1922
|
+
fix: fix || null,
|
|
1860
1923
|
};
|
|
1861
1924
|
}
|
|
1862
1925
|
|
|
1926
|
+
// Cap the log tail we echo into a tool result — enough to see the real error
|
|
1927
|
+
// (a few dozen lines) without flooding the model's context.
|
|
1928
|
+
const BUILD_LOG_TAIL_MAX = 1600;
|
|
1929
|
+
function formatFailureDetail({ logTail, consoleUrl, fix } = {}) {
|
|
1930
|
+
const parts = [];
|
|
1931
|
+
if (logTail) {
|
|
1932
|
+
const t = logTail.length > BUILD_LOG_TAIL_MAX ? "…\n" + logTail.slice(-BUILD_LOG_TAIL_MAX) : logTail;
|
|
1933
|
+
parts.push(`Build log (tail):\n${t}`);
|
|
1934
|
+
}
|
|
1935
|
+
if (fix) parts.push(`Suggested fix: ${fix}`);
|
|
1936
|
+
if (consoleUrl) parts.push(`Full log: ${consoleUrl}`);
|
|
1937
|
+
return parts.length ? "\n" + parts.join("\n") : "";
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1863
1940
|
// Poll until terminal or the budget runs out. Never throws — an unreachable
|
|
1864
1941
|
// poll endpoint degrades to { status: "unknown" } and the caller keeps the
|
|
1865
1942
|
// do-not-claim-live wording (the safe direction).
|
|
@@ -1911,8 +1988,16 @@ export async function runCheckDeploy(ctx, { poll_url, grid } = {}) {
|
|
|
1911
1988
|
}
|
|
1912
1989
|
if (verdict.status === "failed") {
|
|
1913
1990
|
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
|
-
|
|
1991
|
+
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.` +
|
|
1992
|
+
formatFailureDetail(verdict),
|
|
1993
|
+
structured: {
|
|
1994
|
+
status: "failed",
|
|
1995
|
+
live: false,
|
|
1996
|
+
...(verdict.error ? { error: verdict.error } : {}),
|
|
1997
|
+
...(verdict.logTail ? { build_log_tail: verdict.logTail } : {}),
|
|
1998
|
+
...(verdict.fix ? { suggested_fix: verdict.fix } : {}),
|
|
1999
|
+
...(verdict.consoleUrl ? { build_log_url: verdict.consoleUrl } : {}),
|
|
2000
|
+
},
|
|
1916
2001
|
};
|
|
1917
2002
|
}
|
|
1918
2003
|
return {
|
package/src/tools/register.js
CHANGED
|
@@ -274,7 +274,19 @@ export function registerTools(server, ctx) {
|
|
|
274
274
|
? " or `artifact_files` — a multi-file app inline. "
|
|
275
275
|
: ", `path` — a local folder/file, or `artifact_files` — inline files. ") +
|
|
276
276
|
"If you need to edit a page but don't have its HTML, call grid_get_app_source first, then deploy with " +
|
|
277
|
-
"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
|
+
// but only where the CLI is actually signed in (see the login caveat).
|
|
281
|
+
"RELIABILITY: `html` and `artifact_files` are sent INLINE (copied through this call), which can " +
|
|
282
|
+
"truncate large files (lockfiles, binaries) on a multi-file app. For a real framework (Next.js, etc.), " +
|
|
283
|
+
"a lockfile, or binary assets — and for re-plugging an existing runtime app — the disk-based CLI is more " +
|
|
284
|
+
"reliable: `npx -y @cloudgrid-io/cli plug` in the app folder. BUT it only works where the CLI is already " +
|
|
285
|
+
"signed in — a terminal or Claude Code that ran `grid login` once. Do NOT attempt `grid login` inside a " +
|
|
286
|
+
"chat sandbox: its login is a long poll and the sandbox is ephemeral, so it will not complete. If you can " +
|
|
287
|
+
"only deploy inline here and would have to omit large files, do NOT drop them silently — tell the user " +
|
|
288
|
+
"and offer to run the full deploy from Claude Code or a terminal (same entity_id). Reserve inline for a " +
|
|
289
|
+
"single page or a few small text files. (CloudGrid calls this operation 'plug'.)",
|
|
278
290
|
inputSchema: plugInputSchema,
|
|
279
291
|
outputSchema: {
|
|
280
292
|
entity_id: z.string().optional().describe("Globally unique — pass back as target_entity_id to re-plug."),
|
|
@@ -283,6 +295,7 @@ export function registerTools(server, ctx) {
|
|
|
283
295
|
url: z.string().optional().describe("Canonical serving URL (stable across re-plugs; server-composed, flat-arch-aware)."),
|
|
284
296
|
poll_url: z.string().optional().describe("Deploy status path while building (runtimes only)."),
|
|
285
297
|
status: z.string().optional().describe("live | building | created | updated …"),
|
|
298
|
+
source: z.string().optional().describe("Transport used for this deploy: `path` (disk), `html`, or `artifact_files` (inline). Inline copies risk truncation on large files."),
|
|
286
299
|
claim_url: z.string().optional().describe("Anon create only: sign-in link to claim ownership."),
|
|
287
300
|
claim_message: z.string().optional().describe("Anon create only: the claim nudge to relay."),
|
|
288
301
|
owner_token: z.string().optional().describe("Anonymous drops: the bearer owner token (re-plug + claim). Re-minted on every anonymous edit — persist the newest."),
|
|
@@ -701,6 +714,9 @@ export function registerTools(server, ctx) {
|
|
|
701
714
|
live: z.boolean().describe("True only when the build finished successfully and the URL serves."),
|
|
702
715
|
url: z.string().optional().describe("The live URL, when known."),
|
|
703
716
|
error: z.string().optional().describe("User-language failure reason, when the build failed."),
|
|
717
|
+
build_log_tail: z.string().optional().describe("Sanitized tail of the Cloud Build log (the real error), when the build failed."),
|
|
718
|
+
suggested_fix: z.string().optional().describe("A suggested fix for the failure, when the platform provides one."),
|
|
719
|
+
build_log_url: z.string().optional().describe("Cloud Build console URL for the full log, when available."),
|
|
704
720
|
},
|
|
705
721
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
|
|
706
722
|
},
|