@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 +1 -1
- package/src/index.js +99 -11
- package/src/tools/proxy.js +70 -15
package/package.json
CHANGED
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
|
|
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,
|
|
566
|
+
# TALKING TO THE USER — proactive, plain language, no internal machinery
|
|
566
567
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
567
568
|
|
|
568
|
-
|
|
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
|
|
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
|
|
577
|
-
| "
|
|
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"
|
|
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 *"
|
|
590
|
-
2. **
|
|
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 production → previsualizació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
|
|
593
|
-
5. **Progress updates in sentences, not tool names.** *"
|
|
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
|
|
package/src/tools/proxy.js
CHANGED
|
@@ -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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
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)
|