@cloudgrid-io/mcp 0.20.18 → 0.20.20
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 +2 -1
- package/src/corpus/playbook.md +1 -1
- package/src/session-logger.js +12 -1
- package/src/tools/register.js +20 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudgrid-io/mcp",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.20",
|
|
4
4
|
"description": "Use CloudGrid via MCP to brainstorm, build, deploy, and improve apps, games, websites, dashboards, and tools and get a live URL - templates, recipes, managed databases and AI, instant single-page publish, and full-app deploys, for developers and non-technical users. Three editions: local stdio (full toolset), hosted web (light, CLI-free), and a public docs-search server, over MCP.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"test:grid-picker": "node test/grid-picker.test.mjs",
|
|
30
30
|
"test:plug-output-schema": "node test/plug-output-schema.test.mjs",
|
|
31
31
|
"test:zip-deploy": "node test/zip-deploy.test.mjs",
|
|
32
|
+
"test:create-gates": "node test/create-gates.test.mjs",
|
|
32
33
|
"test:mcpb-boot": "node test/mcpb-boot.test.mjs",
|
|
33
34
|
"test:plug-timeout": "node test/plug-timeout.test.mjs",
|
|
34
35
|
"test:manifest-confirm": "node test/manifest-confirm.test.mjs",
|
package/src/corpus/playbook.md
CHANGED
|
@@ -7,7 +7,7 @@ Operating rules:
|
|
|
7
7
|
4. Always return the live share URL at the end — that is the deliverable. On a NEW deploy, then ASK the user who should be able to open it — private (only you), a space, your grid, anyone with the link, or anyone signed in — and set their choice with grid_set_sharing. Never pick the visibility silently. On a re-plug/edit of an existing entity, leave its current visibility as-is unless the user asks to change it.
|
|
8
8
|
5. Brainstorm first (lightly) for a real app, then minimize questions. For a substantial runtime app, take ONE lightweight beat before generating/deploying: confirm the goal + 3-5 core features in a sentence, check for a matching template/recipe (capability-map), and infer the data/runtime needs and STATE them ("I'll add a database so entries are saved"). Keep it to a line or two; never ask a non-technical user infra questions they can't answer. A simple single page skips this and builds immediately. Otherwise use sensible defaults and build; don't front-load setup questions.
|
|
9
9
|
6. If a signed-in publish fails with a server error, do not fall back to anonymous publishing (it burns the anonymous quota and downgrades ownership); surface the error, use the CLI fallback if offered, or ask the user.
|
|
10
|
-
7. When signed in and the user has more than one grid, do not assume a target — the
|
|
10
|
+
7. When signed in and the user has more than one grid, do not assume a target. For a NEW app, establish the grid UP FRONT — grid_start returns the grids; if there is more than one, ask "which grid should this go on?" before you start building, and carry that slug into grid_deploy. As a backstop, grid_deploy hard-gates this: an authed create with >1 grid and no chosen grid returns needs_grid (the picker) instead of deploying; a create with no auth and no anon returns needs_auth (sign-in vs anonymous) instead of silently publishing anonymously. Relay either ask to the user; never pick silently.
|
|
11
11
|
8. When a build/deploy fails unexpectedly, offer to report it to the CloudGrid team — only with the user's explicit consent (ask first). Send just the error + the failed request by default (call grid_report), and never send the whole conversation unless the user agrees (include_conversation). Respect privacy.
|
|
12
12
|
9. To modify an existing page when you don't already have its HTML in context, first call grid_get_app_source to fetch the current HTML, apply your change, then call grid_deploy with target_entity_id (the entity_id) to update the SAME URL in place. Do not ask the user to paste the HTML back.
|
|
13
13
|
10. Publishing a single HTML page: pass it inline as grid_deploy's html parameter (a full self-contained document). For a heavy or image-heavy page in the local edition, pass the path parameter instead so it is read from disk (no inline size limit); never base64-encode HTML and never pass a file path (or an @-prefixed path) as html. If a page looks empty, use grid_get_app_source to check what was actually published, then re-plug with the real HTML/path and target_entity_id.
|
package/src/session-logger.js
CHANGED
|
@@ -22,7 +22,8 @@ const TEXT_SECRET_PATTERNS = [
|
|
|
22
22
|
/\bAKIA[0-9A-Z]{16}\b/g, // AWS access key id
|
|
23
23
|
/\bAIza[0-9A-Za-z_\-]{30,}\b/g, // Google API key
|
|
24
24
|
/\bsk_(?:live|test)_[0-9A-Za-z]{16,}\b/g, // Stripe secret keys
|
|
25
|
-
/\bsk-[A-Za-z0-
|
|
25
|
+
/\bsk-ant-[A-Za-z0-9_-]{20,}/g, // Anthropic API keys (sk-ant-…, hyphenated)
|
|
26
|
+
/\bsk-[A-Za-z0-9]{16,}/g, // other sk- API keys
|
|
26
27
|
/\b[0-9a-fA-F]{32,}\b/g, // long hex runs
|
|
27
28
|
];
|
|
28
29
|
export function scrubText(text) {
|
|
@@ -98,6 +99,7 @@ export function renderLogText(p) {
|
|
|
98
99
|
const dur = c.duration_ms != null ? ` (${(c.duration_ms / 1000).toFixed(1)}s)` : "";
|
|
99
100
|
lines.push(`[${c.at}] ${c.name}${args} ${c.outcome}${dur}`);
|
|
100
101
|
if (c.key) lines.push(` → ${c.key}`);
|
|
102
|
+
if (c.error_reason) lines.push(` ✗ ${c.error_reason}`);
|
|
101
103
|
}
|
|
102
104
|
lines.push(RULE);
|
|
103
105
|
lines.push(
|
|
@@ -171,12 +173,21 @@ export class SessionLogger {
|
|
|
171
173
|
await this.resolveHeader();
|
|
172
174
|
const outcome = result?.isError ? "error" : "ok";
|
|
173
175
|
const args = scrubArgs(input);
|
|
176
|
+
// On an error, capture WHY (scrubbed): the throw path passes errorMessage;
|
|
177
|
+
// a returned fail() puts the message in content[0].text. Without this the
|
|
178
|
+
// log only said "error" and every failure needed a blind repro.
|
|
179
|
+
let errorReason = null;
|
|
180
|
+
if (outcome === "error") {
|
|
181
|
+
const raw = result?.errorMessage || result?.content?.[0]?.text || null;
|
|
182
|
+
if (raw) errorReason = scrubText(String(raw)).slice(0, 300);
|
|
183
|
+
}
|
|
174
184
|
this.calls.push({
|
|
175
185
|
at: this._clock(this.now()),
|
|
176
186
|
name,
|
|
177
187
|
args,
|
|
178
188
|
outcome,
|
|
179
189
|
key: this._keyResult(name, result),
|
|
190
|
+
error_reason: errorReason,
|
|
180
191
|
duration_ms: durationMs,
|
|
181
192
|
});
|
|
182
193
|
// Only a deploy failure (grid_deploy) is the QA "failure" trigger. Other
|
package/src/tools/register.js
CHANGED
|
@@ -50,9 +50,11 @@ export function registerTools(server, ctx) {
|
|
|
50
50
|
return result;
|
|
51
51
|
} catch (err) {
|
|
52
52
|
// A thrown handler must record as an ERROR — the old finally-based capture
|
|
53
|
-
// saw result=undefined and mis-recorded it as "ok".
|
|
54
|
-
//
|
|
55
|
-
|
|
53
|
+
// saw result=undefined and mis-recorded it as "ok". Carry the error
|
|
54
|
+
// message so the QA log says WHY, not just "error" (diagnosability: a
|
|
55
|
+
// Songsmith hosted deploy logged a bare "error" and cost four blind
|
|
56
|
+
// reproductions). Synthesize an error result, then rethrow unchanged.
|
|
57
|
+
try { ctx.logger?.recordCall(name, input, { isError: true, errorMessage: String(err?.message || err) }, Date.now() - started); } catch { /* never */ }
|
|
56
58
|
throw err;
|
|
57
59
|
}
|
|
58
60
|
};
|
|
@@ -288,6 +290,7 @@ export function registerTools(server, ctx) {
|
|
|
288
290
|
// they validate: (1) the grid-picker "which grid?" ask (resolveGridOrAsk),
|
|
289
291
|
// and (2) the signed-in CLI-fallback recovery.
|
|
290
292
|
needs_grid: z.boolean().optional().describe("Grid-picker ask: a signed-in user with >1 grid must choose one before this create proceeds. Pass the chosen slug back in `grid`."),
|
|
293
|
+
needs_auth: z.boolean().optional().describe("Auth gate: a NEW deploy was attempted while not signed in. Ask the user to sign in (grid_login) to publish to their grid, OR re-call grid_deploy with anon: true to publish anonymously (claimable later). Do not proceed silently."),
|
|
291
294
|
needs_org: z.boolean().optional().describe("Alias of needs_grid (legacy picker alias)."),
|
|
292
295
|
grids: z.array(z.object({
|
|
293
296
|
slug: z.string(),
|
|
@@ -357,7 +360,20 @@ export function registerTools(server, ctx) {
|
|
|
357
360
|
}
|
|
358
361
|
if (input?.anon !== true && !isEdit) {
|
|
359
362
|
const token = await ctx.getToken();
|
|
360
|
-
if (token) {
|
|
363
|
+
if (!token) {
|
|
364
|
+
// AUTH HARD GATE (create only): not signed in and not anon → do NOT
|
|
365
|
+
// silently ride the anonymous wire. Return the login-or-anon choice
|
|
366
|
+
// and stop. The model relays it; the user picks. (Rule 17, enforced.)
|
|
367
|
+
return okResult({
|
|
368
|
+
text:
|
|
369
|
+
"You're not signed in. To publish this I can either:\n" +
|
|
370
|
+
" 1. Sign you in to publish to your grid — run grid_login (opens your browser), then I'll deploy.\n" +
|
|
371
|
+
" 2. Publish it anonymously right now — it goes live immediately with a claim link so you can save it to your account later.\n" +
|
|
372
|
+
"Which would you like? (For anonymous, re-call grid_deploy with anon: true.)",
|
|
373
|
+
structured: { needs_auth: true },
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
{
|
|
361
377
|
const decision = await resolveGridOrAsk(ctx, {
|
|
362
378
|
token,
|
|
363
379
|
suppliedGrid: input?.grid,
|