@dypai-ai/mcp 1.5.9 → 1.5.10

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.5.9",
3
+ "version": "1.5.10",
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",
package/src/index.js CHANGED
@@ -683,9 +683,10 @@ Internally this means:
683
683
 
684
684
  1. edit backend files
685
685
  2. validate local backend changes
686
- 3. save them to the preview environment
687
- 4. test the preview version when practical
688
- 5. then tell the user it is ready to try
686
+ 3. test changed endpoint YAML with \`dypai_test_endpoint(mode:'local')\` when practical
687
+ 4. save them to the preview environment
688
+ 5. test the preview version when practical
689
+ 6. then tell the user it is ready to try
689
690
 
690
691
  Never ask the user whether to run the internal save-to-preview step. It is safe, reversible, and required for the user to test the actual change.
691
692
 
@@ -881,12 +882,12 @@ Editing files inside \`dypai/\` only changes YOUR DISK. The platform doesn't see
881
882
  \`\`\`
882
883
 
883
884
  Practical consequences — internalize these:
884
- - **Never publish backend changes just to test them.** Backend changes are testable before production: save them to preview, verify with \`dypai_test_endpoint(mode:'draft')\` when possible, then tell the user exactly what to try in preview.
885
+ - **Never publish backend changes just to test them.** First test the local YAML directly with \`dypai_test_endpoint(mode:'local')\`; only after that save to preview with \`dypai_push\` and verify the staged draft when needed.
885
886
  - **After EVERY meaningful backend change set, call \`dypai_push\`.** Don't batch a session's worth of edits hoping to push at the end — if you forget, the user tests the preview and sees the OLD behavior. The push is cheap, idempotent, and creates ONE preview version per resource (subsequent pushes overwrite the pending preview version, not stack new ones).
886
887
  - **\`dypai_push\` is the internal save-to-preview step. It is NOT a production publish.** Live traffic is untouched. You can run it repeatedly without affecting real users. In user-facing prose, say "listo para probar" or "en previsualización", not "pushed" or "draft".
887
888
  - **The preview host (\`dev-<project_id>.dypai.dev\`) only sees what you've saved to preview.** A change still only on disk is invisible to the user's preview. If the user says "I tested it and nothing changed", first check whether the backend change was saved to preview after the last edit.
888
889
  - **\`dypai_validate\` before \`dypai_push\`** — push runs validate as a pre-flight, but running it explicitly first gives you the lint output without committing. Cheap insurance.
889
- - **Order during a multi-step backend feature**: edit → \`dypai_validate\` → \`dypai_push\` → \`dypai_test_endpoint(mode:'draft')\` (or tell the user to test preview). Repeat per change. ONLY when the user explicitly approves production do \`manage_drafts(operation:'list')\` → \`manage_drafts(operation:'publish', confirm:true)\`.
890
+ - **Order during a multi-step backend feature**: edit → \`dypai_validate\` → \`dypai_test_endpoint(mode:'local')\` → \`dypai_push\` → \`dypai_test_endpoint(mode:'draft')\` when you need to verify the saved preview. Repeat per coherent change. ONLY when the user explicitly approves production do \`manage_drafts(operation:'list')\` → \`manage_drafts(operation:'publish', confirm:true)\`.
890
891
  - **DDL is the exception**: \`execute_sql\` with CREATE / ALTER / DROP TABLE applies to live IMMEDIATELY (no preview layer for schema). Preview only exists for endpoints / webhooks / crons / realtime policies. Summarize destructive DDL to the user before running it.
891
892
 
892
893
  ## User intent → tool to call (decision table)
@@ -898,7 +899,7 @@ Use this BEFORE picking a tool. If unsure which row matches, ask the user.
898
899
  | "Create a new project" | \`search_project_templates\` (find a starter) | \`create_project(template_slug: ...)\` |
899
900
  | "Show me what we have" / "I want to work on existing project X" | \`list_projects\` → \`dypai_pull\` (backend) + \`manage_frontend(sync)\` (frontend) | Read \`dypai/\` files + \`src/\` |
900
901
  | "This is a private admin app / public site / user portal / multi-role app" | \`manage_project_access_profile(operation:'update')\` | Then implement the actual auth/UI/data behavior normally |
901
- | "Add/change a backend endpoint, table, cron, webhook, agent, integration" | Edit files in \`dypai/\` | \`dypai_validate\` → \`dypai_push\` |
902
+ | "Add/change a backend endpoint, table, cron, webhook, agent, integration" | Edit files in \`dypai/\` | \`dypai_validate\` → \`dypai_test_endpoint(mode:'local')\` for changed endpoints → \`dypai_push\` |
902
903
  | "Publish my backend changes" / "make it live" | \`manage_drafts(operation:'list')\` to show what's pending | \`manage_drafts(operation:'publish', confirm:true)\` |
903
904
  | "Test an endpoint before publishing" | \`dypai_test_endpoint(mode:'local')\` (your edits) or \`(mode:'draft')\` (after push) | — |
904
905
  | "Test the new endpoint from my local frontend, end-to-end, before publishing" | Tell user: their local frontend already points to \`https://dev-<project_id>.dypai.dev\` (set by \`manage_frontend(sync)\`), which serves drafts on top of live. So after \`dypai_push\` the local UI hits the draft overlay automatically — nothing else to do. | — |
@@ -930,21 +931,23 @@ User: "Add a /api/list-tasks endpoint that returns the current user's tasks, and
930
931
  2. manage_frontend(operation:'sync', ...) # materialize frontend if not already on disk
931
932
  3. # Backend: create the endpoint
932
933
  Write dypai/endpoints/list-tasks.yaml # trigger.http_api auth_mode:jwt + dypai_database query
933
- 4. dypai_validate # catch typos before saving to preview
934
- 5. dypai_push # saves to preview, NOT production
935
- 6. dypai_test_endpoint(endpoint:'list-tasks', mode:'draft', as_user:'<user_id>')
936
- # verifies the preview version; do NOT publish just to test
937
- 7. # Frontend: call the new endpoint from React
934
+ 4. dypai_validate # catch YAML/placeholder issues
935
+ 5. dypai_test_endpoint(endpoint:'list-tasks', mode:'local', as_user:'<user_id>')
936
+ # verifies the local YAML before saving anything to preview
937
+ 6. dypai_push # saves to preview, NOT production
938
+ 7. dypai_test_endpoint(endpoint:'list-tasks', mode:'draft', as_user:'<user_id>')
939
+ # optional final sanity: verifies the preview version; do NOT publish just to test
940
+ 8. # Frontend: call the new endpoint from React
938
941
  Edit src/pages/Dashboard.tsx # useEndpoint('list-tasks')
939
- 8. # Test locally/browser if available. Then tell the user in plain language:
942
+ 9. # Test locally/browser if available. Then tell the user in plain language:
940
943
  # "Ya está listo para probar. Abre la previsualización y revisa la lista de tareas. Todavía no está publicado para tus usuarios."
941
- 9. # ONLY after the user confirms it is good:
944
+ 10. # ONLY after the user confirms it is good:
942
945
  manage_drafts(operation:'list') # internal: inspect what will publish
943
- 10. manage_drafts(operation:'publish', confirm:true) # backend live after explicit approval
944
- 11. manage_frontend(operation:'deploy', sourceDirectory, confirm:true) # frontend live after explicit approval
946
+ 11. manage_drafts(operation:'publish', confirm:true) # backend live after explicit approval
947
+ 12. manage_frontend(operation:'deploy', sourceDirectory, confirm:true) # frontend live after explicit approval
945
948
  \`\`\`
946
949
 
947
- **Testing rule**: never publish backend changes just to test them. Backend can be verified from the preview version. **Production order rule**: when you are truly publishing a full-stack change, publish backend BEFORE deploying the frontend; otherwise the live UI may call backend functionality that is not live yet.
950
+ **Testing rule**: never publish backend changes just to test them. Verify local YAML first with \`dypai_test_endpoint(mode:'local')\`, then save to preview and test \`mode:'draft'\` or the dev URL when needed. **Production order rule**: when you are truly publishing a full-stack change, publish backend BEFORE deploying the frontend; otherwise the live UI may call backend functionality that is not live yet.
948
951
 
949
952
  ## Debugging user-reported errors — \`search_logs\` is your starting point
950
953
 
@@ -1030,7 +1033,7 @@ Mental translations: "edge function" → workflow with one code node; "cron" →
1030
1033
  ## Top gotchas (the expensive ones)
1031
1034
 
1032
1035
  1. **Forgetting \`WHERE user_id = \${current_user_id}\`** — users see each other's data. #1 multi-tenancy bug. The engine does NOT auto-filter. RLS doesn't exist.
1033
- 2. **Editing YAML without \`dypai_push\`** — your change is on YOUR DISK only. Local frontend (which points at the draft overlay) keeps serving the old version. Symptom: *"I tested it locally and nothing changed"*. Always push after each meaningful change set.
1036
+ 2. **Editing YAML without \`dypai_push\`** — \`dypai_test_endpoint(mode:'local')\` can test your file edits, but the preview/frontend cannot see them until \`dypai_push\` saves them to draft. Symptom: *"I tested it in preview and nothing changed"*. Test local first, then push when the changed endpoint is ready for preview.
1034
1037
  3. **Treating \`dypai_push\` as a deploy** — it's "save as draft", not publish. Live traffic is untouched until \`manage_drafts(publish, confirm:true)\`. Push freely, only ask the user before publish.
1035
1038
  4. **\`public\` auth_mode with \`\${current_user_id}\`** — no JWT → placeholder empty → SQL fails or returns wrong data. Use \`jwt\` if you need the user.
1036
1039
  5. **Missing \`return: true\`** — endpoint returns \`null\`. Every path that should produce an HTTP response needs one node with \`return: true\`.
@@ -1,7 +1,8 @@
1
1
  /**
2
- * dypai_test — YAML-defined tests against endpoints. Zero engine changes:
3
- * orchestrates `execute_sql` + `test_workflow` (impersonation) + `execute_sql`
4
- * using what already exists.
2
+ * dypai_test — YAML-defined tests against endpoints. By default, endpoint
3
+ * names run through dypai_test_endpoint(mode:'local'), so tests execute the
4
+ * YAML currently on disk before dypai_push. UUID endpoint_id tests keep the
5
+ * legacy remote test_workflow path for backward compatibility.
5
6
  *
6
7
  * File layout (committable under dypai/tests/):
7
8
  * endpoint: create-order # or endpoint_id: <uuid>
@@ -26,6 +27,7 @@ import { join, resolve as resolvePath } from "path"
26
27
  import YAML from "yaml"
27
28
  import { proxyToolCall } from "../proxy.js"
28
29
  import { readLocalConfig } from "./planner.js"
30
+ import { dypaiTestEndpointTool } from "./test-endpoint.js"
29
31
 
30
32
  // ─── Test file discovery ────────────────────────────────────────────────────
31
33
 
@@ -58,8 +60,8 @@ function firstCellOf(res) {
58
60
  return k ? row[k] : undefined
59
61
  }
60
62
 
61
- /** Resolve an endpoint name to its UUID via system.endpoints (the remote
62
- * test_workflow tool only accepts endpoint_id, not endpoint_name). */
63
+ /** Resolve an endpoint name to its UUID via system.endpoints.
64
+ * Kept for legacy endpoint_id/remote test paths. */
63
65
  const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
64
66
  async function resolveEndpointId(projectId, ref, cache) {
65
67
  if (UUID_RE.test(ref)) return ref
@@ -167,22 +169,41 @@ async function runSingleTest(test, fileCtx) {
167
169
  }
168
170
 
169
171
  try {
170
- // Resolve endpoint endpoint_id (remote only accepts UUIDs)
172
+ // Prefer testing the local endpoint YAML by name. This keeps the normal
173
+ // agent loop cheap: edit file -> dypai_test/dypai_test_endpoint -> push.
171
174
  const endpointRef = test.endpoint || test.endpoint_id || fileCtx.endpoint
172
175
  if (!endpointRef) {
173
176
  return { ...result, status: "error", errors: ["No endpoint specified. Set `endpoint` at file root or test level."] }
174
177
  }
175
- const endpointId = await resolveEndpointId(fileCtx.projectId, endpointRef, fileCtx.endpointCache)
176
-
177
- const execArgs = {
178
- project_id: fileCtx.projectId,
179
- endpoint_id: endpointId,
180
- data: test.input || {},
181
- trace_mode: "minimal", // keep tests fast; detail on failure via dypai_trace
178
+ const testMode = test.mode || fileCtx.mode || "local"
179
+ let runResponse
180
+ if (!UUID_RE.test(endpointRef)) {
181
+ runResponse = await dypaiTestEndpointTool.execute({
182
+ endpoint: endpointRef,
183
+ mode: testMode,
184
+ input: test.input || {},
185
+ as_user: test.as_user,
186
+ trace_mode: "minimal",
187
+ root_dir: fileCtx.rootDir,
188
+ project_id: fileCtx.projectId,
189
+ // dypai_test is often a suite with many cases; run dypai_validate once
190
+ // before the suite if you want lint gating. Individual endpoint tests
191
+ // keep their own pre-flight validation by default.
192
+ skip_validation: test.skip_validation ?? fileCtx.skipValidation ?? true,
193
+ })
194
+ } else {
195
+ // Backward-compatible path for old tests that hard-code endpoint_id.
196
+ // This cannot read local YAML because UUIDs refer to remote rows.
197
+ const execArgs = {
198
+ project_id: fileCtx.projectId,
199
+ endpoint_id: endpointRef,
200
+ data: test.input || {},
201
+ trace_mode: "minimal",
202
+ draft_mode: testMode === "live" ? false : true,
203
+ }
204
+ if (test.as_user) execArgs.impersonated_user_id = test.as_user
205
+ runResponse = await proxyToolCall("test_workflow", execArgs)
182
206
  }
183
- if (test.as_user) execArgs.impersonated_user_id = test.as_user
184
-
185
- const runResponse = await proxyToolCall("test_workflow", execArgs)
186
207
 
187
208
  // Normalize what counts as the workflow "result body" (vary by engine version)
188
209
  const body = runResponse?.result ?? runResponse?.data ?? runResponse?.output ?? runResponse
@@ -194,12 +215,16 @@ async function runSingleTest(test, fileCtx) {
194
215
  // expect.success — did the workflow complete?
195
216
  if ("success" in expect) {
196
217
  const status = trace?.status ?? trace?.workflow?.status
197
- const actualSuccess = status
218
+ const actualSuccess = runResponse?.success === false
219
+ ? false
220
+ : status
198
221
  ? status === "completed"
199
222
  : !runResponse?.error // fallback: presence of error field
200
223
  if (actualSuccess !== expect.success) {
201
224
  result.errors.push(`expected success=${expect.success}, got ${actualSuccess}`)
202
225
  }
226
+ } else if (runResponse?.success === false) {
227
+ result.errors.push(`execution error: ${runResponse.error || "endpoint test failed"}`)
203
228
  }
204
229
 
205
230
  // expect.response — match body
@@ -260,19 +285,21 @@ async function runSingleTest(test, fileCtx) {
260
285
  export const dypaiTestTool = {
261
286
  name: "dypai_test",
262
287
  description:
263
- "Run YAML-defined tests from dypai/tests/*.test.yaml. Each test does: setup_sql test_workflow (with impersonation) response assertions db_queries assertions teardown_sql. " +
264
- "Uses existing remote tools (execute_sql, test_workflow) no engine changes needed. " +
288
+ "Run YAML-defined tests from dypai/tests/*.test.yaml. By default, endpoint tests reference endpoint names and execute the LOCAL YAML from dypai/endpoints/** without requiring dypai_push. " +
289
+ "Each test does: setup_sql → dypai_test_endpoint/test_workflow (with impersonation) response assertions db_queries assertions → teardown_sql. " +
265
290
  "Pass `only` to run a subset (substring match on test name).",
266
291
  inputSchema: {
267
292
  type: "object",
268
293
  properties: {
269
294
  project_id: { type: "string", description: "Project UUID. Auto-resolved from dypai.config.yaml." },
270
295
  root_dir: { type: "string", default: "./dypai" },
296
+ mode: { type: "string", enum: ["local", "draft", "live"], default: "local", description: "Endpoint source for tests that reference endpoint names. local reads YAML on disk; draft/live test engine versions." },
297
+ skip_validation: { type: "boolean", default: true, description: "Pass through to local endpoint tests. Default true for suites; run dypai_validate separately for lint gating." },
271
298
  only: { type: "string", description: "Only run tests whose name includes this substring." },
272
299
  file: { type: "string", description: "Relative path to a single test file under dypai/tests/." },
273
300
  },
274
301
  },
275
- async execute({ project_id, root_dir = "./dypai", only, file } = {}) {
302
+ async execute({ project_id, root_dir = "./dypai", mode = "local", skip_validation = true, only, file } = {}) {
276
303
  const rootDir = resolvePath(process.cwd(), root_dir)
277
304
  const config = await readLocalConfig(rootDir)
278
305
  const projectId = project_id || config?.project_id
@@ -316,6 +343,8 @@ export const dypaiTestTool = {
316
343
  endpoint: doc.endpoint || doc.endpoint_id,
317
344
  rootDir,
318
345
  endpointCache: new Map(),
346
+ mode: doc.mode || mode,
347
+ skipValidation: doc.skip_validation ?? skip_validation,
319
348
  }
320
349
  for (const t of tests) {
321
350
  if (only && !(t.name || "").toLowerCase().includes(only.toLowerCase())) continue