@cloudgrid-io/mcp 0.4.5 → 0.4.6

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudgrid-io/mcp",
3
- "version": "0.4.5",
3
+ "version": "0.4.6",
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
@@ -166,7 +166,7 @@ function tryOpenBrowser(url) {
166
166
  // ── Org listing (bearer-authed, web edition) ──────────────────────────────────
167
167
  // Fetches the signed-in user's orgs via GET /api/v2/orgs. The JWT does not
168
168
  // carry orgs (claims: sub, email, name, iat, exp), so the API is the canonical
169
- // source. Returns [{slug, name, role}].
169
+ // source. Returns [{slug, name, role, render_ready}].
170
170
  async function fetchUserOrgs(token) {
171
171
  try {
172
172
  const res = await fetch(`${API_BASE}/api/v2/orgs`, {
@@ -179,6 +179,7 @@ async function fetchUserOrgs(token) {
179
179
  slug: o.slug ?? "",
180
180
  name: o.name ?? o.slug ?? "",
181
181
  role: o.role ?? "member",
182
+ render_ready: o.render_ready ?? true, // default true for older APIs
182
183
  }));
183
184
  } catch {
184
185
  return [];
@@ -646,9 +647,11 @@ export function registerTools(server, ctx) {
646
647
  const orgs = await fetchUserOrgs(token);
647
648
  const activeOrg = await ctx.getActiveOrg();
648
649
  const suppliedOrg = input?.org;
649
- const validOrg = suppliedOrg && orgs.some((o) => o.slug === suppliedOrg);
650
- if (validOrg) {
651
- // Supplied org matches a real org slug — publish to it.
650
+ const matchedOrg = suppliedOrg && orgs.find((o) => o.slug === suppliedOrg);
651
+ if (matchedOrg) {
652
+ // Supplied org matches — proceed with it. The agent should have
653
+ // already checked render_ready via cloudgrid_orgs and warned the
654
+ // user if this org isn't set up yet. We don't block here.
652
655
  input = { ...(input || {}), org: suppliedOrg };
653
656
  } else if (orgs.length > 1) {
654
657
  // No valid org supplied and multiple orgs — ask once.
@@ -657,20 +660,40 @@ export function registerTools(server, ctx) {
657
660
  ...o,
658
661
  is_active: o.slug === activeOrg,
659
662
  }));
660
- annotated.sort((a, b) => (b.is_active ? 1 : 0) - (a.is_active ? 1 : 0));
663
+ // Sort: active org first, then ready orgs, then not-ready orgs.
664
+ annotated.sort((a, b) => {
665
+ if (a.is_active !== b.is_active) return b.is_active ? 1 : -1;
666
+ if (a.render_ready !== b.render_ready) return b.render_ready ? 1 : -1;
667
+ return 0;
668
+ });
661
669
  const lines = ["Which org should this be published to?"];
662
670
  for (const o of annotated) {
663
- const tag = o.is_active ? " (your active org)" : "";
664
- lines.push(` ${o.slug} — ${o.name} (${o.role})${tag}`);
671
+ const tags = [];
672
+ if (o.is_active) tags.push("your active org");
673
+ if (!o.render_ready) tags.push("not set up yet");
674
+ const suffix = tags.length ? ` (${tags.join(", ")})` : "";
675
+ lines.push(` ${o.slug} — ${o.name} (${o.role})${suffix}`);
665
676
  }
666
677
  lines.push("Pass the org slug in the org parameter to publish.");
678
+ const readyCount = annotated.filter((o) => o.render_ready).length;
679
+ if (readyCount === 0) {
680
+ lines.push("Note: none of your orgs are fully set up yet. You can use anonymous: true as a fallback.");
681
+ }
667
682
  return okResult({
668
683
  text: lines.join("\n"),
669
684
  structured: { needs_org: true, orgs: annotated },
670
685
  ...(ctx.edition === "web" ? { meta: { "openai/outputTemplate": ORG_PICKER_URI } } : {}),
671
686
  });
672
687
  } else if (orgs.length === 1) {
673
- // Single org — publish to it silently.
688
+ if (!orgs[0].render_ready) {
689
+ // Single org but not set up — tell the agent so it can warn
690
+ // the user and offer anonymous drop as fallback.
691
+ const annotated = [{ ...orgs[0], is_active: orgs[0].slug === activeOrg, render_ready: false }];
692
+ return okResult({
693
+ text: `Your only org "${orgs[0].slug}" isn't fully set up yet — pages published there may not load. You can drop anonymously (set anonymous: true) or wait until provisioning completes, then re-run.`,
694
+ structured: { needs_org: true, org_not_ready: true, orgs: annotated },
695
+ });
696
+ }
674
697
  input = { ...(input || {}), org: orgs[0].slug };
675
698
  }
676
699
  }
@@ -810,7 +833,7 @@ export function registerTools(server, ctx) {
810
833
  server.registerTool(
811
834
  "cloudgrid_orgs",
812
835
  {
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.",
836
+ description: "List the signed-in user's organizations. Returns each org's slug, name, role, and render_ready status. Orgs where render_ready is false are still provisioning — pages published there may not load yet. Prefer a render_ready org; if the user insists on a not-ready one, warn them that pages may not load and suggest waiting or choosing a ready org instead. Requires sign-in.",
814
837
  inputSchema: {},
815
838
  outputSchema: {
816
839
  orgs: z.array(z.object({
@@ -818,6 +841,7 @@ export function registerTools(server, ctx) {
818
841
  name: z.string().describe("Human-readable org name."),
819
842
  role: z.string().describe("User's role in the org."),
820
843
  is_active: z.boolean().optional().describe("True if this is the user's currently active org."),
844
+ render_ready: z.boolean().describe("True if the org's DNS and TLS are provisioned and pages will load. False means the org is still being set up."),
821
845
  })).describe("The user's org memberships."),
822
846
  },
823
847
  annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
@@ -836,11 +860,23 @@ export function registerTools(server, ctx) {
836
860
  ...o,
837
861
  is_active: o.slug === activeOrg,
838
862
  }));
839
- annotated.sort((a, b) => (b.is_active ? 1 : 0) - (a.is_active ? 1 : 0));
863
+ // Sort: active org first, then ready orgs, then not-ready orgs.
864
+ annotated.sort((a, b) => {
865
+ if (a.is_active !== b.is_active) return b.is_active ? 1 : -1;
866
+ if (a.render_ready !== b.render_ready) return b.render_ready ? 1 : -1;
867
+ return 0;
868
+ });
840
869
  const lines = annotated.map((o) => {
841
- const tag = o.is_active ? " (your active org)" : "";
842
- return `${o.slug} — ${o.name} (${o.role})${tag}`;
870
+ const tags = [];
871
+ if (o.is_active) tags.push("your active org");
872
+ if (!o.render_ready) tags.push("not set up yet");
873
+ const suffix = tags.length ? ` (${tags.join(", ")})` : "";
874
+ return `${o.slug} — ${o.name} (${o.role})${suffix}`;
843
875
  });
876
+ const readyCount = annotated.filter((o) => o.render_ready).length;
877
+ if (readyCount === 0 && annotated.length > 0) {
878
+ lines.push("\nNone of your orgs are fully set up yet. You can use an anonymous drop as a fallback, or wait until provisioning completes.");
879
+ }
844
880
  return okResult({ text: lines.join("\n"), structured: { orgs: annotated } });
845
881
  },
846
882
  );
@@ -13,8 +13,11 @@ body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;b
13
13
  .org-btn{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-radius:8px;border:1px solid rgba(138,107,255,.3);background:transparent;color:#e0e0e0;cursor:pointer;transition:all .15s;font-size:13px;text-align:left}
14
14
  .org-btn:hover{border-color:#8a6bff;background:rgba(138,107,255,.1)}
15
15
  .org-btn.loading{opacity:.6;pointer-events:none}
16
+ .org-btn.not-ready{opacity:.65;border-color:rgba(255,200,50,.3)}
17
+ .org-btn.not-ready:hover{border-color:rgba(255,200,50,.6);background:rgba(255,200,50,.06)}
16
18
  .org-name{font-weight:500;color:#fff}
17
19
  .org-role{font-size:11px;color:#9ca3af}
20
+ .org-warn{font-size:10px;color:#e2b340;margin-left:6px}
18
21
  .status{font-size:12px;color:#9ca3af;margin-top:12px}
19
22
  </style>
20
23
  </head>
@@ -43,9 +46,10 @@ body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;b
43
46
  '<div class="orgs">';
44
47
  for (var i = 0; i < data.orgs.length; i++) {
45
48
  var org = data.orgs[i];
49
+ var notReady = org.render_ready === false;
46
50
  html +=
47
- '<button class="org-btn" data-slug="' + esc(org.slug) + '">' +
48
- '<span class="org-name">' + esc(org.name) + '</span>' +
51
+ '<button class="org-btn' + (notReady ? ' not-ready' : '') + '" data-slug="' + esc(org.slug) + '">' +
52
+ '<span class="org-name">' + esc(org.name) + (notReady ? '<span class="org-warn">not set up yet</span>' : '') + '</span>' +
49
53
  '<span class="org-role">' + esc(org.role) + '</span>' +
50
54
  '</button>';
51
55
  }