@cloudgrid-io/mcp 0.20.21 → 0.20.23

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.21",
3
+ "version": "0.20.23",
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": {
@@ -30,6 +30,7 @@
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
32
  "test:create-gates": "node test/create-gates.test.mjs",
33
+ "test:create-project-args": "node test/create-project-args.test.mjs",
33
34
  "test:mcpb-boot": "node test/mcpb-boot.test.mjs",
34
35
  "test:plug-timeout": "node test/plug-timeout.test.mjs",
35
36
  "test:manifest-confirm": "node test/manifest-confirm.test.mjs",
@@ -49,7 +50,8 @@
49
50
  "snapshot:corpus": "node scripts/snapshot-corpus.mjs",
50
51
  "build:mcpb": "node scripts/build-mcpb.mjs",
51
52
  "test:widgets": "node test/apps-widgets.test.mjs",
52
- "test:fork": "node test/fork.test.mjs"
53
+ "test:fork": "node test/fork.test.mjs",
54
+ "test:set-sharing": "node test/set-sharing.test.mjs"
53
55
  },
54
56
  "dependencies": {
55
57
  "@modelcontextprotocol/sdk": "^1.0.0",
@@ -68,7 +68,7 @@ export const CLI_NPX_PKG = "@cloudgrid-io/cli@latest";
68
68
  // server-side with HTTP 426) — a gate below the floor green-lights CLIs the API
69
69
  // rejects on every call (issue #59). Bump this in lockstep with every floor
70
70
  // raise (CLI release protocol checklist).
71
- export const MIN_CLI_VERSION = "0.15.2";
71
+ export const MIN_CLI_VERSION = "0.15.14";
72
72
 
73
73
  // Upload/create POST budget. The build itself is async (server returns 202 +
74
74
  // poll_url); this bounds only the request→response, so a stalled server errors
@@ -1398,6 +1398,9 @@ export async function runPlug(ctx, input, deps = {}) {
1398
1398
  ctx.state.lastDrop = {
1399
1399
  entity_id: data.entity_id ?? null,
1400
1400
  url: url ?? null,
1401
+ // Kind lets grid_set_sharing route to the right visibility surface
1402
+ // (inspirations vs runtime entities) without a re-detect round-trip.
1403
+ kind: data.detection?.kind ?? data.kind ?? null,
1401
1404
  owner_token: useAnonWire ? (freshOwnerToken ?? ownerToken ?? null) : null,
1402
1405
  };
1403
1406
  }
@@ -1619,10 +1622,11 @@ export async function runDownload(ctx, { id, version }) {
1619
1622
  };
1620
1623
  }
1621
1624
 
