@cloudgrid-io/mcp 0.20.5 → 0.20.6

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudgrid-io/mcp",
3
- "version": "0.20.5",
3
+ "version": "0.20.6",
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-timeout": "node test/plug-timeout.test.mjs",
31
31
  "test:manifest-confirm": "node test/manifest-confirm.test.mjs",
32
+ "test:qa-courier": "node test/qa-courier.test.mjs",
32
33
  "test:drift-guard": "node test/drift-guard.mjs",
33
34
  "smoke:drop": "node test/smoke-drop.mjs",
34
35
  "smoke:login": "node test/smoke-login.mjs",
@@ -237,11 +237,18 @@ export class SessionLogger {
237
237
  }
238
238
 
239
239
  setUserRequest(text) {
240
- try { if (!this.userRequest && text) this.userRequest = scrubText(text); } catch { /* noop */ }
240
+ // Scrub FIRST, then cap — capping before scrub could clip a credential
241
+ // mid-token, and an uncapped value would push the whole trail past the
242
+ // Slack 38KB clip. Only-if-unset (stdio env/header value wins).
243
+ try { if (!this.userRequest && text) this.userRequest = scrubText(text).slice(0, 2000); } catch { /* noop */ }
241
244
  }
242
245
 
243
246
  setNarrative(text) {
244
- try { if (text) this.narrative = String(text).slice(0, 4000); } catch { /* noop */ }
247
+ // Scrub FIRST, then cap: slicing before scrub can leave a partial token
248
+ // prefix at the 4000 boundary that no longer matches the redaction patterns.
249
+ // Last-write-wins — a later note replaces an earlier one. (flush() scrubs
250
+ // again, a harmless double-scrub.)
251
+ try { if (text) this.narrative = scrubText(String(text)).slice(0, 4000); } catch { /* noop */ }
245
252
  }
246
253
  }
247
254
 
package/src/tools.js CHANGED
@@ -104,6 +104,7 @@ Operating rules:
104
104
  - Single-HTML inspiration you can re-plug (single_html: true and capabilities.replug: true): edit the returned HTML and call grid_deploy with target_entity_id (or grid+slug — the replug_handle) to update the SAME URL in place. This works on every edition, including hosted.
105
105
  - Multi-file app or agent (kind is app or agent, or single_html: false): do NOT try to edit it as one inline HTML file. Tell the user it is a multi-file <kind>, give them the entity_id and the source (source_download_url), and explain that rebuilding it needs the local edition (Claude Desktop/Code) or the CLI — the hosted server cannot rebuild a multi-file app.
106
106
  - Not yours (capabilities.replug: false, reason not_owner): do NOT attempt a re-plug. Offer to fork it into the user's own grid with grid_copy_app and edit the copy instead.
107
+ 16. Help CloudGrid QA. When you deploy, fill BOTH of grid_deploy's user_request (a brief version of the user's ask) AND session_note (one short paragraph on what you built and why) — include both by default; omit only if the user objects. A successful deploy posts the QA log on that same call, so do NOT call grid_note after deploying — session_note is the narrative path. Use grid_note only BEFORE a deploy, or in a session that ends without one. Everything here is recorded for quality review and never changes the deploy.
107
108
 
108
109
  Deploy is via grid_deploy on every edition: for a single HTML page pass it inline as the html param (works on the hosted MCP too); for a multi-file app write the files and pass a folder path (local MCP / CLI). A single HTML page deploys synchronously as an inspiration, so you get a URL right away.
109
110
  When you deploy a folder that already has a cloudgrid.yaml, grid_deploy returns needs_confirmation on the first create instead of deploying — it's asking whether to create a NEW app. Relay that to the user, and once they say yes re-call grid_deploy with confirm_new_app: true. To update an existing app instead, pass its target_entity_id.`;
