@cloudgrid-io/mcp 0.11.0 → 0.11.2

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.11.0",
3
+ "version": "0.11.2",
4
4
  "description": "MCP server for CloudGrid. Three editions: a local stdio server (full toolset), a hosted web server (light, CLI-free toolset), and a public docs server (search_cloudgrid_documentation) over MCP Streamable HTTP.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -21,6 +21,7 @@
21
21
  "test:source": "node test/source-fetch.test.mjs",
22
22
  "test:app-with-data": "node test/app-with-data.test.mjs",
23
23
  "test:self-describing": "node test/self-describing.test.mjs",
24
+ "test:canonical-yaml": "node test/canonical-yaml.test.mjs",
24
25
  "test:content": "node test/content-handling.test.mjs",
25
26
  "test:self-healing": "node test/self-healing.test.mjs",
26
27
  "test:grid-picker": "node test/grid-picker.test.mjs",
@@ -8,7 +8,10 @@ template by its `when:` triggers, adopt that template's `needs:`, then deploy:
8
8
  - **anything needing `needs:` → runtime** (async build, **local edition** only)
9
9
  via `gridctl_plug` on a linked folder.
10
10
 
11
- Fetch this doc any time with `gridctl_fetch("doc", "capability-map")`.
11
+ Fetch this doc any time with `gridctl_fetch("doc", "capability-map")`. For the full
12
+ cloudgrid.yaml schema (every field, the `needs:` injection table, the
13
+ requires-vs-needs caveat, validation rules), fetch the companion reference:
14
+ `gridctl_fetch("doc", "cloudgrid-yaml")`.
12
15
 
13
16
  ## The 6 templates
14
17
 
