@cloudgrid-io/mcp 0.4.1 → 0.4.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.
- package/package.json +1 -1
- package/src/tools.js +63 -42
- package/src/web.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudgrid-io/mcp",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
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": {
|
package/src/tools.js
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
import { execFile } from "node:child_process";
|
|
12
12
|
import { promisify } from "node:util";
|
|
13
13
|
import { readFile } from "node:fs/promises";
|
|
14
|
-
import { readFileSync } from "node:fs";
|
|
15
|
-
import { basename } from "node:path";
|
|
14
|
+
import { readFileSync, existsSync, statSync } from "node:fs";
|
|
15
|
+
import { basename, resolve } from "node:path";
|
|
16
16
|
import { z } from "zod";
|
|
17
17
|
import { newLoginCode, buildLoginUrl, pollStatusOnce, decodeJwt } from "./auth.js";
|
|
18
18
|
|
|
@@ -59,11 +59,29 @@ function okResult({ text, structured, meta }) {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
// ── CLI wrapping (local edition only) ──────────────────────────────────────────
|
|
62
|
-
|
|
62
|
+
|
|
63
|
+
// Resolve and validate a caller-supplied working directory. Returns the resolved
|
|
64
|
+
// absolute path, or process.cwd() when omitted.
|
|
65
|
+
function resolveCwd(cwd) {
|
|
66
|
+
if (cwd === undefined || cwd === null || cwd === "") return undefined; // let execFile default
|
|
67
|
+
const abs = resolve(cwd);
|
|
68
|
+
if (!existsSync(abs)) {
|
|
69
|
+
throw new Error(`Directory does not exist: ${abs}`);
|
|
70
|
+
}
|
|
71
|
+
if (!statSync(abs).isDirectory()) {
|
|
72
|
+
throw new Error(`Not a directory: ${abs}`);
|
|
73
|
+
}
|
|
74
|
+
return abs;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function runCloudgrid(args, opts = {}) {
|
|
78
|
+
const cwd = resolveCwd(opts.cwd);
|
|
63
79
|
try {
|
|
64
80
|
const { stdout, stderr } = await execFileAsync("cloudgrid", args, {
|
|
65
81
|
maxBuffer: 16 * 1024 * 1024,
|
|
66
82
|
timeout: 10 * 60 * 1000,
|
|
83
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
84
|
+
...(cwd ? { cwd } : {}),
|
|
67
85
|
});
|
|
68
86
|
return (stdout || stderr || "").trim() || "Done.";
|
|
69
87
|
} catch (err) {
|
|
@@ -80,10 +98,16 @@ async function runCloudgrid(args) {
|
|
|
80
98
|
}
|
|
81
99
|
}
|
|
82
100
|
|
|
83
|
-
function cliTool(buildArgs) {
|
|
101
|
+
function cliTool(buildArgs, { cwdParam = false } = {}) {
|
|
84
102
|
return async (input) => {
|
|
85
103
|
try {
|
|
86
|
-
|
|
104
|
+
const params = input || {};
|
|
105
|
+
const opts = {};
|
|
106
|
+
if (cwdParam) {
|
|
107
|
+
// Accept cwd, directory, or dir as the working-directory override.
|
|
108
|
+
opts.cwd = params.cwd ?? params.directory ?? params.dir;
|
|
109
|
+
}
|
|
110
|
+
return ok(await runCloudgrid(buildArgs(params), opts));
|
|
87
111
|
} catch (err) {
|
|
88
112
|
return fail(err.message);
|
|
89
113
|
}
|
|
@@ -572,36 +596,28 @@ export function registerTools(server, ctx) {
|
|
|
572
596
|
structured: { needs_sign_in: true, login_url: url },
|
|
573
597
|
});
|
|
574
598
|
}
|
|
575
|
-
//
|
|
576
|
-
//
|
|
577
|
-
//
|
|
578
|
-
// picker was shown and the re-call supplies a valid slug do we honor
|
|
579
|
-
// it. This prevents the model from silently guessing and skipping
|
|
580
|
-
// the user's choice.
|
|
599
|
+
// Stateless org disambiguation — no dependency on prior-call state
|
|
600
|
+
// so it works even when the client reconnects on every tool call
|
|
601
|
+
// (ChatGPT Apps SDK behaviour).
|
|
581
602
|
{
|
|
582
603
|
const orgs = await fetchUserOrgs(token);
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
return okResult({
|
|
599
|
-
text: lines.join("\n"),
|
|
600
|
-
structured: { needs_org: true, orgs },
|
|
601
|
-
meta: { "openai/outputTemplate": ORG_PICKER_URI },
|
|
602
|
-
});
|
|
603
|
-
}
|
|
604
|
+
const suppliedOrg = input?.org;
|
|
605
|
+
const validOrg = suppliedOrg && orgs.some((o) => o.slug === suppliedOrg);
|
|
606
|
+
if (validOrg) {
|
|
607
|
+
// Supplied org matches a real org slug — publish to it.
|
|
608
|
+
input = { ...(input || {}), org: suppliedOrg };
|
|
609
|
+
} else if (orgs.length > 1) {
|
|
610
|
+
// No valid org supplied and multiple orgs — ask once.
|
|
611
|
+
const lines = ["Which org should this be published to?"];
|
|
612
|
+
for (const o of orgs) lines.push(` ${o.slug} — ${o.name} (${o.role})`);
|
|
613
|
+
lines.push("Pass the org slug in the org parameter to publish.");
|
|
614
|
+
return okResult({
|
|
615
|
+
text: lines.join("\n"),
|
|
616
|
+
structured: { needs_org: true, orgs },
|
|
617
|
+
meta: { "openai/outputTemplate": ORG_PICKER_URI },
|
|
618
|
+
});
|
|
604
619
|
} else if (orgs.length === 1) {
|
|
620
|
+
// Single org — publish to it silently.
|
|
605
621
|
input = { ...(input || {}), org: orgs[0].slug };
|
|
606
622
|
}
|
|
607
623
|
}
|
|
@@ -782,6 +798,7 @@ export function registerTools(server, ctx) {
|
|
|
782
798
|
description: z.string().optional().describe("Initial one-line description."),
|
|
783
799
|
dir: z.string().optional().describe("Target directory. Defaults to ./<name>."),
|
|
784
800
|
org: z.string().optional().describe("Override the active org for this init."),
|
|
801
|
+
cwd: z.string().optional().describe("Working directory. The CLI runs in this directory. Defaults to the MCP server's working directory."),
|
|
785
802
|
},
|
|
786
803
|
{ readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
787
804
|
cliTool(({ kind, name, type, description, dir, org }) => {
|
|
@@ -791,7 +808,7 @@ export function registerTools(server, ctx) {
|
|
|
791
808
|
if (dir) args.push("--dir", dir);
|
|
792
809
|
if (org) args.push("--org", org);
|
|
793
810
|
return args;
|
|
794
|
-
}),
|
|
811
|
+
}, { cwdParam: true }),
|
|
795
812
|
);
|
|
796
813
|
|
|
797
814
|
server.tool(
|
|
@@ -801,6 +818,7 @@ export function registerTools(server, ctx) {
|
|
|
801
818
|
target: z.string().optional().describe("Path or URL. Omit to deploy the entity linked to the current directory."),
|
|
802
819
|
org: z.string().optional().describe("Pick or override the org."),
|
|
803
820
|
no_deploy: z.boolean().optional().describe("Register the entity but do not build or deploy."),
|
|
821
|
+
cwd: z.string().optional().describe("Working directory. The CLI runs in this directory. Defaults to the MCP server's working directory."),
|
|
804
822
|
},
|
|
805
823
|
{ readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
806
824
|
cliTool(({ target, org, no_deploy }) => {
|
|
@@ -808,9 +826,9 @@ export function registerTools(server, ctx) {
|
|
|
808
826
|
if (target) args.push(target);
|
|
809
827
|
if (org) args.push("--org", org);
|
|
810
828
|
if (no_deploy) args.push("--no-deploy");
|
|
811
|
-
args.push("--no-clipboard", "--no-notify");
|
|
829
|
+
args.push("--auto", "--no-clipboard", "--no-notify");
|
|
812
830
|
return args;
|
|
813
|
-
}),
|
|
831
|
+
}, { cwdParam: true }),
|
|
814
832
|
);
|
|
815
833
|
|
|
816
834
|
server.tool(
|
|
@@ -966,7 +984,7 @@ export function registerTools(server, ctx) {
|
|
|
966
984
|
confirm: z.literal(true).describe("Must be true to proceed."),
|
|
967
985
|
},
|
|
968
986
|
{ readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
969
|
-
cliTool(({ name }) => ["unplug", name]),
|
|
987
|
+
cliTool(({ name }) => ["unplug", name, "--skip-confirm"]),
|
|
970
988
|
);
|
|
971
989
|
|
|
972
990
|
server.tool(
|
|
@@ -977,7 +995,7 @@ export function registerTools(server, ctx) {
|
|
|
977
995
|
confirm: z.literal(true).describe("Must be true to proceed."),
|
|
978
996
|
},
|
|
979
997
|
{ readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
980
|
-
cliTool(({ name }) => ["delete", name]),
|
|
998
|
+
cliTool(({ name }) => ["delete", name, "--yes"]),
|
|
981
999
|
);
|
|
982
1000
|
|
|
983
1001
|
server.tool(
|
|
@@ -1015,19 +1033,20 @@ export function registerTools(server, ctx) {
|
|
|
1015
1033
|
name: z.string().describe("Entity slug."),
|
|
1016
1034
|
key: z.string().optional().describe("Variable name. Required for get and set."),
|
|
1017
1035
|
value: z.string().optional().describe("Variable value. Required for set."),
|
|
1036
|
+
cwd: z.string().optional().describe("Working directory. The CLI runs in this directory. Defaults to the MCP server's working directory."),
|
|
1018
1037
|
},
|
|
1019
1038
|
{ readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
1020
1039
|
cliTool(({ action, name, key, value }) => {
|
|
1021
1040
|
if (action === "set") {
|
|
1022
1041
|
if (!key || value === undefined) throw new Error("key and value are required for set");
|
|
1023
|
-
return ["env", "set", name, key
|
|
1042
|
+
return ["env", "set", name, `${key}=${value}`];
|
|
1024
1043
|
}
|
|
1025
1044
|
if (action === "get") {
|
|
1026
1045
|
if (!key) throw new Error("key is required for get");
|
|
1027
|
-
return ["env", "get",
|
|
1046
|
+
return ["env", "get", key, name];
|
|
1028
1047
|
}
|
|
1029
1048
|
return ["env", "list", name];
|
|
1030
|
-
}),
|
|
1049
|
+
}, { cwdParam: true }),
|
|
1031
1050
|
);
|
|
1032
1051
|
|
|
1033
1052
|
server.tool(
|
|
@@ -1038,6 +1057,7 @@ export function registerTools(server, ctx) {
|
|
|
1038
1057
|
name: z.string().describe("Entity slug."),
|
|
1039
1058
|
key: z.string().optional().describe("Secret name. Required for set."),
|
|
1040
1059
|
value: z.string().optional().describe("Secret value. Required for set."),
|
|
1060
|
+
cwd: z.string().optional().describe("Working directory. The CLI runs in this directory. Defaults to the MCP server's working directory."),
|
|
1041
1061
|
},
|
|
1042
1062
|
{ readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
1043
1063
|
cliTool(({ action, name, key, value }) => {
|
|
@@ -1046,7 +1066,7 @@ export function registerTools(server, ctx) {
|
|
|
1046
1066
|
return ["secrets", "set", name, key, value];
|
|
1047
1067
|
}
|
|
1048
1068
|
return ["secrets", "list", name];
|
|
1049
|
-
}),
|
|
1069
|
+
}, { cwdParam: true }),
|
|
1050
1070
|
);
|
|
1051
1071
|
|
|
1052
1072
|
server.tool(
|
|
@@ -1055,6 +1075,7 @@ export function registerTools(server, ctx) {
|
|
|
1055
1075
|
{
|
|
1056
1076
|
template: z.string().optional().describe("Template name."),
|
|
1057
1077
|
dir: z.string().optional().describe("Target directory."),
|
|
1078
|
+
cwd: z.string().optional().describe("Working directory. The CLI runs in this directory. Defaults to the MCP server's working directory."),
|
|
1058
1079
|
},
|
|
1059
1080
|
{ readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
1060
1081
|
cliTool(({ template, dir }) => {
|
|
@@ -1062,7 +1083,7 @@ export function registerTools(server, ctx) {
|
|
|
1062
1083
|
if (template) args.push(template);
|
|
1063
1084
|
if (dir) args.push("--dir", dir);
|
|
1064
1085
|
return args;
|
|
1065
|
-
}),
|
|
1086
|
+
}, { cwdParam: true }),
|
|
1066
1087
|
);
|
|
1067
1088
|
|
|
1068
1089
|
server.tool(
|
package/src/web.js
CHANGED
|
@@ -55,7 +55,7 @@ function makeWebContext(sessionId) {
|
|
|
55
55
|
let sessionToken = null;
|
|
56
56
|
return {
|
|
57
57
|
edition: "web",
|
|
58
|
-
state: { pendingLoginCode: null, lastAnonClaim: null, lastDrop: null, anonCookie: null
|
|
58
|
+
state: { pendingLoginCode: null, lastAnonClaim: null, lastDrop: null, anonCookie: null },
|
|
59
59
|
canOpenBrowser: false,
|
|
60
60
|
// Transport OAuth wins; the in-tool login flow is the fallback.
|
|
61
61
|
getToken: async () => sessionAuth[sessionId] ?? sessionToken,
|