@cloudgrid-io/mcp 0.20.33 → 0.20.35

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.33",
3
+ "version": "0.20.35",
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": {
@@ -18,7 +18,7 @@ Operating rules:
18
18
  15. Editing an existing thing from just its URL (a fresh chat, no prior context — e.g. "change the background to green here <url>"): call grid_get_app_source(url) first. It resolves the entity_id and returns the current HTML plus its kind, single_html, capabilities, and replug_handle — read those to pick the branch:
19
19
  - Single-HTML inspiration you can re-plug (single_html: true and capabilities.replug: true): edit the returned HTML and call grid_plug with target_entity_id (or grid+slug — the replug_handle) to update the SAME URL in place. This works on every edition, including hosted.
20
20
  - 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.
21
- - 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.
21
+ - 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_fork and edit the copy instead.
22
22
  16. Help CloudGrid QA. When you deploy, fill BOTH of grid_plug'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.
23
23
 
24
24
  17. Auth check before an owned deploy, with an anonymous fallback. grid_start reports signed_in. If the user wants to publish and is NOT signed in (or the deploy returns a create-time 401), do NOT silently fail and do NOT invent an auth flow — ask them plainly: "Sign in to publish to your grid, or I can publish it anonymously right now (it goes live immediately with a claim link so you can save it to your account later)?" If they choose anonymous, re-call grid_plug with anon: true and surface the claim_url + owner_token. This is distinct from rule 6: rule 6 forbids the anon fallback on a SERVER error (5xx) for a signed-in user (it burns quota and downgrades ownership); THIS rule is about not being authenticated at all, where anonymous is a legitimate, offered choice.
