@cloudgrid-io/mcp 0.5.1 → 0.5.3

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.
Files changed (3) hide show
  1. package/README.md +13 -6
  2. package/package.json +2 -1
  3. package/src/tools.js +98 -52
package/README.md CHANGED
@@ -13,6 +13,13 @@ The local edition wraps the `cloudgrid` CLI for authenticated operations (the CL
13
13
  handles auth, org context, and error formatting) and calls the API directly for the
14
14
  drop, claim, and login tools.
15
15
 
16
+ ## CLI compatibility
17
+
18
+ MCP 0.5.2 is tested against CLI 0.10.1. The lazy-npx fallback pins
19
+ `@cloudgrid-io/cli@~0.10.1` so a future CLI major with renamed verbs cannot
20
+ silently break a released MCP. A CI drift guard (`npm run test:drift-guard`)
21
+ asserts every wrapped verb exists in the CLI help.
22
+
16
23
  ## Prerequisite
17
24
 
18
25
  Install and log in to the CLI:
@@ -67,24 +74,24 @@ also work in the web edition where no CLI exists. `cloudgrid_login` writes the s
67
74
  | `cloudgrid_logs` | `cloudgrid logs` | Snapshot of recent logs. Does not stream. Read-only. |
68
75
  | `cloudgrid_share` | `cloudgrid visibility set` | Set visibility, default link. |
69
76
  | `cloudgrid_feedback` | `cloudgrid feedback list` | Read the org feedback feed. Read-only. |
70
- | `cloudgrid_brain` | `cloudgrid brain refresh` | Re-run the Grid Brain hooks. |
71
77
  | `cloudgrid_whoami` | `cloudgrid whoami` | Show the signed-in user and active org. Read-only. |
72
78
  | `cloudgrid_use` | `cloudgrid use` | Switch the active org. |
73
79
  | `cloudgrid_logout` | `cloudgrid logout` | Sign out and clear local credentials. Destructive. |
74
80
  | `cloudgrid_status` | `cloudgrid status` | Org dashboard or entity detail. Read-only. |
75
81
  | `cloudgrid_info` | `cloudgrid info` | Entity metadata. Read-only. |
76
- | `cloudgrid_builds` | `cloudgrid builds` | Recent builds and deploys. Read-only. |
77
- | `cloudgrid_grid` | `cloudgrid grid` | List entities on the hub. Read-only. |
78
- | `cloudgrid_rename` | `cloudgrid rename` | Rename an entity. |
82
+ | `cloudgrid_get` | `cloudgrid get grids\|entities\|spaces` | List grids, entities, or spaces. Read-only. |
83
+ | `cloudgrid_describe_grid` | `cloudgrid describe grid <slug>` | Grid detail. Read-only. |
84
+ | `cloudgrid_pickup` | `cloudgrid pickup <name>` | Download an entity's source and bind the folder. |
85
+ | `cloudgrid_rename` | `cloudgrid rename` | Rename an entity's display name. |
79
86
  | `cloudgrid_unplug` | `cloudgrid unplug` | Take an entity off the grid. Destructive; requires confirm. |
80
- | `cloudgrid_delete` | `cloudgrid delete` | Archive and delete an entity. Destructive; requires confirm. |
87
+ | `cloudgrid_delete` | `cloudgrid delete entity` | Archive an inspiration. Destructive; requires confirm. |
81
88
  | `cloudgrid_rollback` | `cloudgrid rollback` | Rollback to a previous version. |
82
89
  | `cloudgrid_versions` | `cloudgrid versions` | List published versions. Read-only. |
83
90
  | `cloudgrid_env` | `cloudgrid env` | Get, set, or list environment variables. |
84
91
  | `cloudgrid_secrets` | `cloudgrid secrets` | Set or list secret names. Never returns secret values. |
85
92
  | `cloudgrid_scaffold` | `cloudgrid scaffold` | Generate starter files. |
86
93
  | `cloudgrid_doctor` | `cloudgrid doctor` | Run local diagnostics. Read-only. |
87
- | `cloudgrid_open` | `cloudgrid open` | Return the public URL. Does not open a browser. Read-only. |
94
+ | `cloudgrid_open` | `cloudgrid open --print` | Return the public URL. Does not open a browser. Read-only. |
88
95
 
89
96
  `cloudgrid_share` and `cloudgrid_visibility` overlap on purpose: `cloudgrid_share`
90
97
  wraps the CLI and defaults to `link`; `cloudgrid_visibility` is direct API, takes an
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudgrid-io/mcp",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "MCP server for CloudGrid. Three editions: a local stdio server (full toolset), a hosted web server (light, CLI-free toolset), and a public docs server (search_cloudgrid_documentation) over MCP Streamable HTTP.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,6 +16,7 @@
16
16
  "start": "node src/index.js",
