@cloudgrid-io/mcp 0.4.3 → 0.4.5
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 +4 -2
- package/src/tools.js +104 -43
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudgrid-io/mcp",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
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": {
|
|
@@ -22,9 +22,11 @@
|
|
|
22
22
|
"smoke:web": "node test/smoke-web.mjs",
|
|
23
23
|
"smoke:redrop": "node test/smoke-redrop.mjs",
|
|
24
24
|
"smoke:oauth": "node test/smoke-oauth.mjs",
|
|
25
|
-
"test:auth": "node test/auth.test.mjs"
|
|
25
|
+
"test:auth": "node test/auth.test.mjs",
|
|
26
|
+
"build:mcpb": "node scripts/build-mcpb.mjs"
|
|
26
27
|
},
|
|
27
28
|
"dependencies": {
|
|
29
|
+
"@cloudgrid-io/cli": "^0.9.20",
|
|
28
30
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
29
31
|
"express": "^4.22.2",
|
|
30
32
|
"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
15
|
import { readFileSync, existsSync, statSync } from "node:fs";
|
|
15
|
-
import { basename, resolve } from "node:path";
|
|
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
|
|
|
@@ -74,15 +75,52 @@ function resolveCwd(cwd) {
|
|
|
74
75
|
return abs;
|
|
75
76
|
}
|
|
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() {
|
|
81
|
+
try {
|
|
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
|
+
|
|
77
93
|
async function runCloudgrid(args, opts = {}) {
|
|
78
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
|
|
79
122
|
try {
|
|
80
|
-
const { stdout, stderr } = await execFileAsync("cloudgrid", args,
|
|
81
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
82
|
-
timeout: 10 * 60 * 1000,
|
|
83
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
84
|
-
...(cwd ? { cwd } : {}),
|
|
85
|
-
});
|
|
123
|
+
const { stdout, stderr } = await execFileAsync("cloudgrid", args, execOpts);
|
|
86
124
|
return (stdout || stderr || "").trim() || "Done.";
|
|
87
125
|
} catch (err) {
|
|
88
126
|
if (err && err.code === "ENOENT") {
|
|
@@ -563,6 +601,7 @@ export function registerTools(server, ctx) {
|
|
|
563
601
|
slug: z.string().describe("Org slug to pass as the org parameter."),
|
|
564
602
|
name: z.string().describe("Human-readable org name."),
|
|
565
603
|
role: z.string().describe("User's role in the org."),
|
|
604
|
+
is_active: z.boolean().optional().describe("True if this is the user's currently active org."),
|
|
566
605
|
})).optional().describe("The user's orgs, when org choice is needed."),
|
|
567
606
|
needs_sign_in: z.boolean().optional().describe("True when sign-in is needed before dropping."),
|
|
568
607
|
login_url: z.string().optional().describe("Sign-in URL when authentication is needed."),
|
|
@@ -596,11 +635,16 @@ export function registerTools(server, ctx) {
|
|
|
596
635
|
structured: { needs_sign_in: true, login_url: url },
|
|
597
636
|
});
|
|
598
637
|
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// Stateless org disambiguation — both editions, when authenticated.
|
|
641
|
+
// No dependency on prior-call state so it works even when the client
|
|
642
|
+
// reconnects on every tool call (ChatGPT Apps SDK behaviour).
|
|
643
|
+
if (input?.anonymous !== true) {
|
|
644
|
+
const token = await ctx.getToken();
|
|
645
|
+
if (token) {
|
|
603
646
|
const orgs = await fetchUserOrgs(token);
|
|
647
|
+
const activeOrg = await ctx.getActiveOrg();
|
|
604
648
|
const suppliedOrg = input?.org;
|
|
605
649
|
const validOrg = suppliedOrg && orgs.some((o) => o.slug === suppliedOrg);
|
|
606
650
|
if (validOrg) {
|
|
@@ -608,13 +652,22 @@ export function registerTools(server, ctx) {
|
|
|
608
652
|
input = { ...(input || {}), org: suppliedOrg };
|
|
609
653
|
} else if (orgs.length > 1) {
|
|
610
654
|
// No valid org supplied and multiple orgs — ask once.
|
|
655
|
+
// Mark the active org so the agent can offer it as the default.
|
|
656
|
+
const annotated = orgs.map((o) => ({
|
|
657
|
+
...o,
|
|
658
|
+
is_active: o.slug === activeOrg,
|
|
659
|
+
}));
|
|
660
|
+
annotated.sort((a, b) => (b.is_active ? 1 : 0) - (a.is_active ? 1 : 0));
|
|
611
661
|
const lines = ["Which org should this be published to?"];
|
|
612
|
-
for (const o of
|
|
662
|
+
for (const o of annotated) {
|
|
663
|
+
const tag = o.is_active ? " (your active org)" : "";
|
|
664
|
+
lines.push(` ${o.slug} — ${o.name} (${o.role})${tag}`);
|
|
665
|
+
}
|
|
613
666
|
lines.push("Pass the org slug in the org parameter to publish.");
|
|
614
667
|
return okResult({
|
|
615
668
|
text: lines.join("\n"),
|
|
616
|
-
structured: { needs_org: true, orgs },
|
|
617
|
-
meta: { "openai/outputTemplate": ORG_PICKER_URI },
|
|
669
|
+
structured: { needs_org: true, orgs: annotated },
|
|
670
|
+
...(ctx.edition === "web" ? { meta: { "openai/outputTemplate": ORG_PICKER_URI } } : {}),
|
|
618
671
|
});
|
|
619
672
|
} else if (orgs.length === 1) {
|
|
620
673
|
// Single org — publish to it silently.
|
|
@@ -753,36 +806,44 @@ export function registerTools(server, ctx) {
|
|
|
753
806
|
},
|
|
754
807
|
);
|
|
755
808
|
|
|
756
|
-
// Org listing —
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
},
|
|
770
|
-
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
|
|
771
|
-
},
|
|
772
|
-
async () => {
|
|
773
|
-
const token = await ctx.getToken();
|
|
774
|
-
if (!token) {
|
|
775
|
-
return fail("You are not signed in. Run cloudgrid_login first.");
|
|
776
|
-
}
|
|
777
|
-
const orgs = await fetchUserOrgs(token);
|
|
778
|
-
if (orgs.length === 0) {
|
|
779
|
-
return okResult({ text: "No organizations found.", structured: { orgs: [] } });
|
|
780
|
-
}
|
|
781
|
-
const lines = orgs.map((o) => `${o.slug} — ${o.name} (${o.role})`);
|
|
782
|
-
return okResult({ text: lines.join("\n"), structured: { orgs } });
|
|
809
|
+
// Org listing — both editions.
|
|
810
|
+
server.registerTool(
|
|
811
|
+
"cloudgrid_orgs",
|
|
812
|
+
{
|
|
813
|
+
description: "List the signed-in user's organizations. Returns each org's slug, name, and the user's role. Use to discover which org to publish to. Requires sign-in.",
|
|
814
|
+
inputSchema: {},
|
|
815
|
+
outputSchema: {
|
|
816
|
+
orgs: z.array(z.object({
|
|
817
|
+
slug: z.string().describe("Org slug."),
|
|
818
|
+
name: z.string().describe("Human-readable org name."),
|
|
819
|
+
role: z.string().describe("User's role in the org."),
|
|
820
|
+
is_active: z.boolean().optional().describe("True if this is the user's currently active org."),
|
|
821
|
+
})).describe("The user's org memberships."),
|
|
783
822
|
},
|
|
784
|
-
|
|
785
|
-
|
|
823
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
|
|
824
|
+
},
|
|
825
|
+
async () => {
|
|
826
|
+
const token = await ctx.getToken();
|
|
827
|
+
if (!token) {
|
|
828
|
+
return fail("You are not signed in. Run cloudgrid_login first.");
|
|
829
|
+
}
|
|
830
|
+
const orgs = await fetchUserOrgs(token);
|
|
831
|
+
if (orgs.length === 0) {
|
|
832
|
+
return okResult({ text: "No organizations found.", structured: { orgs: [] } });
|
|
833
|
+
}
|
|
834
|
+
const activeOrg = await ctx.getActiveOrg();
|
|
835
|
+
const annotated = orgs.map((o) => ({
|
|
836
|
+
...o,
|
|
837
|
+
is_active: o.slug === activeOrg,
|
|
838
|
+
}));
|
|
839
|
+
annotated.sort((a, b) => (b.is_active ? 1 : 0) - (a.is_active ? 1 : 0));
|
|
840
|
+
const lines = annotated.map((o) => {
|
|
841
|
+
const tag = o.is_active ? " (your active org)" : "";
|
|
842
|
+
return `${o.slug} — ${o.name} (${o.role})${tag}`;
|
|
843
|
+
});
|
|
844
|
+
return okResult({ text: lines.join("\n"), structured: { orgs: annotated } });
|
|
845
|
+
},
|
|
846
|
+
);
|
|
786
847
|
|
|
787
848
|
if (ctx.edition !== "local") return; // web edition stops here — no CLI tools
|
|
788
849
|
|