@cloudgrid-io/mcp 0.2.8 → 0.3.0

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 (3) hide show
  1. package/README.md +37 -10
  2. package/package.json +1 -1
  3. package/src/tools.js +223 -1
package/README.md CHANGED
@@ -43,30 +43,57 @@ It speaks MCP over stdio. Point any MCP client at the `cloudgrid-mcp` command.
43
43
 
44
44
  ## Tools
45
45
 
46
+ ### Direct-API tools (both editions)
47
+
46
48
  | Tool | Wraps | Notes |
47
49
  |---|---|---|
48
- | `cloudgrid_drop` | `POST /api/v2/drop/auto` | Artifact drop. Anonymous, or owned if signed in. No CLI. Direct API. |
50
+ | `cloudgrid_drop` | `POST /api/v2/drop/auto` | Artifact drop. Anonymous, or owned if signed in. Direct API. |
49
51
  | `cloudgrid_claim` | `POST /api/v2/anon-claim` | Claim an anonymous drop into the signed-in account. Direct API. |
50
- | `cloudgrid_login` | `GET /auth/login` | Start a CLI-free sign-in; returns a URL to open. Calls the API directly. |
52
+ | `cloudgrid_login` | `GET /auth/login` | Start a CLI-free sign-in; returns a URL to open. Direct API. |
51
53
  | `cloudgrid_login_status` | `GET /auth/status` | Finish the sign-in; saves the token to the shared CLI credentials. |
52
54
  | `cloudgrid_visibility` | `PATCH /api/v2/inspirations/<id>` | Change who can see a drop (private, space, authenticated, org, link). Needs sign-in. Direct API; also in the web edition. |
53
- | `cloudgrid_init` | `cloudgrid init` | Register an app or agent; optionally seed a web service. |
54
- | `cloudgrid_plug` | `cloudgrid plug` | Deploy a directory or URL. |
55
- | `cloudgrid_logs` | `cloudgrid logs` | Snapshot of recent logs. Does not stream. |
56
- | `cloudgrid_share` | `cloudgrid visibility set` | Set visibility, default link. |
57
- | `cloudgrid_feedback` | `cloudgrid feedback list` | Read the org feedback feed. |
58
- | `cloudgrid_brain` | `cloudgrid brain refresh` | Re-run the Grid Brain hooks. |
59
55
 
60
56
  `cloudgrid_drop`, `cloudgrid_claim`, `cloudgrid_visibility`, and the two
61
- `cloudgrid_login` tools do not wrap the CLI they call the API directly, so they
57
+ `cloudgrid_login` tools do not wrap the CLI -- they call the API directly, so they
62
58
  also work in the web edition where no CLI exists. `cloudgrid_login` writes the same
63
59
  `~/.cloudgrid/credentials` the CLI uses, so the two share one identity.
64
60
 
