@dypai-ai/mcp 1.4.2 → 1.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/api.js +20 -4
- package/src/auto-update.js +44 -1
- package/src/index.js +185 -17
- package/src/tools/deploy.js +172 -127
- package/src/tools/frontend.js +65 -7
- package/src/tools/scaffold.js +6 -2
- package/src/tools/sync/diff.js +88 -7
- package/src/tools/sync/pull.js +75 -8
- package/src/tools/sync/push.js +129 -96
- package/src/tools/sync/test-endpoint.js +217 -73
- package/src/tools/sync/validate.js +415 -48
- package/src/tools/sync.js +103 -20
- package/src/tools/status.js +0 -94
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -18,11 +18,15 @@ function getConfig() {
|
|
|
18
18
|
return {
|
|
19
19
|
token: process.env.DYPAI_TOKEN || "",
|
|
20
20
|
apiUrl: process.env.DYPAI_API_URL || DEFAULT_API_URL,
|
|
21
|
+
// Escape hatch for local dev against an API that doesn't have the
|
|
22
|
+
// gzip decompression middleware yet. Set DYPAI_NO_GZIP=1 to send
|
|
23
|
+
// bodies as plain JSON regardless of size.
|
|
24
|
+
noGzip: process.env.DYPAI_NO_GZIP === "1" || process.env.DYPAI_NO_GZIP === "true",
|
|
21
25
|
}
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
export function request(method, path, body) {
|
|
25
|
-
const { token, apiUrl } = getConfig()
|
|
29
|
+
const { token, apiUrl, noGzip } = getConfig()
|
|
26
30
|
const url = `${apiUrl}${path}`
|
|
27
31
|
|
|
28
32
|
return new Promise((resolve, reject) => {
|
|
@@ -33,7 +37,7 @@ export function request(method, path, body) {
|
|
|
33
37
|
let payload = jsonStr ? Buffer.from(jsonStr) : null
|
|
34
38
|
let useGzip = false
|
|
35
39
|
|
|
36
|
-
if (payload && payload.length > GZIP_THRESHOLD) {
|
|
40
|
+
if (payload && payload.length > GZIP_THRESHOLD && !noGzip) {
|
|
37
41
|
payload = gzipSync(payload)
|
|
38
42
|
useGzip = true
|
|
39
43
|
}
|
|
@@ -59,9 +63,21 @@ export function request(method, path, body) {
|
|
|
59
63
|
res.on("end", () => {
|
|
60
64
|
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
61
65
|
try { resolve(JSON.parse(buf)) } catch { resolve(buf) }
|
|
62
|
-
|
|
63
|
-
reject(new Error(`HTTP ${res.statusCode}: ${buf.slice(0, 500)}`))
|
|
66
|
+
return
|
|
64
67
|
}
|
|
68
|
+
// Build a richer error for quota-specific 429s so MCP tools can surface
|
|
69
|
+
// them to the agent without parsing raw HTTP strings.
|
|
70
|
+
let parsedBody = null
|
|
71
|
+
try { parsedBody = JSON.parse(buf) } catch {}
|
|
72
|
+
const err = new Error(`HTTP ${res.statusCode}: ${buf.slice(0, 500)}`)
|
|
73
|
+
err.statusCode = res.statusCode
|
|
74
|
+
err.body = parsedBody
|
|
75
|
+
const detail = parsedBody && parsedBody.detail
|
|
76
|
+
if (detail && typeof detail === "object" && detail.error) {
|
|
77
|
+
err.code = detail.error
|
|
78
|
+
err.detail = detail
|
|
79
|
+
}
|
|
80
|
+
reject(err)
|
|
65
81
|
})
|
|
66
82
|
})
|
|
67
83
|
req.on("error", reject)
|
package/src/auto-update.js
CHANGED
|
@@ -21,6 +21,11 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
21
21
|
const PKG_PATH = join(__dirname, "..", "package.json");
|
|
22
22
|
const PKG_NAME = "@dypai-ai/mcp";
|
|
23
23
|
const REGISTRY_URL = `https://registry.npmjs.org/${PKG_NAME}/latest`;
|
|
24
|
+
// Dist-tags endpoint is tiny (<200B) — used to check if there's a CRITICAL
|
|
25
|
+
// release the user must upgrade to immediately, bypassing the 6h throttle.
|
|
26
|
+
// To mark a version as critical after publish:
|
|
27
|
+
// npm dist-tag add @dypai-ai/mcp@1.4.5 critical
|
|
28
|
+
const DIST_TAGS_URL = `https://registry.npmjs.org/-/package/${PKG_NAME}/dist-tags`;
|
|
24
29
|
const CHECK_TIMEOUT_MS = 2000;
|
|
25
30
|
const THROTTLE_HOURS = 6;
|
|
26
31
|
const STATE_FILE = join(tmpdir(), "dypai-mcp-update-state.json");
|
|
@@ -68,6 +73,32 @@ async function fetchLatestManifest() {
|
|
|
68
73
|
}
|
|
69
74
|
}
|
|
70
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Fetch the `critical` dist-tag (if published). Used to bypass the 6h throttle
|
|
78
|
+
* when a release is important enough that users must upgrade on next spawn.
|
|
79
|
+
*
|
|
80
|
+
* Returns the critical version string (e.g. "1.4.5") or null if no critical
|
|
81
|
+
* tag is set, the registry is unreachable, or the response is malformed.
|
|
82
|
+
*
|
|
83
|
+
* Cost: one tiny JSON fetch (~200 bytes) per spawn. Adds ~50-150ms to startup
|
|
84
|
+
* but runs in parallel with the rest of the MCP init, so wall-clock impact is
|
|
85
|
+
* usually zero.
|
|
86
|
+
*/
|
|
87
|
+
async function fetchCriticalVersion() {
|
|
88
|
+
const ctrl = new AbortController();
|
|
89
|
+
const timer = setTimeout(() => ctrl.abort(), CHECK_TIMEOUT_MS);
|
|
90
|
+
try {
|
|
91
|
+
const res = await fetch(DIST_TAGS_URL, { signal: ctrl.signal });
|
|
92
|
+
if (!res.ok) return null;
|
|
93
|
+
const tags = await res.json();
|
|
94
|
+
return typeof tags?.critical === "string" ? tags.critical : null;
|
|
95
|
+
} catch {
|
|
96
|
+
return null;
|
|
97
|
+
} finally {
|
|
98
|
+
clearTimeout(timer);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
71
102
|
/**
|
|
72
103
|
* After npm publish there's a 30s–2min window where the registry knows the
|
|
73
104
|
* version but the tarball is not yet replicated across the CDN. If we clear
|
|
@@ -145,7 +176,19 @@ export async function checkForUpdates({ force = false } = {}) {
|
|
|
145
176
|
const current = getCurrentVersion();
|
|
146
177
|
if (!current) return { skipped: "no current version" };
|
|
147
178
|
|
|
148
|
-
//
|
|
179
|
+
// ── Critical release check (bypasses the 6h throttle) ────────────────────
|
|
180
|
+
// If an ops person has run `npm dist-tag add @dypai-ai/mcp@X.Y.Z critical`,
|
|
181
|
+
// every spawn picks that up and forces an upgrade regardless of when the
|
|
182
|
+
// last normal check ran. Used for security / data-loss bug fixes where
|
|
183
|
+
// 6-24h propagation is too slow.
|
|
184
|
+
const criticalVersion = await fetchCriticalVersion();
|
|
185
|
+
const hasCritical = criticalVersion && compareVersions(criticalVersion, current) > 0;
|
|
186
|
+
if (hasCritical) {
|
|
187
|
+
log(`CRITICAL update required: ${current} → ${criticalVersion} (bypassing throttle)`);
|
|
188
|
+
force = true;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Throttle (skipped when force or critical)
|
|
149
192
|
if (!force) {
|
|
150
193
|
const state = readState();
|
|
151
194
|
if (state.lastCheckAt) {
|
package/src/index.js
CHANGED
|
@@ -110,7 +110,7 @@ const REMOTE_TOOLS = [
|
|
|
110
110
|
// Note: `get_app_tables` is intentionally NOT exposed — dypai/schema.sql already
|
|
111
111
|
// caches table info locally (auto-refreshed on DDL). For ad-hoc introspection,
|
|
112
112
|
// use execute_sql against information_schema.
|
|
113
|
-
{ name: "execute_sql", description: "
|
|
113
|
+
{ name: "execute_sql", description: "BACKEND ONLY — executes any SQL query on the project database (PostgreSQL). Supports SELECT, INSERT, UPDATE, DELETE, CREATE TABLE, ALTER TABLE, DROP TABLE. Platform schemas (system, auth, storage) are read-only for security. DDL on public.* auto-refreshes dypai/schema.sql. Note: DDL applies IMMEDIATELY to the live database (no draft stage) — for destructive DDL like DROP TABLE / TRUNCATE, summarize the impact and get the user's explicit OK before calling.", inputSchema: { type: "object", properties: { project_id: { type: "string" }, sql: { type: "string", description: "SQL query to execute" } }, required: ["sql"] } },
|
|
114
114
|
|
|
115
115
|
// ── API Endpoints ─────────────────────────────────────────────────────────
|
|
116
116
|
// Full CRUD + exploration goes through the git-first flow:
|
|
@@ -230,6 +230,56 @@ Operations:
|
|
|
230
230
|
},
|
|
231
231
|
},
|
|
232
232
|
|
|
233
|
+
// ── Drafts (production-only staging area) ────────────────────────────────
|
|
234
|
+
// manage_drafts wraps the cloud SDK that talks to /api/engine/{id}/endpoints/
|
|
235
|
+
// Single tool, three ops (list / publish / discard) — same shape as
|
|
236
|
+
// manage_users / manage_roles / manage_storage so the agent's mental
|
|
237
|
+
// model stays uniform. Every project starts in draft-publish mode by
|
|
238
|
+
// default: dypai_push stages mutations as drafts and the user (or
|
|
239
|
+
// agent on their behalf) publishes when ready.
|
|
240
|
+
{
|
|
241
|
+
name: "manage_drafts",
|
|
242
|
+
description: `BACKEND ONLY — inspect, publish, or discard pending backend changes (drafts).
|
|
243
|
+
|
|
244
|
+
Mental model: every change made by \`dypai_push\` (endpoints, webhooks,
|
|
245
|
+
crons, realtime policies) is staged as a DRAFT first. Drafts do NOT
|
|
246
|
+
affect live traffic; they only show up in the live config once the user
|
|
247
|
+
publishes them. This is the universal default — works the same on every
|
|
248
|
+
project, no environment flags to worry about. The frontend is a separate
|
|
249
|
+
stack — use \`manage_frontend(deploy)\` to publish frontend changes.
|
|
250
|
+
|
|
251
|
+
Operations:
|
|
252
|
+
- list: Return pending drafts grouped by resource type. Read-only,
|
|
253
|
+
no confirmation. Run this BEFORE publish/discard so the user
|
|
254
|
+
sees exactly what will ship or be thrown away.
|
|
255
|
+
- publish: Atomically apply ALL pending drafts to live (creates, updates,
|
|
256
|
+
deletions) and invalidate affected caches. Requires confirm:true.
|
|
257
|
+
- discard: Drop pending drafts without applying. By default discards
|
|
258
|
+
every draft; pass resource_names:[...] to scope the discard
|
|
259
|
+
to specific endpoints. Requires confirm:true.
|
|
260
|
+
|
|
261
|
+
Typical flow:
|
|
262
|
+
1. dypai_push → changes saved as drafts
|
|
263
|
+
2. manage_drafts(operation:'list') → show user what's pending
|
|
264
|
+
3. (optional) test the draft from local dev / preview
|
|
265
|
+
4. manage_drafts(operation:'publish', confirm:true) → make it live
|
|
266
|
+
OR manage_drafts(operation:'discard', confirm:true) → throw it away`,
|
|
267
|
+
inputSchema: {
|
|
268
|
+
type: "object",
|
|
269
|
+
properties: {
|
|
270
|
+
project_id: { type: "string", description: "Project UUID. Required for user tokens; auto-detected for project tokens." },
|
|
271
|
+
operation: { type: "string", enum: ["list", "publish", "discard"], description: "Operation to perform." },
|
|
272
|
+
confirm: { type: "boolean", description: "Required true for publish and discard. Without it the tool returns a confirmation_required hint instead of mutating.", default: false },
|
|
273
|
+
resource_names: { type: "array", items: { type: "string" }, description: "Optional. For discard: scope to drafts whose resource_name matches one of these. Ignored by list/publish." },
|
|
274
|
+
},
|
|
275
|
+
required: ["operation"],
|
|
276
|
+
allOf: [
|
|
277
|
+
{ if: { properties: { operation: { const: "publish" } }, required: ["operation"] }, then: { required: ["confirm"] } },
|
|
278
|
+
{ if: { properties: { operation: { const: "discard" } }, required: ["operation"] }, then: { required: ["confirm"] } },
|
|
279
|
+
],
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
|
|
233
283
|
// ── Storage ───────────────────────────────────────────────────────────────
|
|
234
284
|
// manage_storage covers BOTH bucket-level and object-level operations.
|
|
235
285
|
// The remote also accepts the legacy name `list_buckets` (alias) so older
|
|
@@ -405,7 +455,109 @@ endpoint YAML and \`dypai_push\`. This tool does NOT modify the definition.`,
|
|
|
405
455
|
|
|
406
456
|
// ── Server Instructions ──────────────────────────────────────────────────────
|
|
407
457
|
|
|
408
|
-
const SERVER_INSTRUCTIONS = `You are building full-stack applications on the DYPAI platform. You handle
|
|
458
|
+
const SERVER_INSTRUCTIONS = `You are building full-stack applications on the DYPAI platform. You handle BACKEND (workflow endpoints, database, auth, realtime) and FRONTEND (SDK integration, React/Vite/Next code).
|
|
459
|
+
|
|
460
|
+
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
461
|
+
# QUICK START — read this section even if you skip everything else
|
|
462
|
+
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
463
|
+
|
|
464
|
+
## What you ship — two completely separate stacks
|
|
465
|
+
|
|
466
|
+
| Stack | What it is | Where it lives | How you change it | How you publish |
|
|
467
|
+
|---|---|---|---|---|
|
|
468
|
+
| **BACKEND** | Workflow endpoints (HTTP, cron, webhook, telegram), DB schema, realtime policies | \`dypai/\` folder | Edit YAMLs / SQL files / realtime.yaml on disk | \`dypai_push\` (saves draft) → \`manage_drafts(publish, confirm:true)\` |
|
|
469
|
+
| **FRONTEND** | React/Vite/Next bundle (UI, SDK calls) | \`src/\`, \`public/\`, \`package.json\` | Edit React code | \`manage_frontend(deploy, confirm:true)\` |
|
|
470
|
+
|
|
471
|
+
Two things to internalize:
|
|
472
|
+
1. The two stacks are SHIPPED INDEPENDENTLY. Editing backend never touches frontend, and vice versa.
|
|
473
|
+
2. Backend has a draft stage; frontend does NOT. Both publish operations require \`confirm:true\`.
|
|
474
|
+
|
|
475
|
+
## Backend lifecycle — "save" = \`dypai_push\` (this is the rule that trips up new agents)
|
|
476
|
+
|
|
477
|
+
Editing files inside \`dypai/\` only changes YOUR DISK. The platform doesn't see them, the draft overlay doesn't serve them, the local frontend can't call them. There are exactly **three** states and only \`dypai_push\` and \`manage_drafts(publish)\` move you between them:
|
|
478
|
+
|
|
479
|
+
\`\`\`
|
|
480
|
+
┌────────────┐ edit YAML / SQL / md ┌────────────┐ dypai_push ┌────────────┐ manage_drafts(publish, confirm:true) ┌────────────┐
|
|
481
|
+
│ LIVE │ ──────────────────────► │ LOCAL DISK │ ────────────► │ DRAFT │ ──────────────────────────────────────► │ LIVE │
|
|
482
|
+
│ (engine) │ (no platform impact) │ (your edit)│ (stages it) │ (overlay) │ (atomic, all drafts at once) │ (engine) │
|
|
483
|
+
└────────────┘ └────────────┘ └────────────┘ └────────────┘
|
|
484
|
+
▲
|
|
485
|
+
draft overlay │ served at https://dev-<project_id>.dypai.dev
|
|
486
|
+
│ (what the user's local frontend points to)
|
|
487
|
+
\`\`\`
|
|
488
|
+
|
|
489
|
+
Practical consequences — internalize these:
|
|
490
|
+
- **After EVERY meaningful change set, call \`dypai_push\`.** Don't batch a session's worth of edits hoping to push at the end — if you forget, the user tests the local UI and sees the OLD behavior, gets confused, and you waste a debug round-trip blaming the code. The push is cheap, idempotent, and creates ONE draft per resource (subsequent pushes overwrite the draft, not stack new ones).
|
|
491
|
+
- **\`dypai_push\` is the "save" button. It is NOT a publish.** Live traffic is untouched. You can push 20 times in a row without affecting a single user. Tell the user that explicitly when they ask "did it ship?" — push = staged draft, publish = live.
|
|
492
|
+
- **The draft overlay (\`dev-<project_id>.dypai.dev\`) only sees what you've pushed.** A change still on disk is invisible to the local frontend. If the user says "I tested it locally and nothing changed", your first check is "did I run \`dypai_push\` after the last edit?".
|
|
493
|
+
- **\`dypai_validate\` before \`dypai_push\`** — push runs validate as a pre-flight, but running it explicitly first gives you the lint output without committing. Cheap insurance.
|
|
494
|
+
- **Order during a multi-step feature**: edit → \`dypai_validate\` → \`dypai_push\` → \`dypai_test_endpoint(mode:'draft')\` (or tell the user to test their local UI). Repeat per change. ONLY at the end, when the user signs off, do \`manage_drafts(operation:'list')\` → \`manage_drafts(operation:'publish', confirm:true)\`.
|
|
495
|
+
- **DDL is the exception**: \`execute_sql\` with CREATE / ALTER / DROP TABLE applies to live IMMEDIATELY (no draft stage for schema). Drafts only exist for endpoints / webhooks / crons / realtime policies. Summarize destructive DDL to the user before running it.
|
|
496
|
+
|
|
497
|
+
## User intent → tool to call (decision table)
|
|
498
|
+
|
|
499
|
+
Use this BEFORE picking a tool. If unsure which row matches, ask the user.
|
|
500
|
+
|
|
501
|
+
| If the user asks to... | First call | Then |
|
|
502
|
+
|---|---|---|
|
|
503
|
+
| "Create a new project" | \`search_project_templates\` (find a starter) | \`create_project(template_slug: ...)\` |
|
|
504
|
+
| "Show me what we have" / "I want to work on existing project X" | \`list_projects\` → \`dypai_pull\` (backend) + \`manage_frontend(sync)\` (frontend) | Read \`dypai/\` files + \`src/\` |
|
|
505
|
+
| "Add/change a backend endpoint, table, cron, webhook, agent, integration" | Edit files in \`dypai/\` | \`dypai_validate\` → \`dypai_push\` |
|
|
506
|
+
| "Publish my backend changes" / "make it live" | \`manage_drafts(operation:'list')\` to show what's pending | \`manage_drafts(operation:'publish', confirm:true)\` |
|
|
507
|
+
| "Test an endpoint before publishing" | \`dypai_test_endpoint(mode:'local')\` (your edits) or \`(mode:'draft')\` (after push) | — |
|
|
508
|
+
| "Test the new endpoint from my local frontend, end-to-end, before publishing" | Tell user: their local frontend already points to \`https://dev-<project_id>.dypai.dev\` (set by \`manage_frontend(sync)\`), which serves drafts on top of live. So after \`dypai_push\` the local UI hits the draft overlay automatically — nothing else to do. | — |
|
|
509
|
+
| "Throw away my backend changes" | \`manage_drafts(operation:'discard', confirm:true)\` | — |
|
|
510
|
+
| "Change the UI / change colors / add a page" | Edit files in \`src/\` | \`manage_frontend(deploy, confirm:true)\` |
|
|
511
|
+
| "Publish the new UI" / "ship the frontend" | \`manage_frontend(deploy, confirm:true)\` | (deploy is the publish — there is no separate step) |
|
|
512
|
+
| "Roll back" | Backend: \`get_endpoint_versions\` then write old code back. Frontend: re-deploy older source. | — |
|
|
513
|
+
| "Upload a file / a CSV / seed data" | \`bulk_upsert\` (data) or \`manage_storage(upload_file)\` (binary) | — |
|
|
514
|
+
|
|
515
|
+
## Confirm rules — the ONLY operations that need \`confirm:true\`
|
|
516
|
+
|
|
517
|
+
There are exactly THREE write operations that mutate live state and require explicit user approval (return a \`confirmation_required\` hint without it):
|
|
518
|
+
|
|
519
|
+
1. **\`manage_drafts(operation:'publish', confirm:true)\`** — promotes backend drafts to live.
|
|
520
|
+
2. **\`manage_drafts(operation:'discard', confirm:true)\`** — throws away pending backend drafts.
|
|
521
|
+
3. **\`manage_frontend(operation:'deploy', confirm:true)\`** — replaces the live frontend bundle.
|
|
522
|
+
|
|
523
|
+
Everything else (\`dypai_push\`, \`execute_sql\`, \`bulk_upsert\`, all read tools) does NOT require confirm. \`dypai_push\` is safe by design: it stages drafts, so you can iterate freely without affecting the live site.
|
|
524
|
+
|
|
525
|
+
When you receive a \`confirmation_required\` response: SUMMARIZE the change to the user in plain language (what will go live, any warnings about pending backend drafts), wait for an explicit "yes" / "go ahead", then re-call with \`confirm:true\`.
|
|
526
|
+
|
|
527
|
+
## End-to-end example — adding a feature that touches BOTH backend and frontend
|
|
528
|
+
|
|
529
|
+
User: "Add a /api/list-tasks endpoint that returns the current user's tasks, and show them on the dashboard."
|
|
530
|
+
|
|
531
|
+
\`\`\`
|
|
532
|
+
1. dypai_pull(project_id) # materialize backend if not already on disk
|
|
533
|
+
2. manage_frontend(operation:'sync', ...) # materialize frontend if not already on disk
|
|
534
|
+
3. # Backend: create the endpoint
|
|
535
|
+
Write dypai/endpoints/list-tasks.yaml # trigger.http_api auth_mode:jwt + dypai_database query
|
|
536
|
+
4. dypai_validate # catch typos before push
|
|
537
|
+
5. dypai_push # stages as draft, NOT live yet
|
|
538
|
+
6. dypai_test_endpoint(name:'list-tasks', mode:'draft', as_user:'<uuid>') # verify the staged version
|
|
539
|
+
7. manage_drafts(operation:'list') # show pending changes to user
|
|
540
|
+
8. # ASK USER: "Ready to publish list-tasks to live?"
|
|
541
|
+
9. manage_drafts(operation:'publish', confirm:true) # backend now live ✅
|
|
542
|
+
10. # Frontend: call the new endpoint from React
|
|
543
|
+
Edit src/pages/Dashboard.tsx # useEndpoint('list-tasks')
|
|
544
|
+
11. # ASK USER: "Ready to deploy the dashboard UI?"
|
|
545
|
+
12. manage_frontend(operation:'deploy', sourceDirectory, confirm:true) # frontend now live ✅
|
|
546
|
+
\`\`\`
|
|
547
|
+
|
|
548
|
+
**Order matters**: publish backend BEFORE deploying frontend. Otherwise the new UI calls an endpoint that doesn't exist on live yet → 404s for users. The \`manage_frontend(deploy)\` confirmation hint will warn you if backend drafts are still pending.
|
|
549
|
+
|
|
550
|
+
## What you do NOT have to think about
|
|
551
|
+
|
|
552
|
+
- "Development vs production environment" — the user never sees this. Backend changes always go through draft-and-publish. Frontend changes always go through deploy. That's the whole model.
|
|
553
|
+
- "Create auth endpoints" — auth is built into the SDK. \`dypai.auth.signInWithPassword()\` works out of the box. NEVER write a login/signup workflow.
|
|
554
|
+
- "RLS / row-level security" — there is none. You filter by \`\${current_user_id}\` in your SQL WHERE clauses. Forgetting this is the #1 multi-tenancy bug.
|
|
555
|
+
- "Rate limiting / CORS / JWT verification" — handled by the engine.
|
|
556
|
+
- "Promoting projects to production" — every new project already has the draft-publish flow enabled. \`manage_project(promote_to_production)\` is legacy and you should never need it.
|
|
557
|
+
|
|
558
|
+
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
559
|
+
# DEEP REFERENCE — the rest of this document
|
|
560
|
+
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
409
561
|
|
|
410
562
|
## Getting Started
|
|
411
563
|
1. list_projects() → pick project_id.
|
|
@@ -431,14 +583,15 @@ Mental translations:
|
|
|
431
583
|
## Build Backend (git-first workflow)
|
|
432
584
|
Endpoints live in ./dypai/ — there is NO create_endpoint / update_endpoint / add_node tool.
|
|
433
585
|
|
|
434
|
-
1. Tables: \`execute_sql\` for DDL. \`schema.sql\` auto-refreshes.
|
|
435
|
-
2. Endpoints:
|
|
586
|
+
1. Tables: \`execute_sql\` for DDL. \`schema.sql\` auto-refreshes. **DDL is the only backend mutation that bypasses drafts — it hits live immediately.**
|
|
587
|
+
2. Endpoints / realtime / webhooks / crons:
|
|
436
588
|
- Edit/Write YAMLs in \`dypai/endpoints/<group>/<name>.yaml\`.
|
|
437
589
|
- Long SQL / prompts / code go in \`dypai/sql/\`, \`dypai/prompts/\`, \`dypai/code/\` (referenced via \`query_file\`, \`system_prompt_file\`, \`code_file\`).
|
|
438
|
-
- \`dypai_validate\` → catches placeholder / schema / credential / node-param errors.
|
|
590
|
+
- \`dypai_validate\` → catches placeholder / schema / credential / node-param errors. Run before push (push also runs it as pre-flight).
|
|
439
591
|
- \`dypai_diff\` → preview changes (read-only).
|
|
440
|
-
- \`dypai_push\` →
|
|
441
|
-
|
|
592
|
+
- \`dypai_push\` → **stages your edits as drafts on the platform**. This is the "save" step. NOT a publish. Run after every meaningful change set, not just at the end of a session — until you push, neither the engine nor the local frontend (which talks to the draft overlay) can see your edits.
|
|
593
|
+
- \`manage_drafts(operation:'publish', confirm:true)\` → ONLY when the user signs off. Promotes ALL pending drafts atomically to live.
|
|
594
|
+
3. \`dypai_test_endpoint\` to execute an endpoint against the engine. Three sources via \`mode\`: \`local\` (default — your YAML on disk, BEFORE push, fastest while iterating), \`draft\` (the version staged by \`dypai_push\` but not yet published — what live will look like after \`manage_drafts(publish)\`), \`live\` (currently deployed). Canonical loop: edit → \`dypai_validate\` → \`dypai_test_endpoint(mode:'local')\` → \`dypai_push\` → \`dypai_test_endpoint(mode:'draft')\` (or user tests local UI on the draft overlay) → \`manage_drafts(publish, confirm:true)\`.
|
|
442
595
|
|
|
443
596
|
## Picking nodes — catalog-first
|
|
444
597
|
|
|
@@ -494,7 +647,14 @@ Any endpoint can be flagged \`tool: true\` to be callable by \`agent\` nodes. Ho
|
|
|
494
647
|
|
|
495
648
|
## Testing & Debugging
|
|
496
649
|
|
|
497
|
-
|
|
650
|
+
Four ways to validate a backend change, in increasing fidelity:
|
|
651
|
+
|
|
652
|
+
1. **\`dypai_test_endpoint(mode:'local')\`** — runs YOUR YAML on disk against the engine BEFORE \`dypai_push\`. Fastest feedback loop while iterating on a single endpoint. Pass \`as_user\` UUID for jwt endpoints.
|
|
653
|
+
2. **\`dypai_test_endpoint(mode:'draft')\`** — runs the version staged by \`dypai_push\` (i.e. exactly what \`manage_drafts(publish)\` will promote). Use as the final isolated check before publishing.
|
|
654
|
+
3. **End-to-end from the local frontend** (Layer 2.5 draft overlay) — after \`dypai_push\`, the user's local frontend already calls \`https://dev-<project_id>.dypai.dev\` (set by \`manage_frontend(sync)\`), which serves drafts on top of live. So real UI flows hit the draft transparently. No setup, no env-flip, no headers. Read-only impact on prod data — drafts share the SAME database as live.
|
|
655
|
+
4. **\`dypai_test_endpoint(mode:'live')\`** — repro a bug that's already in production.
|
|
656
|
+
|
|
657
|
+
Other tools:
|
|
498
658
|
- **\`dypai_test\`** — YAML regression suites at \`dypai/tests/<name>.test.yaml\` with assertions (equals, matches, contains, type, exists, gte, lte) + setup_sql / teardown_sql.
|
|
499
659
|
- **\`dypai_validate\`** — static linting (placeholders, tables, columns, node params, credentials). Run before EVERY push.
|
|
500
660
|
- **Prod debugging**: \`get_recent_workflow_activity(only_errors=true)\` surfaces recent failures.
|
|
@@ -577,7 +737,8 @@ query_file: /absolute/path/sql/get-orders.sql
|
|
|
577
737
|
- \`/api/v0/<endpoint_name>\` — HTTP endpoints
|
|
578
738
|
- \`/api/v0/webhooks/<endpoint_name>\` — webhook endpoints (different path prefix)
|
|
579
739
|
- \`/public/<path>\` — media served from the storage bucket (auto-populated on deploy; see "Frontend deploy")
|
|
580
|
-
- \`https://<project_id>.dypai.
|
|
740
|
+
- \`https://<project_id>.dypai.dev\` — engine base URL serving LIVE traffic (what the deployed frontend's SDK points to)
|
|
741
|
+
- \`https://dev-<project_id>.dypai.dev\` — engine base URL serving the **draft overlay** (Layer 2.5): drafts staged via \`dypai_push\` are served here, falling back to live for anything not drafted. This is what the SDK in \`.env.local\` points to during local frontend development, so a local UI can validate backend drafts end-to-end BEFORE \`manage_drafts(publish)\`.
|
|
581
742
|
|
|
582
743
|
## Endpoint YAML skeleton (top-level fields)
|
|
583
744
|
|
|
@@ -785,6 +946,8 @@ Pre-configured at \`src/lib/dypai.ts\`. Every method returns \`{ data, error }\`
|
|
|
785
946
|
- **Credentials not created** → workflow fails with "credential not found". Check \`get_app_credentials\` before referencing one in a node. Create in dashboard (not via MCP yet).
|
|
786
947
|
- **Binary files in \`dypai/code/\`** → only text code files here. Binary assets go to the frontend \`public/\` or to a bucket.
|
|
787
948
|
- **\`dypai_push\` without \`dypai_validate\`** → pushing a broken workflow. Always validate first.
|
|
949
|
+
- **Editing a YAML and forgetting \`dypai_push\`** → the user reloads their local frontend (which points at the draft overlay \`dev-<project_id>.dypai.dev\`) and sees the OLD behavior because your edit only exists on YOUR DISK. Symptom: "I tested it locally and nothing changed." First check: did you push? Push after every meaningful change set, not at the end.
|
|
950
|
+
- **Treating \`dypai_push\` as a deploy** → It's a "save as draft", not a publish. Live traffic is unaffected until \`manage_drafts(publish, confirm:true)\`. Don't ask the user "ready to ship?" before push — push freely, only ask before publish.
|
|
788
951
|
- **Frontend dev server + remote media** → media files are auto-uploaded to the storage bucket on deploy but \`vite dev\` doesn't proxy to it. Run \`manage_frontend(sync)\` first to pull media to disk.
|
|
789
952
|
|
|
790
953
|
## Frontend
|
|
@@ -797,19 +960,23 @@ SDK is pre-configured at \`src/lib/dypai.ts\`. Import \`dypai\` from there. Ever
|
|
|
797
960
|
- **Realtime hooks**: \`useRealtime\`, \`useChannel\`, \`useChannelMessages\` (see Realtime section)
|
|
798
961
|
- **Rule**: NEVER \`fetch()\` directly — always through the SDK
|
|
799
962
|
|
|
800
|
-
**\`.env\`
|
|
963
|
+
**\`.env.local\` is auto-managed by \`manage_frontend(sync)\`** — when missing, sync writes it for you pointing at the **draft overlay** (\`https://dev-<project_id>.dypai.dev\`) so your local frontend transparently consumes backend drafts. The variable name follows your framework: \`VITE_DYPAI_URL\` for Vite, \`NEXT_PUBLIC_DYPAI_URL\` for Next.js. **Do not overwrite a user-authored \`.env.local\`** — sync respects an existing file. Only create it manually if \`env_file_missing: true\` in the sync response AND you have a reason to deviate.
|
|
964
|
+
|
|
801
965
|
\`\`\`bash
|
|
802
|
-
# Vite
|
|
803
|
-
VITE_DYPAI_URL=https
|
|
804
|
-
|
|
805
|
-
# Next.js
|
|
806
|
-
NEXT_PUBLIC_DYPAI_URL=https
|
|
966
|
+
# What sync writes (Vite)
|
|
967
|
+
VITE_DYPAI_URL=https://dev-<project_id>.dypai.dev
|
|
968
|
+
|
|
969
|
+
# What sync writes (Next.js)
|
|
970
|
+
NEXT_PUBLIC_DYPAI_URL=https://dev-<project_id>.dypai.dev
|
|
807
971
|
\`\`\`
|
|
808
|
-
|
|
972
|
+
|
|
973
|
+
For **production** the deployed frontend bundle automatically receives the live URL (\`https://<project_id>.dypai.dev\`, no \`dev-\` prefix) injected as a build-time env var by \`manage_frontend(deploy)\` — the user never has to flip URLs by hand. Without an \`.env.local\` the local SDK has no engine to call → every API call fails. It's always the env var, not the code.
|
|
809
974
|
|
|
810
975
|
## Frontend deploy
|
|
811
976
|
|
|
812
|
-
\`manage_frontend(operation: deploy, sourceDirectory, project_id)\` →
|
|
977
|
+
\`manage_frontend(operation: deploy, sourceDirectory, project_id, confirm:true)\` → ships the frontend bundle. **Replaces the live site IMMEDIATELY** — no draft stage, no automatic rollback. \`confirm:true\` is REQUIRED; without it the tool returns a \`confirmation_required\` hint (and warns if backend drafts are still pending — you should publish those FIRST so the frontend doesn't call non-existent endpoints).
|
|
978
|
+
|
|
979
|
+
Returns immediately with build_status=queued. Poll \`operation: build_status\` every ~5s until "success" or "failure". On failure: \`operation: list_deployments\` → \`operation: logs\` with deployment_id. Supports Vite, React, Next.js, Astro, SvelteKit, Nuxt, Remix, Angular, CRA (auto-detected).
|
|
813
980
|
|
|
814
981
|
The deploy is delta by default: only files changed since last deploy are sent. Large media (>25MB) surfaces in \`assets_requiring_action\` with ready-to-exec \`manage_storage(upload_file)\` calls.
|
|
815
982
|
|
|
@@ -822,6 +989,7 @@ The deploy is delta by default: only files changed since last deploy are sent. L
|
|
|
822
989
|
- \`manage_users\` / \`manage_roles\` — app-level RBAC (users via better-auth).
|
|
823
990
|
- \`manage_schedules\` / \`manage_webhooks\` — pause/resume/history. To change the DEFINITION, edit the endpoint YAML and push.
|
|
824
991
|
- \`manage_storage\` — buckets + objects. \`upload_file\` reads local path, signs URL, PUTs direct to the storage bucket, registers. Max 100MB/file. → \`search_docs("file storage")\`.
|
|
992
|
+
- \`manage_drafts\` — universal draft-and-publish workflow. \`dypai_push\` always saves changes as drafts; use \`list\` to show the user what's pending, \`publish\` (confirm:true) to apply them atomically to live, \`discard\` to throw them away. Test pending drafts with \`dypai_test_endpoint(mode:'draft')\` before publishing.
|
|
825
993
|
|
|
826
994
|
## Deep docs — search_docs topic map
|
|
827
995
|
|