@@ -29,35 +32,35 @@ users/sessions, log in, or store submissions → it is persistent → `app-with-
29
32
 
30
33
  `needs:` is a MAP declaring infrastructure capabilities. Values are `true` or an
31
34
  engine hint. Cron is NOT a need — it is a **service type** (`type: cron` with
32
- `schedule` + `timezone`). See the cloudgrid.yaml reference §5, §12, §14.
35
+ `schedule` + `timezone`). See the full cloudgrid.yaml reference §5–§6 via
36
+ `gridctl_fetch("doc", "cloudgrid-yaml")`.
33
37
 
34
38
  | `needs:` key | Provides | Injected env var(s) | Status |
35
39
  |---|---|---|---|
36
- | `database: true` | MongoDB primary datastore | `DATABASE_MONGODB_URL` (+legacy `MONGODB_URL`) | **Today via `requires: [mongodb]`** — `needs:` pending #1527 |
37
- | `cache: true` | Redis, LRU eviction | `CACHE_REDIS_URL` (+legacy `REDIS_URL`) | **Today via `requires: [redis]`** — `needs:` pending #1527 |
38
- | `kv: true` | Redis, no eviction | `KV_REDIS_URL` | Pending #1527 |
39
- | `queue: true` | Redis durable job queue | `QUEUE_REDIS_URL` | Pending #1527 |
40
- | `pubsub: true` | Redis pub/sub broadcast | `PUBSUB_REDIS_URL` | Pending #1527 |
41
- | `vector: pgvector` | pgvector embeddings DB | `VECTOR_PGVECTOR_URL` (+legacy `PGVECTOR_URL`) | Pending #1527 |
42
- | `object_storage: true` | GCS bucket | `OBJECT_STORAGE_GCS_BUCKET`, `OBJECT_STORAGE_GCS_REGION` | Pending #1527 |
43
- | `disk: true` | Persistent filesystem at `/data` | `DISK_PATH` | Pending #1527 |
44
- | `ai: true` | AI Gateway access | `AI_GATEWAY_URL` | **Today** (AI Gateway) |
40
+ | `database: true` | MongoDB primary datastore | `DATABASE_MONGODB_URL` (+legacy `MONGODB_URL`) | **Injects via `needs:`** |
41
+ | `cache: true` | Redis, LRU eviction | `CACHE_REDIS_URL` (+legacy `REDIS_URL`) | **Injects via `needs:`** |
42
+ | `kv: true` | Redis, no eviction | `KV_REDIS_URL` | Injects via `needs:` |
43
+ | `queue: true` | Redis durable job queue | `QUEUE_REDIS_URL` | Injects via `needs:` |
44
+ | `pubsub: true` | Redis pub/sub broadcast | `PUBSUB_REDIS_URL` | Injects via `needs:` |
45
+ | `vector: pgvector` | pgvector embeddings DB | `VECTOR_PGVECTOR_URL` (+legacy `PGVECTOR_URL`) | **Injects via `needs:`** |
46
+ | `object_storage: true` | GCS bucket | `OBJECT_STORAGE_GCS_BUCKET`, `OBJECT_STORAGE_GCS_REGION` | Injects via `needs:` |
47
+ | `disk: true` | Persistent filesystem at `/data` | `DISK_PATH` | Injects via `needs:` |
48
+ | `ai: true` | AI Gateway access | `AI_GATEWAY_URL` | Injects via `needs:` |
45
49
  | `type: cron` (service) | Scheduled job (`schedule`, `timezone`) | — | Service type, not a need |
46
50
 
47
- ### Injection status — today vs pending #1527
51
+ ### Injection status
48
52
 
49
- - **Injects TODAY:** `database` and `cache` — but ONLY via the deprecated
50
- `requires:` field (`requires: [mongodb]` → `MONGODB_URL`; `requires: [redis]`
51
- → `REDIS_URL`). `needs:`-based provisioning does NOT inject anything on the
52
- live deployer yet (bug #1527). `ai` (AI Gateway) is available today.
53
- - **Pending #1527:** `needs:`-based provisioning for everything — `vector`,
54
- `queue`, `pubsub`, `kv`, `object_storage`, `disk`, and `needs: database` /
55
- `needs: cache` once the deployer honors `needs:`.
53
+ - **`needs:` injects the connection env vars today.** `needs: { database: true }`
54
+ → `DATABASE_MONGODB_URL` (+legacy `MONGODB_URL`); `needs: { cache: true }` →
55
+ `CACHE_REDIS_URL` (+legacy `REDIS_URL`); `needs: { vector: pgvector }` →
56
+ `VECTOR_PGVECTOR_URL`; and so on across the nine needs. Author `needs:`.
57
+ (*Historical note: the deployer once did not inject from `needs:` — platform
58
+ bug #1527, now fixed and verified live.*)
59
+ - **`requires:` is the deprecated v1 alias.** Don't author new yaml with it.
56
60
  - **`needs:` and `requires:` cannot both be active** in one yaml — the validator
57
- rejects the combination ("use one or the other"). So a DB template ships
58
- `requires: [mongodb]` active with the canonical `needs: { database: true }`
59
- shown only as a **comment** (and declared in the workflow frontmatter as
60
- metadata), flipping to `needs:` when #1527 lands.
61
+ rejects the combination ("use one or the other"). So a DB template ships the
62
+ canonical `needs: { database: true }` active, with NO `requires:` (and declares
63
+ `needs: database` in the workflow frontmatter as metadata).
61
64
 
62
65
  ## How to choose
63
66
 
@@ -0,0 +1,569 @@
1
+ # cloudgrid.yaml reference — the full manifest schema for agents
2
+
3
+ > **Canonical source:** `cloudgrid-yaml-reference.md` in the CloudGrid platform
4
+ > repo. This is the agent-facing distribution
5
+ > of that reference — practically complete, not a verbatim dump. **Keep in sync**
6
+ > with the upstream: when the platform reference changes, update this doc.
7
+ >
8
+ > Fetch this doc any time with `gridctl_fetch("doc", "cloudgrid-yaml")`.
9
+ > For matching an intent to a template, see the companion
10
+ > `gridctl_fetch("doc", "capability-map")`.
11
+
12
+ `cloudgrid.yaml` is the manifest at the **root of your project** that defines your
13
+ entity (an app or an agent). It declares the services your entity runs, the
14
+ infrastructure it needs, and how it behaves. `grid init` creates it; `grid plug`
15
+ reads it to build and deploy.
16
+
17
+ **Wiring a database or cache? Use `needs:`** — `needs: {database: true}` is
18
+ canonical and the deployer injects the connection env vars from it. See §7 for the
19
+ `needs:`-vs-`requires:` note. `requires:` is the deprecated v1 alias; don't author
20
+ new yaml with it.
21
+
22
+ ---
23
+
24
+ ## 1. Minimal, real, deployable examples
25
+
26
+ These are the shapes verified against live deploys. Copy one and adapt it.
27
+
28
+ ### Static site → deploys as an inspiration (instant)
29
+
30
+ A single-file / static bundle. No `needs:`. Publishes instantly via
31
+ `gridctl_drop`, works on any edition (hosted or local).
32
+
33
+ ```yaml
34
+ name: landing-page
35
+ services:
36
+ web:
37
+ type: static
38
+ path: /
39
+ ```
40
+
41
+ ### Node.js app
42
+
43
+ Code lives under `services/web/` (`src/index.js` + `package.json`). Runtime deploy
44
+ (async, local edition only).
45
+
46
+ ```yaml
47
+ name: my-api
48
+ services:
49
+ web:
50
+ type: node
51
+ path: /
52
+ ```
53
+
54
+ ### Next.js app
55
+
56
+ ```yaml
57
+ name: my-dashboard
58
+ services:
59
+ web:
60
+ type: nextjs
61
+ path: /
62
+ ```
63
+
64
+ ### Next.js + database — the canonical shape
65
+
66
+ Declare the datastore with `needs: {database: true}`. The deployer provisions
67
+ Mongo and injects `DATABASE_MONGODB_URL` (plus the legacy `MONGODB_URL` alias) at
68
+ dev-time and runtime. `requires:` is the deprecated v1 alias — don't author new
69
+ yaml with it, and never set `needs:` and `requires:` together (the validator
70
+ rejects the combination). See §7 for the details.
71
+
72
+ This mirrors the live-verified `app-with-data` template exactly: app code under
73
+ `services/web/`, and the app reads `process.env.DATABASE_MONGODB_URL` (falling
74
+ back to the legacy `MONGODB_URL`) **lazily** (inside a getter, never at module top
75
+ level — a top-level read fails `next build`).
76
+
77
+ ```yaml
78
+ # Rename this app. The grid injects the DB connection string (and a Redis URL if
79
+ # you add a cache) as environment variables at runtime — do NOT set them
80
+ # yourself, and never commit a connection string or secret.
81
+ #
82
+ # `needs: { database: true }` is canonical: the deployer provisions Mongo and
83
+ # injects DATABASE_MONGODB_URL (plus the legacy MONGODB_URL alias). `requires:` is
84
+ # the deprecated v1 alias — don't mix the two (the validator rejects it).
85
+ name: my-app
86
+ services:
87
+ web:
88
+ type: nextjs
89
+ path: /
90
+ needs:
91
+ database: true
92
+ ```
93
+
94
+ ### Agent — a cron job
95
+
96
+ An agent is an app with `kind: agent` and an `agent:` block. Cron is a **service
97
+ type**, not a need. `type: cron` requires a `schedule` and cannot have `path` or
98
+ `port`.
99
+
100
+ ```yaml
101
+ name: nightly-sync
102
+ kind: agent
103
+ agent:
104
+ purpose: Sync data from an external API every night
105
+ services:
106
+ sync:
107
+ type: cron
108
+ schedule: "0 9 * * *" # 9am daily
109
+ timezone: UTC
110
+ ```
111
+
112
+ > A cron/agent that also needs a database declares it the canonical way:
113
+ > `needs: {database: true}` (not the deprecated `requires:`).
114
+
115
+ ---
116
+
117
+ ## 2. Full annotated example (the kitchen sink)
118
+
119
+ Every supported field, commented. **No real project uses all of these at once** —
120
+ this is a reference, not a template. (The `needs:` block here is the canonical
121
+ schema — the deployer injects the connection env vars from it; see §7.)
122
+
123
+ ```yaml
124
+ # ─── Identity ───────────────────────────────────────────────────────
125
+ name: my-app # Entity slug (required). 2-42 chars, lowercase + hyphens.
126
+ description: Order processing API # One-line summary (max 280 chars). Optional.
127
+ icon: "📦" # Single emoji or short text (max 64 chars). Optional.
128
+ kind: app # 'app' (default) or 'agent'. Optional.
129
+
130
+ # ─── Networking ─────────────────────────────────────────────────────
131
+ expose: true # Serve public HTTP traffic (default: true).
132
+ # Set to false for internal-only entities.
133
+ custom_domains: # Additional FQDNs the entity serves on. Optional.
134
+ - orders.example.com
135
+
136
+ # ─── Health probe (entity-level override) ───────────────────────────
137
+ probe: # HTTP health probe override. Optional.
138
+ path: /healthz # Probe path (default: /).
139
+ expected: "<500" # Expected status: '<500' (default), '200', '2xx'.
140
+
141
+ # ─── Deploy notifications ──────────────────────────────────────────
142
+ notify: true # Slack notification on deploy failure (default: true).
143
+
144
+ # ─── Scaling ────────────────────────────────────────────────────────
145
+ scale: auto # 'auto' (default), 'alert', or integer 1-10.
146
+
147
+ # ─── Infrastructure needs (v2, CANONICAL — the deployer injects from these) ─
148
+ needs:
149
+ database: true # Mongo (org-tier). Injects DATABASE_MONGODB_URL.
150
+ cache: true # Redis with LRU eviction. Injects CACHE_REDIS_URL.
151
+ kv: true # Redis, no eviction. Injects KV_REDIS_URL.
152
+ queue: true # Redis for BullMQ etc. Injects QUEUE_REDIS_URL.
153
+ pubsub: true # Redis pub/sub. Injects PUBSUB_REDIS_URL.
154
+ vector: pgvector # pgvector. Injects VECTOR_PGVECTOR_URL.
155
+ object_storage: true # GCS bucket. Injects OBJECT_STORAGE_GCS_BUCKET.
156
+ disk: true # Persistent filesystem at /data. Injects DISK_PATH.
157
+ ai: true # AI Gateway access. Injects AI_GATEWAY_URL.
158
+
159
+ # ─── Inter-entity communication ────────────────────────────────────
160
+ calls: # Entities this app calls (outbound grants).
161
+ - org:atomic/billing-api
162
+ - org:atomic/content-service
163
+
164
+ callers: # Inbound caller policy.
165
+ policy: auto # 'auto' or 'manual' (default: 'manual').
166
+ scope: org # 'org', 'space', or 'explicit' (default: 'org').
167
+ fallback: deny # 'deny' or 'warn' (default: 'deny').
168
+
169
+ # ─── Vault ──────────────────────────────────────────────────────────
170
+ vault: # Map of ENV_VAR: vault_item_key. Optional.
171
+ STRIPE_KEY: stripe-live-key
172
+ SENDGRID_API_KEY: sendgrid-key
173
+
174
+ # ─── Connectors and hooks ──────────────────────────────────────────
175
+ connectors:
176
+ - id: team-slack
177
+ target: org # 'system' or 'org'
178
+ channel: "#deploys"
179
+
180
+ hooks: # Outbound events. on ∈ release.published |
181
+ - on: deploy.failed # deploy.failed | feedback.received | entity.created
182
+ via: team-slack # Must match a connector id above.
183
+ template: "Deploy failed for {entity_name}: {error}"
184
+
185
+ triggers: # Inbound events routed into your app.
186
+ - from: team-slack
187
+ event: message
188
+ channel: "#support"
189
+ route: /webhooks/slack # Path in your app that receives the POST.
190
+
191
+ # ─── Local development ─────────────────────────────────────────────
192
+ dev:
193
+ snapshot:
194
+ mask: # Fields to redact in dev snapshots (collection.field).
195
+ - users.email
196
+ - users.phone
197
+
198
+ # ─── Services ──────────────────────────────────────────────────────
199
+ services:
200
+ web:
201
+ type: nextjs # 'node', 'nextjs', 'python', 'static', or 'cron'.
202
+ path: / # HTTP route prefix. false = internal-only.
203
+ port: 3000 # Container port (default: 8080).
204
+ base_image: distroless # 'alpine' or 'distroless' (nextjs only).
205
+ health: # Health probe config.
206
+ path: /api/health
207
+ expected: "200"
208
+ env: # Static env vars (not secrets).
209
+ LOG_LEVEL: info
210
+ depends_on: # Wait for these before starting.
211
+ - worker
212
+
213
+ worker:
214
+ type: node
215
+ path: false # No public URL — internal service.
216
+ source:
217
+ path: src/worker # Custom source location (instead of services/worker/).
218
+ needs: # Per-service needs override (merged per-key).
219
+ queue: true # Adds queue (inherits database, cache from app-level).
220
+ persist: # Service-level persistence.
221
+ - name: scratch
222
+ path: /tmp/scratch
223
+ size: 500Mi
224
+
225
+ reports:
226
+ type: cron
227
+ schedule: "0 9 * * 1" # Every Monday at 9am.
228
+ timezone: UTC # 'UTC', 'EST', or 'PST'.
229
+ run: job # 'job' (run code) or an HTTP URL.
230
+
231
+ frontend:
232
+ type: static
233
+ path: /app
234
+ build: # Build step for static sites.
235
+ command: npm run build
236
+ output: dist
237
+ env:
238
+ VITE_API_URL: https://api.example.com
239
+ node_version: "22" # Node version for build: '18', '20', '22', '24'. STRING, not int.
240
+ ```
241
+
242
+ ---
243
+
244
+ ## 3. Top-level fields
245
+
246
+ ### Required
247
+
248
+ | Field | Type | Description |
249
+ |-------|------|-------------|
250
+ | `name` | string | Entity slug. 2-42 chars, lowercase alphanumeric + hyphens, starts/ends with a letter or digit. Becomes part of the public URL. |
251
+ | `services` | map | One or more named service configs. See §4. |
252
+
253
+ ### Optional
254
+
255
+ | Field | Type | Default | Description |
256
+ |-------|------|---------|-------------|
257
+ | `description` | string | — | One-line summary, max 280 chars. Auto-generated by Grid Brain if unset. |
258
+ | `icon` | string | — | Single emoji or short text, max 64 chars. |
259
+ | `kind` | string | `app` | `app` or `agent` (an agent is an app with an `agent:` block). |
260
+ | `expose` | boolean | `true` | Whether the entity serves public HTTP. `false` = internal-only. |
261
+ | `custom_domains` | string[] | — | Additional FQDNs served. |
262
+ | `probe` | object | — | Entity-level health probe override: `{ path, expected }`. |
263
+ | `notify` | boolean | `true` | Slack notification on deploy failure. |
264
+ | `scale` | string \| int | `auto` | `auto`, `alert`, or integer 1-10. |
265
+ | `needs` | object | — | Infrastructure capabilities (canonical). See §5 + caveat box. |
266
+ | `calls` | string[] | — | Entities this app calls outbound. |
267
+ | `callers` | object | — | Inbound caller policy. |
268
+ | `vault` | map | — | `ENV_VAR: vault_item_key` mappings. |
269
+ | `connectors` / `hooks` / `triggers` | array | — | Connector refs, outbound event hooks, inbound trigger webhooks. |
270
+ | `dev` | object | — | Local development config (`grid dev`). |
271
+ | `agent` | object | — | Agent metadata (loosely typed: `purpose`, `schedule`, `trigger`). |
272
+ | `auto_depends_on_private_requires` | boolean | `true` | Auto-inject private resources as service deps. |
273
+
274
+ ### Deprecated (still accepted)
275
+
276
+ | Field | Replacement | Notes |
277
+ |-------|-------------|-------|
278
+ | `domain` | `custom_domains` | Single custom domain string. |
279
+ | `network` | `expose` | `{ public: bool }`. |
280
+ | `requires` | `needs` | v1 infra requirements. Deprecated — author `needs:` instead. **Cannot be mixed with `needs`** (the validator rejects the combination). |
281
+ | `persist` (top-level) | `needs.disk` | v1 entity-level filesystem persistence. |
282
+
283
+ ---
284
+
285
+ ## 4. Services
286
+
287
+ Each service is a named entry under `services:`. The name is the service identifier
288
+ (same slug rules as the entity name). **By default, a service's code lives at
289
+ `services/<service-name>/`** — `path:` is the URL mount, NOT the filesystem path. A
290
+ service named `web` → the CLI looks for `services/web/`. (Override with `source.path`.)
291
+
292
+ ### Service types
293
+
294
+ | Type | What it runs | Required files | Default port | `path`? |
295
+ |------|-------------|----------------|--------------|---------|
296
+ | `node` | Node.js server | `src/index.js` (or `.ts`), `package.json` | 8080 | yes |
297
+ | `nextjs` | Next.js app (standalone output) | `next.config.*`, `package.json` | 8080 | yes |
298
+ | `python` | Python server | `src/main.py`, `requirements.txt` | 8080 | yes |
299
+ | `static` | Static files served by nginx (optional `build`) | `index.html` or build output | 8080 | yes |
300
+ | `cron` | Scheduled job | `src/job.js` or `src/main.py` | none | **no** — needs `schedule`, no `path`/`port` |
301
+
302
+ ### Service fields
303
+
304
+ | Field | Type | Required | Default | Applies to |
305
+ |-------|------|----------|---------|-----------|
306
+ | `type` | string | Yes | — | All |
307
+ | `path` | string \| `false` | No | — | All except cron |
308
+ | `port` | integer (1-65535) | No | 8080 | All except cron |
309
+ | `lang` | string | No | — | node only (`javascript` \| `typescript`) |
310
+ | `base_image` | string | No | `distroless` | nextjs only (`alpine` \| `distroless`) |
311
+ | `source` | object | No | — | All — `{ path }`, default `services/<name>/`, `.` = project root |
312
+ | `build` | object | No | — | static only — `{ command, output, env }` |
313
+ | `node_version` | string | No | `"22"` | static build (`"18"`/`"20"`/`"22"`/`"24"` — STRING) |
314
+ | `health` | bool \| string \| object | No | TCP | All except cron |
315
+ | `env` | map (string values) | No | — | All (reserved names forbidden) |
316
+ | `schedule` | string (cron) | Required for cron | — | cron only |
317
+ | `run` | string | No | `job` | cron only (`job` or an HTTP URL) |
318
+ | `timezone` | string | No | `UTC` | cron only (`UTC`/`EST`/`PST`) |
319
+ | `depends_on` | string[] | No | — | All except cron (no self/circular, no cron) |
320
+ | `persist` | bool \| array | No | — | All except cron |
321
+ | `needs` | object | No | — | All (overrides/extends app-level per-key) |
322
+
323
+ ### `path` — HTTP routing
324
+
325
+ `"/"` serves at root; `"/api"` under a prefix; `false` = internal only (no public
326
+ URL); omitted = treated as `false` with a soft warning. **Only one service can
327
+ claim `"/"`.**
328
+
329
+ ### `source` — custom source location
330
+
331
+ ```yaml
332
+ source:
333
+ path: src/worker # relative to project root; no leading /, no .., no globs
334
+ ```
335
+
336
+ Use `.` for a flat layout where the service code lives at the project root:
337
+
338
+ ```yaml
339
+ services:
340
+ web:
341
+ type: node
342
+ path: /
343
+ source:
344
+ path: .
345
+ ```
346
+
347
+ ### `build` — static site build step (static only)
348
+
349
+ ```yaml
350
+ build:
351
+ command: npm run build # required
352
+ output: dist # built output dir (default: dist)
353
+ env:
354
+ VITE_API_URL: https://api.example.com
355
+ ```
356
+
357
+ ### `health` — probe config
358
+
359
+ Omitted / `false` → TCP probe. `true` → HTTP at `/health`. `"/healthz"` → HTTP at
360
+ that path. `{ path, expected }` → HTTP with expected status
361
+ (`healthy` (default, 2xx/3xx), `<500`, `200`, `2xx`).
362
+
363
+ ### `env` — static environment variables
364
+
365
+ String key-values injected at runtime (not build). **Reserved names (rejected):**
366
+ `PORT`, `APP_NAME`, `SERVICE_NAME`, `NODE_ENV`, `MONGODB_URL`, `REDIS_URL`,
367
+ `AI_GATEWAY_URL`, `N8N_WEBHOOK_URL`, and any key starting with `CLOUDGRID_`. Use
368
+ `grid secrets set` for sensitive values.
369
+
370
+ ### Per-service `needs` override
371
+
372
+ Merge is per-key — service values override app-level for that key; absent keys
373
+ inherit:
374
+
375
+ ```yaml
376
+ needs: # App-level
377
+ database: true
378
+ cache: true
379
+ pubsub: true
380
+ services:
381
+ worker:
382
+ type: node
383
+ path: false
384
+ needs:
385
+ queue: true # Added
386
+ pubsub: false # Overridden (opted out)
387
+ # Effective: database: true, cache: true, queue: true, pubsub: false
388
+ ```
389
+
390
+ ---
391
+
392
+ ## 5. Needs (infrastructure) — the nine needs
393
+
394
+ The `needs:` block declares infrastructure capabilities; the platform provisions
395
+ them and injects connection details as env vars. **Cron is NOT a need — it is a
396
+ service type.**
397
+
398
+ | Need | Provides | Injected env var(s) | Backing |
399
+ |------|----------|---------------------|---------|
400
+ | `database` | Primary datastore | `DATABASE_MONGODB_URL` (+legacy `MONGODB_URL`) | MongoDB |
401
+ | `cache` | Key-value store, LRU eviction | `CACHE_REDIS_URL` (+legacy `REDIS_URL`) | Redis |
402
+ | `kv` | Key-value store, no eviction | `KV_REDIS_URL` | Redis |
403
+ | `queue` | Durable job queue substrate | `QUEUE_REDIS_URL` | Redis |
404
+ | `pubsub` | Broadcast pub/sub | `PUBSUB_REDIS_URL` | Redis |
405
+ | `vector` | Vector embeddings DB | `VECTOR_PGVECTOR_URL` (+legacy `PGVECTOR_URL`) | pgvector (Postgres) |
406
+ | `object_storage` | Object/blob bucket | `OBJECT_STORAGE_GCS_BUCKET`, `OBJECT_STORAGE_GCS_REGION` | GCS |
407
+ | `disk` | Persistent filesystem mount | `DISK_PATH` | PVC |
408
+ | `ai` | AI Gateway access | `AI_GATEWAY_URL` | CloudGrid AI Gateway |
409
+
410
+ `cache`, `kv`, `queue`, `pubsub` all share one physical Redis but each gets a
411
+ distinct env var. `cache` runs `allkeys-lru` eviction (derived data); the other
412
+ three run `noeviction` (durable data).
413
+
414
+ ### Usage forms
415
+
416
+ ```yaml
417
+ needs:
418
+ database: true # Grid decides tier
419
+ database: pool # Shared Mongo (free)
420
+ database: dedicated # Entity-private Mongo
421
+ database: { tier: external, secret: MY_DB } # Bring your own (value from vault)
422
+
423
+ cache: true # Platform Redis
424
+ cache: { engine: external, secret: MY_REDIS } # Bring your own (same forms for kv/queue/pubsub)
425
+
426
+ vector: true # Platform default engine
427
+ vector: pgvector # pgvector explicitly
428
+ vector: { engine: pgvector, dim: 1536 } # With dimension hint
429
+
430
+ object_storage: true # Default size (100MB)
431
+ object_storage: { size: 10Gi } # Custom size
432
+
433
+ disk: true # 50MB at /data
434
+ disk: { size: 1Gi, mount: /var/data } # Custom (forbidden mounts: /proc, /sys, /dev)
435
+
436
+ ai: true # Enable AI Gateway (ai: false = disabled, default)
437
+ ```
438
+
439
+ ### Injected environment variables
440
+
441
+ **Always injected** (do not set these yourself):
442
+
443
+ | Variable | Description |
444
+ |----------|-------------|
445
+ | `PORT` | Port your service should listen on (your `port:` or 8080). |
446
+ | `APP_NAME` | Entity slug. |
447
+ | `SERVICE_NAME` | Service name (the key under `services:`). |
448
+ | `NODE_ENV` | `production` in deployed environments. |
449
+
450
+ **Per `needs:` declaration:** as in the table above. Legacy aliases
451
+ (`MONGODB_URL`, `REDIS_URL`, `PGVECTOR_URL`) are injected alongside the canonical
452
+ names during the transition; new apps should read the canonical names
453
+ (`DATABASE_MONGODB_URL`, etc.) with the legacy name as a fallback.
454
+
455
+ ### Persistence (disk)
456
+
457
+ Two ways to declare filesystem persistence — use `needs.disk` (recommended) or
458
+ service-level `persist:` (fine-grained). Do not mix the entity-level flag form and
459
+ the service-level verbose array form.
460
+
461
+ ```yaml
462
+ # Recommended:
463
+ needs:
464
+ disk: { size: 1Gi, mount: /var/sqlite }
465
+
466
+ # Fine-grained, per-service:
467
+ services:
468
+ web:
469
+ type: node
470
+ path: /
471
+ persist:
472
+ - name: db
473
+ path: /data/sqlite
474
+ size: 1Gi
475
+ ```
476
+
477
+ Persistence pins the service to 1 replica with a `Recreate` rollout strategy
478
+ (single-writer constraint).
479
+
480
+ ---
481
+
482
+ ## 6. Validation rules (the ones agents trip on)
483
+
484
+ The platform validates `cloudgrid.yaml` at plug time — strictly.
485
+
486
+ 1. **No `Dockerfile`.** Images are generated from `services.*.type`; a Dockerfile
487
+ is silently ignored.
488
+ 2. **No `org:` or `entity_id:` in the YAML.** Identity lives in
489
+ `.cloudgrid/link.json` (gitignored), not the manifest.
490
+ 3. **No mixing `requires:` and `needs:`.** Use one or the other — the validator
491
+ rejects the combination. Author `needs:` (canonical); `requires:` is the
492
+ deprecated v1 alias.
493
+ 4. **No mixed persistence forms.** Entity-level flag form and service-level
494
+ verbose array form cannot coexist.
495
+ 5. **Unknown fields are rejected** with a "did you mean?" suggestion.
496
+ 6. **Reserved env var names cannot be set** in `services.*.env` (see §4 `env`).
497
+ 7. **`port` must be an integer 1-65535** (not a float). Forbidden on `type: cron`.
498
+ 8. **`depends_on`:** no self-references, no circular dependencies, no references to
499
+ cron services.
500
+ 9. **`node_version` must be a string** (`"22"`, not `22`).
501
+ 10. **Cron services require `schedule`** and cannot have `path` or `port`.
502
+ 11. **Entity names** must match `^[a-z][a-z0-9-]*[a-z0-9]$` (2-42 chars) — no
503
+ leading/trailing hyphens.
504
+
505
+ ### File layout
506
+
507
+ ```
508
+ my-app/
509
+ ├── cloudgrid.yaml
510
+ ├── .cloudgrid/link.json # gitignored; local binding to the server-side entity
511
+ ├── services/
512
+ │ ├── web/ # service "web" → services/web/
513
+ │ │ ├── package.json
514
+ │ │ └── src/index.js
515
+ │ └── worker/
516
+ │ ├── package.json
517
+ │ └── src/index.js
518
+ └── .gitignore # includes .cloudgrid/
519
+ ```
520
+
521
+ Use `source.path: .` for a flat project where the code lives at the root.
522
+
523
+ ---
524
+
525
+ ## 7. `needs:` vs `requires:` — and the live deploy mechanics (READ THIS)
526
+
527
+ Bake these into any yaml you author:
528
+
529
+ - **`needs:` is canonical and it injects.** `needs: {database: true}` provisions
530
+ Mongo and injects the connection env vars — `DATABASE_MONGODB_URL` plus the
531
+ legacy `MONGODB_URL` alias. Likewise `cache: true` → `CACHE_REDIS_URL` (+legacy
532
+ `REDIS_URL`), `vector: pgvector` → `VECTOR_PGVECTOR_URL` (+legacy
533
+ `PGVECTOR_URL`), and so on across the nine needs (see §5). An app reading the
534
+ legacy names keeps working because the aliases are injected alongside.
535
+ (*Historical note: the deployer once did not inject from `needs:` — platform
536
+ issue #1527, now fixed and verified live. The old `requires:` workaround is
537
+ obsolete.*)
538
+ - **`requires:` is the deprecated v1 alias** — `requires: [mongodb]` /
539
+ `requires: [redis]`. The CLI warns it's deprecated. **Don't author new yaml with
540
+ it**; use `needs:` instead.
541
+ - **`needs:` and `requires:` together are HARD-REJECTED** by the validator ("use
542
+ one or the other"). So author **`needs:`-only** — never set both.
543
+ - **Static single-file pages deploy as inspirations** via `gridctl_drop` — instant,
544
+ synchronous, works on the hosted edition and any client.
545
+ - **A `cloudgrid.yaml` with `services:` makes it a runtime** — async build,
546
+ **local edition only** (Claude Desktop/Code or the CLI, not hosted). `plug`
547
+ returns `status: building`; poll status until live, then use the returned live
548
+ URL. Re-plug the same entity to update the same URL.
549
+ - **Read injected env vars lazily** (inside a getter/handler), never at module top
550
+ level — a top-level read of `DATABASE_MONGODB_URL` fails `next build`, which
551
+ imports the module for route analysis before the grid injects the var.
552
+
553
+ The canonical DB shape (mirrors the `app-with-data` template):
554
+
555
+ ```yaml
556
+ name: my-app
557
+ services:
558
+ web:
559
+ type: nextjs
560
+ path: /
561
+ needs:
562
+ database: true
563
+ ```
564
+
565
+ ---
566
+
567
+ *This doc is the agent-facing distribution of the platform's canonical
568
+ `cloudgrid-yaml-reference.md`. See also `gridctl_fetch("doc", "capability-map")`
569
+ for matching a user intent to a template + the `needs:` injection status.*
@@ -2,32 +2,34 @@
2
2
 
3
3
  A filled reference imitating the `app-with-data` template: a persistent team
4
4
  task board. Same stack (Next.js App Router + the `mongodb` driver, grid-shared
5
- Mongo via `process.env.MONGODB_URL`), slightly richer than the bare todo
6
- template — tasks have an `assignee` and a `status` (todo / doing / done), and
7
- the API supports updating status via PATCH.
5
+ Mongo via `process.env.DATABASE_MONGODB_URL`), slightly richer than the bare
6
+ todo template — tasks have an `assignee` and a `status` (todo / doing / done),
7
+ and the API supports updating status via PATCH.
8
8
 
9
9
  Data persists in the grid-shared Mongo, so the whole team sees the same board
10
10
  across sessions and refresh.
11
11
 
12
12
  **Same three proven rules as the template:** (1) app code lives under
13
13
  **`services/web/`** (the service name, NOT the repo root — `path:` is the URL
14
- mount); (2) `MONGODB_URL` is read **lazily inside the `getDb` getter**, never at
15
- module top level (a top-level read fails `next build`); (3) the datastore is
16
- declared with **`requires: [mongodb]`, not `needs:`** — the deployer only
17
- injects `MONGODB_URL` from `requires:`.
14
+ mount); (2) the DB connection string is read **lazily inside the `getDb`
15
+ getter**, never at module top level (a top-level read fails `next build`); (3)
16
+ the datastore is declared with the canonical **`needs: { database: true }`** —
17
+ the deployer provisions Mongo and injects `DATABASE_MONGODB_URL` (plus the
18
+ legacy `MONGODB_URL` alias). Don't use the deprecated `requires:` alias.
18
19
 
19
20
  ## cloudgrid.yaml
20
21
 
21
22
  ```yaml