@@ -69,5 +69,5 @@ new_expires_at, role_granted: "owner", ... }`.
69
69
  ## Notes
70
70
 
71
71
  - One pickup claims one entity. To claim several, repeat with each entity id + token.
72
- - The MCP `grid_claim_anonymous_deploy` tool does this for you, and remembers the entity id +
72
+ - The MCP `grid_pickup` tool does this for you, and remembers the entity id +
73
73
  token from the last anonymous drop so you can claim with no arguments.
@@ -1635,6 +1635,41 @@ export async function runFork(ctx, { id, into_org_slug, name, source_version_id
1635
1635
  };
1636
1636
  }
1637
1637
 
1638
+ // Remix: cross-grid "make my own copy". Unlike fork (same-grid, byte-carrying),
1639
+ // remix mints a NEW entity in ANY grid the caller can build in, keeps lineage
1640
+ // back to the source, and strips the source's secrets/connection credentials —
1641
+ // the remixer supplies their own before plugging. Hits POST
1642
+ // /api/v2/runtimes/:id/remix.
1643
+ export async function runRemix(ctx, { id, into_org_slug, name, source_version_id }) {
1644
+ const data = await authedApiCall(ctx, {
1645
+ method: "POST",
1646
+ pathName: `/api/v2/runtimes/${encodeURIComponent(id)}/remix`,
1647
+ body: {
1648
+ ...(into_org_slug ? { into_org_slug } : {}),
1649
+ ...(name ? { name } : {}),
1650
+ ...(source_version_id ? { source_version_id } : {}),
1651
+ },
1652
+ verb: "Remix",
1653
+ });
1654
+ const gridSlug = data?.grid_slug ?? data?.org?.slug ?? null;
1655
+ const lines = [
1656
+ `Remixed: ${data?.name ?? id} (entity_id=${data?.entity_id ?? "?"})${gridSlug ? ` in grid ${gridSlug}` : ""}`,
1657
+ `Lineage: forked_from=${data?.forked_from ?? "?"}. Secrets were not copied — set your own before you plug.`,
1658
+ ];
1659
+ return {
1660
+ text: lines.join("\n"),
1661
+ structured: {
1662
+ entity_id: data?.entity_id ?? null,
1663
+ name: data?.name ?? null,
1664
+ kind: data?.kind ?? null,
1665
+ grid_slug: gridSlug,
1666
+ forked_from: data?.forked_from ?? null,
1667
+ forked_from_version_id: data?.forked_from_version_id ?? null,
1668
+ current_version_id: data?.current_version_id ?? null,
1669
+ },
1670
+ };
1671
+ }
1672
+
1638
1673
  // Download: signed, time-limited (15-minute) source-bundle URLs. No entity is
1639
1674
  // created and no registry state changes.
1640
1675
  export async function runDownload(ctx, { id, version }) {
@@ -23,6 +23,7 @@ import {
23
23
  runClaim,
24
24
  runPlug,
25
25
  runFork,
26
+ runRemix,
26
27
  runDownload,
27
28
  runVisibility,
28
29
  runSource,
@@ -76,35 +77,20 @@ export function registerTools(server, ctx) {
76
77
  }
77
78
  };
78
79
 
79
- // Every primary tool records its shape here so a deprecated alias can be
80
- // registered under an OLD name with the same handler + schema but a
81
- // redirect-only, keyword-free description (so the model never mis-selects it).
82
- const registered = {};
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.
83
86
  const reg = (name, config, handler) => {
84
- registered[name] = { kind: "reg", config, handler };
85
87
  server.registerTool(name, config, withCapture(name, handler));
86
88
  };
87
89
 
88
90
  const regTool = (name, description, schema, annotations, handler) => {
89
- registered[name] = { kind: "tool", description, schema, annotations, handler };
90
91
  server.tool(name, description, schema, annotations, withCapture(name, handler));
91
92
  };
92
93
 
93
- // Register OLD tool names as deprecated aliases of their new (clearer) names.
94
- // Same handler/schema; description is redirect-only so it never wins selection.
95
- // Skips silently if the primary isn't registered in this edition (e.g. a
96
- // CLI-only tool on the web edition).
97
- const registerAlias = (oldName, newName) => {
98
- const r = registered[newName];
99
- if (!r) return;
100
- const redirect = `Deprecated alias of ${newName}. Always call ${newName} instead; this name is kept for backward compatibility.`;
101
- if (r.kind === "reg") {
102
- server.registerTool(oldName, { ...r.config, description: redirect }, withCapture(oldName, r.handler));
103
- } else {
104
- server.tool(oldName, redirect, r.schema, r.annotations, withCapture(oldName, r.handler));
105
- }
106
- };
107
-
108
94
  // ── Widget resources (web edition, ChatGPT Apps SDK) ──────────────────────
109
95
  if (ctx.edition === "web") {
110
96
  server.registerResource("cloudgrid-live-result", LIVE_RESULT_URI, {
@@ -134,19 +120,22 @@ export function registerTools(server, ctx) {
134
120
 
135
121
  // ── Direct-API tools (both editions) ──────────────────────────────────────
136
122
 
137
- // Claim — both editions.
123
+ // Pickup — both editions. Adopts an entity into the signed-in account's active
124
+ // grid (ownership transfer via POST /api/v2/entities/:id/pickup). Pass an
125
+ // anonymous drop's owner token as `claim_token` to take over something you
126
+ // dropped anonymously — that path replaces the old standalone "claim" verb.
138
127
  reg(
139
- "grid_claim_anonymous_deploy",
128
+ "grid_pickup",
140
129
  {
141
- description: "Claim an anonymous drop into the signed-in account, so it becomes owned and stops expiring on the anonymous schedule. Use after the user signs in to keep something they dropped anonymously. The public URL does not change. The claim token IS the drop's owner_token (one bearer capability for both edit and claim — anonymous edits refresh it, so always use the newest). Requires sign-in (grid_login). Calls the API directly.",
130
+ description: "Pick up (adopt) an entity into the signed-in account's active grid — an ownership transfer, keeping the same public URL. Use it to take over an entity you have access to, or to keep an anonymous drop you created: pass the drop's `claim_token` and it becomes owned and stops expiring on the anonymous schedule. Requires sign-in (grid_login). Calls the API directly, so it works on every edition. (This is the direct-API adopt; on the local edition grid_edit_existing_app additionally downloads the source and links the folder for editing.)",
142
131
  inputSchema: {
143
- claim_token: z.string().optional().describe("The claim/owner token from an anonymous drop (owner_token in the drop result; also embedded in claim_url)."),
144
- claim_url: z.string().optional().describe("The claim_url from an anonymous drop; the token is read from it."),
145
- entity_id: z.string().optional().describe("The entity id of the anonymous drop to claim. Defaults to this session's last anonymous drop."),
132
+ entity_id: z.string().optional().describe("The entity id to pick up. Defaults to this session's last anonymous drop."),
133
+ claim_token: z.string().optional().describe("For an anonymous drop: its owner token (the owner_token in the drop result, also embedded in claim_url). Passing it takes over that drop."),
134
+ claim_url: z.string().optional().describe("Alternative to claim_token for an anonymous drop — the claim_url from the drop result; the token is read from it."),
146
135
  },
147
136
  outputSchema: {
148
- claimed: z.number().describe("Number of drops claimed."),
149
- urls: z.array(z.string()).describe("URLs of the claimed drops."),
137
+ claimed: z.number().describe("Number of entities picked up."),
138
+ urls: z.array(z.string()).describe("Public URLs of the picked-up entities."),
150
139
  },
151
140
  annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
152
141
  },
@@ -426,23 +415,24 @@ export function registerTools(server, ctx) {
426
415
  }
427
416
  };
428
417
  reg("grid_plug", plugConfig, plugHandler);
429
- // `grid_deploy` is kept as a deprecated back-compat alias that redirects to
430
- // grid_plug (for any client still calling the old name). It is keyword-free so
431
- // the model never selects it — "deploy"/"publish"/"ship"/"make live" live in
432
- // grid_plug's own description, so deploy-intent routing lands on grid_plug.
433
- registerAlias("grid_deploy", "grid_plug");
418
+ // NOTE: no `grid_deploy` alias — the tool is grid_plug only. "deploy"/
419
+ // "publish"/"ship"/"make live" live in grid_plug's description, so
420
+ // deploy-intent routing lands there without a second listed name.
434
421
 
435
- // ── grid_copy_app / grid_download_source — direct-API verbs (spec v2 §5–6) ──────
422
+ // ── grid_fork / grid_remix / grid_download — direct-API copy/source verbs ──────
423
+ // fork = same-grid byte-carrying copy; remix = cross-grid copy with secrets
424
+ // stripped; download = signed source-bundle URLs. All direct-API (both editions).
436
425
  reg(
437
- "grid_copy_app",
426
+ "grid_fork",
438
427
  {
439
428
  description:
440
- "Start a NEW entity from an existing runtime (copy-on-write, lineage recorded). Lands in the " +
441
- "source's home grid by default; cross-grid only for system templates or forkable:'public' sources. " +
442
- "Requires sign-in. Kind-aware: forks a runtime (app/agent) OR an inspiration — " +
443
- "tries POST /api/v2/runtimes/:id/fork and falls back to /api/v2/inspirations/:id/fork.",
429
+ "Fork an entity: start a NEW entity from an existing one (copy-on-write, lineage recorded), carrying " +
430
+ "the source bytes. Lands in the source's home grid by default; cross-grid only for system templates " +
431
+ "or forkable:'public' sources — for a general cross-grid copy use grid_remix instead. Requires sign-in. " +
432
+ "Kind-aware: forks a runtime (app/agent) OR an inspiration — tries POST /api/v2/runtimes/:id/fork and " +
433
+ "falls back to /api/v2/inspirations/:id/fork.",
444
434
  inputSchema: {
445
- id: z.string().describe("The source runtime: a canonical UUID or <grid-slug>/<entity-slug>."),
435
+ id: z.string().describe("The source entity: a canonical UUID or <grid-slug>/<entity-slug>."),
446
436
  into_org_slug: z.string().optional().describe("Destination grid slug. Required only when you belong to more than one grid."),
447
437
  name: z.string().optional().describe("Slug for the new entity. Omit to derive one from the source."),
448
438
  source_version_id: z.string().optional().describe("Fork an older version instead of HEAD, e.g. v_a1b2c3d."),
@@ -469,12 +459,49 @@ export function registerTools(server, ctx) {
469
459
  );
470
460
 
471
461
  reg(
472
- "grid_download_source",
462
+ "grid_remix",
473
463
  {
474
464
  description:
475
- "Fetch the source bundle last deployed for a runtime: one signed, time-limited (15-minute) read URL " +
476
- "per service tarball. No entity is created and no registry state changes. Requires sign-in. Calls " +
477
- "GET /api/v2/runtimes/:id/source directly.",
465
+ "Remix an app: make your own copy of any app you can SEE (link/public/authenticated, or a grid you " +
466
+ "belong to) into ANY grid you can build in — cross-grid is allowed. Mints a NEW entity with lineage " +
467
+ "back to the source, WITHOUT the source's secrets or connection credentials (set your own before you " +
468
+ "plug). Distinct from grid_fork, which is same-grid and byte-carrying. Requires sign-in. Calls POST " +
469
+ "/api/v2/runtimes/:id/remix.",
470
+ inputSchema: {
471
+ id: z.string().describe("The source app: a canonical UUID or <grid-slug>/<entity-slug>."),
472
+ into_org_slug: z.string().optional().describe("Grid to create your copy in. Required only when you belong to more than one grid."),
473
+ name: z.string().optional().describe("Slug for your copy. Omit to derive one from the source."),
474
+ source_version_id: z.string().optional().describe("Remix an older version instead of HEAD, e.g. v_a1b2c3d."),
475
+ },
476
+ outputSchema: {
477
+ entity_id: z.string().nullable().describe("Your copy's entity id."),
478
+ name: z.string().nullable().describe("Your copy's slug."),
479
+ kind: z.string().nullable().describe("app | agent."),
480
+ grid_slug: z.string().nullable().describe("The grid your copy landed in."),
481
+ forked_from: z.string().nullable().describe("Source entity_id."),
482
+ forked_from_version_id: z.string().nullable().describe("Source version, when a specific one was remixed."),
483
+ current_version_id: z.string().nullable().describe("Your copy's current version id."),
484
+ },
485
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
486
+ },
487
+ async (input) => {
488
+ try {
489
+ if (!input?.id) return fail("`id` is required (a canonical UUID or <grid-slug>/<entity-slug>).");
490
+ return okResult(await runRemix(ctx, input));
491
+ } catch (err) {
492
+ return fail(err.message);
493
+ }
494
+ },
495
+ );
496
+
497
+ reg(
498
+ "grid_download",
499
+ {
500
+ description:
501
+ "Download an entity's source: the bundle last deployed for a runtime, as one signed, time-limited " +
502
+ "(15-minute) read URL per service tarball. No entity is created and no registry state changes (unlike " +
503
+ "grid_pickup/grid_fork/grid_remix, which adopt or copy). Requires sign-in. Calls GET " +
504
+ "/api/v2/runtimes/:id/source directly.",
478
505
  inputSchema: {
479
506
  id: z.string().describe("The runtime to download: a canonical UUID or <grid-slug>/<entity-slug>."),
480
507
  version: z.string().optional().describe("Download an older version's bundle instead of HEAD, e.g. v_a1b2c3d."),
@@ -511,7 +538,7 @@ export function registerTools(server, ctx) {
511
538
  "(replug/fork), and replug_handle — read those to decide whether to edit in place (single-HTML + " +
512
539
  "capabilities.replug), fall back for a multi-file app/agent (use source_download_url + the local " +
513
540
  "edition/CLI), or offer a fork when it isn't yours. For multi-file or runtime (app/agent) source " +
514
- "bundles, use grid_download_source (signed tarball URLs). Reads the HTML from the API server-side; " +
541
+ "bundles, use grid_download (signed tarball URLs). Reads the HTML from the API server-side; " +
515
542
  "read-only, creates nothing.",
516
543
  inputSchema: {
517
544
  entity_id: z.string().optional().describe("The drop's durable id. Defaults to this session's last drop."),
@@ -928,17 +955,6 @@ export function registerTools(server, ctx) {
928
955
  },
929
956
  );
930
957
 
931
- // ── Kept alias (both editions): grid_fetch has real muscle memory in saved
932
- // prompts and older docs. Every other legacy alias was dropped in 0.20.8 -
933
- // 16 alias schemas were pure ListTools context weight on every session.
934
- registerAlias("grid_fetch", "grid_get_template");
935
- // grid_set_sharing → grid_visibility (2026-07-20, founder rename). Same handler
936
- // and schema; old name kept as a redirect-only deprecated alias so any client
937
- // still calling it works while the model prefers the new. MUST live here, above
938
- // the local cutoff: grid_visibility is a both-editions tool, so the alias has
939
- // to register on the web edition too (hosted callers keep back-compat).
940
- registerAlias("grid_set_sharing", "grid_visibility");
941
-
942
958
  if (ctx.edition !== "local") return; // web edition stops here — no CLI tools
943
959
 
944
960
  // ── CLI-wrapping tools (local edition only) ───────────────────────────────
@@ -1257,9 +1273,4 @@ export function registerTools(server, ctx) {
1257
1273
  }),
1258
1274
  );
1259
1275
 
1260
- // ── Deprecated aliases (tool-name cleanup): OLD CLI-tool names kept callable
1261
- // (local edition) as redirect-only aliases of their new, clearer names.
1262
- // Kept alias (local): grid_logs - muscle memory. The other legacy CLI-tool
1263
- // aliases were dropped in 0.20.8 (see CHANGELOG).
1264
- registerAlias("grid_logs", "grid_view_logs");
1265
1276
  }