@dypai-ai/mcp 1.0.10 → 1.2.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dypai-ai/mcp",
3
- "version": "1.0.10",
3
+ "version": "1.2.0",
4
4
  "description": "DYPAI MCP Server — AI agent toolkit for building and deploying full-stack apps",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -24,5 +24,8 @@
24
24
  "type": "git",
25
25
  "url": "https://github.com/DYPAI-SOLUTIONS/dypai-mcp"
26
26
  },
27
- "homepage": "https://dypai.ai"
27
+ "homepage": "https://dypai.ai",
28
+ "dependencies": {
29
+ "yaml": "^2.6.0"
30
+ }
28
31
  }
package/src/index.js CHANGED
@@ -21,12 +21,34 @@
21
21
 
22
22
  import { createInterface } from "readline"
23
23
  import { checkForUpdates } from "./auto-update.js"
24
- import { deployTool } from "./tools/deploy.js"
24
+ // manage_frontend consolidates what used to be 5 tools (deploy_frontend, get_frontend_status,
25
+ // get_build_status, list_deployments, get_deployment_logs) into a single operation-dispatched
26
+ // tool. The heavy deploy logic still lives in ./tools/deploy.js (exported as deployFromSource).
27
+ import { manageFrontendTool } from "./tools/frontend.js"
25
28
  import { scaffoldTool } from "./tools/scaffold.js"
26
- import { addDomainTool, listDomainsTool, removeDomainTool } from "./tools/domains.js"
27
- import { frontendStatusTool, buildStatusTool, listDeploymentsTool, getDeploymentLogsTool } from "./tools/status.js"
29
+ import { manageDomainTool } from "./tools/domains.js"
28
30
  import { bulkUpsertTool } from "./tools/bulk-upsert.js"
31
+ // dypaiTestTool (YAML test-suite runner) is intentionally not imported — deferred to v2.
32
+ // The format works but needs fixtures/auto-rollback/scaffolder + proper docs before being surfaced.
33
+ // File still lives at ./tools/sync/test.js and is re-exported from ./tools/sync/index.js
34
+ // so the implementation is preserved; just not wired into the catalog.
35
+ // dypaiDescribeTool removed from the catalog — its output is now inlined as
36
+ // the `overview` block returned by dypai_pull. One fewer tool, one fewer decision
37
+ // for the agent. The implementation still lives at ./tools/sync/describe.js
38
+ // in case we want to resurrect it as a read-only peek for multi-project flows.
39
+ import { dypaiPullTool, dypaiDiffTool, dypaiPushTool, dypaiValidateTool, dypaiTestEndpointTool } from "./tools/sync/index.js"
40
+ // Codegen removed from v1 entirely — neither the standalone tool nor the
41
+ // auto-triggers on pull/push/DDL are wired in. The agent reads dypai/schema.sql
42
+ // and endpoint YAMLs directly and casts at the frontend edge when needed
43
+ // (same mental model as using raw Supabase without the CLI-generated types).
44
+ // The ./tools/codegen.js file is preserved on disk in case v2 re-introduces a
45
+ // leaner version that only emits the Database type without wrappers.
29
46
  import { proxyToolCall } from "./tools/proxy.js"
47
+ import { enrichSuccess, enrichError } from "./tools/enrich.js"
48
+ import { maybeRefreshSchemaAfterExecuteSql } from "./tools/sql-side-effects.js"
49
+ import { withProjectContext, invalidateProjectContext } from "./tools/project-context.js"
50
+ // summarizeDypaiTraceResponse (from ./tools/trace-summarize.js) is kept on
51
+ // disk for when dypai_trace is re-enabled, but not imported here.
30
52
 
31
53
  // ── Self-update ─────────────────────────────────────────────────────────────
32
54
  // Throttled (6h) check against the npm registry. If a newer version is
@@ -39,17 +61,21 @@ await checkForUpdates().catch(() => {})
39
61
 
40
62
  const LOCAL_TOOLS = [
41
63
  // ── Frontend & Deploy ─────────────────────────────────────────────────────
42
- deployTool,
64
+ manageFrontendTool,
43
65
  scaffoldTool,
44
- buildStatusTool,
45
- listDeploymentsTool,
46
- getDeploymentLogsTool,
47
66
  // ── Domains ───────────────────────────────────────────────────────────────
48
- addDomainTool,
49
- listDomainsTool,
50
- removeDomainTool,
67
+ manageDomainTool,
51
68
  // ── Data ──────────────────────────────────────────────────────────────────
52
69
  bulkUpsertTool,
70
+ // ── Git-first source of truth ─────────────────────────────────────────────
71
+ // dypai_describe was merged into dypai_pull (now returns an `overview` block).
72
+ dypaiPullTool,
73
+ dypaiValidateTool,
74
+ dypaiDiffTool,
75
+ dypaiPushTool,
76
+ dypaiTestEndpointTool,
77
+ // dypaiTestTool (YAML test-suite runner) — hidden until v2. See import comment above.
78
+ // dypaiCodegenTool removed from v1 — see codegen import comment above.
53
79
  ]