22
- # NOTE: use `requires:` (NOT `needs:`). The deployer only injects MONGODB_URL
23
- # from `requires:`; `needs:` builds fine but injects NO DB connection (500s).
23
+ # `needs: { database: true }` is canonical: the deployer provisions Mongo and
24
+ # injects DATABASE_MONGODB_URL (plus the legacy MONGODB_URL alias). `requires:`
25
+ # is the deprecated v1 alias — don't mix the two (the validator rejects it).
24
26
  name: team-task-board
25
27
  services:
26
28
  web:
27
29
  type: nextjs
28
30
  path: /
29
- requires:
30
- - mongodb
31
+ needs:
32
+ database: true
31
33
  ```
32
34
 
33
35
  ## services/web/package.json
@@ -50,16 +52,18 @@ requires:
50
52
  ## services/web/lib/db.js
51
53
 
52
54
  ```js
53
- // Cached Mongo client. MONGODB_URL is injected by the grid (dev + runtime).
54
- // Resolve it LAZILY inside the getter — a top-level read fails `next build`.
55
+ // Cached Mongo client. The grid injects DATABASE_MONGODB_URL (plus the legacy
56
+ // MONGODB_URL alias) at dev + runtime. Read the canonical var first, fall back to
57
+ // the legacy alias. Resolve it LAZILY inside the getter — a top-level read fails
58
+ // `next build`.
55
59
  import { MongoClient } from "mongodb";
