@dypai-ai/mcp 1.5.5 → 1.5.7

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.5",
3
+ "version": "1.5.7",
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
@@ -115,6 +115,7 @@ const REMOTE_TOOLS = [
115
115
  // ── Project ───────────────────────────────────────────────────────────────
116
116
  { name: "list_projects", description: "Lists all projects you have access to across your organizations. Returns project id, name, description, organization, subscription plan, and status. Use this as the first step to discover which projects are available, then pass project_id to other tools.", inputSchema: { type: "object", properties: { organization_id: { type: "string", description: "Optional. Filter projects by organization UUID." } }, required: [] } },
117
117
  { name: "get_project", description: "Gets detailed information about a specific project. Returns project name, description, organization, plan, status, engine URL, frontend slug, and timestamps.", inputSchema: { type: "object", properties: { project_id: { type: "string" } }, required: ["project_id"] } },
118
+ { name: "list_ai_models", description: "List only the DYPAI Managed AI models that are active for a project. Returns the project-gated OpenRouter model catalog, monthly included AI credits, monthly hard cap, RPM limit, max output tokens, active/available counts, and the exact node parameters to use. Call this before creating or editing an AI Agent node with DYPAI Managed models. Agents must not invent or use inactive model ids. Use provider='openrouter' and do NOT set credential_id; DYPAI uses the platform OpenRouter key and bills usage to the organization.", inputSchema: { type: "object", properties: { project_id: { type: "string", description: "Project UUID whose plan and Model Gateway settings determine the active Managed AI catalog." } }, required: ["project_id"] } },
118
119
  { name: "create_project", description: "Create a new DYPAI project (free plan). Creates a full project with database, engine, GitHub repo, and frontend hosting. BLOCKS by default until provisioning finishes (~60s typical, 120s max) — when it returns, the project_id is ready to use with execute_sql, endpoint tools, etc. Pass wait_until_ready:false for batch flows.\n\nName collision: if another project in the same org already uses the name (case-insensitive), returns {error:'name_taken', existing_project_id, suggestions:[...]}. Pick a different name or use the existing project.\n\nIMPORTANT: before calling, check for a matching template with `search_project_templates`. Passing a `template_slug` drops in a ready-made schema + endpoints + UI that cover 70% of common app types. Only create a blank project if nothing matches.", inputSchema: { type: "object", properties: { name: { type: "string", description: "Project name (e.g. 'My Veterinary App')" }, organization_id: { type: "string", description: "Optional. Uses default org if omitted." }, description: { type: "string" }, template_slug: { type: "string", description: "RECOMMENDED. Project template slug to start from (e.g. 'clinic', 'gym', 'waitlist', 'blank'). Always call search_project_templates first to find the best match." }, wait_until_ready: { type: "boolean", description: "If true (default), blocks until provisioning completes and the project is ready for all operations. If false, returns immediately with status='provisioning' — caller must poll get_project before using.", default: true } }, required: ["name"] } },
119
120
  { 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: [] } },
120
121
 
@@ -353,7 +354,7 @@ Notes:
353
354
  // ── Triggers (Schedules + Webhooks) ──────────────────────────────────────
354
355
  // These tools OBSERVE + CONTROL trigger runtime state. The DEFINITION lives
355
356
  // in the endpoint YAML (`trigger.schedule` / `trigger.webhook`) — to change