61
+ ### CLI-wrapping tools (local edition only)
62
+
63
+ | Tool | Wraps | Notes |
64
+ |---|---|---|
65
+ | `cloudgrid_init` | `cloudgrid init` | Register an app or agent; optionally seed a web service. |
66
+ | `cloudgrid_plug` | `cloudgrid plug` | Deploy a directory or URL. |
67
+ | `cloudgrid_logs` | `cloudgrid logs` | Snapshot of recent logs. Does not stream. Read-only. |
68
+ | `cloudgrid_share` | `cloudgrid visibility set` | Set visibility, default link. |
69
+ | `cloudgrid_feedback` | `cloudgrid feedback list` | Read the org feedback feed. Read-only. |
70
+ | `cloudgrid_brain` | `cloudgrid brain refresh` | Re-run the Grid Brain hooks. |
71
+ | `cloudgrid_whoami` | `cloudgrid whoami` | Show the signed-in user and active org. Read-only. |
72
+ | `cloudgrid_use` | `cloudgrid use` | Switch the active org. |
73
+ | `cloudgrid_logout` | `cloudgrid logout` | Sign out and clear local credentials. Destructive. |
74
+ | `cloudgrid_status` | `cloudgrid status` | Org dashboard or entity detail. Read-only. |
75
+ | `cloudgrid_info` | `cloudgrid info` | Entity metadata. Read-only. |
76
+ | `cloudgrid_builds` | `cloudgrid builds` | Recent builds and deploys. Read-only. |
77
+ | `cloudgrid_grid` | `cloudgrid grid` | List entities on the hub. Read-only. |
78
+ | `cloudgrid_rename` | `cloudgrid rename` | Rename an entity. |
79
+ | `cloudgrid_unplug` | `cloudgrid unplug` | Take an entity off the grid. Destructive; requires confirm. |
80
+ | `cloudgrid_delete` | `cloudgrid delete` | Archive and delete an entity. Destructive; requires confirm. |
81
+ | `cloudgrid_rollback` | `cloudgrid rollback` | Rollback to a previous version. |
82
+ | `cloudgrid_versions` | `cloudgrid versions` | List published versions. Read-only. |
83
+ | `cloudgrid_env` | `cloudgrid env` | Get, set, or list environment variables. |
84
+ | `cloudgrid_secrets` | `cloudgrid secrets` | Set or list secret names. Never returns secret values. |
85
+ | `cloudgrid_scaffold` | `cloudgrid scaffold` | Generate starter files. |
86
+ | `cloudgrid_doctor` | `cloudgrid doctor` | Run local diagnostics. Read-only. |
87
+ | `cloudgrid_open` | `cloudgrid open` | Return the public URL. Does not open a browser. Read-only. |
88
+
65
89
  `cloudgrid_share` and `cloudgrid_visibility` overlap on purpose: `cloudgrid_share`
66
90
  wraps the CLI and defaults to `link`; `cloudgrid_visibility` is direct API, takes an
67
- explicit scope, and defaults its target to the session's last drop it is the one
91
+ explicit scope, and defaults its target to the session's last drop -- it is the one
68
92
  the web edition gets.
69
93
 
94
+ All tools carry MCP annotations (`readOnlyHint`, `destructiveHint`,
95
+ `openWorldHint`) for clients that support them.
96
+
70
97
  ## Test
71
98
 