54
80
 
55
81
  const localToolMap = new Map(LOCAL_TOOLS.map(t => [t.name, t]))
@@ -62,15 +88,11 @@ const FALLBACK_REMOTE_TOOLS = [
62
88
  { name: "get_project", description: "Get project details.", inputSchema: { type: "object", properties: { project_id: { type: "string" } }, required: ["project_id"] } },
63
89
  { name: "create_project", description: "Create a new project (free plan).", inputSchema: { type: "object", properties: { name: { type: "string" } }, required: ["name"] } },
64
90
  { name: "execute_sql", description: "Run SQL on the project's database.", inputSchema: { type: "object", properties: { project_id: { type: "string" }, sql: { type: "string" } }, required: ["sql"] } },
65
- { name: "get_app_tables", description: "List database tables.", inputSchema: { type: "object", properties: { project_id: { type: "string" } }, required: [] } },
66
91
  { name: "create_endpoint", description: "Create an API endpoint.", inputSchema: { type: "object", properties: { project_id: { type: "string" }, name: { type: "string" }, method: { type: "string" }, workflow_code: { type: "object" } }, required: ["name", "method", "workflow_code"] } },
67
92
  { name: "update_endpoint", description: "Update an endpoint.", inputSchema: { type: "object", properties: { project_id: { type: "string" }, name: { type: "string" } }, required: ["name"] } },
68
93
  { name: "delete_endpoint", description: "Delete an endpoint.", inputSchema: { type: "object", properties: { project_id: { type: "string" }, name: { type: "string" } }, required: ["name"] } },
69
- { name: "search_endpoints", description: "Search endpoints.", inputSchema: { type: "object", properties: { project_id: { type: "string" } }, required: [] } },
70
- { name: "test_workflow", description: "Test an endpoint.", inputSchema: { type: "object", properties: { project_id: { type: "string" }, endpoint_name: { type: "string" } }, required: ["endpoint_name"] } },
71
94
  { name: "manage_users", description: "Manage auth users.", inputSchema: { type: "object", properties: { project_id: { type: "string" }, operation: { type: "string" } }, required: ["operation"] } },
72
95
  { name: "manage_roles", description: "Manage roles.", inputSchema: { type: "object", properties: { project_id: { type: "string" }, operation: { type: "string" } }, required: ["operation"] } },
73
- { name: "get_auth_users", description: "List users.", inputSchema: { type: "object", properties: { project_id: { type: "string" } }, required: [] } },
74
96
  { name: "list_buckets", description: "List storage buckets.", inputSchema: { type: "object", properties: { project_id: { type: "string" } }, required: [] } },
75
97
  { name: "search_docs", description: "Search documentation.", inputSchema: { type: "object", properties: { query: { type: "string" } }, required: ["query"] } },
76
98
  { name: "search_workflow_templates", description: "Search workflow templates.", inputSchema: { type: "object", properties: { query: { type: "string" } }, required: ["query"] } },
@@ -87,27 +109,43 @@ const REMOTE_TOOLS = [
87
109
  { name: "get_app_credentials", description: "Lists available credentials in the current application. Returns API keys, anon key, service role key, and engine URL needed for SDK configuration.", inputSchema: { type: "object", properties: { project_id: { type: "string" } }, required: [] } },
88
110
 
89
111
  // ── Database ──────────────────────────────────────────────────────────────
90
- { name: "execute_sql", description: "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.", inputSchema: { type: "object", properties: { project_id: { type: "string" }, sql: { type: "string", description: "SQL query to execute" } }, required: ["sql"] } },
91
- { name: "get_app_tables", description: "Query the project's database tables. Returns all tables with their columns, types, constraints, and indexes.", inputSchema: { type: "object", properties: { project_id: { type: "string" } }, required: [] } },
112
+ // Note: `get_app_tables` is intentionally NOT exposed dypai/schema.sql already
113
+ // caches table info locally (auto-refreshed on DDL). For ad-hoc introspection,
114
+ // use execute_sql against information_schema.
115
+ { name: "execute_sql", description: "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.", inputSchema: { type: "object", properties: { project_id: { type: "string" }, sql: { type: "string", description: "SQL query to execute" } }, required: ["sql"] } },
92
116
 
93
117
  // ── API Endpoints ─────────────────────────────────────────────────────────
94
- { name: "create_endpoint", description: "Creates an endpoint as a workflow (nodes + edges). The workflow defines what the endpoint does: database queries, HTTP calls, AI agents, logic, transformations, etc.", inputSchema: { type: "object", properties: { project_id: { type: "string" }, name: { type: "string", description: "URL-friendly name (e.g. 'get-products')" }, method: { type: "string", description: "GET, POST, PUT, DELETE, PATCH" }, description: { type: "string" }, workflow_code: { type: "object", description: "Workflow definition with nodes and edges" } }, required: ["name", "method", "workflow_code"] } },
95
- { name: "update_endpoint", description: "Updates fields of an existing endpoint. Only fields in 'updates' are modified — everything else stays the same.", inputSchema: { type: "object", properties: { project_id: { type: "string" }, name: { type: "string" }, workflow_code: { type: "object" }, description: { type: "string" }, method: { type: "string" } }, required: ["name"] } },
96
- { name: "delete_endpoint", description: "Permanently deletes an endpoint. Irreversible operation.", inputSchema: { type: "object", properties: { project_id: { type: "string" }, name: { type: "string" } }, required: ["name"] } },
97
- { name: "search_endpoints", description: "Search endpoints or retrieve the full detail of one by ID. Returns name, method, description, group, workflow code.", inputSchema: { type: "object", properties: { project_id: { type: "string" }, query: { type: "string" }, endpoint_id: { type: "string" } }, required: [] } },
98
- { name: "test_workflow", description: "Test an endpoint's workflow with sample input data. Returns the execution result including node outputs and errors. Great for debugging.", inputSchema: { type: "object", properties: { project_id: { type: "string" }, endpoint_name: { type: "string" }, input: { type: "object", description: "Test input data" }, method: { type: "string" } }, required: ["endpoint_name"] } },
99
- { name: "edit_workflow", description: "Edit a workflow using find-and-replace on the JSON text. Best for quick fixes — SQL queries, parameters, strings. Works like a code editor. USE THIS FOR MOST EDITS instead of rewriting the entire workflow.", inputSchema: { type: "object", properties: { project_id: { type: "string" }, endpoint_id: { type: "string" }, old_string: { type: "string", description: "Text to find in the workflow JSON" }, new_string: { type: "string", description: "Replacement text" } }, required: ["endpoint_id", "old_string", "new_string"] } },
100
- { name: "add_node", description: "Add a new node to an existing workflow and connect it. Specify the node type, parameters, and where to insert it.", inputSchema: { type: "object", properties: { project_id: { type: "string" }, endpoint_id: { type: "string" }, node_type: { type: "string" }, parameters: { type: "object" }, after_node: { type: "string", description: "ID of the node to insert after" } }, required: ["endpoint_id", "node_type"] } },
101
- { name: "remove_node", description: "Remove a node from a workflow. By default, reconnects surrounding nodes to maintain the flow.", inputSchema: { type: "object", properties: { project_id: { type: "string" }, endpoint_id: { type: "string" }, node_id: { type: "string" } }, required: ["endpoint_id", "node_id"] } },
102
- { name: "get_endpoint_versions", description: "List version history for an endpoint. Shows previous workflow snapshots that can be restored with rollback_endpoint.", inputSchema: { type: "object", properties: { project_id: { type: "string" }, endpoint_name: { type: "string" } }, required: ["endpoint_name"] } },
103
- { name: "rollback_endpoint", description: "Rollback an endpoint's workflow to a previous version. Use get_endpoint_versions first to see available versions.", inputSchema: { type: "object", properties: { project_id: { type: "string" }, endpoint_name: { type: "string" }, version_id: { type: "string" } }, required: ["endpoint_name", "version_id"] } },
104
- { name: "search_nodes", description: "Discover and query available nodes for building workflows. Shows what building blocks exist: HTTP requests, database, email, Stripe, Telegram, AI agent, etc.", inputSchema: { type: "object", properties: { query: { type: "string", description: "What you need (e.g. 'send email', 'stripe', 'database')" } }, required: ["query"] } },
105
- { name: "manage_endpoint_groups", description: "Organize endpoints into groups (folders). Operations: create, list, update, delete.", inputSchema: { type: "object", properties: { project_id: { type: "string" }, operation: { type: "string", description: "create, list, update, delete" } }, required: ["operation"] } },
118
+ // Full CRUD + exploration goes through the git-first flow:
119
+ // dypai_pull (materialize + overview) edit files dypai_diff dypai_push
120
+ // search_endpoints removed on purpose: having both files and a remote search confuses the agent.
121
+ // Only kept: test_workflow (runtime debug), versions/rollback (emergency).
122
+ // Note: test_workflow is NOT exposed use dypai_test_endpoint (local tool).
123
+ // It reads the local YAML, inlines *_file refs, and tests the EDITED version
124
+ // against the engine. test_workflow still exists on the remote and is called
125
+ // internally by dypai_test_endpoint just not user-facing.
126
+ //
127
+ // dypai_trace is temporarily hidden until the engine implements debug-on-error
128
+ // (buffer events in memory, flush to Redis only when an execution fails).
129
+ // Without that, production executions have no per-node trace only the
130
+ // listing + top-level error_message would be available, which is confusing
131
+ // given the tool promises full traces. Re-enable once the engine captures
132
+ // traces for real prod runs, not just test_workflow debug calls.
133
+ // { name: "dypai_trace", description: "READ HISTORICAL executions — does NOT run anything. Use ONLY when a user reports a bug that already happened and you need to inspect the real failure. Two modes: execution_id → fetch specific past trace; endpoint_id + only_failed:true → list recent real failures.", inputSchema: { type: "object", properties: { project_id: { type: "string" }, execution_id: { type: "string" }, endpoint_id: { type: "string" }, only_failed: { type: "boolean", default: false }, limit: { type: "integer", default: 5 }, full: { type: "boolean", default: false } }, required: [] } },
134
+ { name: "get_endpoint_versions", description: "Dual-mode remote version history for an endpoint. Captures EVERY write to the remote (dashboard, push, API), so it sees changes your local git doesn't.\n\n- Without `version_number`: lists versions (metadata only — version, description, created_at).\n- With `version_number`: returns that version's FULL workflow_code. You can then restore it manually by writing it back via git (preferred) or by calling the remote directly.\n\nTypical recovery flow when a teammate edited in the dashboard and broke something:\n 1) get_endpoint_versions(endpoint_name: 'x') → pick the last good version\n 2) get_endpoint_versions(endpoint_name: 'x', version_number: N) → inspect the workflow_code\n 3) dypai_pull → write back to local YAML, review with git, dypai_push\n\nFor 'what did I change locally' use `git log dypai/endpoints/<name>.yaml` instead.", inputSchema: { type: "object", properties: { project_id: { type: "string" }, endpoint_name: { type: "string", description: "Endpoint name as declared in its YAML (e.g. 'create-order')." }, version_number: { type: "integer", description: "Optional. When provided, returns that version's full workflow_code instead of the list." }, limit: { type: "integer", description: "List mode only. Max versions (default 10, max 50).", default: 10 }, since: { type: "string", description: "List mode only. ISO date." }, before: { type: "string", description: "List mode only. ISO date." } }, required: ["endpoint_name"] } },
135
+ // rollback_endpoint is intentionally hidden — git-first makes it redundant.
136
+ // For reverting an endpoint: use get_endpoint_versions(version_number: N) to
137
+ // fetch the old workflow_code, write it back to dypai/endpoints/<name>.yaml
138
+ // (dypai_pull handles this), review with git, then dypai_push. That keeps
139
+ // the change reviewable in git instead of being an invisible DB rewrite.
140
+ // { name: "rollback_endpoint", description: "EMERGENCY rollback ...", inputSchema: { ... } },
141
+ { name: "search_nodes", description: "Semantic (vector) search over the node catalog by intent/meaning. Use when you don't know the node_type name — e.g. 'send email', 'process payment', 'transform array'. For looking up a KNOWN node_type's parameters, read dypai/node-catalog.json locally instead (faster, offline, no token used).", inputSchema: { type: "object", properties: { query: { type: "string", description: "What you need (e.g. 'send email', 'stripe', 'database')" } }, required: ["query"] } },
106
142
 
107
143
  // ── Auth & Users ──────────────────────────────────────────────────────────
108
- { name: "manage_users", description: "Manage authenticated users. Operations: create, delete, ban, unban, update_role, set_password.", inputSchema: { type: "object", properties: { project_id: { type: "string" }, operation: { type: "string", description: "create, delete, ban, unban, update_role, set_password" } }, required: ["operation"] } },
144
+ // Note: `get_auth_users` is intentionally NOT exposed manage_users covers
145
+ // create/delete/ban/update_role/set_password; for listing/reading, use
146
+ // execute_sql against auth.users (read-only) or manage_users with list op.
147
+ { name: "manage_users", description: "Manage authenticated users. Operations: create, list, delete, ban, unban, update_role, set_password.", inputSchema: { type: "object", properties: { project_id: { type: "string" }, operation: { type: "string", description: "create, list, delete, ban, unban, update_role, set_password" } }, required: ["operation"] } },
109
148
  { name: "manage_roles", description: "Manage user roles. Operations: create, list, update, delete.", inputSchema: { type: "object", properties: { project_id: { type: "string" }, operation: { type: "string", description: "create, list, update, delete" } }, required: ["operation"] } },
110
- { name: "get_auth_users", description: "List authenticated users with their email, role, status, and last login.", inputSchema: { type: "object", properties: { project_id: { type: "string" } }, required: [] } },
111
149
 
112
150
  // ── Storage ───────────────────────────────────────────────────────────────
113
151
  { name: "list_buckets", description: "Manage storage buckets for the project. List buckets with their configuration (public/private, file size limits, allowed MIME types).", inputSchema: { type: "object", properties: { project_id: { type: "string" } }, required: [] } },
@@ -120,64 +158,144 @@ const REMOTE_TOOLS = [
120
158
 
121
159
  // ── Server Instructions ──────────────────────────────────────────────────────
122
160
 
123
- const SERVER_INSTRUCTIONS = `You are building full-stack applications on the DYPAI platform. You handle backend (endpoints, database, auth) AND frontend (SDK integration, UI code).
161
+ 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).
124
162
 
125
163
  ## Getting Started
126
- 1. Call list_projects() to find your project_id
127
- 2. Use get_app_tables and search_endpoints to understand existing state
128
- 3. BEFORE implementing any feature, call search_docs with the topic (e.g. "auth", "upload files", "real-time") to get the correct SDK patterns
129
- 4. Build backend first (tables + endpoints), then frontend
130
-
131
- ## Build Backend
132
- 1. Create tables with execute_sql (check get_app_tables first to avoid duplicates)
133
- 2. Create endpoints with create_endpoint using workflow_code
134
- 3. Test with test_workflow immediately after creating/updating
135
- 4. Use search_workflow_templates to find ready-made workflow patterns
136
- 5. Use search_project_templates when the user wants a starter app or template-based project
137
- 6. Use search_nodes to discover available node types
138
- 7. Use search_docs when unsure about patterns
164
+ 1. Call list_projects() to find your project_id (skip if you already know it).
165
+ 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.
166
+ 3. BEFORE implementing an unfamiliar feature, call search_docs with the topic (e.g. "auth", "upload files", "realtime", "stripe").
167
+ 4. Build backend first, then frontend.
168
+
169
+ ## Build Backend (git-first workflow)
170
+ 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.
171
+
172
+ 1. Tables: execute_sql for DDL (CREATE TABLE, ALTER TABLE). schema.sql refreshes automatically.
173
+ 2. Endpoints:
174
+ a. dypai_pull (once, to initialize ./dypai/)
175
+ b. Create or edit YAML files in dypai/endpoints/<group>/<name>.yaml with Edit/Write
176
+ c. Long SQL / prompts / code go in dypai/sql/ dypai/prompts/ dypai/code/ and are referenced from the YAML via query_file, system_prompt_file, code_file
177
+ d. dypai_validate to catch placeholder / schema / credential / node-param errors BEFORE pushing
178
+ e. dypai_diff to preview what will change (read-only)
179
+ f. dypai_push to apply
180
+ 3. Test each change with dypai_test_endpoint (runs the LOCAL YAML — tests the edit before pushing).
181
+
182
+ ## Node reference — read the local catalog BEFORE picking
183
+
184
+ \`dypai/node-catalog.json\` is the single source of truth for every node_type this engine exposes, with its full input/output schema. It's cached locally by \`dypai_pull\` and auto-refreshed. **Read it directly with your Read tool** — don't call search_nodes, don't guess parameter names, don't invent node_types. If a type isn't in node-catalog.json, it doesn't exist.
185
+
186
+ Typical flow when adding a step to a workflow:
187
+ 1. Decide the CONCEPT (DB insert? HTTP call? shape fields? branching?).
188
+ 2. Scan \`dypai/node-catalog.json\` for a native node that covers that concept.
189
+ 3. If you find one: read its input_schema from the same file and configure the node.
190
+ 4. If nothing native fits naturally: consider \`javascript_code\` / \`python_code\` (see below).
191
+
192
+ \`search_nodes(query="...")\` is a semantic/vector fallback for when the intent is vague AND you can't locate it by name in node-catalog.json. It's rarely needed once you have the file open.
193
+
194
+ ## Node selection — think before choosing
195
+
196
+ javascript_code is a fine tool. The problem is picking it reflexively without considering native alternatives. Native nodes are validated by dypai_validate, observable per-node in traces, and readable in PR diffs — so when one fits, it's the better default. JS wins when the logic is genuinely custom and bundling multiple concerns is clearer than chaining 4+ native nodes.
197
+
198
+ **Before adding a javascript_code node, pause and check:** is there a native node in \`dypai/node-catalog.json\` that already expresses this concept?
199
+
200
+ | If the step is... | Default to this native node | Only JS if... |
201
+ |---|---|---|
202
+ | SELECT / INSERT / UPDATE / DELETE on a table | \`dypai_database\` | You need tight branching between multiple queries in a single atomic block |
203
+ | Reshaping / renaming / merging fields | \`set_fields\` | The transformation is arbitrary business logic (scoring, ranking, pricing rules) |
204
+ | if / else / early return | \`logic\` | Branching is mixed with other custom logic that would fragment oddly |
205
+ | Iterate an array | \`foreach\` (or \`filter\`, \`merge\`) | You need stateful accumulation or complex reduce across items |
206
+ | Call an external HTTP API | \`http_request\` | You're orchestrating multiple API calls with interdependent logic |
207
+ | LLM prompt / tool use | \`agent\` | Always use agent — it handles memory, tools, streaming, retries |
208
+ | Stripe / Telegram / Slack / Resend / WhatsApp / Google Sheets / etc. | The dedicated integration node | Provider not covered and you fall back to \`http_request\` |
209
+ | Upload / download / delete files | \`dypai_storage\` | Never — use the node |
210
+ | Dates / crypto / delays | \`datetime\`, \`crypto\`, \`wait_delay\` | Never — use the node |
211
+
212
+ **When javascript_code is the right choice (legitimate cases):**
213
+ - Domain-specific scoring, pricing or validation with many branches that \`logic\` alone would fragment
214
+ - Stateful aggregation over an array that \`foreach\` can't express cleanly
215
+ - One-off glue between 2-3 sources that would otherwise need 5+ native nodes just to express intermediate shapes
216
+ - Any business logic where writing a clear \`async function main(data, ctx)\` is genuinely simpler than chaining nodes
217
+
218
+ **Red flags that you're picking JS by reflex (not by fit):**
219
+ - The JS body is basically one \`await db.query(...)\` → use \`dypai_database\`
220
+ - The JS body is \`return { a: data.x, b: nodes.prev.y }\` → use \`set_fields\`
221
+ - The JS body is one \`await http.post(...)\` → use \`http_request\`
222
+ - The JS body is \`if (x > 100) return {...} else return {...}\` → use \`logic\`
223
+
224
+ When you hit a red flag, replace the JS node. When the JS legitimately does more than one concern glued together, keep it — that's what it's for.
225
+
226
+ ## Realtime (WebSocket subscriptions)
227
+ - Declarative policies live in dypai/realtime.yaml. Tables NOT declared there → deny-by-default for subscribers.
228
+ - To enable live updates on a table: add an entry with a subscribe_filter expression (same \${current_user_id} placeholders you use in endpoints).
229
+ - Row mutations auto-fire notifications to subscribers that match the filter.
230
+ - Client-side: \`dypai.realtime.subscribe(table, { filter, event }, callback)\`.
231
+ - See search_docs "realtime" for patterns.
232
+
233
+ ## Testing & Debugging
234
+
235
+ - **"Let me run this endpoint and see"** → \`dypai_test_endpoint\`
236
+ 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.
237
+
238
+ - 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.
239
+
240
+ - Historical production traces are not inspectable via MCP yet — direct the user to the dashboard if they need to debug a specific past execution.
241
+
242
+ ## Where things live
243
+ - dypai/endpoints/foo.yaml — endpoint definition (top-level, no group)
244
+ - dypai/endpoints/Admin/foo.yaml — endpoint in group "Admin" (subfolder = group)
245
+ - dypai/sql/foo.sql — SQL extracted when > 500 chars
246
+ - dypai/prompts/foo.md — agent system prompts extracted when > 800 chars
247
+ - dypai/code/foo.js — JavaScript/Python extracted when > 500 chars (escape hatch only)
248
+ - dypai/schema.sql — DDL of public.* tables (read BEFORE writing SQL; auto-refreshed on DDL)
249
+ - dypai/node-catalog.json — every node_type with its input/output schemas
250
+ - dypai/realtime.yaml — realtime subscription policies (optional)
251
+ - dypai/tests/*.test.yaml — reserved for the v2 suite runner; leave empty for now
252
+ - dypai/dypai.config.yaml — project identity (don't hand-edit)
253
+ - dypai/.dypai/ — local cache (gitignored)
254
+ - 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).
255
+ - All *_file paths inside YAML are relative to dypai/ root (not to the YAML file).
139
256
 
140
257
  ## Build Frontend
141
258
  - SDK client is already configured at src/lib/dypai.ts — just import it:
142
259
  import { dypai } from './lib/dypai'
260
+ - Types: \`import { api } from '@/dypai'\` for typed endpoint calls with autocomplete.
143
261
  - API calls: dypai.api.get(name), dypai.api.post(name, body), dypai.api.put(), dypai.api.delete()
144
262
  - Auth: dypai.auth.signInWithPassword(), signUp(), signOut(), getSession()
263
+ - Realtime: dypai.realtime.subscribe(table, opts, callback)
145
264
  - Files: dypai.api.upload(name, file) — always upload from browser
146
265
  - Every method returns { data, error } — never throws
147
266
  - Do NOT call the API directly with fetch() — always use the SDK
148
267
  - Do NOT create auth endpoints — auth is built-in via SDK (dypai.auth.*)
149
268
 
150
269
  ## Deploy Frontend
151
- - Use deploy_frontend tool with the project's source directory
152
- - The tool pushes source, then waits for the build to finish (~1-2 min)
153
- - If the build succeeds, the response includes the live URL
154
- - If the build FAILS, the response includes the error logs — read them carefully, fix the code, and redeploy
155
- - Common build failures: missing dependencies (add to package.json), TypeScript errors, import path issues
156
- - Supports: Vite, React, Next.js, Astro, SvelteKit, Nuxt, Remix, Angular, Vinext, CRA, and more
157
- - Framework is auto-detected from package.json — correct build config is set automatically
158
- - If build times out (>2 min), use get_build_status / get_deployment_logs to check manually
270
+ - \`manage_frontend(operation: deploy, sourceDirectory, project_id)\` uploads source, returns immediately with build_status=queued.
271
+ - Poll \`manage_frontend(operation: build_status)\` every ~5s until status is "success" or "failure" (typical build: 20-60s).
272
+ - On failure: \`manage_frontend(operation: list_deployments)\` → pick the failed deployment_id \`manage_frontend(operation: logs, deployment_id)\`.
273
+ - Supports: Vite, React, Next.js, Astro, SvelteKit, Nuxt, Remix, Angular, CRA, and more (auto-detected).
159
274
 
160
275
  ## Import Data
161
276
  - Use bulk_upsert to import CSV or JSON files into database tables
162
277
  - Great for seeding initial data (products, categories, config)
163
278
 
164
279
  ## Custom Domains
165
- - Use add_domain to connect a user's own domain
166
- - The user configures a CNAME record at their registrar
167
- - SSL is automatic via Cloudflare
280
+ - manage_domain(operation: list | add | remove | verify) — single tool for all domain ops.
281
+ - add returns the CNAME the user must configure at their registrar. SSL provisions automatically once DNS propagates.
282
+ - verify forces an on-demand DNS/SSL re-check (normally the platform polls in the background).
168
283
 
169
284
  ## Endpoint Rules
170
- - Auth modes: jwt (default, for users), api_key (server-to-server), public (read-only public data)
171
- - NEVER use public for endpoints that write data
172
- - Placeholders: \${input.<field>}, \${nodes.<node_id>.<field>}, \${current_user_id}
173
- - \${current_user_id} only works with jwt auth mode
174
- - Always test endpoints after creating them
285
+ - Auth modes: jwt (default for user-authenticated requests), api_key (server-to-server), public (read-only, anonymous).
286
+ - NEVER use public for endpoints that write data.
287
+ - Placeholders: \${input.<field>}, \${nodes.<node_id>.<field>}, \${current_user_id}, \${current_user_role}.
288
+ - \${current_user_id} / \${current_user_role} only work with jwt auth mode.
175
289
 
176
290
  ## Common Mistakes
177
- - Do NOT create auth endpointsauth is built-in via SDK (dypai.auth.*)
178
- - Do NOT use fetch() directlyuse the SDK (dypai.api.*)
179
- - Do NOT assume endpoints existcheck with search_endpoints first
180
- - Do NOT skip testing always test_workflow after create/update
291
+ - Do NOT default to javascript_codeuse native nodes (see decision tree above).
292
+ - Do NOT create auth endpointsauth is built-in via SDK (dypai.auth.*).
293
+ - Do NOT use fetch() in frontend use the SDK (dypai.api.*).
294
+ - Do NOT edit endpoints without running dypai_pull first (you'd overwrite unknown state).
295
+ - Do NOT skip dypai_validate — it catches schema/placeholder/credential errors before push.
296
+ - Do NOT push without dypai_diff — always preview first.
297
+ - Do NOT look up tables / endpoints remotely when a local dypai/ exists. Use Read/Glob/Grep on the files.
298
+ - 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.
181
299
  `
182
300
 
183
301
  // ── MCP Protocol (JSON-RPC over stdio) ──────────────────────────────────────
@@ -223,10 +341,57 @@ async function handleRequest(msg) {
223
341
  // Local tool — execute directly
224
342
  if (localToolMap.has(name)) {
225
343
  result = await localToolMap.get(name).execute(args || {})
344
+ // dypai_pull may have (re)written dypai.config.yaml — refresh the
345
+ // cached project_id so subsequent remote calls use the new value.
346
+ if (name === "dypai_pull") invalidateProjectContext()
226
347
  }
227
- // Remote tool — proxy to MCP server
348
+ // Remote tool — proxy to MCP server (with thin enrichment layer)
228
349
  else if (REMOTE_TOOLS.some(t => t.name === name)) {
229
- result = await proxyToolCall(name, args || {})
350
+ try {
351
+ // Auto-inject project_id from dypai.config.yaml if the tool
352
+ // declares it and the caller didn't pass one. Keeps the agent
353
+ // from having to repeat itself and keeps metrics properly tagged.
354
+ const toolDef = REMOTE_TOOLS.find(t => t.name === name)
355
+ let finalArgs = withProjectContext(toolDef, args || {})
356
+
357
+ // Resolve endpoint_name → endpoint_id for tools that accept only the UUID.
358
+ // Keeps the agent-facing API name-based while the remote keeps its
359
+ // UUID-based contract. Best-effort: if the lookup fails we still pass
360
+ // through whatever the caller provided so they see the remote's error.
361
+ if (name === "get_endpoint_versions" && finalArgs?.endpoint_name && !finalArgs?.endpoint_id) {
362
+ try {
363
+ const pid = finalArgs.project_id
364
+ const sqlArgs = { project_id: pid, sql: `SELECT id FROM system.endpoints WHERE name = '${String(finalArgs.endpoint_name).replace(/'/g, "''")}' LIMIT 1` }
365
+ const row = await proxyToolCall("execute_sql", sqlArgs)
366
+ const rows = Array.isArray(row?.rows) ? row.rows : (Array.isArray(row) ? row : null)
367
+ const id = rows?.[0]?.id
368
+ if (id) {
369
+ finalArgs = { ...finalArgs, endpoint_id: id }
370
+ delete finalArgs.endpoint_name
371
+ }
372
+ } catch { /* fall through, let the remote complain */ }
373
+ }
374
+
375
+ const raw = await proxyToolCall(name, finalArgs)
376
+ result = enrichSuccess(name, raw)
377
+
378
+ // Side effect: if the user ran schema-changing DDL via execute_sql,
379
+ // re-dump dypai/schema.sql so the validator stays in sync.
380
+ if (name === "execute_sql") {
381
+ const refresh = await maybeRefreshSchemaAfterExecuteSql(args || {}, result)
382
+ if (refresh.refreshed) {
383
+ result = { ...(typeof result === "object" ? result : { data: result }), schema_refreshed: true, schema_path: refresh.path }
384
+ } else if (refresh.error) {
385
+ result = { ...(typeof result === "object" ? result : { data: result }), schema_refresh_warning: refresh.error }
386
+ }
387
+ }
388
+
389
+ // Note: test_workflow is no longer agent-facing (wrapped by
390
+ // dypai_test_endpoint). dypai_trace is temporarily hidden until
391
+ // the engine captures debug traces for real production executions.
392
+ } catch (e) {
393
+ throw enrichError(name, e)
394
+ }
230
395
  }
231
396
  else {
232
397
  return makeError(id, -32601, `Unknown tool: ${name}`)