356
- // a cron expression or webhook path, edit the YAML and `dypai_push`.
357
+ // a cron expression or webhook path, edit the YAML and \`dypai_push\`.
357
358
  {
358
359
  name: "manage_schedules",
359
360
  description: `Observe + control cron schedules at runtime, by endpoint name.
@@ -562,35 +563,122 @@ A freshly created project has **zero** local files. The create response gives yo
562
563
  **Rule of thumb: if you can't \`Read\` it from disk, you can't touch it. Sync before you guess.**
563
564
 
564
565
  # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
565
- # TALKING TO THE USER — plain language, not tool names
566
+ # TALKING TO THE USER — proactive, plain language, no internal machinery
566
567
  # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
567
568
 
568
- The user is **not a developer watching your tool calls**. They read only your prose. Never narrate actions by their technical name translate every tool call into the real-world outcome.
569
+ Assume most DYPAI users are non-technical. Many are not developers, do not know what an endpoint is, and should not need to learn DYPAI's internal workflow. The user should be able to say what they want in normal language; your job is to translate that into technical work, testing, and a guided path to publication.
570
+
571
+ The user sees only TWO meaningful states:
572
+
573
+ 1. **Ready to test / listo para probar** — the change is available in their preview and does not affect real users.
574
+ 2. **Published / publicado** — the change is live for real users.
575
+
576
+ Everything else is internal agent machinery. Do not make the user learn about \`dypai_push\`, drafts, overlays, workflow YAML, MCP tools, merge/publish mechanics, or endpoint staging unless they explicitly ask how it works under the hood.
577
+
578
+ ## Be proactive with non-technical users
579
+
580
+ Do not wait for the user to know the next step. They often only know the outcome they want.
581
+
582
+ When the user asks for a feature, bugfix, or change:
583
+
584
+ 1. Understand the desired product behavior.
585
+ 2. Choose a sensible implementation path.
586
+ 3. Make the change end-to-end when possible.
587
+ 4. If backend behavior changed, automatically save it to preview.
588
+ 5. Test what you can yourself.
589
+ 6. Give the user exactly one clear next action.
590
+ 7. Publish to production only after explicit approval.
591
+
592
+ Never ask:
593
+
594
+ - "Should I run dypai_push?"
595
+ - "Should I push the endpoints?"
596
+ - "Should I stage this draft?"
597
+ - "Should I merge/publish the draft?"
598
+ - "Should I call manage_drafts?"
599
+
600
+ Say instead:
601
+
602
+ - "Ya lo he dejado listo para que lo pruebes."
603
+ - "Pruébalo en la previsualización."
604
+ - "Todavía no afecta a tus usuarios reales."
605
+ - "Cuando me digas que está bien, lo publico."
606
+ - "¿Quieres que lo publique ya para tus usuarios?"
607
+
608
+ ## Internal workflow you MUST follow
609
+
610
+ After changing backend behavior, ALWAYS make the change available in preview before telling the user to test it.
611
+
612
+ Internally this means:
613
+
614
+ 1. edit backend files
615
+ 2. validate local backend changes
616
+ 3. save them to the preview environment
617
+ 4. test the preview version when practical
618
+ 5. then tell the user it is ready to try
619
+
620
+ 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.
621
+
622
+ Publishing to production is different: it changes what real users see and ALWAYS needs explicit user approval.
623
+
624
+ ## If the user asks "how do I see it?"
625
+
626
+ If the user asks "how do I see it?", "where do I test it?", "what now?", "pero como veo esto", or similar, do not explain backend states. Give the shortest practical next action in human terms.
627
+
628
+ Good:
629
+
630
+ - "Abre la previsualización y prueba crear un pedido."
631
+ - "Ve al panel de administración y edita un producto."
632
+ - "Prueba iniciar sesión con este usuario."
633
+ - "Pulsa 'Crear producto'. Deberías ver el nuevo producto en la lista sin recargar."
634
+ - "Dime si lo ves bien y lo publico."
635
+
636
+ Bad:
637
+
638
+ - "Está en el draft overlay."
639
+ - "He hecho dypai_push."
640
+ - "Falta manage_drafts publish."
641
+ - "El endpoint está staged."
569
642
 
570
643
  ## Translation rule of thumb
571
644
 
572
- Replace the VERB with what the user perceives:
645
+ Replace tool-speak with the outcome the user perceives:
573
646
 
574
647
  | Instead of (tool-speak) | Say (human) |
575
648
  |---|---|
576
- | "Voy a hacer dypai_push / I'll call dypai_push" | "Voy a subir los cambios al borrador para que los pruebes" |
577
- | "Voy a ejecutar manage_drafts(publish)" | "Voy a publicar los cambios en producción" |
649
+ | "Voy a hacer dypai_push / I'll call dypai_push" | "Voy a dejar los cambios listos para que los pruebes" |
650
+ | "He hecho dypai_push" | "Ya lo he dejado listo para probar" |
651
+ | "Voy a ejecutar manage_drafts(publish)" | "Voy a publicarlo para tus usuarios" |
652
+ | "He mergeado el draft" | "He publicado los cambios" |
578
653
  | "Ejecutando dypai_pull" | "Un momento, sincronizo tu proyecto" |
579
654
  | "Calling manage_frontend(deploy)" | "Voy a desplegar la nueva versión de tu web" |
580
655
  | "Running execute_sql to add a column" | "Voy a añadir un campo nuevo a la tabla X" |
581
656
  | "manage_database(operation:'apply_migration')" | "Voy a aplicar los cambios de estructura a la base de datos" |
582
657
  | "search_logs returned a 500" | "He mirado los registros: el error viene de..." |
583
658
  | "manage_drafts(list) shows 3 pending" | "Tienes 3 cambios pendientes de publicar" |
584
- | "On the draft overlay" / "en la overlay de draft" | "En tu entorno de previsualización" o "en el borrador" |
659
+ | "On the draft overlay" / "en la overlay de draft" | "En tu entorno de previsualización" |
585
660
  | "dypai_validate rejected the workflow" | "Hay un problema con la configuración:" |
586
661
 
587
662
  ## Principles
588
663
 
589
- 1. **State outcomes, not function calls.** The user cares about *"guardado"*, *"publicado"*, *"desplegado"*, *"probado"* — not *"pushed"*, *"dispatched"*, *"invalidated"*.
590
- 2. **Draft vs liveborrador vs producción / previsualización vs en vivo.** Never say "overlay", "engine", "endpoint hit" in user-facing prose.
664
+ 1. **State outcomes, not function calls.** The user cares about *"listo para probar"*, *"publicado"*, *"desplegado"*, *"probado"* — not *"pushed"*, *"staged"*, *"invalidated"*, or *"merged"*.
665
+ 2. **Preview vs productionprevisualización vs producción.** Prefer *"listo para probar"* and *"publicado"*. Avoid *"draft"*, *"borrador"*, *"mergear draft"*, *"overlay"*, *"engine"*, and *"endpoint hit"* in user-facing prose unless the user used those words first.
591
666
  3. **Errors: summarize, don't paste.** *"Falló porque estás comparando tipos distintos en la SQL"* beats pasting a Postgres stack.
592
- 4. **Confirmations use real-world verbs.** *"¿Lo publico a producción?"* not *"¿Ejecuto manage_drafts(publish, confirm:true)?"*.
593
- 5. **Progress updates in sentences, not tool names.** *"Primero guardo los cambios, luego te los muestro para que los pruebes, y si te cuadra los publicamos"* beats a 3-bullet list of tool calls.
667
+ 4. **Confirmations use real-world verbs.** *"¿Lo publico para tus usuarios?"* not *"¿Ejecuto manage_drafts(publish, confirm:true)?"*.
668
+ 5. **Progress updates in sentences, not tool names.** *"Estoy haciendo el cambio; cuando esté listo te digo exactamente dónde probarlo"* beats a bullet list of tool calls.
669
+ 6. **End every completed change with one practical next step.** Examples: *"Pruébalo en la previsualización"*, *"Dime si quieres que lo publique"*, *"Prueba hacer una compra con tarjeta de test"*.
670
+
671
+ ## Preferred user-facing phrases
672
+
673
+ Use phrases like:
674
+
675
+ - "Estoy haciendo el cambio."
676
+ - "Ya lo he dejado listo para que lo pruebes."
677
+ - "Abre la previsualización y prueba este flujo concreto: ..."
678
+ - "Todavía no afecta a tus usuarios reales."
679
+ - "Cuando me confirmes que está bien, lo publico."
680
+ - "He revisado el error en los registros y viene de..."
681
+ - "He actualizado la base de datos para añadir el nuevo campo."
594
682
 
595
683
  ## When you CAN mention a tool name
596
684
 
@@ -16,6 +16,70 @@ const MCP_ENDPOINT = `${MCP_BASE}/mcp`
16
16
 
17
17
  let sessionId = null
18
18
 
19
+ function extractLastSseData(text) {
20
+ const events = text.split(/\r?\n\r?\n/).filter(Boolean)
21
+ for (let i = events.length - 1; i >= 0; i--) {
22
+ const dataLines = events[i]
23
+ .split(/\r?\n/)
24
+ .filter((line) => line.startsWith("data:"))
25
+ .map((line) => line.replace(/^data:\s?/, ""))
26
+ .filter((line) => line && line !== "[DONE]")
27
+ if (dataLines.length) return dataLines.join("\n")
28
+ }
29
+ return null
30
+ }
31
+
32
+ function decodeTransportValue(value) {
33
+ let current = value
34
+ for (let i = 0; i < 5; i++) {
35
+ if (typeof current !== "string") return current
36
+ const trimmed = current.trim()
37
+ const sseData = extractLastSseData(trimmed)
38
+ const candidate = sseData || trimmed
39
+ try {
40
+ current = JSON.parse(candidate)
41
+ } catch {
42
+ return current
43
+ }
44
+ }
45
+ return current
46
+ }
47
+
48
+ function normalizeJsonRpcResponse(response) {
49
+ let current = decodeTransportValue(response)
50
+
51
+ for (let i = 0; i < 5; i++) {
52
+ if (!current || typeof current !== "object") return current
53
+
54
+ // Some HTTP/SSE transports get wrapped as { result: "<jsonrpc...>" } after
55
+ // a fallback parse path. Unwrap that so callers always see the real MCP
56
+ // JSON-RPC response instead of a stringified envelope.
57
+ if (
58
+ Object.keys(current).length === 1
59
+ && typeof current.result === "string"
60
+ ) {
61
+ const decoded = decodeTransportValue(current.result)
62
+ if (decoded !== current.result) {
63
+ current = decoded
64
+ continue
65
+ }
66
+ }
67
+
68
+ if (
69
+ current.result
70
+ && typeof current.result === "object"
71
+ && current.result.jsonrpc
72
+ ) {
73
+ current = current.result
74
+ continue
75
+ }
76
+
77
+ return current
78
+ }
79
+
80
+ return current
81
+ }
82
+
19
83
  function mcpRequest(body) {
20
84
  const token = process.env.DYPAI_TOKEN || ""
21
85
 
@@ -50,18 +114,9 @@ function mcpRequest(body) {
50
114
  res.on("end", () => {
51
115
  if (res.statusCode >= 200 && res.statusCode < 300) {
52
116
  // Handle SSE responses (text/event-stream)
53
- if (buf.includes("data: ")) {
54
- const lines = buf.split("\n").filter(l => l.startsWith("data: "))
55
- const lastData = lines[lines.length - 1]
56
- if (lastData) {
57
- try {
58
- resolve(JSON.parse(lastData.replace("data: ", "")))
59
- } catch {
60
- resolve({ result: buf })
61
- }
62
- } else {
63
- resolve({ result: buf })
64
- }
117
+ const sseData = extractLastSseData(buf)
118
+ if (sseData) {
119
+ try { resolve(JSON.parse(sseData)) } catch { resolve({ result: sseData }) }
65
120
  } else {
66
121
  try { resolve(JSON.parse(buf)) } catch { resolve({ result: buf }) }
67
122
  }
@@ -116,7 +171,7 @@ async function ensureInitialized() {
116
171
  export async function proxyToolCall(toolName, args) {
117
172
  await ensureInitialized()
118
173
 
119
- const response = await mcpRequest({
174
+ const response = normalizeJsonRpcResponse(await mcpRequest({
120
175
  jsonrpc: "2.0",
121
176
  id: `proxy-${Date.now()}`,
122
177
  method: "tools/call",
@@ -124,7 +179,7 @@ export async function proxyToolCall(toolName, args) {
124
179
  name: toolName,
125
180
  arguments: args || {},
126
181
  },
127
- })
182
+ }))
128
183
 
129
184
  // Extract result from JSON-RPC response
130
185
  if (response.result) {
@@ -137,7 +192,7 @@ export async function proxyToolCall(toolName, args) {
137
192
 
138
193
  if (text != null) {
139
194
  let parsed
140
- try { parsed = JSON.parse(text) } catch { parsed = text }
195
+ parsed = decodeTransportValue(text)
141
196
  // Some remote errors come as a plain string without isError flag
142
197
  if (typeof parsed === "string" && /^(Error:|Input validation error:|You don't have)/i.test(parsed)) {
143
198
  throw new Error(parsed)