1622
- // Change an inspiration's visibility. Authed, direct API — works on the hosted
1623
- // edition where the CLI-wrapping share tool is unavailable. Defaults to the drop
1624
- // made in this session, so "make it private" needs no ids.
1625
- export async function runVisibility(ctx, { target, visibility, org }) {
1625
+ // Change an entity's visibility — inspiration OR runtime app/agent. Authed,
1626
+ // direct API — works on the hosted edition where the CLI-wrapping share tool is
1627
+ // unavailable. Kind-aware routing (see below). Defaults to the drop made in this
1628
+ // session, so "make it private" needs no ids.
1629
+ export async function runVisibility(ctx, { target, visibility, kind, org }) {
1626
1630
  const token = await ctx.getToken();
1627
1631
  if (!token) {
1628
1632
  throw new Error("Changing visibility needs an owner. Run grid_login first.");
@@ -1638,33 +1642,86 @@ export async function runVisibility(ctx, { target, visibility, org }) {
1638
1642
  headers["X-CloudGrid-Grid"] = orgSlug;
1639
1643
  headers["X-CloudGrid-Org"] = orgSlug;
1640
1644
  }
1641
- let res;
1642
- try {
1643
- res = await fetch(`${API_BASE}/api/v2/inspirations/${encodeURIComponent(id)}`, {
1644
- method: "PATCH",
1645
- headers,
1646
- body: JSON.stringify({ visibility }),
1647
- });
1648
- } catch (err) {
1649
- throw new Error(`Could not reach CloudGrid at ${API_BASE}: ${err.message}`);
1650
- }
1651
- const raw = await res.text();
1652
- let data = null;
1653
- try { data = JSON.parse(raw); } catch { /* handled below */ }
1654
- if (!res.ok) {
1655
- const msg = data?.error?.message || raw || `HTTP ${res.status}`;
1656
- const hint = data?.error?.details?.[0]?.hint;
1657
- throw new Error(`Visibility change failed (HTTP ${res.status}): ${msg}${hint ? ` ${hint}` : ""}`);
1645
+
1646
+ // Two visibility surfaces with two vocabularies:
1647
+ // - inspirations: PATCH /api/v2/inspirations/:id { private|space|authenticated|org|link }
1648
+ // - runtimes: PATCH /api/v2/entities/:id/visibility { private|authenticated|grid|link }
1649
+ // Whole-grid visibility is the legacy `org` on inspirations and the canonical
1650
+ // `grid` on runtimes. Normalize the caller's value to `grid`, then map per
1651
+ // route. `space` exists only on inspirations.
1652
+ const vis = visibility === "org" ? "grid" : visibility;
1653
+ const runtimeValue = vis; // private | authenticated | grid | link
1654
+ const inspirationValue = vis === "grid" ? "org" : vis; // private | space | authenticated | org | link
1655
+
1656
+ const k = kind || ctx.state.lastDrop?.kind || null;
1657
+ const isRuntimeKind = k === "app" || k === "agent";
1658
+
1659
+ if (vis === "space" && isRuntimeKind) {
1660
+ throw new Error(
1661
+ "'space' visibility isn't available for runtime apps/agents. Use grid, authenticated, private, or link.",
1662
+ );
1658
1663
  }
1659
- const lines = [`Visibility is now ${visibility}.`];
1660
- if (data?.url) lines.push(data.url);
1661
- return {
1662
- text: lines.join("\n"),
1663
- structured: {
1664
- visibility,
1665
- ...(data?.url ? { url: data.url } : {}),
1666
- },
1664
+
1665
+ const patch = async (pathName, value) => {
1666
+ let res;
1667
+ try {
1668
+ res = await fetch(`${API_BASE}${pathName}`, {
1669
+ method: "PATCH",
1670
+ headers,
1671
+ body: JSON.stringify({ visibility: value }),
1672
+ });
1673
+ } catch (err) {
1674
+ throw new Error(`Could not reach CloudGrid at ${API_BASE}: ${err.message}`);
1675
+ }
1676
+ const raw = await res.text();
1677
+ let data = null;
1678
+ try { data = JSON.parse(raw); } catch { /* handled by caller */ }
1679
+ return { res, data, raw };
1667
1680
  };
1681
+
1682
+ const runtimePath = `/api/v2/entities/${encodeURIComponent(id)}/visibility`;
1683
+ const inspirationPath = `/api/v2/inspirations/${encodeURIComponent(id)}`;
1684
+
1685
+ // Route order. `space` is inspiration-only; a known runtime kind goes straight
1686
+ // to the entities route; a known inspiration goes straight to the inspiration
1687
+ // route; an unknown kind tries the runtime route first and falls back to the
1688
+ // inspiration route on a not-found (mirrors runFork).
1689
+ let order;
1690
+ if (vis === "space") order = ["inspiration"];
1691
+ else if (isRuntimeKind) order = ["runtime"];
1692
+ else if (k === "inspiration") order = ["inspiration"];
1693
+ else order = ["runtime", "inspiration"];
1694
+
1695
+ let last;
1696
+ for (let i = 0; i < order.length; i++) {
1697
+ const route = order[i];
1698
+ last = route === "runtime"
1699
+ ? await patch(runtimePath, runtimeValue)
1700
+ : await patch(inspirationPath, inspirationValue);
1701
+ if (last.res.ok) {
1702
+ const lines = [`Visibility is now ${vis}.`];
1703
+ if (last.data?.url) lines.push(last.data.url);
1704
+ return {
1705
+ text: lines.join("\n"),
1706
+ structured: {
1707
+ visibility: vis,
1708
+ ...(last.data?.url ? { url: last.data.url } : {}),
1709
+ },
1710
+ };
1711
+ }
1712
+ // Fall back runtime→inspiration only when the id isn't a runtime entity
1713
+ // (a genuine 4xx like NOT_OWNER must surface, not silently re-route).
1714
+ const code = last.data?.error?.code;
1715
+ const canFallback =
1716
+ route === "runtime" &&
1717
+ i < order.length - 1 &&
1718
+ (last.res.status === 404 || code === "NOT_FOUND" || code === "NOT_A_RUNTIME");
1719
+ if (!canFallback) break;
1720
+ }
1721
+
1722
+ const msg = last?.data?.error?.message || last?.raw || `HTTP ${last?.res?.status}`;
1723
+ const hint = last?.data?.error?.details?.[0]?.hint;
1724
+ throw new Error(`Visibility change failed (HTTP ${last?.res?.status}): ${msg}${hint ? ` ${hint}` : ""}`);
1668
1725
  }
1669
1726
 
1670
1727
  // Max HTML we'll return inline from runSource. Past this, we return the first
@@ -28,6 +28,21 @@ import {
28
28
  runSource,
29
29
  } from "./deploy.js";
30
30
 
31
+ // Build the argv for grid_create_project → `grid new` (CLI >= 0.15.14; `init`
32
+ // is a deprecated alias we no longer author). Modern shape: `new <slug>` with
33
+ // `--agent` for agents (NOT the legacy `init <kind> <slug>` positional). The
34
+ // old `--description` flag was removed from the CLI, so it is gone here too.
35
+ // Exported pure so the argv is unit-tested without spawning a CLI.
36
+ export function buildCreateProjectArgs({ kind, name, type, needs, dir, org } = {}) {
37
+ const args = ["new", name];
38
+ if (kind === "agent") args.push("--agent");
39
+ if (type) args.push("--type", type);
40
+ if (Array.isArray(needs) && needs.length) args.push("--needs", needs.join(","));
41
+ if (dir) args.push("--dir", dir);
42
+ if (org) args.push("--grid", org); // CLI 0.12 dropped --org for --grid (same slug)
43
+ return args;
44
+ }
45
+
31
46
  // ── Registration ───────────────────────────────────────────────────────────────
32
47
  // Registers the tools onto `server`. ctx.edition decides whether the CLI-wrapping
33
48
  // tools are included (they need a local machine).
@@ -605,10 +620,11 @@ export function registerTools(server, ctx) {
605
620
  reg(
606
621
  "grid_set_sharing",
607
622
  {
608
- description: "Change who can see a CloudGrid inspiration: private, space, authenticated, org, or link (anyone with the URL). Use when the user wants to make a drop private, restrict who sees it, or open it up — including right after a drop, with no target id needed. Defaults to the drop made in this session. Requires sign-in. Calls the API directly.",
623
+ description: "Change who can see a CloudGrid inspiration OR runtime app/agent: private, authenticated, grid (everyone in the grid), or link (anyone with the URL). `space` is inspiration-only. Use when the user wants to make something private, restrict who sees it, or open it up — including right after a drop, with no target id needed. Kind-aware: routes runtime apps/agents to the entities visibility surface and inspirations to the inspiration surface automatically. Defaults to the drop made in this session. Requires sign-in. Calls the API directly.",
609
624
  inputSchema: {
610
- visibility: z.enum(["private", "space", "authenticated", "org", "link"]).describe("The new scope."),
625
+ visibility: z.enum(["private", "space", "authenticated", "grid", "org", "link"]).describe("The new scope. Use `grid` for whole-grid visibility (`org` is the deprecated alias). `space` is inspiration-only."),
611
626
  target: z.string().optional().describe("Entity id. Defaults to this session's last drop."),
627
+ kind: z.enum(["inspiration", "app", "agent"]).optional().describe("Entity kind. Omit to auto-detect from this session's last drop (falls back to trying the runtime surface, then the inspiration surface)."),
612
628
  org: z.string().optional().describe("Grid of the entity. Defaults to the active grid."),
613
629
  },
614
630
  outputSchema: {
@@ -861,26 +877,19 @@ export function registerTools(server, ctx) {
861
877
 
862
878
  regTool(
863
879
  "grid_create_project",
864
- "Register a new CloudGrid app or agent, optionally seeding a web service. Wraps `grid new` (`init` remains an alias).",
880
+ "Scaffold a new CloudGrid app or agent folder (cloudgrid.yaml + a web service), optionally pre-declaring resources. Wraps `grid new` (scaffolds locally; no server entity until you grid_deploy).",
865
881
  {
866
- kind: z.enum(["app", "agent"]).describe("Entity kind."),
882
+ kind: z.enum(["app", "agent"]).describe("Entity kind. 'agent' scaffolds an agent: block."),
867
883
  name: z.string().describe("Slug: 3-40 lowercase alphanumerics and hyphens."),
868
884
  type: z.enum(["node", "nextjs", "python", "static"]).optional().describe("Seed a web service of this type."),
869
- description: z.string().optional().describe("Initial one-line description."),
870
- dir: z.string().optional().describe("Target directory. Defaults to ./<name>."),
871
- org: z.string().optional().describe("Override the active grid for this project."),
885
+ needs: z.array(z.enum(["database", "cache", "kv", "queue", "pubsub", "vector", "ai", "disk"])).optional()
886
+ .describe("Pre-declare infrastructure needs in the scaffolded cloudgrid.yaml (e.g. [\"database\",\"ai\"])."),
887
+ dir: z.string().optional().describe("Target directory. Defaults to the current folder."),
888
+ org: z.string().optional().describe("Override the active grid this project will target."),
872
889
  cwd: z.string().optional().describe("Working directory. The CLI runs in this directory. Defaults to the MCP server's working directory."),
873
890
  },
874
891
  { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
875
- cliTool(({ kind, name, type, description, dir, org }) => {
876
- const args = ["init", kind, name];
877
- if (type) args.push("--type", type);
878
- if (description) args.push("--description", description);
879
- if (dir) args.push("--dir", dir);
880
- // CLI 0.12 dropped `--org` in favour of `--grid` (same slug).
881
- if (org) args.push("--grid", org);
882
- return args;
883
- }, { cwdParam: true }),
892
+ cliTool((input) => buildCreateProjectArgs(input), { cwdParam: true }),
884
893
  );
885
894
 
886
895
  // NOTE: grid_deploy is no longer CLI-wrapping — the unified direct-API verb
package/src/tools.js CHANGED
@@ -35,6 +35,7 @@ export {
35
35
  runPlug,
36
36
  runFork,
37
37
  runSource,
38
+ runVisibility,
38
39
  } from "./tools/deploy.js";
39
- export { registerTools } from "./tools/register.js";
40
+ export { registerTools, buildCreateProjectArgs } from "./tools/register.js";
40
41
  export { decodeJwt } from "./auth.js";