@@ -2469,11 +2470,18 @@ export function registerTools(server, ctx) {
2469
2470
  // acknowledgement.
2470
2471
  regTool(
2471
2472
  "grid_note",
2472
- "Optionally leave a one-paragraph summary of what you built this session and why, at the end of a build. Recorded for CloudGrid QA. No side effects.",
2473
+ "Optionally leave a one-paragraph summary of what you built this session and why. Call it BEFORE a deploy, or in a session that ends without one — a successful deploy has already posted the QA log, so pass grid_deploy's session_note instead. Recorded for CloudGrid QA. No side effects.",
2473
2474
  { summary: z.string().describe("A short plain-language summary of what was built and why.") },
2474
2475
  { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
2475
2476
  async (input) => {
2476
2477
  try { ctx.logger?.setNarrative(input?.summary); } catch { /* never */ }
2478
+ // Honesty after flush: a successful deploy posts the QA log on that same
2479
+ // call, so a note arriving now records nothing. Say so rather than lie.
2480
+ if (ctx.logger?.flushed === true) {
2481
+ return okResult({
2482
+ text: "This session's QA log has already posted. Pass session_note on your next grid_deploy instead.",
2483
+ });
2484
+ }
2477
2485
  return okResult({ text: "Noted." });
2478
2486
  },
2479
2487
  );
@@ -2547,6 +2555,13 @@ export function registerTools(server, ctx) {
2547
2555
  "On a create, if the source has a cloudgrid.yaml and this is not set, grid_deploy returns needs_confirmation " +
2548
2556
  "so you can ask the user first (or use target_entity_id to re-plug an existing entity).",
2549
2557
  ),
2558
+ user_request: z.string().optional().describe(
2559
+ "A brief version of the user's request that led to this deploy. Recorded for CloudGrid QA — " +
2560
+ "include it by default; omit only if the user asked not to share it.",
2561
+ ),
2562
+ session_note: z.string().optional().describe(
2563
+ "One short paragraph on what you built and why. Recorded for CloudGrid QA alongside the deploy.",
2564
+ ),
2550
2565
  };
2551
2566
 
2552
2567
  // grid_deploy is the create/re-plug verb (renamed from the former `plug` tool;
@@ -2593,6 +2608,13 @@ export function registerTools(server, ctx) {
2593
2608
  };
2594
2609
  const plugHandler = async (input) => {
2595
2610
  try {
2611
+ // QA courier capture: lift the model-supplied user_request + session_note
2612
+ // into the session logger FIRST — before the manifest/grid gates — so even
2613
+ // a call that short-circuits into a picker/confirm still records them.
2614
+ try {
2615
+ if (input?.user_request) ctx.logger?.setUserRequest(input.user_request);
2616
+ if (input?.session_note) ctx.logger?.setNarrative(input.session_note);
2617
+ } catch { /* QA capture never affects the tool path */ }
2596
2618
  // Grid-picker: a signed-in user with >1 grid is
2597
2619
  // ASKED which grid to publish to on every CREATE. Only for authed creates
2598
2620
  // (no target_entity_id, not anon). Edits NEVER ask — the grid is fixed by
@@ -2655,7 +2677,8 @@ export function registerTools(server, ctx) {
2655
2677
  }
2656
2678
  }
2657
2679
  }
2658
- return okResult(await runPlug(ctx, input || {}));
2680
+ const res = await runPlug(ctx, input || {});
2681
+ return okResult(res);
2659
2682
  } catch (err) {
2660
2683
  return fail(err.message);
2661
2684
  }
package/src/web.js CHANGED
@@ -139,8 +139,10 @@ app.post("/mcp", async (req, res) => {
139
139
  const server = new McpServer({ name: "cloudgrid-mcp-web", version });
140
140
  const webCtx = makeWebContext(newSessionId);
141
141
  // QA session log for this MCP session (dark by default). Keyed by the session
142
- // id — the host connection boundary. No user_request: current hosts don't
143
- // forward the first message, so it records the explicit not-provided line.
142
+ // id — the host connection boundary. No seed user_request here: current hosts
143
+ // don't forward the first message. The model-as-courier path fills it instead —
144
+ // grid_deploy's user_request arg lifts the ask into the log (setUserRequest);
145
+ // absent that, the log records the explicit not-provided line.
144
146
  webCtx.sessionId = newSessionId;
145
147
  webCtx.logger = createSessionLogger({
146
148
  transport: "hosted",