@dypai-ai/mcp 1.5.3 → 1.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -166,7 +166,7 @@ const REMOTE_TOOLS = [
|
|
|
166
166
|
// and fail to learn the contract. Mirrors the validation the remote does.
|
|
167
167
|
{
|
|
168
168
|
name: "manage_users",
|
|
169
|
-
description: `Manage authenticated users
|
|
169
|
+
description: `Manage authenticated users.
|
|
170
170
|
|
|
171
171
|
Operations:
|
|
172
172
|
- list: Paginated user listing. Optional: search, limit (1-100, default 20), offset, sort_by, sort_order.
|
|
@@ -177,14 +177,14 @@ Operations:
|
|
|
177
177
|
- delete: Permanently remove a user (user_id).
|
|
178
178
|
- ban / unban: Block / unblock login (user_id; ban supports ban_reason and ban_expires_in seconds).
|
|
179
179
|
|
|
180
|
-
NOTE: user IDs are TEXT (
|
|
180
|
+
NOTE: user IDs are TEXT alphanumeric strings (~32 chars, like "G1LIBXsbMLxUrs99ebCaL9X4auxW26AC"), NOT UUIDs. They live in auth."user".id.`,
|
|
181
181
|
inputSchema: {
|
|
182
182
|
type: "object",
|
|
183
183
|
properties: {
|
|
184
184
|
project_id: { type: "string", description: "Project UUID. Required for user tokens; auto-detected for project tokens." },
|
|
185
185
|
operation: { type: "string", enum: ["list", "get", "create", "set_password", "update_role", "delete", "ban", "unban"] },
|
|
186
186
|
// Fields used by various operations
|
|
187
|
-
user_id: { type: "string", description: "User ID (TEXT,
|
|
187
|
+
user_id: { type: "string", description: "User ID (TEXT alphanumeric, NOT a UUID; from auth.\"user\".id). Required for: get, set_password, update_role, delete, ban, unban." },
|
|
188
188
|
email: { type: "string", description: "User email. Required for: create." },
|
|
189
189
|
password: { type: "string", description: "User password (min 6 chars). Required for: create, set_password." },
|
|
190
190
|
name: { type: "string", description: "Display name. Optional for: create." },
|
|
@@ -477,7 +477,7 @@ const SERVER_INSTRUCTIONS = `You are building full-stack applications on the DYP
|
|
|
477
477
|
- ❌ *"¿Prefieres Tailwind o CSS Modules?"*
|
|
478
478
|
- ❌ Asking "which framework" unless the user explicitly says *"I want to compare platforms"* or *"what are my options"*.
|
|
479
479
|
|
|
480
|
-
These are ALL dead signals: Next.js is already what DYPAI scaffolds. The DB is already PostgreSQL (via DYPAI engine). Auth is
|
|
480
|
+
These are ALL dead signals: Next.js is already what DYPAI scaffolds. The DB is already PostgreSQL (via DYPAI engine). Auth is built in. Tailwind is already in the templates. Prisma / ORMs are not used — you write SQL directly in workflow endpoints.
|
|
481
481
|
|
|
482
482
|
## What to do when the user says "I want to build X"
|
|
483
483
|
|
|
@@ -485,10 +485,18 @@ First reflex, always:
|
|
|
485
485
|
|
|
486
486
|
1. **Acknowledge briefly** what they want to build (one short line, their language).
|
|
487
487
|
2. **\`search_project_templates(query: "<keywords from their request>")\`** — keywords in their language. Templates cover common app types (gym, clinic, waitlist, saas dashboard, etc.).
|
|
488
|
-
3. **
|
|
489
|
-
|
|
488
|
+
3. **Decide: template or blank?** Default is **blank**. A template is only the right pick when the match is OBVIOUS and STRONG:
|
|
489
|
+
- ✅ User says *"app para mi gimnasio"* + there's \`gym-manager\` (exact domain + feature overlap) → template.
|
|
490
|
+
- ❌ User says *"algo para gestionar reservas"* + there's \`gym-manager\` (soft match, many interpretations) → **blank**. Don't assume they want the gym's specific schema (classes, memberships, check-ins) — they didn't ask for it.
|
|
491
|
+
- ❌ User is a dev with a concrete spec (*"crea un proyecto con estas 3 tablas y estos endpoints"*) → **blank**, always. Respect their design.
|
|
492
|
+
- ❌ No template returned at all → **blank**.
|
|
493
|
+
4. **Call it** → \`create_project(name: "<their name>", template_slug: "<matched_slug>" | "blank")\`.
|
|
494
|
+
If you went with a template, acknowledge in ONE line what's included so the user can push back: *"Lo arranco con la plantilla X, que trae socios, clases y pagos. ¿Te vale o prefieres algo más simple?"*
|
|
495
|
+
If you went blank, just say: *"Arranco un proyecto en blanco y lo construimos a medida."*
|
|
490
496
|
5. **After \`create_project\`** → ask for an absolute workspace path, then \`dypai_pull\` + \`manage_frontend(sync)\` (see next section).
|
|
491
497
|
|
|
498
|
+
**The template system exists to save time when the fit is obvious, not to force-match every request.** When in doubt → blank is always correct. Iterating up from blank is cheaper than deleting 80% of a mismatched template.
|
|
499
|
+
|
|
492
500
|
## The one legit follow-up question
|
|
493
501
|
|
|
494
502
|
After deciding to build on DYPAI, you may ask ONE question (not five) to size scope if the request is genuinely vague:
|
|
@@ -531,6 +539,8 @@ Before the first \`execute_sql\`, before the first file edit, before ANYTHING:
|
|
|
531
539
|
1. **Look at the workspace.** Is there a \`dypai/\` folder? A \`src/\`? A \`package.json\`? If you're using a Read/ls tool, check now.
|
|
532
540
|
2. **Missing backend (\`dypai/\` absent)?** You have no endpoint YAMLs, no \`schema.sql\`, no node catalog. Cannot write queries, cannot push, cannot reason about the schema.
|
|
533
541
|
3. **Missing frontend (\`src/\` or \`package.json\` absent)?** You have no React/Vite/Next code. Cannot change the UI, cannot run \`npm install\`, cannot test locally.
|
|
542
|
+
|
|
543
|
+
**Having \`src/\` on disk does NOT mean the backend is on disk.** \`dypai/\` is a SEPARATE folder. Check for \`dypai/schema.sql\` specifically — if it's missing, the backend isn't materialized, even if the frontend clearly is. This is the #1 trap in hosts that default to file-first workflows (Claude Code especially): they see \`src/\` + \`package.json\` and assume the project is complete. Always verify \`dypai/schema.sql\` exists before touching data, queries, or endpoints.
|
|
534
544
|
4. **If either is missing → sync FIRST:**
|
|
535
545
|
- Ask the user for an absolute workspace path if you don't have one (e.g. \`/Users/me/code/my-app\`).
|
|
536
546
|
- Run \`dypai_pull(project_id, out_dir: <abs>/dypai)\` — materializes backend (endpoints, SQL, prompts, schema.sql, node-catalog.json).
|
|
@@ -851,7 +861,7 @@ Mental translations: "edge function" → workflow with one code node; "cron" →
|
|
|
851
861
|
|
|
852
862
|
## Common app recipes (one-liners)
|
|
853
863
|
|
|
854
|
-
- **Auth-gated CRUD**: table with \`user_id UUID
|
|
864
|
+
- **Auth-gated CRUD**: table with \`user_id TEXT\` (auth IDs are TEXT, not UUID), jwt endpoints, SQL always filters by \`\${current_user_id}\`, frontend \`<ProtectedRoute>\`.
|
|
855
865
|
- **Admin panel**: endpoints with \`allowed_roles: [admin]\`. Same SQL, no user filter (admin sees all). Promote via \`manage_users(update_role)\`.
|
|
856
866
|
- **AI chat**: single endpoint with \`agent\` node + \`memory_key: "chat:\${current_user_id}"\`. Frontend \`dypai.api.stream()\`.
|
|
857
867
|
- **Payments (Stripe)**: \`stripe\` node for ops. Webhook endpoint with \`trigger.webhook\` + \`stripe_webhook: true\` (auto-verifies signature).
|
|
@@ -879,7 +889,7 @@ SDK is pre-configured at \`src/lib/dypai.ts\` (or \`src/dypai.ts\`). Import \`dy
|
|
|
879
889
|
|
|
880
890
|
- \`bulk_upsert\` — CSV/JSON → table. Seed data.
|
|
881
891
|
- \`manage_domain\` — custom domains. \`add\` returns CNAME to configure. \`verify\` rechecks DNS/SSL. → \`search_docs("manage domain")\`.
|
|
882
|
-
- \`manage_users\` / \`manage_roles\` — app-level RBAC
|
|
892
|
+
- \`manage_users\` / \`manage_roles\` — app-level RBAC. Create/ban/assign roles.
|
|
883
893
|
- \`manage_schedules\` / \`manage_webhooks\` — pause/resume/history. To change the DEFINITION, edit the YAML and push.
|
|
884
894
|
- \`manage_storage\` — buckets + objects. \`upload_file\` reads local path, signs URL, PUTs, registers. Max 100MB/file. → \`search_docs("file storage")\`.
|
|
885
895
|
- \`manage_drafts\` — universal draft/publish. \`list\` before \`publish\` so the user sees what's shipping. \`discard\` to throw away. Always pair with \`dypai_test_endpoint(mode:'draft')\` before publish.
|
package/src/tools/sql-guard.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
// Schemas the agent must not modify. SELECT against them stays allowed.
|
|
20
20
|
const PROTECTED_SCHEMAS = new Set([
|
|
21
|
-
"auth", //
|
|
21
|
+
"auth", // auth user/session tables — engine-owned
|
|
22
22
|
"storage", // file upload metadata — engine-owned
|
|
23
23
|
"system", // DYPAI internals (endpoints, credentials, etc.)
|
|
24
24
|
"information_schema", // PG metadata catalog
|
|
@@ -127,7 +127,7 @@ export const dypaiDescribeTool = {
|
|
|
127
127
|
placeholders_available: [
|
|
128
128
|
"${input.<field>} — request body / query params",
|
|
129
129
|
"${nodes.<node_id>.<field>} — output of a previous node",
|
|
130
|
-
"${current_user_id} —
|
|
130
|
+
"${current_user_id} — id of the authenticated user (TEXT, NOT a UUID; jwt auth only)",
|
|
131
131
|
"${current_user_role} — role name of the authenticated user",
|
|
132
132
|
],
|
|
133
133
|
|
package/src/tools/sync/pull.js
CHANGED
|
@@ -206,7 +206,7 @@ output:
|
|
|
206
206
|
#
|
|
207
207
|
# \${input.<field>} — the request body / query params
|
|
208
208
|
# \${nodes.<id>.<field>} — output of a previous node
|
|
209
|
-
# \${current_user_id} —
|
|
209
|
+
# \${current_user_id} — ID of the JWT-authenticated user (TEXT, NOT a UUID — match against TEXT columns)
|
|
210
210
|
# \${current_user_role} — role name from the JWT
|
|
211
211
|
#
|
|
212
212
|
# Expressions inside placeholders work: arithmetic, comparisons, JS-ish.
|
|
@@ -218,8 +218,9 @@ output:
|
|
|
218
218
|
# plain object/array → binds as ::jsonb
|
|
219
219
|
# Date instance → binds as ::timestamptz
|
|
220
220
|
# text / int / bool → binds without an explicit cast (Postgres infers)
|
|
221
|
-
# So you write SQL naturally — DON'T add
|
|
222
|
-
# Just write: WHERE
|
|
221
|
+
# So you write SQL naturally — DON'T add type casts manually like
|
|
222
|
+
# '\${input.product_id}'::uuid. Just write: WHERE id = \${input.product_id}
|
|
223
|
+
# (and remember: \${current_user_id} is TEXT, so user_id columns must be TEXT too).
|
|
223
224
|
workflow:
|
|
224
225
|
nodes:
|
|
225
226
|
|
|
@@ -243,7 +243,7 @@ export const dypaiTestEndpointTool = {
|
|
|
243
243
|
},
|
|
244
244
|
as_user: {
|
|
245
245
|
type: "string",
|
|
246
|
-
description: "User
|
|
246
|
+
description: "User ID to impersonate. Required for jwt endpoints that read ${current_user_id}. NOTE: user IDs are stored in auth.\"user\".id as a TEXT alphanumeric string (e.g. '9KxggvkPhgpYXITE6koY0vYWJqQzSwaw'), NOT a UUID. Discover IDs with manage_users(operation:'list') or execute_sql(\"SELECT id, email FROM auth.\\\"user\\\" LIMIT 5\"). Common mistake: passing a UUID copied from system.users or a business table.",
|
|
247
247
|
},
|
|
248
248
|
trace_mode: {
|
|
249
249
|
type: "string",
|
|
@@ -1175,7 +1175,7 @@ export async function runValidation(rootDir, projectId) {
|
|
|
1175
1175
|
` user_id TEXT NOT NULL,\\n` +
|
|
1176
1176
|
` created_at TIMESTAMPTZ DEFAULT NOW()\\n` +
|
|
1177
1177
|
` );" })\n` +
|
|
1178
|
-
`IMPORTANT: user_id must be TEXT (not UUID) —
|
|
1178
|
+
`IMPORTANT: user_id must be TEXT (not UUID) — auth.\"user\".id is a TEXT alphanumeric string (~32 chars). ` +
|
|
1179
1179
|
`(Add other columns matching what your endpoint INSERTs.) ` +
|
|
1180
1180
|
`schema.sql will auto-refresh after the DDL. Existing tables: ${knownTablesList}`,
|
|
1181
1181
|
})
|