72
99
  A smoke test spawns the server with a real MCP client, lists the tools, and calls
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudgrid-io/mcp",
3
- "version": "0.2.8",
3
+ "version": "0.3.0",
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
@@ -316,6 +316,8 @@ async function runVisibility(ctx, { target, visibility, org }) {
316
316
  // Registers the tools onto `server`. ctx.edition decides whether the CLI-wrapping
317
317
  // tools are included (they need a local machine).
318
318
  export function registerTools(server, ctx) {
319
+ // ── Direct-API tools (both editions) ──────────────────────────────────────
320
+
319
321
  // Drop — both editions.
320
322
  server.tool(
321
323
  "cloudgrid_drop",
@@ -331,6 +333,7 @@ export function registerTools(server, ctx) {
331
333
  .optional()
332
334
  .describe("Force a new drop even if you already dropped in this session (default: update in place)."),
333
335
  },
336
+ { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
334
337
  async (input) => {
335
338
  try {
336
339
  return ok(await runDrop(ctx, input || {}));
@@ -348,6 +351,7 @@ export function registerTools(server, ctx) {
348
351
  claim_token: z.string().optional().describe("The claim token from an anonymous drop."),
349
352
  claim_url: z.string().optional().describe("The claim_url from an anonymous drop; the token is read from it."),
350
353
  },
354
+ { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
351
355
  async (input) => {
352
356
  try {
353
357
  return ok(await runClaim(ctx, input || {}));
@@ -363,6 +367,7 @@ export function registerTools(server, ctx) {
363
367
  "cloudgrid_login",
364
368
  "Start a CLI-free CloudGrid sign-in. Use when the user wants to log in, sign in, or authenticate, or to claim an anonymous drop. Returns a URL to open in the browser; then call cloudgrid_login_status to finish. Uses CloudGrid's existing OAuth.",
365
369
  {},
370
+ { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
366
371
  async () => {
367
372
  const code = newLoginCode();
368
373
  ctx.state.pendingLoginCode = code;
@@ -381,6 +386,7 @@ export function registerTools(server, ctx) {
381
386
  {
382
387
  code: z.string().optional().describe("The sign-in code. Defaults to the most recent cloudgrid_login."),
383
388
  },
389
+ { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
384
390
  async (input) => {
385
391
  const code = input?.code || ctx.state.pendingLoginCode;
386
392
  if (!code) return fail("No sign-in is in progress. Run cloudgrid_login first.");
@@ -419,6 +425,7 @@ export function registerTools(server, ctx) {
419
425
  target: z.string().optional().describe("Entity id. Defaults to this session's last drop."),
420
426
  org: z.string().optional().describe("Org of the entity. Defaults to the active org."),
421
427
  },
428
+ { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
422
429
  async (input) => {
423
430
  try {
424
431
  return ok(await runVisibility(ctx, input || {}));
@@ -430,7 +437,8 @@ export function registerTools(server, ctx) {
430
437
 
431
438
  if (ctx.edition !== "local") return; // web edition stops here — no CLI tools
432
439
 
433
- // ── CLI-wrapping tools (local edition only) ──
440
+ // ── CLI-wrapping tools (local edition only) ───────────────────────────────
441
+
434
442
  server.tool(
435
443
  "cloudgrid_init",
436
444
  "Register a new CloudGrid app or agent, optionally seeding a web service. Wraps `cloudgrid init`.",
@@ -442,6 +450,7 @@ export function registerTools(server, ctx) {
442
450
  dir: z.string().optional().describe("Target directory. Defaults to ./<name>."),
443
451
  org: z.string().optional().describe("Override the active org for this init."),
444
452
  },
453
+ { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
445
454
  cliTool(({ kind, name, type, description, dir, org }) => {
446
455
  const args = ["init", kind, name];
447
456
  if (type) args.push("--type", type);
@@ -460,6 +469,7 @@ export function registerTools(server, ctx) {
460
469
  org: z.string().optional().describe("Pick or override the org."),
461
470
  no_deploy: z.boolean().optional().describe("Register the entity but do not build or deploy."),
462
471
  },
472
+ { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
463
473
  cliTool(({ target, org, no_deploy }) => {
464
474
  const args = ["plug"];
465
475
  if (target) args.push(target);
@@ -478,6 +488,7 @@ export function registerTools(server, ctx) {
478
488
  tail: z.number().int().positive().optional().describe("Number of recent lines. Default 100."),
479
489
  since: z.string().optional().describe("Only logs newer than this, e.g. 5m, 1h, 2d."),
480
490
  },
491
+ { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
481
492
  cliTool(({ name, tail, since }) => {
482
493
  const args = ["logs"];
483
494
  if (name) args.push(name);
@@ -494,6 +505,7 @@ export function registerTools(server, ctx) {
494
505
  name: z.string().describe("Entity slug."),
495
506
  mode: z.enum(["link", "private", "authenticated", "org", "space"]).optional().describe("Visibility mode. Default link."),
496
507
  },
508
+ { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
497
509
  cliTool(({ name, mode }) => ["visibility", "set", name, mode ?? "link"]),
498
510
  );
499
511
 
@@ -505,6 +517,7 @@ export function registerTools(server, ctx) {
505
517
  limit: z.number().int().positive().max(200).optional().describe("Number of events. Default 50, max 200."),
506
518
  org: z.string().optional().describe("Read another org's feed where you have access."),
507
519
  },
520
+ { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
508
521
  cliTool(({ since, limit, org }) => {
509
522
  const args = ["feedback", "list"];
510
523
  if (since) args.push("--since", since);
@@ -522,6 +535,7 @@ export function registerTools(server, ctx) {
522
535
  wait: z.boolean().optional().describe("Wait for the refresh to finish. Default true."),
523
536
  org: z.string().optional().describe("Target an entity in another org."),
524
537
  },
538
+ { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
525
539
  cliTool(({ name, wait, org }) => {
526
540
  const args = ["brain", "refresh", name];
527
541
  if (wait !== false) args.push("--wait");
@@ -529,6 +543,214 @@ export function registerTools(server, ctx) {
529
543
  return args;
530
544
  }),
531
545
  );
546
+
547
+ // ── New CLI-wrapping tools (local edition only) ───────────────────────────
548
+
549
+ server.tool(
550
+ "cloudgrid_whoami",
551
+ "Show the signed-in user and active org. Wraps `cloudgrid whoami`.",
552
+ {},
553
+ { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
554
+ cliTool(() => ["whoami"]),
555
+ );
556
+
557
+ server.tool(
558
+ "cloudgrid_use",
559
+ "Switch the active org. Wraps `cloudgrid use`.",
560
+ { org: z.string().describe("Org slug to switch to.") },
561
+ { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
562
+ cliTool(({ org }) => ["use", org]),
563
+ );
564
+
565
+ server.tool(
566
+ "cloudgrid_logout",
567
+ "Sign out and clear local credentials. Wraps `cloudgrid logout`.",
568
+ {},
569
+ { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
570
+ cliTool(() => ["logout"]),
571
+ );
572
+
573
+ server.tool(
574
+ "cloudgrid_status",
575
+ "Org dashboard, entity detail, or deploy snapshot. Wraps `cloudgrid status`.",
576
+ { name: z.string().optional().describe("Entity name or trace id. Omit for the org dashboard.") },
577
+ { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
578
+ cliTool(({ name }) => (name ? ["status", name] : ["status"])),
579
+ );
580
+
581
+ server.tool(
582
+ "cloudgrid_info",
583
+ "Show metadata for a CloudGrid entity. Wraps `cloudgrid info`.",
584
+ { name: z.string().optional().describe("Entity name. Omit for the entity linked to the current directory.") },
585
+ { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
586
+ cliTool(({ name }) => {
587
+ const args = ["info"];
588
+ if (name) args.push(name);
589
+ return args;
590
+ }),
591
+ );
592
+
593
+ server.tool(
594
+ "cloudgrid_builds",
595
+ "List recent builds and deploys for an entity. Wraps `cloudgrid builds`.",
596
+ {
597
+ name: z.string().optional().describe("Entity name. Omit for the entity linked to the current directory."),
598
+ limit: z.number().int().positive().optional().describe("Number of builds to show."),
599
+ },
600
+ { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
601
+ cliTool(({ name, limit }) => {
602
+ const args = ["builds"];
603
+ if (name) args.push(name);
604
+ if (limit) args.push("--limit", String(limit));
605
+ return args;
606
+ }),
607
+ );
608
+
609
+ server.tool(
610
+ "cloudgrid_grid",
611
+ "List entities on the hub or org. Wraps `cloudgrid grid`.",
612
+ { org: z.string().optional().describe("Org slug. Omit for the active org.") },
613
+ { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
614
+ cliTool(({ org }) => (org ? ["grid", "--org", org] : ["grid"])),
615
+ );
616
+
617
+ server.tool(
618
+ "cloudgrid_rename",
619
+ "Rename a CloudGrid entity. Wraps `cloudgrid rename`.",
620
+ {
621
+ name: z.string().describe("Current entity slug."),
622
+ new_name: z.string().describe("New slug."),
623
+ },
624
+ { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
625
+ cliTool(({ name, new_name }) => ["rename", name, new_name]),
626
+ );
627
+
628
+ server.tool(
629
+ "cloudgrid_unplug",
630
+ "Take an entity off the grid. Destructive. Wraps `cloudgrid unplug`.",
631
+ {
632
+ name: z.string().describe("Entity slug to take down (required)."),
633
+ confirm: z.literal(true).describe("Must be true to proceed."),
634
+ },
635
+ { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
636
+ cliTool(({ name }) => ["unplug", name]),
637
+ );
638
+
639
+ server.tool(
640
+ "cloudgrid_delete",
641
+ "Archive and delete a CloudGrid entity. Destructive. Wraps `cloudgrid delete`.",
642
+ {
643
+ name: z.string().describe("Entity slug to delete (required)."),
644
+ confirm: z.literal(true).describe("Must be true to proceed."),
645
+ },
646
+ { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
647
+ cliTool(({ name }) => ["delete", name]),
648
+ );
649
+
650
+ server.tool(
651
+ "cloudgrid_rollback",
652
+ "Rollback an entity to a previous version. Wraps `cloudgrid rollback`.",
653
+ {
654
+ name: z.string().describe("Entity slug."),
655
+ to: z.string().optional().describe("Target version tag or id. Omit to roll back one version."),
656
+ },
657
+ { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
658
+ cliTool(({ name, to }) => {
659
+ const args = ["rollback", name];
660
+ if (to) args.push("--to", to);
661
+ return args;
662
+ }),
663
+ );
664
+
665
+ server.tool(
666
+ "cloudgrid_versions",
667
+ "List published versions for an entity. Wraps `cloudgrid versions`.",
668
+ { name: z.string().optional().describe("Entity name. Omit for the entity linked to the current directory.") },
669
+ { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
670
+ cliTool(({ name }) => {
671
+ const args = ["versions"];
672
+ if (name) args.push(name);
673
+ return args;
674
+ }),
675
+ );
676
+
677
+ server.tool(
678
+ "cloudgrid_env",
679
+ "Manage environment variables for an entity. Wraps `cloudgrid env`.",
680
+ {
681
+ action: z.enum(["get", "set", "list"]).describe("get, set, or list."),
682
+ name: z.string().describe("Entity slug."),
683
+ key: z.string().optional().describe("Variable name. Required for get and set."),
684
+ value: z.string().optional().describe("Variable value. Required for set."),
685
+ },
686
+ { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
687
+ cliTool(({ action, name, key, value }) => {
688
+ if (action === "set") {
689
+ if (!key || value === undefined) throw new Error("key and value are required for set");
690
+ return ["env", "set", name, key, value];
691
+ }
692
+ if (action === "get") {
693
+ if (!key) throw new Error("key is required for get");
694
+ return ["env", "get", name, key];
695
+ }
696
+ return ["env", "list", name];
697
+ }),
698
+ );
699
+
700
+ server.tool(
701
+ "cloudgrid_secrets",
702
+ "Set or list secret names for an entity. Never returns secret values. Wraps `cloudgrid secrets`.",
703
+ {
704
+ action: z.enum(["set", "list"]).describe("set or list (names only)."),
705
+ name: z.string().describe("Entity slug."),
706
+ key: z.string().optional().describe("Secret name. Required for set."),
707
+ value: z.string().optional().describe("Secret value. Required for set."),
708
+ },
709
+ { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
710
+ cliTool(({ action, name, key, value }) => {
711
+ if (action === "set") {
712
+ if (!key || value === undefined) throw new Error("key and value are required for set");
713
+ return ["secrets", "set", name, key, value];
714
+ }
715
+ return ["secrets", "list", name];
716
+ }),
717
+ );
718
+
719
+ server.tool(
720
+ "cloudgrid_scaffold",
721
+ "Generate starter files for a CloudGrid entity. Wraps `cloudgrid scaffold`.",
722
+ {
723
+ template: z.string().optional().describe("Template name."),
724
+ dir: z.string().optional().describe("Target directory."),
725
+ },
726
+ { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
727
+ cliTool(({ template, dir }) => {
728
+ const args = ["scaffold"];
729
+ if (template) args.push(template);
730
+ if (dir) args.push("--dir", dir);
731
+ return args;
732
+ }),
733
+ );
734
+
735
+ server.tool(
736
+ "cloudgrid_doctor",
737
+ "Run CloudGrid diagnostics on the local environment. Wraps `cloudgrid doctor`.",
738
+ {},
739
+ { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
740
+ cliTool(() => ["doctor"]),
741
+ );
742
+
743
+ server.tool(
744
+ "cloudgrid_open",
745
+ "Return the public URL for an entity. Does not open a browser. Wraps `cloudgrid open --url`.",
746
+ { name: z.string().optional().describe("Entity name. Omit for the entity linked to the current directory.") },
747
+ { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
748
+ cliTool(({ name }) => {
749
+ const args = ["open", "--url"];
750
+ if (name) args.push(name);
751
+ return args;
752
+ }),
753
+ );
532
754
  }
533
755
 
534
756
  export { decodeJwt };