@dypai-ai/mcp 1.5.17 → 1.5.19
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 +2 -2
- package/src/index.js +4 -3
- package/src/tools/deploy.js +24 -7
- package/src/tools/scaffold.js +6 -5
- package/src/tools/sync/pull.js +37 -7
- package/src/tools/sync/validate.js +13 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dypai-ai/mcp",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.19",
|
|
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",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"license": "MIT",
|
|
23
23
|
"repository": {
|
|
24
24
|
"type": "git",
|
|
25
|
-
"url": "https://github.com/DYPAI-SOLUTIONS/dypai-mcp"
|
|
25
|
+
"url": "git+https://github.com/DYPAI-SOLUTIONS/dypai-mcp.git"
|
|
26
26
|
},
|
|
27
27
|
"homepage": "https://dypai.ai",
|
|
28
28
|
"dependencies": {
|
package/src/index.js
CHANGED
|
@@ -836,9 +836,10 @@ synonym.
|
|
|
836
836
|
- \`"auth defaults"\` — what auth_mode to pick when the user doesn't specify
|
|
837
837
|
|
|
838
838
|
### Integrations
|
|
839
|
-
- \`"
|
|
840
|
-
- \`"
|
|
841
|
-
-
|
|
839
|
+
- \`"stripe payments"\` — **start here for any Stripe work** (credentials, checkout URL placeholders, one-time + webhook checklist)
|
|
840
|
+
- \`"integrations guide"\` — step-by-step integration recipes (Stripe subscriptions; other providers referenced inside)
|
|
841
|
+
- \`"credentials reference"\` — what fields each provider needs (includes stripe + stripe_webhook)
|
|
842
|
+
- To find Stripe: \`search_docs("stripe payments")\` or \`search_docs("integrations guide stripe")\`
|
|
842
843
|
|
|
843
844
|
### Realtime
|
|
844
845
|
- \`"realtime channels"\` — client API for WebSocket subscriptions
|
package/src/tools/deploy.js
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
28
|
import { createHash } from "crypto"
|
|
29
|
-
import { readFileSync, readdirSync, statSync, existsSync, writeFileSync, mkdirSync } from "fs"
|
|
29
|
+
import { readFileSync, readdirSync, statSync, lstatSync, existsSync, writeFileSync, mkdirSync } from "fs"
|
|
30
30
|
import { join, basename, dirname, resolve } from "path"
|
|
31
31
|
import { api } from "../api.js"
|
|
32
32
|
|
|
@@ -37,7 +37,8 @@ const MAX_BUNDLED_FILE = 25 * 1024 * 1024
|
|
|
37
37
|
|
|
38
38
|
// ─── Directories to skip ────────────────────────────────────────────────────
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
// Skipped at any depth in the tree — these never carry user code.
|
|
41
|
+
const IGNORE_DIRS_ANYWHERE = new Set([
|
|
41
42
|
"node_modules", ".git",
|
|
42
43
|
// Build outputs
|
|
43
44
|
"dist", "build", "out", ".output", ".vercel", ".netlify",
|
|
@@ -46,8 +47,16 @@ const IGNORE_DIRS = new Set([
|
|
|
46
47
|
".cache", ".turbo", ".vite", ".parcel-cache", ".wrangler",
|
|
47
48
|
// Test / misc
|
|
48
49
|
"coverage", "storybook-static", "__pycache__", ".idea", ".vscode",
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
])
|
|
51
|
+
|
|
52
|
+
// Skipped ONLY at the project root. `dypai/` at root holds backend metadata
|
|
53
|
+
// (endpoint YAMLs, schema.sql, prompts) materialized by dypai_pull and
|
|
54
|
+
// shipped via dypai_push — it must NOT enter the frontend deploy. But a
|
|
55
|
+
// nested folder named `dypai/` (e.g. `src/integrations/dypai/`) is legitimate
|
|
56
|
+
// user code — the SDK client setup lives there. Matching by name alone (the
|
|
57
|
+
// previous behavior) silently dropped that folder and produced
|
|
58
|
+
// "Could not resolve '../integrations/dypai/client'" build errors.
|
|
59
|
+
const IGNORE_DIRS_AT_ROOT = new Set([
|
|
51
60
|
"dypai",
|
|
52
61
|
])
|
|
53
62
|
|
|
@@ -220,7 +229,7 @@ function classifySkip(path, ext, size) {
|
|
|
220
229
|
return null
|
|
221
230
|
}
|
|
222
231
|
|
|
223
|
-
function collectSource(dir) {
|
|
232
|
+
export function collectSource(dir) {
|
|
224
233
|
const allFiles = []
|
|
225
234
|
const skipped = []
|
|
226
235
|
const textByPath = new Map()
|
|
@@ -231,10 +240,18 @@ function collectSource(dir) {
|
|
|
231
240
|
let entries
|
|
232
241
|
try { entries = readdirSync(d) } catch { return }
|
|
233
242
|
for (const entry of entries) {
|
|
234
|
-
if (
|
|
243
|
+
if (IGNORE_DIRS_ANYWHERE.has(entry)) continue
|
|
244
|
+
if (rel === "" && IGNORE_DIRS_AT_ROOT.has(entry)) continue
|
|
235
245
|
const full = join(d, entry)
|
|
236
246
|
try {
|
|
237
|
-
|
|
247
|
+
// lstat (not stat): a symlink should be classified as a symlink, NOT
|
|
248
|
+
// as the target it points to. Otherwise a folder like `src/sneaky →
|
|
249
|
+
// /Users/me/something` would be walked and code outside the project
|
|
250
|
+
// root would be quietly committed to the user's repo on deploy.
|
|
251
|
+
// Symlinks are skipped outright — DYPAI projects don't need them
|
|
252
|
+
// and there is no safe automatic policy for following them.
|
|
253
|
+
const stat = lstatSync(full)
|
|
254
|
+
if (stat.isSymbolicLink()) continue
|
|
238
255
|
if (stat.isDirectory()) {
|
|
239
256
|
if (entry.startsWith(".")) continue
|
|
240
257
|
walk(full, rel ? `${rel}/${entry}` : entry)
|
package/src/tools/scaffold.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { writeFileSync, mkdirSync, existsSync } from "fs"
|
|
9
|
-
import { join } from "path"
|
|
9
|
+
import { join, dirname } from "path"
|
|
10
10
|
import { api } from "../api.js"
|
|
11
11
|
|
|
12
12
|
export const scaffoldTool = {
|
|
@@ -70,7 +70,7 @@ Use only visible Studio catalog template slugs; do not invent legacy slugs.`,
|
|
|
70
70
|
name: directory.split("/").pop() || "my-app",
|
|
71
71
|
private: true, version: "0.0.1", type: "module",
|
|
72
72
|
scripts: { dev: "vite", build: "vite build", preview: "vite preview" },
|
|
73
|
-
dependencies: { "@dypai-ai/client-sdk": "
|
|
73
|
+
dependencies: { "@dypai-ai/client-sdk": "1.11.0", react: "^19.0.0", "react-dom": "^19.0.0" },
|
|
74
74
|
devDependencies: { "@vitejs/plugin-react": "^4.3.0", vite: "^6.0.0", typescript: "^5.6.0", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0" },
|
|
75
75
|
}, null, 2) },
|
|
76
76
|
{ path: "vite.config.ts", content: `import { defineConfig } from 'vite'\nimport react from '@vitejs/plugin-react'\n\nexport default defineConfig({ plugins: [react()] })\n` },
|
|
@@ -116,12 +116,13 @@ Use only visible Studio catalog template slugs; do not invent legacy slugs.`,
|
|
|
116
116
|
// SDK client helper (lib/dypai.ts)
|
|
117
117
|
files.push({ path: "src/lib/dypai.ts", content: `import { createClient } from "@dypai-ai/client-sdk";\n\nexport const dypai = createClient(import.meta.env.VITE_DYPAI_URL);\n` })
|
|
118
118
|
|
|
119
|
-
// Write files to disk
|
|
119
|
+
// Write files to disk. Use `dirname()` rather than slicing on "/" — on
|
|
120
|
+
// Windows the path separator is "\" so the slice approach produced a
|
|
121
|
+
// garbage parent dir and the writeFileSync below failed with ENOENT.
|
|
120
122
|
let created = 0
|
|
121
123
|
for (const file of files) {
|
|
122
124
|
const fullPath = join(directory, file.path)
|
|
123
|
-
|
|
124
|
-
mkdirSync(dir, { recursive: true })
|
|
125
|
+
mkdirSync(dirname(fullPath), { recursive: true })
|
|
125
126
|
writeFileSync(fullPath, file.content)
|
|
126
127
|
created++
|
|
127
128
|
}
|
package/src/tools/sync/pull.js
CHANGED
|
@@ -503,6 +503,28 @@ function renderYaml(doc) {
|
|
|
503
503
|
})
|
|
504
504
|
}
|
|
505
505
|
|
|
506
|
+
/**
|
|
507
|
+
* Build the state.json payload, restricted to endpoints that were actually
|
|
508
|
+
* written to disk during this pull. The push planner uses this snapshot to
|
|
509
|
+
* detect remote drift since pull (compares remote.updated_at against
|
|
510
|
+
* snapshot.updated_at per endpoint). Including endpoints that failed to
|
|
511
|
+
* serialize would create a phantom "we have a fresh copy of this" claim and
|
|
512
|
+
* silently mask real conflicts on those rows.
|
|
513
|
+
*
|
|
514
|
+
* Exported for unit testing.
|
|
515
|
+
*/
|
|
516
|
+
export function buildStateSnapshot({ endpoints, successfullyPulledIds, projectId, now = new Date() }) {
|
|
517
|
+
return {
|
|
518
|
+
pulled_at: now.toISOString(),
|
|
519
|
+
project_id: projectId,
|
|
520
|
+
endpoints: Object.fromEntries(
|
|
521
|
+
endpoints
|
|
522
|
+
.filter(e => successfullyPulledIds.has(e.id))
|
|
523
|
+
.map(e => [e.name, { id: e.id, updated_at: e.updated_at }])
|
|
524
|
+
),
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
506
528
|
// schema.sql dump lives in ./schema-dump.js (shared with execute_sql auto-refresh)
|
|
507
529
|
|
|
508
530
|
export const dypaiPullTool = {
|
|
@@ -668,6 +690,15 @@ export const dypaiPullTool = {
|
|
|
668
690
|
filesWritten.push("realtime.yaml")
|
|
669
691
|
}
|
|
670
692
|
|
|
693
|
+
// Track which endpoints survived serialization → only those go into
|
|
694
|
+
// state.json. Including failed ones would lie to the push planner about
|
|
695
|
+
// what's locally in sync with remote (the conflict detector compares
|
|
696
|
+
// remote.updated_at against the snapshot per endpoint, and a phantom
|
|
697
|
+
// snapshot row for an endpoint that never made it to disk hides a real
|
|
698
|
+
// conflict). Tracked by row id so endpoint renames at the same name
|
|
699
|
+
// can't accidentally inherit a stale snapshot.
|
|
700
|
+
const successfullyPulled = new Set()
|
|
701
|
+
|
|
671
702
|
for (const rawRow of endpoints) {
|
|
672
703
|
const row = hydrateRow(rawRow)
|
|
673
704
|
try {
|
|
@@ -702,6 +733,7 @@ export const dypaiPullTool = {
|
|
|
702
733
|
|
|
703
734
|
await writeFileEnsured(join(outDir, relPath), content)
|
|
704
735
|
filesWritten.push(relPath)
|
|
736
|
+
successfullyPulled.add(row.id)
|
|
705
737
|
} catch (e) {
|
|
706
738
|
errors.push({ endpoint: row.name, error: e.message })
|
|
707
739
|
}
|
|
@@ -756,13 +788,11 @@ export const dypaiPullTool = {
|
|
|
756
788
|
await writeFile(configPath, configYaml, "utf8")
|
|
757
789
|
|
|
758
790
|
// .dypai/state.json: GITIGNORED — per-endpoint updated_at for conflict detection.
|
|
759
|
-
const state = {
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
),
|
|
765
|
-
}
|
|
791
|
+
const state = buildStateSnapshot({
|
|
792
|
+
endpoints,
|
|
793
|
+
successfullyPulledIds: successfullyPulled,
|
|
794
|
+
projectId: resolvedProjectId,
|
|
795
|
+
})
|
|
766
796
|
await writeFileEnsured(join(outDir, ".dypai", "state.json"), JSON.stringify(state, null, 2) + "\n")
|
|
767
797
|
|
|
768
798
|
// Codegen removed from v1. If we reintroduce it, this is where it wires in.
|
|
@@ -1564,6 +1564,19 @@ function validateEndpoint(entry, ctx) {
|
|
|
1564
1564
|
}
|
|
1565
1565
|
}
|
|
1566
1566
|
|
|
1567
|
+
const webhookCred = doc.trigger?.webhook?.credential
|
|
1568
|
+
if (webhookCred && !ctx.remoteCredentials.has(webhookCred)) {
|
|
1569
|
+
diagnostics.push({
|
|
1570
|
+
severity: "error",
|
|
1571
|
+
rule: "credential_not_found",
|
|
1572
|
+
endpoint: name, file, loc: "trigger.webhook.credential",
|
|
1573
|
+
message: `credential '${webhookCred}' is not defined remotely.`,
|
|
1574
|
+
fix_hint: ctx.remoteCredentials.size
|
|
1575
|
+
? `Available: ${[...ctx.remoteCredentials].join(", ")}. For Stripe webhooks use type stripe_webhook, recommended name stripe-webhook.`
|
|
1576
|
+
: "Create stripe-webhook (type stripe_webhook) in the dashboard. See search_docs('stripe payments').",
|
|
1577
|
+
})
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1567
1580
|
// ── Edge sanity: catch typos in workflow.edges before runtime ────────────
|
|
1568
1581
|
// The engine silently skips edges whose `from`/`to` doesn't resolve to a
|
|
1569
1582
|
// node id, which manifests as "node never ran" — extremely hard to debug.
|