17
17
  "start:web": "node src/web.js",
18
18
  "smoke": "node test/smoke.mjs",
19
+ "test:drift-guard": "node test/drift-guard.mjs",
19
20
  "smoke:drop": "node test/smoke-drop.mjs",
20
21
  "smoke:login": "node test/smoke-login.mjs",
21
22
  "smoke:claim": "node test/smoke-claim.mjs",
package/src/tools.js CHANGED
@@ -75,10 +75,39 @@ function resolveCwd(cwd) {
75
75
  }
76
76
 
77
77
  // Pin the CLI version for the lazy npx fallback so behaviour is reproducible.
78
- const CLI_NPX_PKG = "@cloudgrid-io/cli@^0.9.20";
79
-
80
- // Minimum CLI version that supports the flags the MCP passes (--auto etc.).
81
- const MIN_CLI_VERSION = "0.9.20";
78
+ // MCP 0.5.2 is tested against CLI 0.10.1.
79
+ const CLI_NPX_PKG = "@cloudgrid-io/cli@~0.10.1";
80
+
81
+ // Minimum CLI version that supports the verbs and flags the MCP passes.
82
+ const MIN_CLI_VERSION = "0.10.1";
83
+
84
+ // Verb map for the drift guard: each CLI-wrapping tool's top-level verb(s).
85
+ // The drift-guard test imports this and asserts every verb exists in `cloudgrid --help`.
86
+ export const CLI_TOOL_VERBS = {
87
+ cloudgrid_init: ["init"],
88
+ cloudgrid_plug: ["plug"],
89
+ cloudgrid_logs: ["logs"],
90
+ cloudgrid_share: ["visibility"],
91
+ cloudgrid_feedback: ["feedback"],
92
+ cloudgrid_whoami: ["whoami"],
93
+ cloudgrid_use: ["use"],
94
+ cloudgrid_logout: ["logout"],
95
+ cloudgrid_status: ["status"],
96
+ cloudgrid_info: ["info"],
97
+ cloudgrid_get: ["get"],
98
+ cloudgrid_describe_grid: ["describe"],
99
+ cloudgrid_pickup: ["pickup"],
100
+ cloudgrid_rename: ["rename"],
101
+ cloudgrid_unplug: ["unplug"],
102
+ cloudgrid_delete: ["delete"],
103
+ cloudgrid_rollback: ["rollback"],
104
+ cloudgrid_versions: ["versions"],
105
+ cloudgrid_env: ["env"],
106
+ cloudgrid_secrets: ["secrets"],
107
+ cloudgrid_scaffold: ["scaffold"],
108
+ cloudgrid_doctor: ["doctor"],
109
+ cloudgrid_open: ["open"],
110
+ };
82
111
 
83
112
  // Simple semver comparison: true when `version` >= MIN_CLI_VERSION.
