@dypai-ai/mcp 1.0.9 → 1.1.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 +5 -2
- package/src/index.js +231 -66
- package/src/tools/codegen.js +362 -0
- package/src/tools/deploy.js +27 -97
- package/src/tools/domains.js +49 -52
- package/src/tools/enrich.js +17 -0
- package/src/tools/frontend.js +93 -0
- package/src/tools/project-context.js +84 -0
- package/src/tools/proxy.js +14 -6
- package/src/tools/sql-side-effects.js +97 -0
- package/src/tools/sync/codec.js +185 -0
- package/src/tools/sync/describe.js +149 -0
- package/src/tools/sync/diff.js +83 -0
- package/src/tools/sync/index.js +18 -0
- package/src/tools/sync/planner.js +426 -0
- package/src/tools/sync/pull.js +414 -0
- package/src/tools/sync/push.js +411 -0
- package/src/tools/sync/schema-dump.js +96 -0
- package/src/tools/sync/test-endpoint.js +210 -0
- package/src/tools/sync/test.js +343 -0
- package/src/tools/sync/transforms.js +157 -0
- package/src/tools/sync/validate.js +567 -0
- package/src/tools/trace-summarize.js +178 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dypai-ai/mcp",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.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
|
-
|
|
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 {
|
|
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
|
+
// dypaiCodegenTool is intentionally NOT imported here — codegen runs
|
|
41
|
+
// automatically on dypai_pull / dypai_push / schema-affecting execute_sql.
|
|
42
|
+
// The standalone tool would duplicate that path and could confuse the agent
|
|
43
|
+
// into calling it needlessly. The helper `regenerateTypes` in ./tools/codegen.js
|
|
44
|
+
// is still used internally by those triggers.
|
|
45
|
+
// import { dypaiCodegenTool } from "./tools/codegen.js"
|
|
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
|
-
|
|
64
|
+
manageFrontendTool,
|
|
43
65
|
scaffoldTool,
|
|
44
|
-
buildStatusTool,
|
|
45
|
-
listDeploymentsTool,
|
|
46
|
-
getDeploymentLogsTool,
|
|
47
66
|
// ── Domains ───────────────────────────────────────────────────────────────
|
|
48
|
-
|
|
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 hidden — runs automatically on pull/push/DDL.
|
|
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
|
-
|
|
91
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
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.
|
|
128
|
-
3. BEFORE implementing
|
|
129
|
-
4. Build backend first
|
|
130
|
-
|
|
131
|
-
## Build Backend
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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), regenerates TS types under src/dypai/, 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/ — auto-generated TS types (Database + typed api wrapper). Auto-refreshed on pull/push/DDL.
|
|
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
|
-
-
|
|
152
|
-
-
|
|
153
|
-
-
|
|
154
|
-
-
|
|
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
|
-
-
|
|
166
|
-
-
|
|
167
|
-
- SSL
|
|
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
|
|
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
|
|
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
|
|
178
|
-
- Do NOT
|
|
179
|
-
- Do NOT
|
|
180
|
-
- Do NOT
|
|
291
|
+
- Do NOT default to javascript_code — use native nodes (see decision tree above).
|
|
292
|
+
- Do NOT create auth endpoints — auth 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
|
-
|
|
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}`)
|