56
60
 
57
61
  function clientPromise() {
58
- const uri = process.env.MONGODB_URL;
62
+ const uri = process.env.DATABASE_MONGODB_URL || process.env.MONGODB_URL;
59
63
  if (!uri) {
60
64
  throw new Error(
61
- "MONGODB_URL is not set. The grid injects it — run `grid dev` locally or " +
62
- "deploy with `grid plug`. Do not set it by hand.",
65
+ "DATABASE_MONGODB_URL is not set. The grid injects it — run `grid dev` " +
66
+ "locally or deploy with `grid plug`. Do not set it by hand.",
63
67
  );
64
68
  }
65
69
  if (!globalThis.__mongoClientPromise) {
@@ -244,8 +248,9 @@ export default function Board({ initialTasks }) {
244
248
 
245
249
  - Deploy the same way as the template: `grid dev` locally, `grid plug` to deploy
246
250
  (async — poll status to a live URL). Re-plug the same entity to keep one URL.
247
- - The only store this needs is Mongo (`requires: [mongodb]` — not `needs:`; the
248
- deployer only injects `MONGODB_URL` from `requires:`). Add `redis: private`
249
- only for caching/sessions/pubsub you actually use.
251
+ - The only store this needs is Mongo (`needs: { database: true }` — the deployer
252
+ provisions Mongo and injects `DATABASE_MONGODB_URL`, plus the legacy
253
+ `MONGODB_URL` alias). Add `cache: true` to `needs:` only for
254
+ caching/sessions/pubsub you actually use.
250
255
  - All app code lives under `services/web/`; the connection is read lazily inside
251
256
  `getDb`, never at module top level.
@@ -7,23 +7,26 @@ page whose state is in memory only.
7
7
  ## How the grid gives you a database
8
8
 
9
9
  You do **not** provision a database or set a connection string. In
10
- `cloudgrid.yaml` you declare `requires: [mongodb]`, and the grid:
10
+ `cloudgrid.yaml` you declare `needs: { database: true }`, and the grid:
11
11
 
12
12
  - provisions shared Mongo for the app, and
13
- - injects the connection string as the **`MONGODB_URL`** environment variable —
14
- at dev-time (`grid dev`) and at runtime (after `grid plug`).
15
-
16
- The app reads it via `process.env.MONGODB_URL` in `services/web/lib/db.js` —
17
- **lazily, inside the `getDb` getter, never at module top level** (a top-level
18
- read fails `next build`, which imports the module for route analysis before the
19
- grid injects the var). Never hardcode a connection string; never commit a
20
- secret. (If you also declare `redis: private`, the grid injects `REDIS_URL` the
21
- same way.)
22
-
23
- > **Use `requires:` (NOT `needs:`).** The CLI warns `requires:` is deprecated,
24
- > but the deployer currently only injects `MONGODB_URL` from `requires:` —
25
- > migrating to `needs:` builds fine but injects NO DB connection (every request
26
- > 500s). Keep `requires:` until CloudGrid's deployer honors `needs:`.
13
+ - injects the connection string as the **`DATABASE_MONGODB_URL`** environment
14
+ variable (plus the legacy `MONGODB_URL` alias) — at dev-time (`grid dev`) and
15
+ at runtime (after `grid plug`).
16
+
17
+ The app reads it via `process.env.DATABASE_MONGODB_URL` (with a legacy
18
+ `process.env.MONGODB_URL` fallback) in `services/web/lib/db.js` — **lazily,
19
+ inside the `getDb` getter, never at module top level** (a top-level read fails
20
+ `next build`, which imports the module for route analysis before the grid injects
21
+ the var). Never hardcode a connection string; never commit a secret. (If you also
22
+ declare `cache: true`, the grid injects `CACHE_REDIS_URL` — and the legacy
23
+ `REDIS_URL` alias — the same way.)
24
+
25
+ > **Declare `needs: { database: true }` (the canonical shape).** The deployer
26
+ > provisions Mongo and injects `DATABASE_MONGODB_URL` (plus the legacy
27
+ > `MONGODB_URL` alias). `requires:` is the deprecated v1 alias — don't author new
28
+ > yaml with it, and never set `needs:` and `requires:` together (the validator
29
+ > rejects the combination).
27
30
 
28
31
  ## Service layout
29
32
 
@@ -36,7 +39,7 @@ App code lives under **`services/web/`**, not the template root. `path:` in
36
39
 
37
40
  ```bash
38
41
  npm install
39
- grid dev # runs Next.js with MONGODB_URL injected against dev Mongo
42
+ grid dev # runs Next.js with DATABASE_MONGODB_URL injected against dev Mongo
40
43
  ```
41
44
 
42
45
  ## Deploy
@@ -52,9 +55,9 @@ to update the same URL.
52
55
  ## File tree
53
56
 
54
57
  ```
55
- cloudgrid.yaml # name + services.web (nextjs) + requires: [mongodb]
58
+ cloudgrid.yaml # name + services.web (nextjs) + needs: { database: true }
56
59
  services/web/package.json # next, react, react-dom, mongodb driver only
57
- services/web/lib/db.js # lazy Mongo client from process.env.MONGODB_URL
60
+ services/web/lib/db.js # lazy Mongo client from DATABASE_MONGODB_URL (legacy MONGODB_URL fallback)
58
61
  services/web/app/layout.js # root layout + inline CSS
59
62
  services/web/app/page.js # server component: reads todos from Mongo
60
63
  services/web/app/todo-form.js # client form: POST/DELETE via the API
@@ -67,4 +70,4 @@ services/web/app/api/todos/route.js # GET (list) / POST (add) / DELETE (remove
67
70
  `services/web/app/page.js` (e.g. `submissions`, `tasks`, `entries`).
68
71
  - Change the document fields (add owners, timestamps, statuses).
69
72
  - Add more routes/collections as the app grows.
70
- - Add `redis: private` to `requires` only if you actually need Redis.
73
+ - Add `cache: true` to `needs:` only if you actually need Redis.
@@ -1,19 +1,15 @@
1
- # Rename this app. The grid injects MONGODB_URL (and REDIS_URL if you add a cache)
2
- # as environment variables at runtime — do NOT set them yourself, and never commit
3
- # a connection string or secret.
1
+ # Rename this app. The grid injects the DB connection string (and a Redis URL if
2
+ # you add a cache) as environment variables at runtime — do NOT set them
3
+ # yourself, and never commit a connection string or secret.
4
4
  #
5
- # NOTE: use `requires:` (NOT `needs:`). The CLI warns that `requires:` is deprecated,
6
- # but the deployer currently only injects MONGODB_URL from `requires:` — migrating to
7
- # `needs:` builds fine but injects NO database connection (every request 500s).
8
- # Keep `requires:` until CloudGrid's deployer honors `needs:`.
5
+ # `needs: { database: true }` is the canonical, recommended shape. The deployer
6
+ # provisions Mongo and injects DATABASE_MONGODB_URL (plus the legacy MONGODB_URL
7
+ # alias). `requires:` is the deprecated v1 alias — don't author new yaml with it,
8
+ # and never set `needs:` and `requires:` together (the validator rejects it).
9
9
  name: my-app
10
10
  services:
11
11
  web:
12
12
  type: nextjs
13
13
  path: /
14
- # Canonical capability (metadata): this app needs a database.
15
- # When #1527 lands (deployer honors needs:), replace requires: with:
16
- # needs:
17
- # database: true
18
- requires:
19
- - mongodb
14
+ needs:
15
+ database: true
@@ -9,17 +9,19 @@ so it survives refresh and is shared across sessions — unlike a static page.
9
9
  root.** `path:` in `cloudgrid.yaml` is the URL mount, NOT the filesystem path.
10
10
  The service named `web` → the CLI looks for `services/web/`. App files at the
11
11
  root fail with `Error: Service directory not found: …/services/web`.
12
- 2. **Read `MONGODB_URL` LAZILY (inside the getter), never at module top level.**
13
- The grid injects the DB connection string as the `MONGODB_URL` environment
14
- variable at dev-time and runtime; the app reads `process.env.MONGODB_URL`
15
- inside `getDb`. A top-level `const uri = process.env.MONGODB_URL; if (!uri)
12
+ 2. **Read the DB connection string LAZILY (inside the getter), never at module
13
+ top level.** The grid injects it as the `DATABASE_MONGODB_URL` environment
14
+ variable (plus the legacy `MONGODB_URL` alias) at dev-time and runtime; the
15
+ app reads `process.env.DATABASE_MONGODB_URL || process.env.MONGODB_URL` inside
16
+ `getDb`. A top-level `const uri = process.env.DATABASE_MONGODB_URL; if (!uri)
16
17
  throw` fails `next build` (the module is imported for route analysis before
17
18
  the grid injects the var). Never hardcode a connection string; never commit a
18
19
  secret.
19
- 3. **Declare the datastore with `requires: [mongodb]` — NOT `needs:`.** The CLI
20
- warns `requires:` is deprecated, but the deployer only injects `MONGODB_URL`
21
- from `requires:`; `needs:` builds fine but injects NO DB connection (every
22
- request 500s). Keep `requires:` until the deployer honors `needs:`.
20
+ 3. **Declare the datastore with `needs: { database: true }`.** This is the
21
+ canonical shape — the deployer provisions Mongo and injects
22
+ `DATABASE_MONGODB_URL` (plus the legacy `MONGODB_URL` alias). `requires:` is
23
+ the deprecated v1 alias; don't author new yaml with it, and never set `needs:`
24
+ and `requires:` together (the validator rejects the combination).
23
25
 
24
26
  Write these files into the scaffolded app folder — the app code goes under
25
27
  `services/web/` — adapt the collection/fields to the user's app, then `grid dev`
@@ -28,9 +30,9 @@ Write these files into the scaffolded app folder — the app code goes under
28
30
  ## File tree
29
31
 
30
32
  ```
31
- cloudgrid.yaml # name + services.web (nextjs) + requires: [mongodb]
33
+ cloudgrid.yaml # name + services.web (nextjs) + needs: { database: true }
32
34
  services/web/package.json # next, react, react-dom, mongodb driver only
33
- services/web/lib/db.js # lazy Mongo client from process.env.MONGODB_URL
35
+ services/web/lib/db.js # lazy Mongo client from DATABASE_MONGODB_URL (legacy MONGODB_URL fallback)
34
36
  services/web/app/layout.js # root layout + inline CSS
35
37
  services/web/app/page.js # server component: reads todos from Mongo
36
38
  services/web/app/todo-form.js # client form: POST/DELETE via the API
@@ -40,32 +42,28 @@ services/web/app/api/todos/route.js # GET (list) / POST (add) / DELETE (remo
40
42
  ## cloudgrid.yaml
41
43
 
42
44
  ```yaml
43
- # Rename this app. The grid injects MONGODB_URL (and REDIS_URL if you add a cache)
44
- # as environment variables at runtime — do NOT set them yourself, and never commit
45
- # a connection string or secret.
45
+ # Rename this app. The grid injects the DB connection string (and a Redis URL if
46
+ # you add a cache) as environment variables at runtime — do NOT set them
47
+ # yourself, and never commit a connection string or secret.
46
48
  #
47
- # NOTE: use `requires:` (NOT `needs:`). The CLI warns that `requires:` is deprecated,
48
- # but the deployer currently only injects MONGODB_URL from `requires:` — migrating to
49
- # `needs:` builds fine but injects NO database connection (every request 500s).
50
- # Keep `requires:` until CloudGrid's deployer honors `needs:`.
49
+ # `needs: { database: true }` is the canonical, recommended shape. The deployer
50
+ # provisions Mongo and injects DATABASE_MONGODB_URL (plus the legacy MONGODB_URL
51
+ # alias). `requires:` is the deprecated v1 alias — don't author new yaml with it,
52
+ # and never set `needs:` and `requires:` together (the validator rejects it).
51
53
  name: my-app
52
54
  services:
53
55
  web:
54
56
  type: nextjs
55
57
  path: /
56
- # Canonical capability (metadata): this app needs a database.
57
- # When #1527 lands (deployer honors needs:), replace requires: with:
58
- # needs:
59
- # database: true
60
- requires:
61
- - mongodb
58
+ needs:
59
+ database: true
62
60
  ```
63
61
 
64
- > **Capability:** this template's canonical need is `database: true` (see the
65
- > commented block above and the capability-map). The deployed yaml keeps
66
- > `requires: [mongodb]` because only `requires:` injects `MONGODB_URL` today
67
- > (#1527); `needs:` and `requires:` cannot co-exist active in the same yaml, so
68
- > the canonical `needs:` is shown only as a comment until the deployer honors it.
62
+ > **Capability:** this template's need is `database: true`. The deployer
63
+ > provisions Mongo and injects `DATABASE_MONGODB_URL` (plus the legacy
64
+ > `MONGODB_URL` alias), so an app reading either var works. `requires:` is the
65
+ > deprecated v1 alias — don't mix it with `needs:` (the validator rejects the
66
+ > combination). See the capability-map for the full injection table.
69
67
 
70
68
  ## services/web/package.json
71
69
 
@@ -93,22 +91,23 @@ requires:
93
91
  ```js
94
92
  // Cached MongoDB client for the App Router.
95
93
  //
96
- // The grid injects the connection string as the MONGODB_URL environment variable
97
- // at runtime (after `grid plug`) and under `grid dev` locally. Never hardcode a
98
- // connection string here and never commit a secret.
94
+ // The grid injects the connection string as the DATABASE_MONGODB_URL environment
95
+ // variable (plus the legacy MONGODB_URL alias) at runtime (after `grid plug`) and
96
+ // under `grid dev` locally. Read the canonical var first, fall back to the legacy
97
+ // alias. Never hardcode a connection string here and never commit a secret.
99
98
  //
100
99
  // The env var and client are resolved LAZILY (inside getDb), never at module top
101
100
  // level — otherwise `next build` throws when it imports this module for route
102
- // analysis, before the grid injects MONGODB_URL.
101
+ // analysis, before the grid injects the var.
103
102
  import { MongoClient } from "mongodb";
104
103
 
105
104
  function clientPromise() {
106
- const uri = process.env.MONGODB_URL;
105
+ const uri = process.env.DATABASE_MONGODB_URL || process.env.MONGODB_URL;
107
106
  if (!uri) {
108
107
  throw new Error(
109
- "MONGODB_URL is not set. The grid injects it automatically — run this app " +
110
- "with `grid dev` locally, or deploy it with `grid plug` (the grid injects " +
111
- "MONGODB_URL at runtime). Do not set it by hand.",
108
+ "DATABASE_MONGODB_URL is not set. The grid injects it automatically — run " +
109
+ "this app with `grid dev` locally, or deploy it with `grid plug` (the grid " +
110
+ "injects the DB connection string at runtime). Do not set it by hand.",
112
111
  );
113
112
  }
114
113
  if (!globalThis.__mongoClientPromise) {
@@ -119,7 +118,7 @@ function clientPromise() {
119
118
 
120
119
  export async function getDb() {
121
120
  const client = await clientPromise();
122
- // The default DB comes from the MONGODB_URL path segment the grid injects.
121
+ // The default DB comes from the connection-string path segment the grid injects.
123
122
  return client.db();
124
123
  }
125
124
  ```
@@ -267,5 +266,5 @@ export async function DELETE(request) {
267
266
 
268
267
  - Rename the `todos` collection to your data (`submissions`, `tasks`, `entries`).
269
268
  - Change the document fields; add owners/timestamps/statuses.
270
- - Add `redis: private` to `requires` only if you actually need Redis.
269
+ - Add `cache: true` to `needs:` only if you actually need Redis.
271
270
  - Run `grid dev` to test locally, `grid plug` to deploy (async — poll to live).
@@ -1,21 +1,22 @@
1
1
  // Cached MongoDB client for the App Router.
2
2
  //
3
- // The grid injects the connection string as the MONGODB_URL environment variable
4
- // at runtime (after `grid plug`) and under `grid dev` locally. Never hardcode a
5
- // connection string here and never commit a secret.
3
+ // The grid injects the connection string as the DATABASE_MONGODB_URL environment
4
+ // variable (plus the legacy MONGODB_URL alias) at dev-time (`grid dev`) and
5
+ // runtime (after `grid plug`). Read the canonical var first, fall back to the
6
+ // legacy alias. Never hardcode a connection string here and never commit a secret.
6
7
  //
7
8
  // The env var and client are resolved LAZILY (inside getDb), never at module top
8
9
  // level — otherwise `next build` throws when it imports this module for route
9
- // analysis, before the grid injects MONGODB_URL.
10
+ // analysis, before the grid injects the var.
10
11
  import { MongoClient } from "mongodb";
11
12
 
12
13
  function clientPromise() {
13
- const uri = process.env.MONGODB_URL;
14
+ const uri = process.env.DATABASE_MONGODB_URL || process.env.MONGODB_URL;
14
15
  if (!uri) {
15
16
  throw new Error(
16
- "MONGODB_URL is not set. The grid injects it automatically — run this app " +
17
- "with `grid dev` locally, or deploy it with `grid plug` (the grid injects " +
18
- "MONGODB_URL at runtime). Do not set it by hand.",
17
+ "DATABASE_MONGODB_URL is not set. The grid injects it automatically — run " +
18
+ "this app with `grid dev` locally, or deploy it with `grid plug` (the grid " +
19
+ "injects the DB connection string at runtime). Do not set it by hand.",
19
20
  );
20
21
  }
21
22
  if (!globalThis.__mongoClientPromise) {
@@ -26,6 +27,6 @@ function clientPromise() {
26
27
 
27
28
  export async function getDb() {
28
29
  const client = await clientPromise();
29
- // The default DB comes from the MONGODB_URL path segment the grid injects.
30
+ // The default DB comes from the connection-string path segment the grid injects.
30
31
  return client.db();
31
32
  }
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: persistent-apps
3
- when: A persistent (Mongo-backed) app misbehaves — data doesn't survive refresh, MONGODB_URL is undefined, a runtime deploy seems stuck, or it won't build on hosted.
4
- summary: Symptom, cause, and fix for the common app-with-data (runtime Mongo app) problems — in-memory vs persistent, injected MONGODB_URL, async deploys, and the hosted-edition gate.
3
+ when: A persistent (Mongo-backed) app misbehaves — data doesn't survive refresh, DATABASE_MONGODB_URL is undefined, a runtime deploy seems stuck, or it won't build on hosted.
4
+ summary: Symptom, cause, and fix for the common app-with-data (runtime Mongo app) problems — in-memory vs persistent, injected DATABASE_MONGODB_URL (+legacy MONGODB_URL), async deploys, and the hosted-edition gate.
5
5
  ---
6
6
 
7
7
  # Troubleshooting: persistent apps (app-with-data)
@@ -12,11 +12,11 @@ Match the symptom to the row, apply the fix, and stop.
12
12
  | --- | --- | --- |
13
13
  | Data doesn't persist / resets on refresh | You built a static inspiration (in-memory / localStorage), not a runtime app | Use the `app-with-data` workflow: a real Next.js + Mongo runtime that reads `process.env.MONGODB_URL`. Static templates keep state only in memory. |
14
14
  | `Error: Service directory not found: …/services/web` | App code lives at the repo/template root, not under the service folder | App code must live under `services/<name>/` (e.g. `services/web/`). `path:` in `cloudgrid.yaml` is the URL mount, NOT the filesystem path — the service name is the folder the CLI looks for. |
15
- | `next build` fails / MONGODB_URL error at build time | The DB env var is read at module top level; `next build` imports the module for route analysis before the grid injects the var | Read `process.env.MONGODB_URL` LAZILY inside the getter (e.g. inside `getDb`), never at module load. Moving the check into the function lets the build pass. |
16
- | App builds + deploys but every request 500s with `MONGODB_URL not set` | You used `needs:` in `cloudgrid.yaml`; the deployer doesn't inject the DB from `needs:` yet | Switch to `requires: [mongodb]`. The CLI warns `requires:` is deprecated, but the deployer only injects `MONGODB_URL` from `requires:` (platform bug flagged to Gilad). Keep `requires:` until `needs:` is honored. |
17
- | `MONGODB_URL is undefined` (or the db.js guard throws) | The env var isn't present because you're not running under the grid | Run locally with `grid dev`, or deploy with `grid plug` — the grid injects `MONGODB_URL` at dev-time and runtime. Do NOT set it yourself and never hardcode a connection string. |
15
+ | `next build` fails / DB connection error at build time | The DB env var is read at module top level; `next build` imports the module for route analysis before the grid injects the var | Read `process.env.DATABASE_MONGODB_URL` (with a legacy `MONGODB_URL` fallback) LAZILY inside the getter (e.g. inside `getDb`), never at module load. Moving the check into the function lets the build pass. |
16
+ | App builds + deploys but every request 500s with `DATABASE_MONGODB_URL not set` | The datastore isn't declared, or you declared the deprecated `requires:` instead of `needs:` | Declare `needs: { database: true }` in `cloudgrid.yaml`. The deployer provisions Mongo and injects `DATABASE_MONGODB_URL` (plus the legacy `MONGODB_URL` alias). Never set `needs:` and `requires:` together (the validator rejects it). |
17
+ | `DATABASE_MONGODB_URL is undefined` (or the db.js guard throws) | The env var isn't present because you're not running under the grid | Run locally with `grid dev`, or deploy with `grid plug` — the grid injects `DATABASE_MONGODB_URL` (+legacy `MONGODB_URL`) at dev-time and runtime. Do NOT set it yourself and never hardcode a connection string. |
18
18
  | Deploy seems stuck / no URL returned | Runtime deploys are asynchronous | `plug` returns `status: building`, not a URL. Poll `gridctl_status` (or the poll_url) until the app is live, then return the live URL. Don't treat the build/log link as the deliverable. |
19
19
  | It won't build on the hosted edition | Hosted MCP is inline-only — it can't run the CLI or folder-plug | Runtime apps need the LOCAL edition (Claude Desktop/Code) or the CLI. On hosted, tell the user persistence isn't available there and offer a static version instead. |
20
- | Redis not available | `redis` wasn't declared | Add `redis: private` to `requires` in `cloudgrid.yaml`; the grid then injects `REDIS_URL`. Only add it if the app actually needs Redis. |
20
+ | Redis not available | `cache` wasn't declared | Add `cache: true` to `needs:` in `cloudgrid.yaml`; the grid then injects `CACHE_REDIS_URL` (plus the legacy `REDIS_URL` alias). Only add it if the app actually needs Redis. |
21
21
 
22
22
  See the `app-with-data` workflow for the full recipe.
@@ -4,8 +4,8 @@ when: to-do, task list, notes app, guestbook, CRUD app, form that SAVES/STORES s
4
4
  needs: database
5
5
  deploy: runtime
6
6
  editions: local
7
- capabilities_note: persistent — needs a database (Mongo). Runtime app, async build, local edition only. Canonical need is `database: true` (metadata); the deployed yaml uses `requires: [mongodb]` until #1527 lands.
8
- summary: Build a real persistent Next.js + Mongo runtime app on the grid — edition-gate first, scaffold, put the app under services/web/, wire process.env.MONGODB_URL lazily, declare requires:[mongodb] (not needs:), deploy async, poll to a live URL.
7
+ capabilities_note: persistent — needs a database (Mongo). Runtime app, async build, local edition only. Declare the canonical `needs: { database: true }`; the deployer injects DATABASE_MONGODB_URL (+legacy MONGODB_URL).
8
+ summary: Build a real persistent Next.js + Mongo runtime app on the grid — edition-gate first, scaffold, put the app under services/web/, wire process.env.DATABASE_MONGODB_URL (legacy MONGODB_URL fallback) lazily, declare needs:{database:true} (not requires:), deploy async, poll to a live URL.
9
9
  ---
10
10
 
11
11
  # Workflow: app-with-data
@@ -48,7 +48,7 @@ the entity + `.cloudgrid/link.json` and writes a `cloudgrid.yaml` with an EMPTY
48
48
  `services: {}`. `plug` needs a linked directory, so run `init` FIRST.
49
49
 
50
50
  You then do two things: (a) write the app under **`services/web/`**, and (b) fill
51
- in `cloudgrid.yaml` to the shape below (`services.web` + `requires: [mongodb]`).
51
+ in `cloudgrid.yaml` to the shape below (`services.web` + `needs: { database: true }`).
52
52
 
53
53
  ## 4. Add the datastore + wire Mongo
54
54
 
@@ -63,27 +63,28 @@ in `cloudgrid.yaml` to the shape below (`services.web` + `requires: [mongodb]`).
63
63
  web:
64
64
  type: nextjs
65
65
  path: /
66
- requires:
67
- - mongodb # alias: db
68
- # - redis: private # OPTIONAL — add only if the app needs Redis
66
+ needs:
67
+ database: true
68
+ # cache: true # OPTIONAL — add only if the app needs Redis
69
69
  ```
70
- **Declare the datastore with `requires: [mongodb]` (NOT `needs:`)** — see the
71
- yaml comment in the template. The CLI warns `requires:` is deprecated, but the
72
- deployer only injects `MONGODB_URL` from `requires:`; `needs:` builds fine but
73
- injects NO DB connection (every request 500s). Keep `requires:` until the
74
- deployer honors `needs:`.
70
+ **Declare the datastore with `needs: { database: true }`** — this is the
71
+ canonical shape. The deployer provisions Mongo and injects
72
+ `DATABASE_MONGODB_URL` (plus the legacy `MONGODB_URL` alias) at dev-time and
73
+ runtime. `requires:` is the deprecated v1 alias; don't author new yaml with it,
74
+ and never set `needs:` and `requires:` together (the validator rejects it).
75
75
  2. Fetch the template for the Mongo wiring + CRUD shape:
76
76
  `gridctl_fetch("template", "app-with-data")`. It is a minimal, real
77
77
  Next.js + `mongodb`-driver to-do app under `services/web/`: a lazy client in
78
78
  `services/web/lib/db.js`, an App-Router GET/POST/DELETE route on a `todos`
79
79
  collection, and a page.
80
80
  3. Adapt it to the user's app (rename the collection, change the fields, adjust
81
- the UI). **Read the database from `process.env.MONGODB_URL`** — the grid
82
- injects that env var at dev-time and runtime. Never hardcode a connection
83
- string; never commit a secret.
84
- - **Put the DB connection behind a lazy getter — never read
85
- `process.env.MONGODB_URL` at module top level, or `next build` fails** (the
86
- module is imported for route analysis before the grid injects the var).
81
+ the UI). **Read the database from `process.env.DATABASE_MONGODB_URL`** (falling
82
+ back to the legacy `process.env.MONGODB_URL`) — the grid injects those env vars
83
+ at dev-time and runtime. Never hardcode a connection string; never commit a
84
+ secret.
85
+ - **Put the DB connection behind a lazy getter — never read the connection
86
+ string at module top level, or `next build` fails** (the module is imported
87
+ for route analysis before the grid injects the var).
87
88
  - (Optional) fetch `gridctl_fetch("example", "app-with-data")` for a slightly
88
89
  richer filled reference to imitate.
89
90
 
@@ -96,7 +97,9 @@ and sanity-check before deploying. Don't require it.
96
97
 
97
98
  - API keys / secrets → `gridctl_secrets`.
98
99
  - Non-secret config → `gridctl_env`.
99
- - Do **NOT** set `MONGODB_URL` or `REDIS_URL` yourself — the grid injects them.
100
+ - Do **NOT** set the DB/cache connection vars yourself (`DATABASE_MONGODB_URL`,
101
+ `CACHE_REDIS_URL`, or their legacy `MONGODB_URL` / `REDIS_URL` aliases) — the
102
+ grid injects them.
100
103
 
101
104
  ## 7. Deploy (async)
102
105
 
package/src/tools.js CHANGED
@@ -90,6 +90,7 @@ Operating rules:
90
90
  10. Publishing a heavy or local file: in the local edition use the path parameter so it is read from disk (no inline size limit); never base64-encode HTML and never pass a file path (or an @-prefixed path) as html. If a drop looks empty, use gridctl_source to check what was actually published, then re-plug with the real HTML/path and target_entity_id.
91
91
  11. Persistence check: if the user needs to SAVE data, share state across users/sessions, log in, or store submissions, that's a runtime app-with-data (Mongo-backed), NOT a static page — static templates keep state only in memory and lose it on refresh. Use the app-with-data workflow. This requires the LOCAL edition (Claude Desktop/Code or the CLI); on the hosted edition, tell the user persistence isn't available there and offer a static version.
92
92
  12. To choose what to build: match the request against the workflow when: triggers and the capability-map (gridctl_fetch({kind:"doc", name:"capability-map"})). Pick the template whose needs: matches what the app requires (persistence → database; scheduled → cron; etc.). Static (needs: none) deploys as an inspiration (instant, any edition); anything needing needs: is a runtime (async, local edition).
93
+ 13. Before writing a cloudgrid.yaml, fetch the reference: gridctl_fetch({kind:"doc", name:"cloudgrid-yaml"}) — it has the full schema, the needs: vocabulary, and the requires-vs-needs caveat (the deployer does not inject from needs: yet, so a database/cache today uses requires: [mongodb] / requires: [redis]).
93
94
 
94
95
  Deploy is edition-dependent: on the hosted MCP call the drop tool with the HTML; on local MCP / CLI write the file and run the plug tool. An HTML page deploys synchronously, so you get a URL right away.`;
95
96