84
113
  function meetsMinVersion(version) {
@@ -1006,7 +1035,7 @@ export function registerTools(server, ctx) {
1006
1035
  "Set an entity's visibility and print its URL. Defaults to link (anyone with the URL). Wraps `cloudgrid visibility set`.",
1007
1036
  {
1008
1037
  name: z.string().describe("Entity slug."),
1009
- mode: z.enum(["link", "private", "authenticated", "org", "space"]).optional().describe("Visibility mode. Default link."),
1038
+ mode: z.enum(["link", "private", "authenticated", "grid"]).optional().describe("Visibility mode. Default link."),
1010
1039
  },
1011
1040
  { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
1012
1041
  cliTool(({ name, mode }) => ["visibility", "set", name, mode ?? "link"]),
@@ -1030,23 +1059,6 @@ export function registerTools(server, ctx) {
1030
1059
  }),
1031
1060
  );
1032
1061
 
1033
- server.tool(
1034
- "cloudgrid_brain",
1035
- "Re-run an entity's Grid Brain hooks to re-classify its description, tags, and diagram. Wraps `cloudgrid brain refresh`.",
1036
- {
1037
- name: z.string().describe("Entity slug."),
1038
- wait: z.boolean().optional().describe("Wait for the refresh to finish. Default true."),
1039
- org: z.string().optional().describe("Target an entity in another org."),
1040
- },
1041
- { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
1042
- cliTool(({ name, wait, org }) => {
1043
- const args = ["brain", "refresh", name];
1044
- if (wait !== false) args.push("--wait");
1045
- if (org) args.push("--org", org);
1046
- return args;
1047
- }),
1048
- );
1049
-
1050
1062
  // ── New CLI-wrapping tools (local edition only) ───────────────────────────
1051
1063
 
1052
1064
  server.tool(
@@ -1093,36 +1105,77 @@ export function registerTools(server, ctx) {
1093
1105
  }),
1094
1106
  );
1095
1107
 
1108
+ // cloudgrid_get is the single canonical lister for grids, entities, and spaces
1109
+ // (wraps `cloudgrid get <resource> --json`). It replaces the former
1110
+ // cloudgrid_grid (which wrapped only `get entities`) — retired here so there is
1111
+ // exactly one way to list entities. resource="entities" reproduces the old
1112
+ // cloudgrid_grid behaviour with `grid` mapping to the CLI's `--grid` flag.
1096
1113
  server.tool(
1097
- "cloudgrid_builds",
1098
- "List recent builds and deploys for an entity. Wraps `cloudgrid builds`.",
1114
+ "cloudgrid_get",
1115
+ "List CloudGrid grids, entities, or spaces. Wraps `cloudgrid get <grids|entities|spaces> --json`.",
1099
1116
  {
1100
- name: z.string().optional().describe("Entity name. Omit for the entity linked to the current directory."),
1101
- limit: z.number().int().positive().optional().describe("Number of builds to show."),
1117
+ resource: z.enum(["grids", "entities", "spaces"]).describe("What to list: grids, entities, or spaces."),
1118
+ grid: z.string().optional().describe("Grid slug (entities/spaces only). Omit for the active grid."),
1119
+ kind: z.enum(["app", "agent", "inspiration"]).optional().describe("Filter by kind (entities only)."),
1120
+ status: z.enum(["charged", "live", "dark", "archived"]).optional().describe("Filter by status (entities only)."),
1121
+ space: z.string().optional().describe("Only entities scoped to this space slug (entities only)."),
1122
+ archived: z.boolean().optional().describe("Include archived entities (entities only)."),
1102
1123
  },
1103
1124
  { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
1104
- cliTool(({ name, limit }) => {
1105
- const args = ["builds"];
1106
- if (name) args.push(name);
1107
- if (limit) args.push("--limit", String(limit));
1125
+ cliTool(({ resource, grid, kind, status, space, archived }) => {
1126
+ const args = ["get", resource];
1127
+ // --grid applies to entities and spaces; grids has no such flag.
1128
+ if (grid && resource !== "grids") args.push("--grid", grid);
1129
+ if (resource === "entities") {
1130
+ if (kind) args.push("--kind", kind);
1131
+ if (status) args.push("--status", status);
1132
+ if (space) args.push("--space", space);
1133
+ if (archived) args.push("--archived");
1134
+ }
1135
+ args.push("--json");
1108
1136
  return args;
1109
1137
  }),
1110
1138
  );
1111
1139
 
1112
1140
  server.tool(
1113
- "cloudgrid_grid",
1114
- "List entities on the hub or org. Wraps `cloudgrid grid`.",
1115
- { org: z.string().optional().describe("Org slug. Omit for the active org.") },
1141
+ "cloudgrid_describe_grid",
1142
+ "Show a grid's detail: role, members, spaces, tier, wildcard-TLS state. Wraps `cloudgrid describe grid <slug> --json`.",
1143
+ { grid: z.string().describe("Grid slug to describe.") },
1116
1144
  { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
1117
- cliTool(({ org }) => (org ? ["grid", "--org", org] : ["grid"])),
1145
+ cliTool(({ grid }) => ["describe", "grid", grid, "--json"]),
1146
+ );
1147
+
1148
+ server.tool(
1149
+ "cloudgrid_pickup",
1150
+ "Download an entity's source + cloudgrid.yaml and link the folder to it. Overwrites with --force. Wraps `cloudgrid pickup`.",
1151
+ {
1152
+ name: z.string().describe("Entity slug or id to pick up."),
1153
+ target_dir: z.string().optional().describe("Directory to pick up into (relative to cwd). Defaults to the entity name."),
1154
+ grid: z.string().optional().describe("Grid to resolve the entity in. Defaults to the active grid."),
1155
+ version: z.string().optional().describe("Pick up an older version's source instead of HEAD."),
1156
+ force: z.boolean().optional().describe("Pick up into a non-empty directory."),
1157
+ no_bind: z.boolean().optional().describe("Download source only — skip cloudgrid.yaml and the link."),
1158
+ cwd: z.string().optional().describe("Working directory the CLI runs in. The download lands here; pass an explicit, writable directory."),
1159
+ },
1160
+ { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
1161
+ cliTool(({ name, target_dir, grid, version, force, no_bind }) => {
1162
+ const args = ["pickup", name];
1163
+ if (target_dir) args.push(target_dir);
1164
+ // The CLI's pickup --help exposes `--org <slug>` (legacy naming) for the grid.
1165
+ if (grid) args.push("--org", grid);
1166
+ if (version) args.push("--version", version);
1167
+ if (force) args.push("--force");
1168
+ if (no_bind) args.push("--no-bind");
1169
+ return args;
1170
+ }, { cwdParam: true }),
1118
1171
  );
1119
1172
 
1120
1173
  server.tool(
1121
1174
  "cloudgrid_rename",
1122
- "Rename a CloudGrid entity. Wraps `cloudgrid rename`.",
1175
+ "Rename a CloudGrid entity's display name (slug stays the same). Wraps `cloudgrid rename`.",
1123
1176
  {
1124
- name: z.string().describe("Current entity slug."),
1125
- new_name: z.string().describe("New slug."),
1177
+ name: z.string().describe("Entity slug."),
1178
+ new_name: z.string().describe("New display name."),
1126
1179
  },
1127
1180
  { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
1128
1181
  cliTool(({ name, new_name }) => ["rename", name, new_name]),
@@ -1141,13 +1194,13 @@ export function registerTools(server, ctx) {
1141
1194
 
1142
1195
  server.tool(
1143
1196
  "cloudgrid_delete",
1144
- "Archive and delete a CloudGrid entity. Destructive. Wraps `cloudgrid delete`.",
1197
+ "Archive a CloudGrid inspiration. Destructive. Wraps `cloudgrid delete entity`.",
1145
1198
  {
1146
1199
  name: z.string().describe("Entity slug to delete (required)."),
1147
1200
  confirm: z.literal(true).describe("Must be true to proceed."),
1148
1201
  },
1149
1202
  { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
1150
- cliTool(({ name }) => ["delete", name, "--yes"]),
1203
+ cliTool(({ name }) => ["delete", "entity", name, "--yes"]),
1151
1204
  );
1152
1205
 
1153
1206
  server.tool(
@@ -1159,7 +1212,7 @@ export function registerTools(server, ctx) {
1159
1212
  },
1160
1213
  { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
1161
1214
  cliTool(({ name, to }) => {
1162
- const args = ["rollback", name];
1215
+ const args = ["rollback", name, "--yes"];
1163
1216
  if (to) args.push("--to", to);
1164
1217
  return args;
1165
1218
  }),
@@ -1215,7 +1268,7 @@ export function registerTools(server, ctx) {
1215
1268
  cliTool(({ action, name, key, value }) => {
1216
1269
  if (action === "set") {
1217
1270
  if (!key || value === undefined) throw new Error("key and value are required for set");
1218
- return ["secrets", "set", name, key, value];
1271
+ return ["secrets", "set", name, `${key}=${value}`];
1219
1272
  }
1220
1273
  return ["secrets", "list", name];
1221
1274
  }, { cwdParam: true }),
@@ -1223,19 +1276,12 @@ export function registerTools(server, ctx) {
1223
1276
 
1224
1277
  server.tool(
1225
1278
  "cloudgrid_scaffold",
1226
- "Generate starter files for a CloudGrid entity. Wraps `cloudgrid scaffold`.",
1279
+ "Scaffold service folders declared in cloudgrid.yaml (idempotent). Wraps `cloudgrid scaffold`.",
1227
1280
  {
1228
- template: z.string().optional().describe("Template name."),
1229
- dir: z.string().optional().describe("Target directory."),
1230
1281
  cwd: z.string().optional().describe("Working directory. The CLI runs in this directory. Defaults to the MCP server's working directory."),
1231
1282
  },
1232
1283
  { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
1233
- cliTool(({ template, dir }) => {
1234
- const args = ["scaffold"];
1235
- if (template) args.push(template);
1236
- if (dir) args.push("--dir", dir);
1237
- return args;
1238
- }, { cwdParam: true }),
1284
+ cliTool(() => ["scaffold"], { cwdParam: true }),
1239
1285
  );
1240
1286
 
1241
1287
  server.tool(
@@ -1248,11 +1294,11 @@ export function registerTools(server, ctx) {
1248
1294
 
1249
1295
  server.tool(
1250
1296
  "cloudgrid_open",
1251
- "Return the public URL for an entity. Does not open a browser. Wraps `cloudgrid open --url`.",
1297
+ "Return the public URL for an entity. Does not open a browser. Wraps `cloudgrid open --print`.",
1252
1298
  { name: z.string().optional().describe("Entity name. Omit for the entity linked to the current directory.") },
1253
1299
  { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
1254
1300
  cliTool(({ name }) => {
1255
- const args = ["open", "--url"];
1301
+ const args = ["open", "--print"];
1256
1302
  if (name) args.push(name);
1257
1303
  return args;
1258
1304
  }),