@dypai-ai/mcp 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/api.js +21 -3
- package/src/index.js +402 -347
- package/src/tools/deploy.js +450 -103
- package/src/tools/storage.js +289 -0
- package/src/tools/sync/describe.js +4 -2
- package/src/tools/sync/planner.js +8 -3
- package/src/tools/sync/pull.js +32 -4
- package/src/tools/sync/schema-dump.js +7 -1
- package/src/tools/sync.js +23 -1
package/src/index.js
CHANGED
|
@@ -35,6 +35,7 @@ import { manageFrontendTool } from "./tools/frontend.js"
|
|
|
35
35
|
// import { scaffoldTool } from "./tools/scaffold.js"
|
|
36
36
|
import { manageDomainTool } from "./tools/domains.js"
|
|
37
37
|
import { bulkUpsertTool } from "./tools/bulk-upsert.js"
|
|
38
|
+
import { uploadFile } from "./tools/storage.js"
|
|
38
39
|
// dypaiTestTool (YAML test-suite runner) is intentionally not imported — deferred to v2.
|
|
39
40
|
// The format works but needs fixtures/auto-rollback/scaffolder + proper docs before being surfaced.
|
|
40
41
|
// File still lives at ./tools/sync/test.js and is re-exported from ./tools/sync/index.js
|
|
@@ -235,7 +236,7 @@ Operations:
|
|
|
235
236
|
// local MCP versions still work during rollout. We expose only the new name.
|
|
236
237
|
{
|
|
237
238
|
name: "manage_storage",
|
|
238
|
-
description: `Manage storage: buckets AND the objects inside them.
|
|
239
|
+
description: `Manage storage: buckets AND the objects inside them. Upload files from the local filesystem.
|
|
239
240
|
|
|
240
241
|
Bucket operations:
|
|
241
242
|
- list: Returns all buckets (default operation; no args needed).
|
|
@@ -243,6 +244,14 @@ Bucket operations:
|
|
|
243
244
|
- delete: Delete a bucket and ALL its objects. Args: name. WARNING: irreversible.
|
|
244
245
|
|
|
245
246
|
Object operations (within a specific bucket):
|
|
247
|
+
- upload_file: Upload a local file to a bucket. Args: bucket, local_path; optional
|
|
248
|
+
object_name (default: file basename), prefix (logical subfolder),
|
|
249
|
+
content_type (auto-detected by extension), ensure_bucket (create
|
|
250
|
+
if missing), bucket_public (only used when ensure_bucket creates it).
|
|
251
|
+
Max 100 MB per file. Uploads go DIRECTLY from your machine to
|
|
252
|
+
storage via a presigned URL — no credentials needed on your side.
|
|
253
|
+
Use this for any asset too large or unsupported to bundle with the
|
|
254
|
+
frontend: videos, PDFs, large images, fonts, zip archives, etc.
|
|
246
255
|
- list_objects: List files in a bucket. Args: bucket; optional prefix, limit, offset.
|
|
247
256
|
- delete_object: Delete one file. Args: bucket, name.
|
|
248
257
|
- get_signed_download_url: Generate a temporary URL to view/download a file.
|
|
@@ -250,27 +259,35 @@ Object operations (within a specific bucket):
|
|
|
250
259
|
download (true = force attachment).
|
|
251
260
|
|
|
252
261
|
Notes:
|
|
253
|
-
- UPLOADS are NOT exposed here. Multipart doesn't fit cleanly over MCP stdio.
|
|
254
|
-
Upload via: SDK frontend (\`dypai.api.upload\`) or the \`dypai_storage\` node in a workflow.
|
|
255
262
|
- Bucket names: lowercase, 3-63 chars, [a-z0-9-]. Use hyphens, not underscores.
|
|
256
|
-
- Protected system buckets (e.g. avatars, public, dypai-storage) cannot be deleted
|
|
263
|
+
- Protected system buckets (e.g. avatars, public, dypai-storage) cannot be deleted.
|
|
264
|
+
- For upload_file: every project has a pre-created 'public' bucket you can use without
|
|
265
|
+
creating anything new. Pass bucket:"public" and you're done.
|
|
266
|
+
- Uploads up to 100 MB. For larger files, split them or host externally.`,
|
|
257
267
|
inputSchema: {
|
|
258
268
|
type: "object",
|
|
259
269
|
properties: {
|
|
260
270
|
project_id: { type: "string", description: "Project UUID. Required for user tokens; auto-detected for project tokens." },
|
|
261
271
|
operation: {
|
|
262
272
|
type: "string",
|
|
263
|
-
enum: ["list", "create", "delete", "list_objects", "delete_object", "get_signed_download_url"],
|
|
273
|
+
enum: ["list", "create", "delete", "upload_file", "list_objects", "delete_object", "get_signed_download_url"],
|
|
264
274
|
default: "list",
|
|
265
275
|
},
|
|
266
276
|
// Bucket-level
|
|
267
277
|
name: { type: "string", description: "For create/delete (bucket name) OR for delete_object/get_signed_download_url (object's logical name inside the bucket, e.g. 'avatars/u_123.png')." },
|
|
268
278
|
public: { type: "boolean", description: "If true, bucket files are publicly accessible. Used by: create. Default: false." },
|
|
269
279
|
// Object-level
|
|
270
|
-
bucket: { type: "string", description: "Bucket name. Required by: list_objects, delete_object, get_signed_download_url." },
|
|
271
|
-
prefix: { type: "string", description: "
|
|
280
|
+
bucket: { type: "string", description: "Bucket name. Required by: upload_file, list_objects, delete_object, get_signed_download_url." },
|
|
281
|
+
prefix: { type: "string", description: "Logical subfolder inside the bucket (e.g. 'hero/', 'docs/2026/'). Optional for: upload_file, list_objects." },
|
|
272
282
|
expires_minutes: { type: "integer", minimum: 1, maximum: 1440, description: "Signed URL TTL in minutes. Optional for: get_signed_download_url. Default: 15." },
|
|
273
283
|
download: { type: "boolean", description: "If true, signed URL forces download (Content-Disposition: attachment). Optional for: get_signed_download_url. Default: false." },
|
|
284
|
+
// upload_file params
|
|
285
|
+
local_path: { type: "string", description: "Absolute or relative path to the file on your machine. Required for: upload_file. Example: './public/hero.mp4' or '/Users/me/logo.png'." },
|
|
286
|
+
object_name: { type: "string", description: "Logical name to store the file under in the bucket. Optional for: upload_file. Defaults to the file basename." },
|
|
287
|
+
content_type: { type: "string", description: "Override the auto-detected MIME type. Optional for: upload_file." },
|
|
288
|
+
ensure_bucket: { type: "boolean", description: "If true, create the bucket first if it doesn't exist. Optional for: upload_file. Default: false." },
|
|
289
|
+
bucket_public: { type: "boolean", description: "Only used when ensure_bucket creates the bucket. If true, the new bucket is created as public. Optional for: upload_file. Default: false." },
|
|
290
|
+
source_directory: { type: "string", description: "Optional for: upload_file. If provided, updates the media manifest (dypai/.dypai/media-manifest.json) so future deploys know this file is already in the bucket." },
|
|
274
291
|
// Pagination (used by list and list_objects)
|
|
275
292
|
limit: { type: "integer", description: "Max results. list: default 50, max 200. list_objects: default 20, max 100." },
|
|
276
293
|
offset: { type: "integer", description: "Pagination offset. Default 0." },
|
|
@@ -279,6 +296,7 @@ Notes:
|
|
|
279
296
|
allOf: [
|
|
280
297
|
{ if: { properties: { operation: { const: "create" } }, required: ["operation"] }, then: { required: ["name"] } },
|
|
281
298
|
{ if: { properties: { operation: { const: "delete" } }, required: ["operation"] }, then: { required: ["name"] } },
|
|
299
|
+
{ if: { properties: { operation: { const: "upload_file" } }, required: ["operation"] }, then: { required: ["bucket", "local_path"] } },
|
|
282
300
|
{ if: { properties: { operation: { const: "list_objects" } }, required: ["operation"] }, then: { required: ["bucket"] } },
|
|
283
301
|
{ if: { properties: { operation: { const: "delete_object" } }, required: ["operation"] }, then: { required: ["bucket", "name"] } },
|
|
284
302
|
{ if: { properties: { operation: { const: "get_signed_download_url" } }, required: ["operation"] }, then: { required: ["bucket", "name"] } },
|
|
@@ -389,413 +407,437 @@ endpoint YAML and \`dypai_push\`. This tool does NOT modify the definition.`,
|
|
|
389
407
|
|
|
390
408
|
const SERVER_INSTRUCTIONS = `You are building full-stack applications on the DYPAI platform. You handle backend (endpoints, database, auth, realtime) AND frontend (SDK integration, UI code).
|
|
391
409
|
|
|
410
|
+
## Getting Started
|
|
411
|
+
1. list_projects() → pick project_id.
|
|
412
|
+
2. **dypai_pull FIRST** on any project you haven't worked on in this session — materializes ./dypai/ (endpoints, SQL, prompts, code, schema.sql, node-catalog.json, realtime.yaml) and returns an \`overview\` with endpoint groups, credentials, and tool-enabled endpoints. Backend only.
|
|
413
|
+
3. For frontend code not yet on disk: \`manage_frontend(operation: "sync", targetDirectory: <path>)\`. Independent of dypai_pull — run both for full-stack work.
|
|
414
|
+
4. BEFORE an unfamiliar feature: \`search_docs\` ("auth", "upload files", "realtime", "stripe", etc.).
|
|
415
|
+
5. Build backend first, then frontend.
|
|
416
|
+
|
|
392
417
|
## Creating a new project — template-first
|
|
418
|
+
When the user asks to CREATE a new project, ALWAYS \`search_project_templates(query: "...")\` first with keywords describing the app (e.g. "clinic", "gym", "waitlist", "saas dashboard"). If a template fits, confirm briefly with the user and call \`create_project(name, template_slug: "<slug>")\`. Only use \`template_slug: "blank"\` when nothing matches — blank costs the user hours of boilerplate. If uncertain, ASK before creating.
|
|
393
419
|
|
|
394
|
-
|
|
420
|
+
## Mental model — everything server-side is a workflow
|
|
421
|
+
DYPAI has NO standalone "edge functions" / "serverless functions". Every piece of server-side logic (API endpoints, cron jobs, webhooks, AI agents, background tasks) is a workflow endpoint under \`dypai/endpoints/\`. A workflow can be:
|
|
422
|
+
- **Single \`javascript_code\` / \`python_code\` node** → equivalent to a Vercel/Deno edge function. Put raw code in \`dypai/code/<name>.js\`, reference via \`code_file\`.
|
|
423
|
+
- **Chain of native nodes** → visual, validated, traceable per step (\`dypai_database\`, \`http_request\`, \`agent\`, \`stripe\`, etc.).
|
|
424
|
+
- **Mix** → auth/validation with native nodes, custom logic in a code node.
|
|
395
425
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
426
|
+
Mental translations:
|
|
427
|
+
- "I need an edge function" → workflow with one \`javascript_code\` node (+ free auth, rate limiting, per-step tracing)
|
|
428
|
+
- "I need a cron" → add \`trigger.schedule\` to the YAML
|
|
429
|
+
- "I need a webhook handler" → add \`trigger.webhook\`
|
|
400
430
|
|
|
401
|
-
|
|
431
|
+
## Build Backend (git-first workflow)
|
|
432
|
+
Endpoints live in ./dypai/ — there is NO create_endpoint / update_endpoint / add_node tool.
|
|
402
433
|
|
|
403
|
-
|
|
434
|
+
1. Tables: \`execute_sql\` for DDL. \`schema.sql\` auto-refreshes.
|
|
435
|
+
2. Endpoints:
|
|
436
|
+
- Edit/Write YAMLs in \`dypai/endpoints/<group>/<name>.yaml\`.
|
|
437
|
+
- 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.
|
|
439
|
+
- \`dypai_diff\` → preview changes (read-only).
|
|
440
|
+
- \`dypai_push\` → apply.
|
|
441
|
+
3. \`dypai_test_endpoint\` to run the LOCAL YAML against the engine. Iterate edit → test → push.
|
|
404
442
|
|
|
405
|
-
##
|
|
406
|
-
1. Call list_projects() to find your project_id (skip if you already know it).
|
|
407
|
-
2. **ALWAYS CALL dypai_pull FIRST** on any project you haven't worked on in this session. It materializes ./dypai/ (endpoints, SQL, prompts, code, schema.sql, node-catalog.json, realtime.yaml) and returns an \`overview\` block with endpoint groups, credentials, and tool-enabled endpoints — everything you need to orient yourself in one call. This ONLY brings BACKEND state.
|
|
408
|
-
3. If you're going to read or edit FRONTEND code (React / Vite / Next / etc.) and the source isn't already on this machine, **also call \`manage_frontend(operation: "sync", targetDirectory: <absolute path>)\`** to download it. \`dypai_pull\` and \`manage_frontend(sync)\` are independent: run both when starting fresh on a full-stack project.
|
|
409
|
-
4. BEFORE implementing an unfamiliar feature, call search_docs with the topic (e.g. "auth", "upload files", "realtime", "stripe").
|
|
410
|
-
5. Build backend first, then frontend.
|
|
443
|
+
## Picking nodes — catalog-first
|
|
411
444
|
|
|
412
|
-
|
|
413
|
-
Everything about endpoints lives in files under ./dypai/ — you NEVER call endpoint CRUD tools directly. There is no create_endpoint / update_endpoint / add_node in this MCP anymore.
|
|
445
|
+
\`dypai/node-catalog.json\` (cached by dypai_pull) is the SINGLE source of truth for every node_type and its input/output schema. Read it directly — don't guess params, don't invent node_types. If a type isn't in the catalog, it doesn't exist.
|
|
414
446
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
447
|
+
**Decision table** — check before reaching for \`javascript_code\`:
|
|
448
|
+
|
|
449
|
+
| Step is... | Use | Only JS if... |
|
|
450
|
+
|---|---|---|
|
|
451
|
+
| SELECT/INSERT/UPDATE/DELETE | \`dypai_database\` | Tight branching across multiple queries in one block |
|
|
452
|
+
| Reshape / rename / merge fields | \`set_fields\` | Arbitrary business logic (scoring, pricing rules) |
|
|
453
|
+
| if / else / early return | \`logic\` | Branching mixed with other custom logic |
|
|
454
|
+
| Iterate an array | \`foreach\` (or \`filter\`, \`merge\`) | Stateful accumulation / complex reduce |
|
|
455
|
+
| External HTTP call | \`http_request\` | Orchestrating multiple calls with interdependencies |
|
|
456
|
+
| LLM prompt / tool use | \`agent\` | Always agent — handles memory, tools, streaming |
|
|
457
|
+
| Stripe/Telegram/Slack/Resend/etc | Dedicated integration node | Provider not covered → \`http_request\` |
|
|
458
|
+
| Upload / download / delete files | \`dypai_storage\` | Never |
|
|
459
|
+
| Dates / crypto / delays | \`datetime\`, \`crypto\`, \`wait_delay\` | Never |
|
|
460
|
+
|
|
461
|
+
**JS red flags** (replace with native):
|
|
462
|
+
- \`await db.query(...)\` → \`dypai_database\`
|
|
463
|
+
- \`return { a: data.x, b: nodes.prev.y }\` → \`set_fields\`
|
|
464
|
+
- \`await http.post(...)\` → \`http_request\`
|
|
465
|
+
- \`if (x > 100) return {...} else return {...}\` → \`logic\`
|
|
466
|
+
|
|
467
|
+
JS wins when logic is genuinely custom and bundling multiple concerns is clearer than chaining 4+ native nodes. Native nodes are better otherwise: they're validated, traceable, and readable in diffs.
|
|
468
|
+
|
|
469
|
+
## Realtime (built-in, WebSocket)
|
|
470
|
+
|
|
471
|
+
Four capabilities: DB change notifications, broadcast (ephemeral messaging), presence (who's online), persistent channels (chat with history + DMs + unread counts — zero backend endpoints needed).
|
|
472
|
+
|
|
473
|
+
**Backend**: \`dypai/realtime.yaml\` declares table policies. Tables not there → deny-by-default. Default policy auto-created on CREATE TABLE (filters by \`user_id = \${current_user_id}\` if column exists).
|
|
474
|
+
|
|
475
|
+
**Frontend hooks**: \`useRealtime(table, opts)\` for DB changes, \`useChannel(name, opts)\` for broadcast+presence, \`useChannelMessages(id)\` + \`useChannels()\` for chat.
|
|
476
|
+
|
|
477
|
+
→ Deep details: \`search_docs("realtime channels")\` (client API), \`search_docs("realtime policies")\` (backend YAML format).
|
|
478
|
+
|
|
479
|
+
## Agent tools (endpoints as tools for \`agent\` nodes)
|
|
480
|
+
|
|
481
|
+
Any endpoint can be flagged \`tool: true\` to be callable by \`agent\` nodes. How you give an LLM ability to query DB / send emails / call APIs.
|
|
482
|
+
|
|
483
|
+
**Essentials**:
|
|
484
|
+
- Mark the endpoint with \`tool: true\` + \`tool_description\` (what the LLM sees when deciding to call it — be specific) + \`input:\` JSON Schema (tight schema = tight calls).
|
|
485
|
+
- \`auth_mode: api_key\` for tools (server-to-server). NEVER \`public\` for writes.
|
|
486
|
+
- In the \`agent\` node: \`tools: [endpoint-names]\` — by NAME, the codec resolves to UUIDs.
|
|
487
|
+
|
|
488
|
+
⚠️ **TRAP**: raw engine param is \`tool_ids\` (UUIDs). In YAML ALWAYS write \`tools: [names]\`. Using \`tool_ids: ["my-endpoint"]\` bypasses codec → engine gets literal string → fails silently in production.
|
|
489
|
+
|
|
490
|
+
**Discovery**: \`dypai_pull\` → \`overview.endpoints.tool_endpoints\` lists existing tools. Check before writing duplicates.
|
|
491
|
+
**Validation**: \`dypai_validate\` catches typos (rule \`agent_tool_not_found\`). Engine enforces depth limit 3 on agent→tool→agent chains.
|
|
492
|
+
|
|
493
|
+
→ Full example + patterns: \`search_docs("agent tools")\` or copy from \`dypai/endpoints/_example.yaml.disabled\`.
|
|
494
|
+
|
|
495
|
+
## Testing & Debugging
|
|
424
496
|
|
|
425
|
-
|
|
497
|
+
- **\`dypai_test_endpoint\`** — single endpoint, LOCAL YAML (unpushed edits). Pass \`as_user\` UUID for jwt endpoints. Trace modes: \`smart\` (default), \`full\`, \`minimal\`. Iterate edit → test → push.
|
|
498
|
+
- **\`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
|
+
- **\`dypai_validate\`** — static linting (placeholders, tables, columns, node params, credentials). Run before EVERY push.
|
|
500
|
+
- **Prod debugging**: \`get_recent_workflow_activity(only_errors=true)\` surfaces recent failures.
|
|
426
501
|
|
|
427
|
-
|
|
502
|
+
→ Deep patterns: \`search_docs("testing endpoints")\` (test setup + assertions), \`search_docs("troubleshooting")\` (common failures + fixes).
|
|
428
503
|
|
|
429
|
-
|
|
430
|
-
1. Decide the CONCEPT (DB insert? HTTP call? shape fields? branching?).
|
|
431
|
-
2. Scan \`dypai/node-catalog.json\` for a native node that covers that concept.
|
|
432
|
-
3. If you find one: read its input_schema from the same file and configure the node.
|
|
433
|
-
4. If nothing native fits naturally: consider \`javascript_code\` / \`python_code\` (see below).
|
|
504
|
+
## The \`dypai/\` folder (BACKEND source of truth)
|
|
434
505
|
|
|
435
|
-
|
|
506
|
+
\`dypai_pull\` materializes this at the PROJECT ROOT (sibling of \`src/\`, \`package.json\`, etc. — NOT inside \`src/\`). Everything backend-related lives here. Edit files directly with Edit/Write, then \`dypai_validate\` → \`dypai_diff\` → \`dypai_push\`.
|
|
436
507
|
|
|
437
|
-
|
|
508
|
+
\`\`\`
|
|
509
|
+
<project-root>/
|
|
510
|
+
├── package.json
|
|
511
|
+
├── src/ ← frontend code (React/Vite/Next)
|
|
512
|
+
│ └── lib/dypai.ts ← SDK client, do NOT hand-edit
|
|
513
|
+
├── public/ ← static assets (frontend bundle)
|
|
514
|
+
└── dypai/ ← BACKEND (created by dypai_pull)
|
|
515
|
+
├── dypai.config.yaml ← project id, engine URL. Don't hand-edit.
|
|
516
|
+
├── endpoints/ ← workflow definitions (YOU EDIT these)
|
|
517
|
+
│ ├── list-tasks.yaml
|
|
518
|
+
│ ├── create-order.yaml
|
|
519
|
+
│ └── Admin/ ← subfolder = endpoint group "Admin"
|
|
520
|
+
│ └── ban-user.yaml
|
|
521
|
+
├── sql/ ← extracted SQL (referenced via query_file)
|
|
522
|
+
│ └── complex-report.sql
|
|
523
|
+
├── prompts/ ← agent system prompts (referenced via system_prompt_file)
|
|
524
|
+
│ └── shop-assistant.md
|
|
525
|
+
├── code/ ← raw JS/Python (referenced via code_file)
|
|
526
|
+
│ └── scoring.js
|
|
527
|
+
├── schema.sql ← READ-ONLY. DDL of public.* tables (auto-refreshed on DDL)
|
|
528
|
+
├── node-catalog.json ← READ-ONLY. Every node_type + its I/O schema
|
|
529
|
+
├── realtime.yaml ← realtime subscription policies (edit + push to customize)
|
|
530
|
+
├── tests/ ← YAML test suites (optional)
|
|
531
|
+
│ └── create-order.test.yaml
|
|
532
|
+
└── .dypai/ ← local cache, gitignored — DO NOT touch
|
|
533
|
+
├── deploy-manifest.json ← SHA hashes from last deploy (delta engine)
|
|
534
|
+
└── media-manifest.json ← media files uploaded to R2
|
|
535
|
+
\`\`\`
|
|
438
536
|
|
|
439
|
-
|
|
537
|
+
### Where to put what
|
|
440
538
|
|
|
441
|
-
| If
|
|
539
|
+
| If you need to add... | Put it here | How it's referenced |
|
|
442
540
|
|---|---|---|
|
|
443
|
-
|
|
|
444
|
-
|
|
|
445
|
-
|
|
|
446
|
-
|
|
|
447
|
-
|
|
|
448
|
-
|
|
|
449
|
-
|
|
|
450
|
-
|
|
|
451
|
-
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
-
|
|
461
|
-
-
|
|
462
|
-
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
541
|
+
| A new API endpoint | \`dypai/endpoints/<name>.yaml\` | URL: \`/api/v0/<name>\` |
|
|
542
|
+
| Endpoint organized in a group | \`dypai/endpoints/<Group>/<name>.yaml\` | URL: same — group is organizational only |
|
|
543
|
+
| A cron job | \`dypai/endpoints/<name>.yaml\` with \`trigger.schedule\` | Runs automatically |
|
|
544
|
+
| A webhook handler | \`dypai/endpoints/<name>.yaml\` with \`trigger.webhook\` | URL: \`/api/v0/webhooks/<name>\` |
|
|
545
|
+
| A telegram bot | \`dypai/endpoints/<name>.yaml\` with \`trigger.telegram\` | Webhook auto-registered |
|
|
546
|
+
| SQL too long for a YAML field | \`dypai/sql/<name>.sql\` | \`query_file: sql/<name>.sql\` |
|
|
547
|
+
| Long agent system prompt | \`dypai/prompts/<name>.md\` | \`system_prompt_file: prompts/<name>.md\` |
|
|
548
|
+
| Raw JS/Python function body | \`dypai/code/<name>.js\` or \`.py\` | \`code_file: code/<name>.js\` |
|
|
549
|
+
| A regression test | \`dypai/tests/<name>.test.yaml\` | Run via \`dypai_test\` |
|
|
550
|
+
| Realtime policy | Add entry to \`dypai/realtime.yaml\` | Auto-applied on push |
|
|
551
|
+
| A new database table | Use \`execute_sql\` (CREATE TABLE …) | Auto-reflected in \`schema.sql\` |
|
|
552
|
+
| A credential (OpenAI key, etc.) | Dashboard (not via MCP yet) | Reference by name: \`credential: openai-prod\` |
|
|
553
|
+
|
|
554
|
+
### What NOT to edit
|
|
555
|
+
|
|
556
|
+
- **\`schema.sql\`** — auto-generated snapshot of DB DDL. To change tables: \`execute_sql\` with CREATE/ALTER/DROP TABLE, schema.sql refreshes automatically.
|
|
557
|
+
- **\`node-catalog.json\`** — auto-generated from the engine. Read it to learn node schemas; never hand-edit.
|
|
558
|
+
- **\`dypai.config.yaml\`** — project identity (UUID, slug). Regenerated on pull.
|
|
559
|
+
- **\`.dypai/\`** — local cache. The delta deploy + media manifest live here.
|
|
560
|
+
- **\`src/lib/dypai.ts\`** — SDK client config. Regenerated by \`manage_frontend(sync)\`.
|
|
561
|
+
|
|
562
|
+
### Path resolution inside YAML
|
|
563
|
+
|
|
564
|
+
All \`*_file\` paths are relative to \`dypai/\` root:
|
|
565
|
+
\`\`\`yaml
|
|
566
|
+
# ✅ CORRECT (dypai/sql/get-orders.sql exists)
|
|
567
|
+
query_file: sql/get-orders.sql
|
|
568
|
+
|
|
569
|
+
# ❌ WRONG
|
|
570
|
+
query_file: ./sql/get-orders.sql
|
|
571
|
+
query_file: dypai/sql/get-orders.sql
|
|
572
|
+
query_file: /absolute/path/sql/get-orders.sql
|
|
573
|
+
\`\`\`
|
|
574
|
+
|
|
575
|
+
### Paths on the engine side
|
|
576
|
+
|
|
577
|
+
- \`/api/v0/<endpoint_name>\` — HTTP endpoints
|
|
578
|
+
- \`/api/v0/webhooks/<endpoint_name>\` — webhook endpoints (different path prefix)
|
|
579
|
+
- \`/public/<path>\` — media served from R2 (auto-populated on deploy; see "Frontend deploy")
|
|
580
|
+
- \`https://<project_id>.dypai.app\` — the engine base URL (what the SDK points to)
|
|
581
|
+
|
|
582
|
+
## Endpoint YAML skeleton (top-level fields)
|
|
479
583
|
|
|
480
584
|
\`\`\`yaml
|
|
481
|
-
name:
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
585
|
+
name: create-order # required. URL = /api/v0/create-order
|
|
586
|
+
description: "..." # optional, shown in dashboard
|
|
587
|
+
method: POST # optional (default POST)
|
|
588
|
+
|
|
589
|
+
trigger: # required, exactly ONE of:
|
|
590
|
+
http_api: { auth_mode: jwt } # HTTP (jwt | api_key | public)
|
|
591
|
+
# webhook: { path, methods, stripe_webhook, hmac_secret_env, auth_mode: public }
|
|
592
|
+
# schedule: { cron: "0 9 * * *", timezone: "Europe/Madrid", payload }
|
|
593
|
+
# telegram: { credential: my-bot }
|
|
594
|
+
|
|
595
|
+
input: # optional JSON Schema — engine validates before workflow runs
|
|
488
596
|
type: object
|
|
489
|
-
required: [
|
|
597
|
+
required: [product_id]
|
|
490
598
|
properties:
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
599
|
+
product_id: { type: string }
|
|
600
|
+
|
|
601
|
+
allowed_roles: [admin] # optional — restrict to these roles
|
|
602
|
+
|
|
603
|
+
tool: true # optional — exposes this endpoint to agent nodes
|
|
604
|
+
tool_description: "..." # required when tool:true
|
|
605
|
+
|
|
496
606
|
workflow:
|
|
497
607
|
nodes:
|
|
498
|
-
- id:
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
return: true
|
|
502
|
-
query: SELECT id, name, price, stock FROM products WHERE name ILIKE '%' || \${input.query} || '%' LIMIT \${input.limit}
|
|
608
|
+
- { id: x, type: ..., return: true, ...params }
|
|
609
|
+
edges:
|
|
610
|
+
- { from: a, to: b } # optional — default is declaration order
|
|
503
611
|
\`\`\`
|
|
504
612
|
|
|
505
|
-
|
|
506
|
-
- A tool endpoint is still a regular endpoint (same trigger, same workflow, same deploy). \`tool: true\` is additive.
|
|
507
|
-
- **\`tool_description\` is critical** — this is the prompt the LLM sees when deciding whether to call your tool. Be specific about *when* to use it and *what it returns*. Vague descriptions cause the agent to skip useful tools or misuse them.
|
|
508
|
-
- Define a **tight \`input\` JSON Schema** with \`description\` on each field. The LLM uses the schema to fabricate arguments; sloppy schemas = sloppy calls.
|
|
509
|
-
- Auth mode: usually \`api_key\` (the agent node calls server-side) or \`jwt\` if you need \${current_user_id} in the tool. **Never \`public\`** for tools that write data.
|
|
613
|
+
**Auth modes**: \`jwt\` (user-authed, auto-injects \`\${current_user_id}\` / \`\${current_user_role}\`), \`api_key\` (server-to-server, \`x-api-key\` header), \`public\` (anonymous, NEVER for writes).
|
|
510
614
|
|
|
511
|
-
|
|
615
|
+
**Placeholders**: \`\${input.<f>}\`, \`\${nodes.<id>.<f>}\`, \`\${current_user_id}\` (jwt only), \`\${current_user_role}\` (jwt only), \`\${env.<VAR>}\` (engine env vars from dashboard).
|
|
512
616
|
|
|
513
|
-
|
|
617
|
+
→ Deep reference: \`search_docs("trigger model")\` (full trigger options per type), \`search_docs("workflow patterns")\` (mandatory patterns), \`search_docs("auth defaults")\` (what to use when user doesn't specify).
|
|
514
618
|
|
|
515
|
-
|
|
516
|
-
workflow:
|
|
517
|
-
nodes:
|
|
518
|
-
- id: chat
|
|
519
|
-
type: agent
|
|
520
|
-
provider: openai # or anthropic | google
|
|
521
|
-
model: gpt-4o
|
|
522
|
-
credential: openai-prod # must exist — use get_app_credentials
|
|
523
|
-
system_prompt: |
|
|
524
|
-
You are a shop assistant. Use the search-products tool when the user
|
|
525
|
-
asks about availability, prices, or categories.
|
|
526
|
-
input: \${input.message}
|
|
527
|
-
tools: [search-products, send-email] # ← by name. Codec resolves to UUIDs on push.
|
|
528
|
-
max_iterations: 5
|
|
529
|
-
return: true
|
|
530
|
-
\`\`\`
|
|
619
|
+
## Templates (reuse before reinventing)
|
|
531
620
|
|
|
532
|
-
|
|
621
|
+
Before writing a workflow from scratch, check if it already exists:
|
|
533
622
|
|
|
534
|
-
|
|
623
|
+
- **\`search_workflow_templates(query: "...")\`** — curated templates for common patterns (Stripe checkout, send email via Resend, AI chatbot, CRUD with realtime, Telegram bot, etc.). Returns ready-to-use YAML you can copy into \`dypai/endpoints/\`.
|
|
624
|
+
- **\`search_project_templates(query: "...")\`** — full project starters (clinic, gym, waitlist, saas dashboard). Pass the slug to \`create_project(template_slug: "...")\`.
|
|
625
|
+
- **\`dypai/endpoints/_example.yaml.disabled\`** — auto-shipped on empty projects. Complete tour of every trigger type, every common node, auth modes, agent tools, return paths. Read it when starting fresh.
|
|
535
626
|
|
|
536
|
-
|
|
627
|
+
Pattern: search templates → if match, copy + adapt → else write from scratch using the node-catalog.json.
|
|
537
628
|
|
|
538
|
-
|
|
629
|
+
## dypai_database — query vs mutation
|
|
539
630
|
|
|
540
|
-
|
|
631
|
+
Two operations:
|
|
632
|
+
- **\`operation: query\`** — raw SQL. Use for ALL reads and any complex write (joins, CTEs, subqueries, aggregations, multi-table).
|
|
633
|
+
- **\`operation: mutation\`** — declarative single-table CRUD. The shape decides: \`insert:\` → INSERT, \`insert:\` + \`on_conflict:\` → UPSERT, \`update:\` + \`where:\` → UPDATE, \`delete: true\` + \`where:\` → DELETE.
|
|
541
634
|
|
|
542
|
-
|
|
635
|
+
**Legacy ops** (\`select\`/\`insert\`/\`update\`/\`delete\`/\`upsert\`/\`aggregate\`/\`custom_query\`) still work but don't write new ones — validator warns.
|
|
543
636
|
|
|
544
|
-
|
|
637
|
+
→ Full shapes + examples: \`search_docs("nodes reference")\` or read \`dypai/node-catalog.json\` entry for \`dypai_database\`.
|
|
545
638
|
|
|
546
|
-
##
|
|
639
|
+
## SQL placeholders — no manual casts
|
|
547
640
|
|
|
548
|
-
-
|
|
549
|
-
The only test tool surfaced today. Pass endpoint name + input. Reads the LOCAL YAML (even unpushed edits), inlines *_file refs, runs against the engine with fabricated input. For jwt endpoints pass as_user UUID to impersonate. Iterate tightly: edit → test → fix → test → push.
|
|
550
|
-
|
|
551
|
-
- A YAML-based regression-test suite runner is planned but not yet surfaced — when writing a new endpoint, verify it with dypai_test_endpoint after each edit and document edge cases in the commit message / PR for now.
|
|
552
|
-
|
|
553
|
-
- Historical production traces are not inspectable via MCP yet — direct the user to the dashboard if they need to debug a specific past execution.
|
|
554
|
-
|
|
555
|
-
## Where things live
|
|
556
|
-
- dypai/endpoints/foo.yaml — endpoint definition (top-level, no group)
|
|
557
|
-
- dypai/endpoints/Admin/foo.yaml — endpoint in group "Admin" (subfolder = group)
|
|
558
|
-
- dypai/sql/foo.sql — SQL extracted when > 500 chars
|
|
559
|
-
- dypai/prompts/foo.md — agent system prompts extracted when > 800 chars
|
|
560
|
-
- dypai/code/foo.js — JavaScript/Python extracted when > 500 chars (escape hatch only)
|
|
561
|
-
- dypai/schema.sql — DDL of public.* tables (read BEFORE writing SQL; auto-refreshed on DDL)
|
|
562
|
-
- dypai/node-catalog.json — every node_type with its input/output schemas
|
|
563
|
-
- dypai/realtime.yaml — realtime subscription policies (optional)
|
|
564
|
-
- dypai/tests/*.test.yaml — reserved for the v2 suite runner; leave empty for now
|
|
565
|
-
- dypai/dypai.config.yaml — project identity (don't hand-edit)
|
|
566
|
-
- dypai/.dypai/ — local cache (gitignored)
|
|
567
|
-
- src/dypai/ — NOT auto-generated. If you want TS types for row shapes in the frontend, define them manually based on dypai/schema.sql (a codegen helper may return in a future version).
|
|
568
|
-
- All *_file paths inside YAML are relative to dypai/ root (not to the YAML file).
|
|
569
|
-
|
|
570
|
-
## Build Frontend
|
|
571
|
-
- SDK client is already configured at src/lib/dypai.ts — just import it:
|
|
572
|
-
import { dypai } from './lib/dypai'
|
|
573
|
-
- Types: \`import { api } from '@/dypai'\` for typed endpoint calls with autocomplete.
|
|
574
|
-
- API calls: dypai.api.get(name), dypai.api.post(name, body), dypai.api.put(), dypai.api.delete()
|
|
575
|
-
- Auth: dypai.auth.signInWithPassword(), signUp(), signOut(), getSession()
|
|
576
|
-
- Realtime: dypai.realtime.subscribe(table, opts, callback)
|
|
577
|
-
- Files: dypai.api.upload(name, file) — always upload from browser
|
|
578
|
-
- Every method returns { data, error } — never throws
|
|
579
|
-
- Do NOT call the API directly with fetch() — always use the SDK
|
|
580
|
-
- Do NOT create auth endpoints — auth is built-in via SDK (dypai.auth.*)
|
|
581
|
-
|
|
582
|
-
### \`.env\` file — required for the SDK to reach the engine
|
|
583
|
-
The frontend SDK reads the engine URL from an environment variable. \`.env\` is gitignored, so \`manage_frontend(sync)\` does NOT bring it down. **If the project has no \`.env\` after sync (the response sets \`env_file_missing: true\`), you MUST create it** before running or deploying the frontend:
|
|
641
|
+
The engine binds placeholders as Postgres params (\$1, \$2…), injection-safe. Auto-casts by value shape: UUID-shaped strings → \`\$1::uuid\`, objects/arrays → \`\$1::jsonb\`, Date → \`\$1::timestamptz\`, primitives → inferred.
|
|
584
642
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
643
|
+
Write SQL naturally:
|
|
644
|
+
\`\`\`
|
|
645
|
+
✅ WHERE user_id = \${current_user_id}
|
|
646
|
+
❌ WHERE user_id = '\${current_user_id}'::uuid (redundant — validator warns)
|
|
647
|
+
\`\`\`
|
|
589
648
|
|
|
590
|
-
|
|
591
|
-
|
|
649
|
+
## Canonical YAML slip-ups (typical mistakes the codec catches but don't ship)
|
|
650
|
+
|
|
651
|
+
- **Trigger is TOP-LEVEL**, never a node. \`trigger: { http_api | webhook | schedule | telegram }\`. Don't write \`start_trigger\` as a node.
|
|
652
|
+
- **Nodes use \`type\` (not \`node_type\`) and \`return: true\` (not \`is_return\`)**. Codec accepts aliases — write canonical.
|
|
653
|
+
- **Edges use \`from/to\`** (not \`source/target\`). Branching: \`{ from: cond, to: y, source_handle: "true" }\`.
|
|
654
|
+
- **Params are FLAT on the node**. Only nest under \`parameters:\` when a param name collides with reserved keys (id/type/return/variable).
|
|
655
|
+
- **Multiple \`return: true\` nodes are fine** — execution reaches one per run.
|
|
656
|
+
|
|
657
|
+
When in doubt → \`dypai/endpoints/_example.yaml.disabled\` (shipped on empty projects) or \`search_docs("trigger model")\` for a complete tour.
|
|
658
|
+
|
|
659
|
+
## Workflow execution model (what happens under the hood)
|
|
660
|
+
|
|
661
|
+
- **Nodes form a DAG** from \`edges\`. With no explicit edges, nodes run sequentially in declaration order.
|
|
662
|
+
- **Data flow**: each node reads \`\${input.<field>}\` (endpoint input) and \`\${nodes.<id>.<field>}\` (prior node output). A node's output is whatever its last expression/return produces.
|
|
663
|
+
- **Return**: the HTTP response is the output of the FIRST node reached with \`return: true\`. Multiple \`return: true\` nodes are fine — only one fires per run.
|
|
664
|
+
- **Errors**: an unhandled throw in any node fails the whole workflow → HTTP 500 with the error message. Use the \`logic\` node or try/catch in \`javascript_code\` for controlled error responses.
|
|
665
|
+
- **Timeouts**: default workflow timeout is 30s (raised to 90s on Pro+). Individual nodes don't have sub-timeouts — design around the total.
|
|
666
|
+
- **Concurrency**: per-project, not per-endpoint. Pool-mode projects share a slot budget based on plan.
|
|
667
|
+
|
|
668
|
+
## Auth (better-auth backed)
|
|
669
|
+
|
|
670
|
+
**No DIY auth.** Never write login/signup/session endpoints — the SDK handles everything:
|
|
671
|
+
\`\`\`ts
|
|
672
|
+
await dypai.auth.signUp({ email, password, name })
|
|
673
|
+
await dypai.auth.signInWithPassword({ email, password })
|
|
674
|
+
await dypai.auth.signOut()
|
|
675
|
+
const session = await dypai.auth.getSession() // null if not logged in
|
|
592
676
|
\`\`\`
|
|
593
677
|
|
|
594
|
-
|
|
595
|
-
- Override the base domain with the \`DYPAI_ENGINE_BASE\` env var (default \`dypai.app\`) only for self-hosted setups.
|
|
596
|
-
- Without \`.env\`, every SDK call fails with a network error / 404 because \`createClient(undefined)\` resolves nowhere. Don't try to debug the code — it's the missing env var.
|
|
678
|
+
**Protecting endpoints**: \`trigger.http_api.auth_mode: jwt\` is the default. \`\${current_user_id}\` and \`\${current_user_role}\` are auto-injected into the workflow — use them in SQL WHERE clauses for multi-tenant isolation.
|
|
597
679
|
|
|
598
|
-
|
|
599
|
-
- \`manage_frontend(operation: deploy, sourceDirectory, project_id)\` — uploads source, returns immediately with build_status=queued.
|
|
600
|
-
- Poll \`manage_frontend(operation: build_status)\` every ~5s until status is "success" or "failure" (typical build: 20-60s).
|
|
601
|
-
- On failure: \`manage_frontend(operation: list_deployments)\` → pick the failed deployment_id → \`manage_frontend(operation: logs, deployment_id)\`.
|
|
602
|
-
- Supports: Vite, React, Next.js, Astro, SvelteKit, Nuxt, Remix, Angular, CRA, and more (auto-detected).
|
|
680
|
+
**Role-based access**: add \`allowed_roles: [admin, editor]\` at the endpoint top-level to restrict. Roles live in \`system.roles\` (manage via \`manage_roles\`). Built-in roles: \`authenticated\`, \`admin\`. User role lives in \`auth.users.role\` (manage via \`manage_users\`).
|
|
603
681
|
|
|
604
|
-
|
|
605
|
-
- Use bulk_upsert to import CSV or JSON files into database tables
|
|
606
|
-
- Great for seeding initial data (products, categories, config)
|
|
682
|
+
**Protected frontend routes**: wrap with \`<ProtectedRoute>\` from \`@dypai-ai/client-sdk/react\` — redirects to login if no session.
|
|
607
683
|
|
|
608
|
-
|
|
609
|
-
- manage_domain(operation: list | add | remove | verify) — single tool for all domain ops.
|
|
610
|
-
- add returns the CNAME the user must configure at their registrar. SSL provisions automatically once DNS propagates.
|
|
611
|
-
- verify forces an on-demand DNS/SSL re-check (normally the platform polls in the background).
|
|
684
|
+
**No RLS magic**: the engine does NOT auto-filter queries by user. You MUST write \`WHERE user_id = \${current_user_id}\` in your SQL. Missing this is the #1 multi-tenancy bug.
|
|
612
685
|
|
|
613
|
-
|
|
614
|
-
- Auth modes: jwt (default — for user-authenticated requests), api_key (server-to-server), public (read-only, anonymous).
|
|
615
|
-
- NEVER use public for endpoints that write data.
|
|
616
|
-
- Placeholders: \${input.<field>}, \${nodes.<node_id>.<field>}, \${current_user_id}, \${current_user_role}.
|
|
617
|
-
- \${current_user_id} / \${current_user_role} only work with jwt auth mode.
|
|
686
|
+
→ Canonical flows (signup/reset/role upgrade/magic link): \`search_docs("auth flows")\`. What defaults to pick when user doesn't specify: \`search_docs("auth defaults")\`.
|
|
618
687
|
|
|
619
|
-
##
|
|
688
|
+
## Agent node (\`type: agent\`)
|
|
620
689
|
|
|
621
|
-
|
|
622
|
-
need the others:
|
|
690
|
+
Vercel AI SDK under the hood. Supports OpenAI, Anthropic, Google. Key params: \`memory_key\` (persist conversation), \`tools: [names]\` (see Agent tools), \`max_iterations\` (default 5), \`return: true\` for streaming via SSE.
|
|
623
691
|
|
|
624
|
-
|
|
625
|
-
Use for ANY non-trivial read or any complex write. Joins, CTEs, subqueries,
|
|
626
|
-
window functions, FILTER clauses, aggregations, multi-table — all fair game.
|
|
692
|
+
**Cost awareness**: agents with tools + high max_iterations fan out (5-10 LLM calls per chat). For simple completions without tools, \`http_request\` to the provider is cheaper.
|
|
627
693
|
|
|
628
|
-
|
|
629
|
-
- id: dashboard
|
|
630
|
-
type: dypai_database
|
|
631
|
-
operation: query
|
|
632
|
-
return: true
|
|
633
|
-
query: |
|
|
634
|
-
WITH revenue AS (
|
|
635
|
-
SELECT SUM(amount) AS total FROM invoices
|
|
636
|
-
WHERE user_id = \${current_user_id} AND paid_at >= date_trunc('month', NOW())
|
|
637
|
-
)
|
|
638
|
-
SELECT
|
|
639
|
-
(SELECT total FROM revenue) AS revenue,
|
|
640
|
-
(SELECT COUNT(*) FROM tasks WHERE user_id = \${current_user_id} AND status = 'pending') AS pending_tasks
|
|
641
|
-
\`\`\`
|
|
694
|
+
→ Deep details: \`search_docs("agent ai")\` — memory, streaming, structured output, provider specifics.
|
|
642
695
|
|
|
643
|
-
|
|
644
|
-
Use for trivial single-table writes. The shape decides the operation:
|
|
696
|
+
## Core DYPAI nodes (the primitives)
|
|
645
697
|
|
|
646
|
-
|
|
647
|
-
# INSERT
|
|
648
|
-
- id: create
|
|
649
|
-
type: dypai_database
|
|
650
|
-
operation: mutation
|
|
651
|
-
return: true
|
|
652
|
-
table: clients
|
|
653
|
-
insert: { name: \${input.name}, user_id: \${current_user_id} }
|
|
654
|
-
|
|
655
|
-
# UPSERT (insert + on_conflict)
|
|
656
|
-
- id: upsert
|
|
657
|
-
type: dypai_database
|
|
658
|
-
operation: mutation
|
|
659
|
-
return: true
|
|
660
|
-
table: clients
|
|
661
|
-
insert: { id: \${input.id}, name: \${input.name} }
|
|
662
|
-
on_conflict: { columns: [id], action: update }
|
|
663
|
-
|
|
664
|
-
# UPDATE
|
|
665
|
-
- id: update
|
|
666
|
-
type: dypai_database
|
|
667
|
-
operation: mutation
|
|
668
|
-
return: true
|
|
669
|
-
table: clients
|
|
670
|
-
update: { name: \${input.name} }
|
|
671
|
-
where: { id: \${input.id}, user_id: \${current_user_id} }
|
|
672
|
-
|
|
673
|
-
# DELETE
|
|
674
|
-
- id: delete
|
|
675
|
-
type: dypai_database
|
|
676
|
-
operation: mutation
|
|
677
|
-
table: clients
|
|
678
|
-
delete: true
|
|
679
|
-
where: { id: \${input.id}, user_id: \${current_user_id} }
|
|
680
|
-
\`\`\`
|
|
698
|
+
These are the engine-level building blocks you'll use in almost every workflow. Learn these well — integrations are just on top.
|
|
681
699
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
700
|
+
| Node | What it does |
|
|
701
|
+
|---|---|
|
|
702
|
+
| \`dypai_database\` | SQL (query) or declarative CRUD (mutation) against the project's Postgres. THE most-used node. |
|
|
703
|
+
| \`dypai_storage\` | Upload / download / delete files in storage buckets. |
|
|
704
|
+
| \`agent\` | LLM call with tools, memory, streaming. OpenAI / Anthropic / Google. |
|
|
705
|
+
| \`http_request\` | Generic HTTP call for anything without a dedicated integration node. |
|
|
706
|
+
| \`set_fields\` | Reshape / rename / merge fields into a new object. Use instead of JS for trivial transforms. |
|
|
707
|
+
| \`logic\` | if / else / switch branching with multiple outcomes. |
|
|
708
|
+
| \`foreach\` / \`filter\` / \`merge\` | Array iteration, filtering, combining collections. |
|
|
709
|
+
| \`wait_delay\` | Pause the workflow for N seconds (debouncing, rate limiting). |
|
|
710
|
+
| \`datetime\` | Parse, format, arithmetic on dates/times. |
|
|
711
|
+
| \`crypto\` | Hash, HMAC, encrypt/decrypt, generate tokens. |
|
|
712
|
+
| \`javascript_code\` / \`python_code\` | Escape hatch for custom logic that doesn't fit the native nodes. |
|
|
686
713
|
|
|
687
|
-
|
|
688
|
-
|
|
714
|
+
Triggers (separate category — they START a workflow):
|
|
715
|
+
- \`webhook_trigger\` / \`schedule_trigger\` / \`telegram_trigger\` → declared via \`trigger:\` at the endpoint top-level, not as workflow nodes.
|
|
689
716
|
|
|
690
|
-
##
|
|
691
|
-
Inside SQL queries (operation: query, or mutation values) the engine binds
|
|
692
|
-
placeholders as real Postgres parameters (\$1, \$2…) — NOT string interpolation.
|
|
693
|
-
This is injection-safe and type-safe by construction.
|
|
717
|
+
## Finding 3rd-party integrations (native-first rule)
|
|
694
718
|
|
|
695
|
-
|
|
696
|
-
UUID-shaped string → bound as \$1::uuid (e.g. \${current_user_id})
|
|
697
|
-
plain object/array → bound as \$1::jsonb (e.g. \${input.metadata})
|
|
698
|
-
Date instance → bound as \$1::timestamptz
|
|
699
|
-
text / int / bool → bound without cast (Postgres infers from column)
|
|
719
|
+
For external providers (Stripe, Resend, Slack, Telegram, WhatsApp, Google Sheets, PostgreSQL, SMTP, etc.) check the catalog BEFORE reaching for \`http_request\`:
|
|
700
720
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
721
|
+
1. **\`Read dypai/node-catalog.json\`** — single source of truth. Every \`node_type\` with full schema. It's cached locally, grep works.
|
|
722
|
+
2. Search by provider name or concept: "stripe", "email", "telegram", "sheets".
|
|
723
|
+
3. If a dedicated node exists → use it. Better error messages, credential auto-wiring, validator coverage.
|
|
724
|
+
4. If none exists → fall back to \`http_request\` + a credential.
|
|
704
725
|
|
|
705
|
-
|
|
706
|
-
❌ INSERT INTO events(payload) VALUES ('\${input.meta}'::jsonb) (redundant)
|
|
726
|
+
## Credentials (how user secrets flow)
|
|
707
727
|
|
|
708
|
-
|
|
728
|
+
- **Lifecycle**: user creates credentials **in the dashboard** (not via MCP yet). Each has a \`name\` (e.g. \`openai-prod\`, \`stripe-live\`), a \`type\` (matching the integration), and the actual secret.
|
|
729
|
+
- **Referencing**: in YAML, point at a credential by name: \`credential: openai-prod\`. The engine injects the secret at runtime — the secret value NEVER appears in the YAML.
|
|
730
|
+
- **Discovery**: \`get_app_credentials(project_id)\` lists what's already configured. Check this BEFORE writing a node that references a credential — if it doesn't exist, tell the user to create it.
|
|
731
|
+
- **Common trap**: writing \`credential: openai\` when the user named it \`openai-prod\` → workflow fails with "credential not found". Always match the exact name from \`get_app_credentials\`.
|
|
709
732
|
|
|
710
|
-
|
|
711
|
-
- Do NOT default to javascript_code — use native nodes (see decision tree above).
|
|
712
|
-
- Do NOT create auth endpoints — auth is built-in via SDK (dypai.auth.*).
|
|
713
|
-
- Do NOT use fetch() in frontend — use the SDK (dypai.api.*).
|
|
714
|
-
- Do NOT edit endpoints without running dypai_pull first (you'd overwrite unknown state).
|
|
715
|
-
- Do NOT skip dypai_validate — it catches schema/placeholder/credential errors before push.
|
|
716
|
-
- Do NOT push without dypai_diff — always preview first.
|
|
717
|
-
- Do NOT look up tables / endpoints remotely when a local dypai/ exists. Use Read/Glob/Grep on the files.
|
|
718
|
-
- Do NOT push just to test a change — use dypai_test_endpoint to run the LOCAL edited version against the engine. Only push when you're satisfied.
|
|
733
|
+
→ Full credential types + which provider needs what: \`search_docs("credentials reference")\`. Step-by-step integration flows: \`search_docs("integrations guide")\`.
|
|
719
734
|
|
|
720
|
-
##
|
|
735
|
+
## Environment variables
|
|
721
736
|
|
|
722
|
-
|
|
737
|
+
- Set in the **dashboard** (Project → Env Vars). Available to ALL workflows as \`\${env.<NAME>}\`.
|
|
738
|
+
- Use for non-provider secrets or config (webhook secrets, feature flags, custom URLs).
|
|
739
|
+
- Different from frontend \`.env\` (which is for \`VITE_*\` / \`NEXT_PUBLIC_*\` vars the BROWSER reads).
|
|
740
|
+
- Frontend env vars are NOT available in workflows. Workflow env vars are NOT available in the browser. Keep them mentally separate.
|
|
723
741
|
|
|
724
|
-
|
|
725
|
-
\`\`\`yaml
|
|
726
|
-
# ✅ CORRECT
|
|
727
|
-
trigger:
|
|
728
|
-
http_api:
|
|
729
|
-
auth_mode: jwt
|
|
730
|
-
workflow:
|
|
731
|
-
nodes:
|
|
732
|
-
- id: list
|
|
733
|
-
type: dypai_database
|
|
734
|
-
...
|
|
735
|
-
\`\`\`
|
|
736
|
-
\`\`\`yaml
|
|
737
|
-
# ❌ WRONG — start_trigger is NOT a node anymore
|
|
738
|
-
workflow:
|
|
739
|
-
nodes:
|
|
740
|
-
- id: start
|
|
741
|
-
type: start_trigger # the codec auto-strips this and warns
|
|
742
|
-
...
|
|
743
|
-
\`\`\`
|
|
744
|
-
Trigger options: \`http_api\` / \`webhook\` / \`schedule\` / \`telegram\`. \`auth_mode\` is one of \`jwt\` / \`api_key\` / \`public\`.
|
|
742
|
+
## SDK (\`@dypai-ai/client-sdk\`)
|
|
745
743
|
|
|
746
|
-
|
|
747
|
-
\`\`\`yaml
|
|
748
|
-
- id: insert
|
|
749
|
-
type: dypai_database # ✅ canonical
|
|
750
|
-
return: true # ✅ not is_return
|
|
751
|
-
\`\`\`
|
|
752
|
-
The codec accepts \`node_type\` and \`is_return\` for safety, but write the canonical form.
|
|
744
|
+
Pre-configured at \`src/lib/dypai.ts\`. Every method returns \`{ data, error }\` — never throws. Endpoints are called BY NAME (not URL).
|
|
753
745
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
- { from: cond, to: y, source_handle: "true" } # ✅ for branching
|
|
759
|
-
\`\`\`
|
|
746
|
+
**Three namespaces**:
|
|
747
|
+
- \`dypai.api\` → \`get\` / \`post\` / \`put\` / \`delete\` / \`upload\` / \`stream\` (SSE async iterator)
|
|
748
|
+
- \`dypai.auth\` → \`signUp\` / \`signInWithPassword\` / \`signOut\` / \`getSession\` / \`resetPassword\` / \`updateUser\` — NEVER create auth endpoints, the SDK does everything.
|
|
749
|
+
- \`dypai.realtime\` → \`subscribe(table, opts, callback)\` with Postgres-style filters (\`user_id=eq.\${uid}\`)
|
|
760
750
|
|
|
761
|
-
|
|
762
|
-
\`\`\`yaml
|
|
763
|
-
# ✅ CORRECT — params flat on the node
|
|
764
|
-
- id: insert
|
|
765
|
-
type: dypai_database
|
|
766
|
-
operation: insert
|
|
767
|
-
table: orders
|
|
768
|
-
data: { user_id: \${current_user_id}, qty: \${input.qty} }
|
|
769
|
-
\`\`\`
|
|
770
|
-
\`\`\`yaml
|
|
771
|
-
# ❌ AVOID — only use nested parameters: when a param NAME collides with id/type/return/variable
|
|
772
|
-
- id: insert
|
|
773
|
-
type: dypai_database
|
|
774
|
-
parameters:
|
|
775
|
-
operation: insert
|
|
776
|
-
table: orders
|
|
777
|
-
\`\`\`
|
|
751
|
+
**React hooks** (preferred over raw calls): \`DypaiProvider\` wrap, \`useAuth\`, \`useEndpoint\` (GET auto-fetch + refetch), \`useAction\` (mutation), \`useUpload\` (progress), \`useRealtime\`, \`useChannel\`, \`useChannelMessages\`, \`useChannels\`, \`<ProtectedRoute>\`.
|
|
778
752
|
|
|
779
|
-
|
|
780
|
-
Mark \`return: true\` on every node that produces a final HTTP response. Workflow execution only reaches one of them per run.
|
|
753
|
+
→ Full method signatures + examples: \`search_docs("sdk reference")\` (raw SDK), \`search_docs("react hooks")\` (all hooks with params).
|
|
781
754
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
-
|
|
792
|
-
|
|
793
|
-
|
|
755
|
+
## What the engine handles for you (don't reinvent)
|
|
756
|
+
|
|
757
|
+
- **JWT verification** — jwt auth_mode validates the session token automatically. \`\${current_user_id}\` is trusted.
|
|
758
|
+
- **Rate limiting** — per-plan. Returns 429 automatically.
|
|
759
|
+
- **CORS** — allowed origins per project (configured in dashboard).
|
|
760
|
+
- **Request logging** — every execution in \`system.workflow_runs\` with duration, status, tokens (for agents). View via \`get_recent_workflow_activity\`.
|
|
761
|
+
- **Input validation** — if the endpoint has \`input:\` schema, requests with invalid payloads are rejected with 400 + details BEFORE the workflow runs.
|
|
762
|
+
- **SQL injection** — placeholders bind as Postgres params. Safe by construction.
|
|
763
|
+
- **Secrets management** — credentials and env vars never appear in YAML or logs.
|
|
764
|
+
- **Scheduled jobs** — schedule_trigger endpoints are enqueued by the engine on startup + on deploy. No cron daemon to configure.
|
|
765
|
+
- **Webhook retries** — NOT automatic. If you need retries on failures, add an explicit \`wait_delay\` + re-attempt in the workflow.
|
|
766
|
+
|
|
767
|
+
## Common app recipes
|
|
768
|
+
|
|
769
|
+
- **Auth-gated CRUD**: CREATE TABLE with \`user_id UUID\`. Endpoints jwt-auth. SQL always \`WHERE user_id = \${current_user_id}\`. Frontend wraps routes in \`ProtectedRoute\`.
|
|
770
|
+
- **Admin panel**: endpoints with \`allowed_roles: [admin]\`. Same SQL but no user_id filter (admin sees all). Promote users via \`manage_users(operation: update_role)\`.
|
|
771
|
+
- **AI chat**: single endpoint with \`agent\` node + \`memory_key: "chat:\${current_user_id}"\`. Frontend uses \`dypai.api.stream()\` for SSE.
|
|
772
|
+
- **Payments (Stripe)**: \`stripe\` node for operations (checkout, invoices). Webhook endpoint with \`trigger.webhook\` + \`stripe_webhook: true\` receives events — the node auto-verifies the signature.
|
|
773
|
+
- **Cron job**: endpoint with \`trigger.schedule: { cron: "0 9 * * *", timezone: "Europe/Madrid" }\`. Workflow body runs in engine — same syntax as HTTP endpoints.
|
|
774
|
+
- **File uploads**: frontend uses \`dypai.api.upload(endpoint, file)\`. The endpoint is a workflow with one \`dypai_storage\` node (\`operation: upload\`, \`bucket: <name>\`, \`confirm: false\`). SDK handles the signed URL PUT flow.
|
|
775
|
+
- **Live dashboard**: normal endpoint returning aggregated data + frontend \`useRealtime(table, { onInsert: refetch })\`. Auto-updates when rows change.
|
|
776
|
+
- **Multi-tenant**: every row has \`org_id\` or \`user_id\`. Every endpoint filters by \`\${current_user_id}\`. Shared resources → endpoints with \`allowed_roles\`.
|
|
777
|
+
|
|
778
|
+
## Gotchas that will cost time if you don't know them
|
|
779
|
+
|
|
780
|
+
- **\`.env\` missing after sync** → frontend SDK can't reach the engine → every API call 404s. The fix is always creating .env, not debugging code.
|
|
781
|
+
- **\`tool_ids\` vs \`tools\`** → write \`tools: [names]\` in YAML. Using \`tool_ids\` makes the engine receive the name as a literal UUID. Fails silently in prod.
|
|
782
|
+
- **Missing \`return: true\`** → endpoint returns \`null\` or empty. Every path that should produce an HTTP response needs it.
|
|
783
|
+
- **\`public\` auth_mode with placeholders** → \`\${current_user_id}\` is empty → SQL fails or returns wrong data. Use jwt if you need the user.
|
|
784
|
+
- **Forgetting \`WHERE user_id = \${current_user_id}\`** → users see each other's data. The engine does NOT auto-filter.
|
|
785
|
+
- **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
|
+
- **Binary files in \`dypai/code/\`** → only text code files here. Binary assets go to the frontend \`public/\` or to a bucket.
|
|
787
|
+
- **\`dypai_push\` without \`dypai_validate\`** → pushing a broken workflow. Always validate first.
|
|
788
|
+
- **Frontend dev server + R2 media** → media files are auto-uploaded to R2 on deploy but \`vite dev\` doesn't proxy to R2. Run \`manage_frontend(sync)\` first to pull media to disk.
|
|
789
|
+
|
|
790
|
+
## Frontend
|
|
791
|
+
|
|
792
|
+
SDK is pre-configured at \`src/lib/dypai.ts\`. Import \`dypai\` from there. Every method returns \`{ data, error }\` — never throws.
|
|
793
|
+
|
|
794
|
+
- **API calls**: \`dypai.api.get(name)\`, \`.post(name, body)\`, \`.put()\`, \`.delete()\`, \`.upload(name, file)\`
|
|
795
|
+
- **Auth**: \`dypai.auth.signInWithPassword()\`, \`.signUp()\`, \`.signOut()\`, \`.getSession()\` — DO NOT create auth endpoints
|
|
796
|
+
- **Types**: \`import { api } from '@/dypai'\` for typed endpoint calls
|
|
797
|
+
- **Realtime hooks**: \`useRealtime\`, \`useChannel\`, \`useChannelMessages\` (see Realtime section)
|
|
798
|
+
- **Rule**: NEVER \`fetch()\` directly — always through the SDK
|
|
799
|
+
|
|
800
|
+
**\`.env\` required** — \`.env\` is gitignored so \`manage_frontend(sync)\` does NOT fetch it. If \`env_file_missing: true\` in the sync response, create it:
|
|
801
|
+
\`\`\`bash
|
|
802
|
+
# Vite
|
|
803
|
+
VITE_DYPAI_URL=https://<project_id>.dypai.app
|
|
804
|
+
VITE_PROJECT_ID=<project_id>
|
|
805
|
+
# Next.js (NEXT_PUBLIC_ prefix is required)
|
|
806
|
+
NEXT_PUBLIC_DYPAI_URL=https://<project_id>.dypai.app
|
|
794
807
|
\`\`\`
|
|
795
|
-
|
|
808
|
+
Without \`.env\`, all SDK calls fail. It's always the missing env var, not the code.
|
|
809
|
+
|
|
810
|
+
## Frontend deploy
|
|
811
|
+
|
|
812
|
+
\`manage_frontend(operation: deploy, sourceDirectory, project_id)\` → 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
|
+
|
|
814
|
+
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
|
+
|
|
816
|
+
→ Framework-specific gotchas (Next SSR, Vite, Astro, etc.): \`search_docs("frontend frameworks")\`. Deploy tool operations deep dive: \`search_docs("manage frontend")\`.
|
|
817
|
+
|
|
818
|
+
## Other tools
|
|
796
819
|
|
|
797
|
-
|
|
798
|
-
|
|
820
|
+
- \`bulk_upsert\` — import CSV/JSON into a table. Great for seed data.
|
|
821
|
+
- \`manage_domain\` — custom domains. \`add\` returns the CNAME to configure. \`verify\` forces DNS/SSL recheck. → \`search_docs("manage domain")\`.
|
|
822
|
+
- \`manage_users\` / \`manage_roles\` — app-level RBAC (users via better-auth).
|
|
823
|
+
- \`manage_schedules\` / \`manage_webhooks\` — pause/resume/history. To change the DEFINITION, edit the endpoint YAML and push.
|
|
824
|
+
- \`manage_storage\` — buckets + objects. \`upload_file\` reads local path, signs URL, PUTs direct to R2, registers. Max 100MB/file. → \`search_docs("file storage")\`.
|
|
825
|
+
|
|
826
|
+
## Deep docs — search_docs topic map
|
|
827
|
+
|
|
828
|
+
When you need more detail than this prompt gives, \`search_docs\` is vectorized semantic search over the full DYPAI docs. Topics available (query with the keywords in parentheses):
|
|
829
|
+
|
|
830
|
+
- **Orientation**: "platform guide" (what AI can do vs user-manual actions), "project setup" (phased build flow)
|
|
831
|
+
- **Backend**: "git-first workflow", "trigger model", "workflow patterns", "nodes reference", "node types", "javascript code node"
|
|
832
|
+
- **AI**: "agent ai" (agent node deep dive)
|
|
833
|
+
- **Auth**: "auth flows" (canonical patterns), "auth defaults" (what to default to)
|
|
834
|
+
- **Integrations**: "integrations guide", "credentials reference"
|
|
835
|
+
- **Realtime**: "realtime channels" (client API), "realtime policies" (backend YAML)
|
|
836
|
+
- **Storage**: "file storage"
|
|
837
|
+
- **Frontend**: "sdk reference", "react hooks", "frontend frameworks", "manage frontend"
|
|
838
|
+
- **Ops**: "manage domain", "testing endpoints", "troubleshooting"
|
|
839
|
+
|
|
840
|
+
Before writing something you're not 100% sure about → search first. The docs are the detailed truth; this prompt is the map.
|
|
799
841
|
`
|
|
800
842
|
|
|
801
843
|
// ── MCP Protocol (JSON-RPC over stdio) ──────────────────────────────────────
|
|
@@ -817,7 +859,7 @@ async function handleRequest(msg) {
|
|
|
817
859
|
return makeResponse(id, {
|
|
818
860
|
protocolVersion: "2024-11-05",
|
|
819
861
|
capabilities: { tools: {} },
|
|
820
|
-
serverInfo: { name: "dypai", version: "1.3.
|
|
862
|
+
serverInfo: { name: "dypai", version: "1.3.1" },
|
|
821
863
|
instructions: SERVER_INSTRUCTIONS,
|
|
822
864
|
})
|
|
823
865
|
}
|
|
@@ -854,6 +896,19 @@ async function handleRequest(msg) {
|
|
|
854
896
|
const toolDef = REMOTE_TOOLS.find(t => t.name === name)
|
|
855
897
|
let finalArgs = withProjectContext(toolDef, args || {})
|
|
856
898
|
|
|
899
|
+
// manage_storage.upload_file is orchestrated LOCALLY (filesystem →
|
|
900
|
+
// sign → direct PUT to R2 → verify). It's listed under manage_storage
|
|
901
|
+
// so the agent sees one unified tool, but the binary never travels
|
|
902
|
+
// through the MCP transport — that would saturate the remote pod on
|
|
903
|
+
// large files. See tools/storage.js for the 3-step flow.
|
|
904
|
+
if (name === "manage_storage" && finalArgs?.operation === "upload_file") {
|
|
905
|
+
result = await uploadFile(finalArgs)
|
|
906
|
+
return makeResponse(id, {
|
|
907
|
+
content: [{ type: "text", text: typeof result === "string" ? result : JSON.stringify(result, null, 2) }],
|
|
908
|
+
isError: result?.success === false,
|
|
909
|
+
})
|
|
910
|
+
}
|
|
911
|
+
|
|
857
912
|
// Resolve endpoint_name → endpoint_id for tools that accept only the UUID.
|
|
858
913
|
// Keeps the agent-facing API name-based while the remote keeps its
|
|
859
914
|
// UUID-based contract. Best-effort: if the lookup fails we still pass
|