@cloudgrid-io/mcp 0.4.2 → 0.4.4

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 (2) hide show
  1. package/package.json +2 -1
  2. package/src/tools.js +86 -19
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudgrid-io/mcp",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "MCP server for CloudGrid. Two editions: a local stdio server (full toolset) and a hosted web server (light, CLI-free toolset) over MCP Streamable HTTP.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -25,6 +25,7 @@
25
25
  "test:auth": "node test/auth.test.mjs"
26
26
  },
27
27
  "dependencies": {
28
+ "@cloudgrid-io/cli": "^0.9.20",
28
29
  "@modelcontextprotocol/sdk": "^1.0.0",
29
30
  "express": "^4.22.2",
30
31
  "zod": "^3.23.0"
package/src/tools.js CHANGED
@@ -9,10 +9,11 @@
9
9
  // The difference is injected as a `ctx` object, so the tool logic is written once.
10
10
 
11
11
  import { execFile } from "node:child_process";
12
+ import { createRequire } from "node:module";
12
13
  import { promisify } from "node:util";
13
14
  import { readFile } from "node:fs/promises";
14
- import { readFileSync } from "node:fs";
15
- import { basename } from "node:path";
15
+ import { readFileSync, existsSync, statSync } from "node:fs";
16
+ import { basename, dirname, resolve, join } from "node:path";
16
17
  import { z } from "zod";
17
18
  import { newLoginCode, buildLoginUrl, pollStatusOnce, decodeJwt } from "./auth.js";
18
19
 
@@ -59,12 +60,67 @@ function okResult({ text, structured, meta }) {
59
60
  }
60
61
 
61
62
  // ── CLI wrapping (local edition only) ──────────────────────────────────────────
62
- async function runCloudgrid(args) {
63
+
64
+ // Resolve and validate a caller-supplied working directory. Returns the resolved
65
+ // absolute path, or process.cwd() when omitted.
66
+ function resolveCwd(cwd) {
67
+ if (cwd === undefined || cwd === null || cwd === "") return undefined; // let execFile default
68
+ const abs = resolve(cwd);
69
+ if (!existsSync(abs)) {
70
+ throw new Error(`Directory does not exist: ${abs}`);
71
+ }
72
+ if (!statSync(abs).isDirectory()) {
73
+ throw new Error(`Not a directory: ${abs}`);
74
+ }
75
+ return abs;
76
+ }
77
+
78
+ // Resolve the bundled CLI entry point from this package's own node_modules.
79
+ // Returns the absolute path to the JS entry, or null if unresolvable.
80
+ function resolveBundledCli() {
63
81
  try {
64
- const { stdout, stderr } = await execFileAsync("cloudgrid", args, {
65
- maxBuffer: 16 * 1024 * 1024,
66
- timeout: 10 * 60 * 1000,
67
- });
82
+ const require = createRequire(import.meta.url);
83
+ const pkgPath = require.resolve("@cloudgrid-io/cli/package.json");
84
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
85
+ const bin = pkg.bin && pkg.bin.cloudgrid;
86
+ if (!bin) return null;
87
+ return join(dirname(pkgPath), bin);
88
+ } catch {
89
+ return null;
90
+ }
91
+ }
92
+
93
+ async function runCloudgrid(args, opts = {}) {
94
+ const cwd = resolveCwd(opts.cwd);
95
+ const execOpts = {
96
+ maxBuffer: 16 * 1024 * 1024,
97
+ timeout: 10 * 60 * 1000,
98
+ stdio: ["ignore", "pipe", "pipe"],
99
+ ...(cwd ? { cwd } : {}),
100
+ };
101
+
102
+ // 1. Try the bundled CLI (node <cliEntry> ...args)
103
+ const bundled = resolveBundledCli();
104
+ if (bundled) {
105
+ try {
106
+ const { stdout, stderr } = await execFileAsync(
107
+ process.execPath,
108
+ [bundled, ...args],
109
+ execOpts,
110
+ );
111
+ return (stdout || stderr || "").trim() || "Done.";
112
+ } catch (err) {
113
+ const detail = [err && err.stdout, err && err.stderr, err && err.message]
114
+ .filter(Boolean)
115
+ .join("\n")
116
+ .trim();
117
+ throw new Error(detail || "cloudgrid command failed");
118
+ }
119
+ }
120
+
121
+ // 2. Fallback: global `cloudgrid` on PATH
122
+ try {
123
+ const { stdout, stderr } = await execFileAsync("cloudgrid", args, execOpts);
68
124
  return (stdout || stderr || "").trim() || "Done.";
69
125
  } catch (err) {
70
126
  if (err && err.code === "ENOENT") {
@@ -80,10 +136,16 @@ async function runCloudgrid(args) {
80
136
  }
81
137
  }
82
138
 
83
- function cliTool(buildArgs) {
139
+ function cliTool(buildArgs, { cwdParam = false } = {}) {
84
140
  return async (input) => {
85
141
  try {
86
- return ok(await runCloudgrid(buildArgs(input || {})));
142
+ const params = input || {};
143
+ const opts = {};
144
+ if (cwdParam) {
145
+ // Accept cwd, directory, or dir as the working-directory override.
146
+ opts.cwd = params.cwd ?? params.directory ?? params.dir;
147
+ }
148
+ return ok(await runCloudgrid(buildArgs(params), opts));
87
149
  } catch (err) {
88
150
  return fail(err.message);
89
151
  }
@@ -774,6 +836,7 @@ export function registerTools(server, ctx) {
774
836
  description: z.string().optional().describe("Initial one-line description."),
775
837
  dir: z.string().optional().describe("Target directory. Defaults to ./<name>."),
776
838
  org: z.string().optional().describe("Override the active org for this init."),
839
+ cwd: z.string().optional().describe("Working directory. The CLI runs in this directory. Defaults to the MCP server's working directory."),
777
840
  },
778
841
  { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
779
842
  cliTool(({ kind, name, type, description, dir, org }) => {
@@ -783,7 +846,7 @@ export function registerTools(server, ctx) {
783
846
  if (dir) args.push("--dir", dir);
784
847
  if (org) args.push("--org", org);
785
848
  return args;
786
- }),
849
+ }, { cwdParam: true }),
787
850
  );
788
851
 
789
852
  server.tool(
@@ -793,6 +856,7 @@ export function registerTools(server, ctx) {
793
856
  target: z.string().optional().describe("Path or URL. Omit to deploy the entity linked to the current directory."),
794
857
  org: z.string().optional().describe("Pick or override the org."),
795
858
  no_deploy: z.boolean().optional().describe("Register the entity but do not build or deploy."),
859
+ cwd: z.string().optional().describe("Working directory. The CLI runs in this directory. Defaults to the MCP server's working directory."),
796
860
  },
797
861
  { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
798
862
  cliTool(({ target, org, no_deploy }) => {
@@ -800,9 +864,9 @@ export function registerTools(server, ctx) {
800
864
  if (target) args.push(target);
801
865
  if (org) args.push("--org", org);
802
866
  if (no_deploy) args.push("--no-deploy");
803
- args.push("--no-clipboard", "--no-notify");
867
+ args.push("--auto", "--no-clipboard", "--no-notify");
804
868
  return args;
805
- }),
869
+ }, { cwdParam: true }),
806
870
  );
807
871
 
808
872
  server.tool(
@@ -958,7 +1022,7 @@ export function registerTools(server, ctx) {
958
1022
  confirm: z.literal(true).describe("Must be true to proceed."),
959
1023
  },
960
1024
  { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
961
- cliTool(({ name }) => ["unplug", name]),
1025
+ cliTool(({ name }) => ["unplug", name, "--skip-confirm"]),
962
1026
  );
963
1027
 
964
1028
  server.tool(
@@ -969,7 +1033,7 @@ export function registerTools(server, ctx) {
969
1033
  confirm: z.literal(true).describe("Must be true to proceed."),
970
1034
  },
971
1035
  { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
972
- cliTool(({ name }) => ["delete", name]),
1036
+ cliTool(({ name }) => ["delete", name, "--yes"]),
973
1037
  );
974
1038
 
975
1039
  server.tool(
@@ -1007,19 +1071,20 @@ export function registerTools(server, ctx) {
1007
1071
  name: z.string().describe("Entity slug."),
1008
1072
  key: z.string().optional().describe("Variable name. Required for get and set."),
1009
1073
  value: z.string().optional().describe("Variable value. Required for set."),
1074
+ cwd: z.string().optional().describe("Working directory. The CLI runs in this directory. Defaults to the MCP server's working directory."),
1010
1075
  },
1011
1076
  { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
1012
1077
  cliTool(({ action, name, key, value }) => {
1013
1078
  if (action === "set") {
1014
1079
  if (!key || value === undefined) throw new Error("key and value are required for set");
1015
- return ["env", "set", name, key, value];
1080
+ return ["env", "set", name, `${key}=${value}`];
1016
1081
  }
1017
1082
  if (action === "get") {
1018
1083
  if (!key) throw new Error("key is required for get");
1019
- return ["env", "get", name, key];
1084
+ return ["env", "get", key, name];
1020
1085
  }
1021
1086
  return ["env", "list", name];
1022
- }),
1087
+ }, { cwdParam: true }),
1023
1088
  );
1024
1089
 
1025
1090
  server.tool(
@@ -1030,6 +1095,7 @@ export function registerTools(server, ctx) {
1030
1095
  name: z.string().describe("Entity slug."),
1031
1096
  key: z.string().optional().describe("Secret name. Required for set."),
1032
1097
  value: z.string().optional().describe("Secret value. Required for set."),
1098
+ cwd: z.string().optional().describe("Working directory. The CLI runs in this directory. Defaults to the MCP server's working directory."),
1033
1099
  },
1034
1100
  { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
1035
1101
  cliTool(({ action, name, key, value }) => {
@@ -1038,7 +1104,7 @@ export function registerTools(server, ctx) {
1038
1104
  return ["secrets", "set", name, key, value];
1039
1105
  }
1040
1106
  return ["secrets", "list", name];
1041
- }),
1107
+ }, { cwdParam: true }),
1042
1108
  );
1043
1109
 
1044
1110
  server.tool(
@@ -1047,6 +1113,7 @@ export function registerTools(server, ctx) {
1047
1113
  {
1048
1114
  template: z.string().optional().describe("Template name."),
1049
1115
  dir: z.string().optional().describe("Target directory."),
1116
+ cwd: z.string().optional().describe("Working directory. The CLI runs in this directory. Defaults to the MCP server's working directory."),
1050
1117
  },
1051
1118
  { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
1052
1119
  cliTool(({ template, dir }) => {
@@ -1054,7 +1121,7 @@ export function registerTools(server, ctx) {
1054
1121
  if (template) args.push(template);
1055
1122
  if (dir) args.push("--dir", dir);
1056
1123
  return args;
1057
- }),
1124
+ }, { cwdParam: true }),
1058
1125
  );
1059
1126
 
1060
1127
  server.tool(