@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.
- package/package.json +2 -1
- 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.
|
|
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
|
-
|
|
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
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
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
|
|
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",
|
|
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(
|