@cloudgrid-io/mcp 0.20.52 → 0.20.54
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/README.md +78 -3
- package/package.json +8 -6
- package/src/assets/favicon.png +0 -0
- package/src/auth.js +1 -1
- package/src/corpus/install.md +11 -0
- package/src/corpus/playbook.md +22 -3
- package/src/corpus/skill-login.md +1 -1
- package/src/corpus/templates/docs-app/package.json +1 -1
- package/src/corpus/templates/semantic-search/services/backend/requirements.txt +1 -1
- package/src/corpus/templates/semantic-search/services/refresh/requirements.txt +1 -1
- package/src/corpus/templates/semantic-search/services/web/package-lock.json +477 -1162
- package/src/corpus/templates/semantic-search/services/web/package.json +2 -2
- package/src/landing.js +129 -0
- package/src/oauth.js +1 -0
- package/src/playbook.js +2 -2
- package/src/session-logger.js +4 -1
- package/src/tools/constants.js +14 -6
- package/src/tools/deploy.js +188 -33
- package/src/tools/register.js +86 -26
- package/src/web.js +5 -0
- package/src/docs.js +0 -147
package/src/tools/register.js
CHANGED
|
@@ -35,13 +35,14 @@ import { stalenessNote } from "../staleness.js";
|
|
|
35
35
|
// `--agent` for agents (NOT the legacy `init <kind> <slug>` positional). The
|
|
36
36
|
// old `--description` flag was removed from the CLI, so it is gone here too.
|
|
37
37
|
// Exported pure so the argv is unit-tested without spawning a CLI.
|
|
38
|
-
export function buildCreateProjectArgs({ kind, name, type, needs, dir, org } = {}) {
|
|
38
|
+
export function buildCreateProjectArgs({ kind, name, type, needs, dir, grid, org } = {}) {
|
|
39
39
|
const args = ["new", name];
|
|
40
40
|
if (kind === "agent") args.push("--agent");
|
|
41
41
|
if (type) args.push("--type", type);
|
|
42
42
|
if (Array.isArray(needs) && needs.length) args.push("--needs", needs.join(","));
|
|
43
43
|
if (dir) args.push("--dir", dir);
|
|
44
|
-
|
|
44
|
+
const gridSlug = grid || org;
|
|
45
|
+
if (gridSlug) args.push("--grid", gridSlug);
|
|
45
46
|
return args;
|
|
46
47
|
}
|
|
47
48
|
|
|
@@ -86,8 +87,8 @@ export function registerTools(server, ctx) {
|
|
|
86
87
|
server.registerTool(name, config, withCapture(name, handler));
|
|
87
88
|
};
|
|
88
89
|
|
|
89
|
-
const regTool = (name, description, schema, annotations, handler) => {
|
|
90
|
-
server.
|
|
90
|
+
const regTool = (name, title, description, schema, annotations, handler) => {
|
|
91
|
+
server.registerTool(name, { title, description, inputSchema: schema, annotations }, withCapture(name, handler));
|
|
91
92
|
};
|
|
92
93
|
|
|
93
94
|
// ── Widget resources (web edition, ChatGPT Apps SDK) ──────────────────────
|
|
@@ -130,10 +131,13 @@ export function registerTools(server, ctx) {
|
|
|
130
131
|
reg(
|
|
131
132
|
"grid_pickup",
|
|
132
133
|
{
|
|
134
|
+
title: "Copy an app into your grid",
|
|
133
135
|
description: "Pick up an app: make your OWN COPY of any app you can see (like a git fork) into a grid you can build in. It mints a NEW entity with lineage back to the source and WITHOUT the source's secrets (set your own before you plug). Plugging your copy creates/updates YOUR entity — the original is never touched. Requires sign-in. To edit the ORIGINAL entity in place (as its owner or a collaborator), use grid_pull instead.",
|
|
134
136
|
inputSchema: {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
+
entity_id: z.string().describe("The source app to copy: a canonical UUID or <grid-slug>/<entity-slug>."),
|
|
138
|
+
grid: z.string().optional().describe("Grid to create your copy in. Required only when you belong to more than one grid."),
|
|
139
|
+
id: z.string().optional().describe("Alias of entity_id (legacy). Prefer entity_id."),
|
|
140
|
+
into_org_slug: z.string().optional().describe("Alias of grid (legacy). Prefer grid."),
|
|
137
141
|
name: z.string().optional().describe("Slug for your copy. Omit to derive one from the source."),
|
|
138
142
|
source_version_id: z.string().optional().describe("Copy an older version instead of HEAD, e.g. v_a1b2c3d."),
|
|
139
143
|
},
|
|
@@ -150,8 +154,13 @@ export function registerTools(server, ctx) {
|
|
|
150
154
|
},
|
|
151
155
|
async (input) => {
|
|
152
156
|
try {
|
|
153
|
-
|
|
154
|
-
|
|
157
|
+
const resolved = {
|
|
158
|
+
...input,
|
|
159
|
+
id: input?.entity_id || input?.id,
|
|
160
|
+
into_org_slug: input?.grid || input?.into_org_slug,
|
|
161
|
+
};
|
|
162
|
+
if (!resolved.id) return fail("`entity_id` is required (a canonical UUID or <grid-slug>/<entity-slug>).");
|
|
163
|
+
return okResult(await runPickup(ctx, resolved));
|
|
155
164
|
} catch (err) {
|
|
156
165
|
return fail(err.message);
|
|
157
166
|
}
|
|
@@ -162,6 +171,7 @@ export function registerTools(server, ctx) {
|
|
|
162
171
|
reg(
|
|
163
172
|
"grid_pull",
|
|
164
173
|
{
|
|
174
|
+
title: "Pull an app to edit in place",
|
|
165
175
|
description: "Pull an app to continue/edit it IN PLACE — like `git clone` of the SAME entity: your next grid_plug (with its target_entity_id) updates that entity, and the team sees the new version. Requires PUSH ACCESS: you must own it or be a collaborator. If you can only view it, you CANNOT edit or plug it — say so and tell the user they can make their own copy with grid_pickup, or request collaborator access with the CLI `grid collab <entity>` (rolling out soon). Passing an anonymous drop's `claim_token` claims it into your account. Requires sign-in. Calls the API directly (both editions).",
|
|
166
176
|
inputSchema: {
|
|
167
177
|
entity_id: z.string().optional().describe("The entity id to pull. Defaults to this session's last anonymous drop."),
|
|
@@ -194,13 +204,15 @@ export function registerTools(server, ctx) {
|
|
|
194
204
|
reg(
|
|
195
205
|
"grid_create_grid",
|
|
196
206
|
{
|
|
197
|
-
|
|
207
|
+
title: "Create a new grid",
|
|
208
|
+
description: "Create a new grid (workspace) for the signed-in user — they become its admin. Use when the account has no grid yet (grid_plug returns needs_grid_create, or a plug fails with NO_ACTIVE_ORG): suggest a short slug from the user's name or app, CONFIRM it with the user (the slug is permanent and appears in URLs), create, then call grid_plug with grid: <slug>. A brand-new grid provisions in the background (~30s); grid_plug waits for readiness before deploying, so call it right away — no manual delay or polling needed. Never send the user to the console to create a grid by hand. Requires sign-in. Calls the API directly (both editions).",
|
|
198
209
|
inputSchema: {
|
|
199
210
|
slug: z.string().describe("The grid slug: 3-40 lowercase letters, digits, or hyphens, starting with a letter. Permanent — confirm with the user before creating."),
|
|
200
211
|
name: z.string().optional().describe("Display name (defaults to the slug)."),
|
|
201
212
|
},
|
|
202
213
|
outputSchema: {
|
|
203
214
|
created: z.boolean().optional().describe("True when the grid was created."),
|
|
215
|
+
provisioning: z.boolean().optional().describe("True when the grid is still finishing setup in the background. grid_plug waits for readiness before deploying, so call it right away — no manual polling needed."),
|
|
204
216
|
grid: z.object({
|
|
205
217
|
slug: z.string().describe("The new grid's slug — pass it to grid_plug as `grid`."),
|
|
206
218
|
name: z.string().optional().describe("Its display name."),
|
|
@@ -227,6 +239,7 @@ export function registerTools(server, ctx) {
|
|
|
227
239
|
// acknowledgement.
|
|
228
240
|
regTool(
|
|
229
241
|
"grid_note",
|
|
242
|
+
"Leave a session build note",
|
|
230
243
|
"Optionally leave a one-paragraph summary of what you built this session and why. Call it BEFORE a plug, or in a session that ends without one — a successful plug has already posted the QA log, so pass grid_plug's session_note instead. The summary is recorded with the CloudGrid team for QA review.",
|
|
231
244
|
{ summary: z.string().describe("A short plain-language summary of what was built and why.") },
|
|
232
245
|
{ readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
@@ -328,6 +341,7 @@ export function registerTools(server, ctx) {
|
|
|
328
341
|
};
|
|
329
342
|
|
|
330
343
|
const plugConfig = {
|
|
344
|
+
title: "Plug an app into the grid",
|
|
331
345
|
description:
|
|
332
346
|
"Plug an app, website, game, or single HTML page into CloudGrid — the live runtime that runs it and provides its infrastructure — and get a live public URL. " +
|
|
333
347
|
"Use for any request to deploy, publish, host, ship, launch, go live, or share a working link — " +
|
|
@@ -530,6 +544,7 @@ export function registerTools(server, ctx) {
|
|
|
530
544
|
reg(
|
|
531
545
|
"grid_get_app_source",
|
|
532
546
|
{
|
|
547
|
+
title: "Get deployed app source",
|
|
533
548
|
description:
|
|
534
549
|
"Retrieve the CURRENT deployed HTML of an inspiration/drop inline as text, so you can edit it and " +
|
|
535
550
|
"re-plug the SAME URL when you no longer have its source in context (e.g. the user asks to 'change the " +
|
|
@@ -583,6 +598,7 @@ export function registerTools(server, ctx) {
|
|
|
583
598
|
reg(
|
|
584
599
|
"grid_login",
|
|
585
600
|
{
|
|
601
|
+
title: "Sign in to CloudGrid",
|
|
586
602
|
description: "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 grid_login_status to finish. Uses CloudGrid's existing OAuth.",
|
|
587
603
|
inputSchema: {},
|
|
588
604
|
outputSchema: {
|
|
@@ -613,6 +629,7 @@ export function registerTools(server, ctx) {
|
|
|
613
629
|
reg(
|
|
614
630
|
"grid_login_status",
|
|
615
631
|
{
|
|
632
|
+
title: "Check sign-in status",
|
|
616
633
|
description: "Finish a sign-in started by grid_login. Polls once: if you have completed the browser sign-in, it saves your session; otherwise it tells you to finish and try again.",
|
|
617
634
|
inputSchema: {
|
|
618
635
|
code: z.string().optional().describe("The sign-in code. Defaults to the most recent grid_login."),
|
|
@@ -662,7 +679,8 @@ export function registerTools(server, ctx) {
|
|
|
662
679
|
reg(
|
|
663
680
|
"grid_visibility",
|
|
664
681
|
{
|
|
665
|
-
|
|
682
|
+
title: "Change app visibility",
|
|
683
|
+
description: "Change who can see a CloudGrid inspiration OR runtime app/agent. Simple modes: private (only the user), grid (everyone in their grid), link (anyone with the URL — add indexed: true to be findable by search engines). Finer control via the TWO AXES instead: inside (who in the grid: private | spaces | grid) and outside (reach beyond it: none | link | public), with require_signin for a members-only link and `spaces` for selected spaces. 'authenticated' is retired (it maps to a sign-in-required link). Use when the user wants to make something private, restrict who sees it, or open it up — including right after a drop, with no target id needed. Kind-aware routing across both surfaces. Defaults to the drop made in this session. Requires sign-in. Calls the API directly.",
|
|
666
684
|
inputSchema: {
|
|
667
685
|
visibility: z.enum(["private", "grid", "link", "public", "authenticated", "space"]).optional().describe("Simple mode: private | grid | link ('public' is an alias of link — pass indexed: true for search-findable; 'authenticated' is the retired alias for a sign-in-required link; 'space' needs the `spaces` list). Pass either this OR the inside/outside axes."),
|
|
668
686
|
inside: z.enum(["private", "spaces", "grid"]).optional().describe("Axis 1 — who in the grid can see it: private (only the user), spaces (selected spaces — pass `spaces`), or grid (everyone in the grid). Use together with `outside`, instead of `visibility`."),
|
|
@@ -670,9 +688,11 @@ export function registerTools(server, ctx) {
|
|
|
670
688
|
require_signin: z.boolean().optional().describe("With outside: link (or visibility: link) — the link requires a signed-in CloudGrid account."),
|
|
671
689
|
spaces: z.array(z.string()).optional().describe("Space slugs, for inside: spaces (or the legacy space/grid modes)."),
|
|
672
690
|
indexed: z.boolean().optional().describe("With visibility: link — make the page search-engine indexable."),
|
|
673
|
-
|
|
691
|
+
entity_id: z.string().optional().describe("Entity id. Defaults to this session's last drop."),
|
|
674
692
|
kind: z.enum(["inspiration", "app", "agent"]).optional().describe("Entity kind. Omit to auto-detect from this session's last drop (falls back to trying the runtime surface, then the inspiration surface)."),
|
|
675
|
-
|
|
693
|
+
grid: z.string().optional().describe("Grid of the entity. Defaults to the active grid."),
|
|
694
|
+
target: z.string().optional().describe("Alias of entity_id (legacy). Prefer entity_id."),
|
|
695
|
+
org: z.string().optional().describe("Alias of grid (legacy). Prefer grid."),
|
|
676
696
|
},
|
|
677
697
|
outputSchema: {
|
|
678
698
|
visibility: z.string().optional().describe("The legacy mode that was set, when a simple mode was used."),
|
|
@@ -687,7 +707,12 @@ export function registerTools(server, ctx) {
|
|
|
687
707
|
},
|
|
688
708
|
async (input) => {
|
|
689
709
|
try {
|
|
690
|
-
|
|
710
|
+
const resolved = {
|
|
711
|
+
...(input || {}),
|
|
712
|
+
target: input?.entity_id || input?.target,
|
|
713
|
+
org: input?.grid || input?.org,
|
|
714
|
+
};
|
|
715
|
+
return okResult(await runVisibility(ctx, resolved));
|
|
691
716
|
} catch (err) {
|
|
692
717
|
return fail(err.message);
|
|
693
718
|
}
|
|
@@ -701,6 +726,7 @@ export function registerTools(server, ctx) {
|
|
|
701
726
|
reg(
|
|
702
727
|
"grid_check_deploy",
|
|
703
728
|
{
|
|
729
|
+
title: "Check deploy status",
|
|
704
730
|
description:
|
|
705
731
|
"Check whether an async runtime-app build has finished and the app is live. Call this after grid_plug returns status \"building\" — repeat every ~15s until it reports success or failed, and do NOT tell the user the app is live until it does. Defaults to this session's last deploy; pass poll_url from a grid_plug result to check another. Requires sign-in (builds are owned). Calls the API directly — works on every edition, including hosted.",
|
|
706
732
|
inputSchema: {
|
|
@@ -731,6 +757,7 @@ export function registerTools(server, ctx) {
|
|
|
731
757
|
reg(
|
|
732
758
|
"grid_list_grids",
|
|
733
759
|
{
|
|
760
|
+
title: "List your grids",
|
|
734
761
|
description: "List the signed-in user's grids, each with slug, name, role, and provisioning status. A grid that is still provisioning (render_ready false) may not serve pages yet — prefer a ready grid, and if the user insists on a not-ready one, warn them that pages may not load. Requires sign-in.",
|
|
735
762
|
inputSchema: {},
|
|
736
763
|
outputSchema: {
|
|
@@ -784,12 +811,12 @@ export function registerTools(server, ctx) {
|
|
|
784
811
|
// ── Agent Core orientation tools (authed editions: local + web) ───────────
|
|
785
812
|
// These serve the delivery ladder's Orient + Load rungs. They are registered
|
|
786
813
|
// before the local-only cutoff below, so BOTH the local and web (hosted-auth)
|
|
787
|
-
// editions expose them.
|
|
788
|
-
// registerTools, so it never gets them (spec F3).
|
|
814
|
+
// editions expose them.
|
|
789
815
|
|
|
790
816
|
reg(
|
|
791
817
|
"grid_start",
|
|
792
818
|
{
|
|
819
|
+
title: "Orient before building",
|
|
793
820
|
description:
|
|
794
821
|
"Orient before building with CloudGrid — the live runtime environment where the user's apps run WITH the infrastructure they need (managed database, cache, persistent disk, AI with no API keys), any language or stack. Call this FIRST when the user wants to build, create, make, deploy, publish, or generate something. Returns the CloudGrid playbook (operating rules + golden path) and the index of available workflows (presentation, …). After this, match the user's intent to a workflow and call grid_get_template to load it.",
|
|
795
822
|
inputSchema: {},
|
|
@@ -889,6 +916,7 @@ export function registerTools(server, ctx) {
|
|
|
889
916
|
reg(
|
|
890
917
|
"grid_get_template",
|
|
891
918
|
{
|
|
919
|
+
title: "Load a workflow or template",
|
|
892
920
|
description:
|
|
893
921
|
"Load a specific CloudGrid workflow, template, example, rule, or doc by name — deterministic retrieval from the bundled corpus. Use after grid_start to pull the exact recipe/template you need, e.g. grid_get_template({kind:\"workflow\", name:\"presentation\"}) then grid_get_template({kind:\"template\", name:\"deck\"}).",
|
|
894
922
|
inputSchema: {
|
|
@@ -927,8 +955,9 @@ export function registerTools(server, ctx) {
|
|
|
927
955
|
reg(
|
|
928
956
|
"grid_report",
|
|
929
957
|
{
|
|
958
|
+
title: "Report a failure",
|
|
930
959
|
description:
|
|
931
|
-
"Report a genuine CloudGrid failure to the CloudGrid team — ONLY with the user's explicit consent. When a build/deploy or platform call fails unexpectedly, ASK the user first; call this only after they say yes. Send a short `message` (what failed) plus `context` (the tool, inputs, grid, original request, error code/detail). By default it does NOT include the conversation — set include_conversation:true ONLY if the user explicitly agreed to send the chat.
|
|
960
|
+
"Report a genuine CloudGrid failure to the CloudGrid team — ONLY with the user's explicit consent. When a build/deploy or platform call fails unexpectedly, ASK the user first; call this only after they say yes. Send a short `message` (what failed) plus `context` (the tool, inputs, grid, original request, error code/detail). By default it does NOT include the conversation — set include_conversation:true ONLY if the user explicitly agreed to send the chat. Redaction is key-name-only: a value is dropped only when its KEY looks secret, so a secret embedded in a value (e.g. inside HTML) is NOT redacted — do not put secrets in `message` or `context`. Never sends anything the user didn't agree to.",
|
|
932
961
|
inputSchema: {
|
|
933
962
|
message: z
|
|
934
963
|
.string()
|
|
@@ -936,7 +965,7 @@ export function registerTools(server, ctx) {
|
|
|
936
965
|
context: z
|
|
937
966
|
.object({
|
|
938
967
|
tool: z.string().optional().describe("The CloudGrid tool that failed, e.g. grid_plug."),
|
|
939
|
-
inputs: z.any().optional().describe("The failing inputs (e.g. the HTML/args). Keep it minimal
|
|
968
|
+
inputs: z.any().optional().describe("The failing inputs (e.g. the HTML/args). Keep it minimal and do NOT include secrets — a secret embedded in a value here is not redacted."),
|
|
940
969
|
grid: z.string().optional().describe("The grid/org slug involved, if any."),
|
|
941
970
|
original_request: z.string().optional().describe("What the user asked for, in one line."),
|
|
942
971
|
error_code: z.string().optional().describe("The server error code, e.g. INTERNAL_ERROR."),
|
|
@@ -944,7 +973,7 @@ export function registerTools(server, ctx) {
|
|
|
944
973
|
})
|
|
945
974
|
.partial()
|
|
946
975
|
.optional()
|
|
947
|
-
.describe("The failed-request context.
|
|
976
|
+
.describe("The failed-request context. Redaction is key-name-only (a value is dropped only when its KEY looks secret) client-side, and the server applies the same key-name filter — a secret embedded in a value is not redacted by either."),
|
|
948
977
|
include_conversation: z
|
|
949
978
|
.boolean()
|
|
950
979
|
.optional()
|
|
@@ -1000,6 +1029,7 @@ export function registerTools(server, ctx) {
|
|
|
1000
1029
|
|
|
1001
1030
|
regTool(
|
|
1002
1031
|
"grid_create_project",
|
|
1032
|
+
"Scaffold a new project",
|
|
1003
1033
|
"Scaffold a new CloudGrid app or agent folder (cloudgrid.yaml + a web service), optionally pre-declaring resources. Wraps `grid new` (scaffolds locally; no server entity until you grid_plug). Language note: the nextjs starter is TypeScript (writes tsconfig.json + app/*.tsx), but CloudGrid templates are plain JavaScript. When you fill a template, treat the template's files as the source of truth — delete the scaffolded tsconfig.json and app/*.tsx, then write the template's .js files. Never leave both .tsx and .js for the same route.",
|
|
1004
1034
|
{
|
|
1005
1035
|
kind: z.enum(["app", "agent"]).describe("Entity kind. 'agent' scaffolds an agent: block."),
|
|
@@ -1008,7 +1038,8 @@ export function registerTools(server, ctx) {
|
|
|
1008
1038
|
needs: z.array(z.enum(["database", "cache", "kv", "queue", "pubsub", "vector", "ai", "disk"])).optional()
|
|
1009
1039
|
.describe("Pre-declare infrastructure needs in the scaffolded cloudgrid.yaml (e.g. [\"database\",\"ai\"])."),
|
|
1010
1040
|
dir: z.string().optional().describe("Target directory. Defaults to the current folder."),
|
|
1011
|
-
|
|
1041
|
+
grid: z.string().optional().describe("Override the active grid this project will target."),
|
|
1042
|
+
org: z.string().optional().describe("Alias of grid (legacy). Prefer grid."),
|
|
1012
1043
|
cwd: z.string().optional().describe("Working directory. The CLI runs in this directory. Defaults to the MCP server's working directory."),
|
|
1013
1044
|
},
|
|
1014
1045
|
{ readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
@@ -1021,6 +1052,7 @@ export function registerTools(server, ctx) {
|
|
|
1021
1052
|
|
|
1022
1053
|
regTool(
|
|
1023
1054
|
"grid_view_logs",
|
|
1055
|
+
"View entity logs",
|
|
1024
1056
|
"Tail recent logs for an entity. Does not stream; returns a snapshot. Wraps `grid logs`.",
|
|
1025
1057
|
{
|
|
1026
1058
|
name: z.string().optional().describe("Entity name. Omit to use the entity linked to the current directory."),
|
|
@@ -1039,6 +1071,7 @@ export function registerTools(server, ctx) {
|
|
|
1039
1071
|
|
|
1040
1072
|
regTool(
|
|
1041
1073
|
"grid_share",
|
|
1074
|
+
"Set visibility via CLI",
|
|
1042
1075
|
"Set an entity's visibility and print its URL. Defaults to link (anyone with the URL). Wraps `grid visibility set`.",
|
|
1043
1076
|
{
|
|
1044
1077
|
name: z.string().describe("Entity slug."),
|
|
@@ -1050,19 +1083,21 @@ export function registerTools(server, ctx) {
|
|
|
1050
1083
|
|
|
1051
1084
|
regTool(
|
|
1052
1085
|
"grid_feedback",
|
|
1086
|
+
"List feedback events",
|
|
1053
1087
|
"List recent feedback events for the active grid. Read-only. Wraps `grid feedback list`.",
|
|
1054
1088
|
{
|
|
1055
1089
|
since: z.string().optional().describe("Only events newer than this, e.g. 24h, 7d."),
|
|
1056
1090
|
limit: z.number().int().positive().max(200).optional().describe("Number of events. Default 50, max 200."),
|
|
1057
|
-
|
|
1091
|
+
grid: z.string().optional().describe("Read another grid's feed where you have access."),
|
|
1092
|
+
org: z.string().optional().describe("Alias of grid (legacy). Prefer grid."),
|
|
1058
1093
|
},
|
|
1059
1094
|
{ readOnlyHint: true, destructiveHint: false, openWorldHint: true },
|
|
1060
|
-
cliTool(({ since, limit, org }) => {
|
|
1095
|
+
cliTool(({ since, limit, grid, org }) => {
|
|
1061
1096
|
const args = ["feedback", "list"];
|
|
1062
1097
|
if (since) args.push("--since", since);
|
|
1063
1098
|
if (limit) args.push("--limit", String(limit));
|
|
1064
|
-
|
|
1065
|
-
if (
|
|
1099
|
+
const gridSlug = grid || org;
|
|
1100
|
+
if (gridSlug) args.push("--grid", gridSlug);
|
|
1066
1101
|
return args;
|
|
1067
1102
|
}),
|
|
1068
1103
|
);
|
|
@@ -1071,6 +1106,7 @@ export function registerTools(server, ctx) {
|
|
|
1071
1106
|
|
|
1072
1107
|
regTool(
|
|
1073
1108
|
"grid_whoami",
|
|
1109
|
+
"Show current user and grid",
|
|
1074
1110
|
"Show the signed-in user and active grid. Wraps `grid whoami`.",
|
|
1075
1111
|
{},
|
|
1076
1112
|
{ readOnlyHint: true, destructiveHint: false, openWorldHint: true },
|
|
@@ -1079,14 +1115,23 @@ export function registerTools(server, ctx) {
|
|
|
1079
1115
|
|
|
1080
1116
|
regTool(
|
|
1081
1117
|
"grid_switch_grid",
|
|
1118
|
+
"Switch the active grid",
|
|
1082
1119
|
"Switch the active grid. Wraps `grid use`.",
|
|
1083
|
-
{
|
|
1120
|
+
{
|
|
1121
|
+
grid: z.string().optional().describe("Grid slug to switch to."),
|
|
1122
|
+
org: z.string().optional().describe("Alias of grid (legacy). Prefer grid."),
|
|
1123
|
+
},
|
|
1084
1124
|
{ readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
1085
|
-
cliTool(({ org }) =>
|
|
1125
|
+
cliTool(({ grid, org }) => {
|
|
1126
|
+
const slug = grid || org;
|
|
1127
|
+
if (!slug) throw new Error("`grid` is required.");
|
|
1128
|
+
return ["use", slug];
|
|
1129
|
+
}),
|
|
1086
1130
|
);
|
|
1087
1131
|
|
|
1088
1132
|
regTool(
|
|
1089
1133
|
"grid_logout",
|
|
1134
|
+
"Sign out",
|
|
1090
1135
|
"Sign out and clear local credentials. Wraps `grid logout`.",
|
|
1091
1136
|
{},
|
|
1092
1137
|
{ readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
@@ -1095,7 +1140,8 @@ export function registerTools(server, ctx) {
|
|
|
1095
1140
|
|
|
1096
1141
|
regTool(
|
|
1097
1142
|
"grid_status",
|
|
1098
|
-
"
|
|
1143
|
+
"Show grid or entity status",
|
|
1144
|
+
"Grid dashboard, entity detail, or deploy snapshot. Wraps `grid status`.",
|
|
1099
1145
|
{ name: z.string().optional().describe("Entity name or trace id. Omit for the org dashboard.") },
|
|
1100
1146
|
{ readOnlyHint: true, destructiveHint: false, openWorldHint: true },
|
|
1101
1147
|
cliTool(({ name }) => (name ? ["status", name] : ["status"])),
|
|
@@ -1103,6 +1149,7 @@ export function registerTools(server, ctx) {
|
|
|
1103
1149
|
|
|
1104
1150
|
regTool(
|
|
1105
1151
|
"grid_info",
|
|
1152
|
+
"Show entity metadata",
|
|
1106
1153
|
"Show metadata for a CloudGrid entity. Wraps `grid info`.",
|
|
1107
1154
|
{ name: z.string().optional().describe("Entity name. Omit for the entity linked to the current directory.") },
|
|
1108
1155
|
{ readOnlyHint: true, destructiveHint: false, openWorldHint: true },
|
|
@@ -1120,6 +1167,7 @@ export function registerTools(server, ctx) {
|
|
|
1120
1167
|
// cloudgrid_grid behaviour with `grid` mapping to the CLI's `--grid` flag.
|
|
1121
1168
|
regTool(
|
|
1122
1169
|
"grid_get",
|
|
1170
|
+
"List grids, entities, or spaces",
|
|
1123
1171
|
"List CloudGrid grids, entities, or spaces. Wraps `grid get <grids|entities|spaces> --json`.",
|
|
1124
1172
|
{
|
|
1125
1173
|
resource: z.enum(["grids", "entities", "spaces"]).describe("What to list: grids, entities, or spaces."),
|
|
@@ -1147,6 +1195,7 @@ export function registerTools(server, ctx) {
|
|
|
1147
1195
|
|
|
1148
1196
|
regTool(
|
|
1149
1197
|
"grid_describe_grid",
|
|
1198
|
+
"Describe a grid",
|
|
1150
1199
|
"Show a grid's detail: role, members, spaces, tier, wildcard-TLS state. Wraps `grid describe grid <slug> --json`.",
|
|
1151
1200
|
{ grid: z.string().describe("Grid slug to describe.") },
|
|
1152
1201
|
{ readOnlyHint: true, destructiveHint: false, openWorldHint: true },
|
|
@@ -1160,6 +1209,7 @@ export function registerTools(server, ctx) {
|
|
|
1160
1209
|
// pulled — use grid_pickup to copy it, or `grid collab` to request access.
|
|
1161
1210
|
regTool(
|
|
1162
1211
|
"grid_edit_existing_app",
|
|
1212
|
+
"Pull app source to edit locally",
|
|
1163
1213
|
"Continue/edit an EXISTING entity locally: download its source + cloudgrid.yaml and link the folder so your next `grid plug` updates it IN PLACE. Requires push access (owner or collaborator). Wraps `grid pull`. To make your own separate copy instead, use grid_pickup.",
|
|
1164
1214
|
{
|
|
1165
1215
|
name: z.string().describe("Entity slug or id to pull."),
|
|
@@ -1184,6 +1234,7 @@ export function registerTools(server, ctx) {
|
|
|
1184
1234
|
|
|
1185
1235
|
regTool(
|
|
1186
1236
|
"grid_rename",
|
|
1237
|
+
"Rename an entity",
|
|
1187
1238
|
"Rename a CloudGrid entity's display name (slug stays the same). Wraps `grid rename`.",
|
|
1188
1239
|
{
|
|
1189
1240
|
name: z.string().describe("Entity slug."),
|
|
@@ -1195,6 +1246,7 @@ export function registerTools(server, ctx) {
|
|
|
1195
1246
|
|
|
1196
1247
|
regTool(
|
|
1197
1248
|
"grid_take_offline",
|
|
1249
|
+
"Take an entity offline",
|
|
1198
1250
|
"Take an entity off the grid. Destructive. Wraps `grid unplug`.",
|
|
1199
1251
|
{
|
|
1200
1252
|
name: z.string().describe("Entity slug to take down (required)."),
|
|
@@ -1206,6 +1258,7 @@ export function registerTools(server, ctx) {
|
|
|
1206
1258
|
|
|
1207
1259
|
regTool(
|
|
1208
1260
|
"grid_delete",
|
|
1261
|
+
"Delete an entity",
|
|
1209
1262
|
"Archive a CloudGrid inspiration. Destructive. Wraps `grid delete entity`.",
|
|
1210
1263
|
{
|
|
1211
1264
|
name: z.string().describe("Entity slug to delete (required)."),
|
|
@@ -1217,6 +1270,7 @@ export function registerTools(server, ctx) {
|
|
|
1217
1270
|
|
|
1218
1271
|
regTool(
|
|
1219
1272
|
"grid_rollback_deploy",
|
|
1273
|
+
"Roll back to a previous version",
|
|
1220
1274
|
"Rollback an entity to a previous version. Wraps `grid rollback`.",
|
|
1221
1275
|
{
|
|
1222
1276
|
name: z.string().describe("Entity slug."),
|
|
@@ -1232,6 +1286,7 @@ export function registerTools(server, ctx) {
|
|
|
1232
1286
|
|
|
1233
1287
|
regTool(
|
|
1234
1288
|
"grid_list_versions",
|
|
1289
|
+
"List entity versions",
|
|
1235
1290
|
"List published versions for an entity. Wraps `grid versions`.",
|
|
1236
1291
|
{ name: z.string().optional().describe("Entity name. Omit for the entity linked to the current directory.") },
|
|
1237
1292
|
{ readOnlyHint: true, destructiveHint: false, openWorldHint: true },
|
|
@@ -1244,6 +1299,7 @@ export function registerTools(server, ctx) {
|
|
|
1244
1299
|
|
|
1245
1300
|
regTool(
|
|
1246
1301
|
"grid_set_env",
|
|
1302
|
+
"Manage environment variables",
|
|
1247
1303
|
"Manage environment variables for an entity. Wraps `grid env`.",
|
|
1248
1304
|
{
|
|
1249
1305
|
action: z.enum(["get", "set", "list"]).describe("get, set, or list."),
|
|
@@ -1268,6 +1324,7 @@ export function registerTools(server, ctx) {
|
|
|
1268
1324
|
|
|
1269
1325
|
regTool(
|
|
1270
1326
|
"grid_set_secret",
|
|
1327
|
+
"Manage secrets",
|
|
1271
1328
|
"Set or list secret names for an entity. Never returns secret values. Wraps `grid secrets`.",
|
|
1272
1329
|
{
|
|
1273
1330
|
action: z.enum(["set", "list"]).describe("set or list (names only)."),
|
|
@@ -1288,6 +1345,7 @@ export function registerTools(server, ctx) {
|
|
|
1288
1345
|
|
|
1289
1346
|
regTool(
|
|
1290
1347
|
"grid_scaffold",
|
|
1348
|
+
"Scaffold service folders",
|
|
1291
1349
|
"Scaffold service folders declared in cloudgrid.yaml (idempotent). Wraps `grid scaffold`.",
|
|
1292
1350
|
{
|
|
1293
1351
|
cwd: z.string().optional().describe("Working directory. The CLI runs in this directory. Defaults to the MCP server's working directory."),
|
|
@@ -1298,6 +1356,7 @@ export function registerTools(server, ctx) {
|
|
|
1298
1356
|
|
|
1299
1357
|
regTool(
|
|
1300
1358
|
"grid_diagnose",
|
|
1359
|
+
"Run local diagnostics",
|
|
1301
1360
|
"Run CloudGrid diagnostics on the local environment. Wraps `grid doctor`.",
|
|
1302
1361
|
{},
|
|
1303
1362
|
{ readOnlyHint: true, destructiveHint: false, openWorldHint: true },
|
|
@@ -1306,6 +1365,7 @@ export function registerTools(server, ctx) {
|
|
|
1306
1365
|
|
|
1307
1366
|
regTool(
|
|
1308
1367
|
"grid_get_url",
|
|
1368
|
+
"Get entity URL",
|
|
1309
1369
|
"Return the public URL for an entity. Does not open a browser. Wraps `grid open --print`.",
|
|
1310
1370
|
{ name: z.string().optional().describe("Entity name. Omit for the entity linked to the current directory.") },
|
|
1311
1371
|
{ readOnlyHint: true, destructiveHint: false, openWorldHint: true },
|
package/src/web.js
CHANGED
|
@@ -25,6 +25,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
|
|
|
25
25
|
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
26
26
|
import { registerTools, decodeJwt } from "./tools.js";
|
|
27
27
|
import { mountOAuth, bearerChallenge } from "./oauth.js";
|
|
28
|
+
import { mountLanding } from "./landing.js";
|
|
28
29
|
import { createSessionLogger } from "./session-logger.js";
|
|
29
30
|
import { createSink } from "./log-sink.js";
|
|
30
31
|
import { INSTRUCTIONS_WEB } from "./playbook.js";
|
|
@@ -98,6 +99,10 @@ app.use(express.urlencoded({ extended: false })); // OAuth token exchange is for
|
|
|
98
99
|
|
|
99
100
|
app.get("/healthz", (_req, res) => res.json({ ok: true, edition: "web" }));
|
|
100
101
|
|
|
102
|
+
// The human-facing root page and favicon. Mounted on both postures; the auth line
|
|
103
|
+
// it renders is driven by the same REQUIRE_AUTH flag used below.
|
|
104
|
+
mountLanding(app, PUBLIC_BASE, { requireAuth: REQUIRE_AUTH });
|
|
105
|
+
|
|
101
106
|
mountOAuth(app, PUBLIC_BASE, { requireAuth: REQUIRE_AUTH });
|
|
102
107
|
|
|
103
108
|
// One transport per MCP session, keyed by the session id.
|
package/src/docs.js
DELETED
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
// CloudGrid MCP server — docs edition (public, read-only, no auth).
|
|
2
|
-
//
|
|
3
|
-
// A public documentation search server exposing search_cloudgrid_documentation
|
|
4
|
-
// (and an optional cloudgrid_quickstart_guide) over MCP Streamable HTTP. The
|
|
5
|
-
// same Express + StreamableHTTP pattern as the web edition (src/web.js),
|
|
6
|
-
// stripped to the minimum: no auth, no OAuth, no session identity, no secrets.
|
|
7
|
-
// Safe to expose anonymously.
|
|
8
|
-
//
|
|
9
|
-
// The corpus (src/corpus/*.md) is indexed at startup with BM25 keyword search.
|
|
10
|
-
// The search backend is behind a clean interface so it can be swapped to
|
|
11
|
-
// semantic/embedding search later without changing the tool contract.
|
|
12
|
-
//
|
|
13
|
-
// Run: PORT=8080 node src/docs.js Health: GET /healthz
|
|
14
|
-
|
|
15
|
-
import { readFileSync } from "node:fs";
|
|
16
|
-
import { randomUUID } from "node:crypto";
|
|
17
|
-
import express from "express";
|
|
18
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
19
|
-
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
20
|
-
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
21
|
-
import { z } from "zod";
|
|
22
|
-
import { DocsSearch, loadCorpus } from "./docs-search.js";
|
|
23
|
-
|
|
24
|
-
const { version } = JSON.parse(readFileSync(new URL("../package.json", import.meta.url)));
|
|
25
|
-
|
|
26
|
-
const PORT = Number(process.env.PORT || 8080);
|
|
27
|
-
|
|
28
|
-
// Build the search index once at startup.
|
|
29
|
-
const search = loadCorpus(new DocsSearch());
|
|
30
|
-
|
|
31
|
-
// Load the quickstart guide verbatim.
|
|
32
|
-
const QUICKSTART = readFileSync(new URL("./corpus/cookbook.md", import.meta.url), "utf-8");
|
|
33
|
-
|
|
34
|
-
// ── Tool helpers (same convention as tools.js) ────────────────────────────────
|
|
35
|
-
function ok(text) {
|
|
36
|
-
return { content: [{ type: "text", text }] };
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function createDocsServer() {
|
|
40
|
-
const server = new McpServer({ name: "cloudgrid-docs", version });
|
|
41
|
-
|
|
42
|
-
// Naming cleanup (spec §6): the docs tools move to the grid_* family. Both
|
|
43
|
-
// legacy names stay registered as deprecated aliases (same handler) so nothing
|
|
44
|
-
// breaks mid-migration; aliases are removed in a later major.
|
|
45
|
-
// search_cloudgrid_documentation → grid_search_docs
|
|
46
|
-
// cloudgrid_quickstart_guide → grid_quickstart
|
|
47
|
-
const dual = (name, alias, description, schema, handler) => {
|
|
48
|
-
server.tool(name, description, schema, handler);
|
|
49
|
-
server.tool(alias, `(deprecated: use ${name}) ${description}`, schema, handler);
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
dual(
|
|
53
|
-
"grid_search_docs",
|
|
54
|
-
"search_cloudgrid_documentation",
|
|
55
|
-
"Search CloudGrid documentation — guides, tutorials, CLI reference, MCP setup, and skill descriptions. Returns the most relevant doc chunks with title, snippet, and source path.",
|
|
56
|
-
{
|
|
57
|
-
query: z
|
|
58
|
-
.string()
|
|
59
|
-
.describe(
|
|
60
|
-
"The search query, e.g. 'deploy a database app' or 'add the MCP to Cursor'",
|
|
61
|
-
),
|
|
62
|
-
},
|
|
63
|
-
async ({ query }) => {
|
|
64
|
-
const results = search.search(query, 5);
|
|
65
|
-
if (results.length === 0) {
|
|
66
|
-
return ok("No documentation matched your query. Try different keywords.");
|
|
67
|
-
}
|
|
68
|
-
const text = results
|
|
69
|
-
.map(
|
|
70
|
-
(r, i) => `### ${i + 1}. ${r.title}\n${r.snippet}\n_Source: ${r.source}_`,
|
|
71
|
-
)
|
|
72
|
-
.join("\n\n---\n\n");
|
|
73
|
-
return ok(text);
|
|
74
|
-
},
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
dual(
|
|
78
|
-
"grid_quickstart",
|
|
79
|
-
"cloudgrid_quickstart_guide",
|
|
80
|
-
"Get the CloudGrid quickstart guide — the canonical build-and-ship loop from scaffold to deploy to feedback.",
|
|
81
|
-
{},
|
|
82
|
-
async () => ok(QUICKSTART),
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
return server;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// ── HTTP server (mirrors src/web.js, no auth) ─────────────────────────────────
|
|
89
|
-
const app = express();
|
|
90
|
-
app.use(express.json());
|
|
91
|
-
|
|
92
|
-
app.get("/healthz", (_req, res) => res.json({ ok: true, edition: "docs" }));
|
|
93
|
-
|
|
94
|
-
const transports = Object.create(null);
|
|
95
|
-
|
|
96
|
-
app.post("/mcp", async (req, res) => {
|
|
97
|
-
const sessionId = req.headers["mcp-session-id"];
|
|
98
|
-
let transport = sessionId ? transports[sessionId] : undefined;
|
|
99
|
-
|
|
100
|
-
if (transport) {
|
|
101
|
-
await transport.handleRequest(req, res, req.body);
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (sessionId || !isInitializeRequest(req.body)) {
|
|
106
|
-
res.status(400).json({
|
|
107
|
-
jsonrpc: "2.0",
|
|
108
|
-
error: { code: -32000, message: "No valid session. Send an initialize request first." },
|
|
109
|
-
id: null,
|
|
110
|
-
});
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const newSessionId = randomUUID();
|
|
115
|
-
transport = new StreamableHTTPServerTransport({
|
|
116
|
-
sessionIdGenerator: () => newSessionId,
|
|
117
|
-
onsessioninitialized: (sid) => {
|
|
118
|
-
transports[sid] = transport;
|
|
119
|
-
},
|
|
120
|
-
});
|
|
121
|
-
transport.onclose = () => {
|
|
122
|
-
if (transport.sessionId) delete transports[transport.sessionId];
|
|
123
|
-
};
|
|
124
|
-
const server = createDocsServer();
|
|
125
|
-
await server.connect(transport);
|
|
126
|
-
await transport.handleRequest(req, res, req.body);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
async function handleSessionRequest(req, res) {
|
|
130
|
-
const sessionId = req.headers["mcp-session-id"];
|
|
131
|
-
const transport = sessionId ? transports[sessionId] : undefined;
|
|
132
|
-
if (!transport) {
|
|
133
|
-
res.status(400).send("Invalid or missing session id");
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
await transport.handleRequest(req, res);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
app.get("/mcp", handleSessionRequest);
|
|
140
|
-
app.delete("/mcp", handleSessionRequest);
|
|
141
|
-
|
|
142
|
-
app.listen(PORT, () => {
|
|
143
|
-
// eslint-disable-next-line no-console
|
|
144
|
-
console.error(
|
|
145
|
-
`cloudgrid-docs edition listening on :${PORT} (POST /mcp, GET /healthz) — ${search.size} chunks indexed`,
|
|
146
|
-
);
|
|
147
|
-
});
|