@dypai-ai/mcp 1.6.5 → 1.6.6

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.6.5",
3
+ "version": "1.6.6",
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
@@ -553,7 +553,7 @@ async function handleRequest(msg) {
553
553
  return makeResponse(id, {
554
554
  protocolVersion: "2024-11-05",
555
555
  capabilities: { tools: {} },
556
- serverInfo: { name: "dypai", version: "1.6.5" },
556
+ serverInfo: { name: "dypai", version: "1.6.6" },
557
557
  instructions: serverInstructions,
558
558
  })
559
559
  }
@@ -32,24 +32,38 @@ const STUDIO_SAFE_MODULE_KINDS = new Set([
32
32
 
33
33
  function isStudioSafeArtifact(item) {
34
34
  if (!isObject(item)) return false
35
+ if (item.can_apply === true) return true
36
+ if (!item.artifact_type && !item.kind && !item.module_kind && !item.feature_kind && !item.category) return true
35
37
  const artifactType = item.artifact_type || item.kind
36
- const moduleKind = item.module_kind || item.feature_kind
38
+ const moduleKind = item.module_kind || item.feature_kind || item.category
37
39
  return artifactType === "feature" && STUDIO_SAFE_MODULE_KINDS.has(moduleKind)
38
40
  }
39
41
 
40
- function filterStudioSafeSearchResult(raw, limit) {
42
+ function leanArtifact(item) {
43
+ return {
44
+ slug: item.slug,
45
+ name: item.name,
46
+ summary: item.summary || item.description,
47
+ best_for: item.best_for || item.use_when,
48
+ requires: normalizeList(item.requires || item.requires_artifacts).slice(0, 5),
49
+ includes: normalizeList(item.includes || item.provides_capabilities).slice(0, 6),
50
+ next_step: item.next_step || (item.slug ? `manage_project_artifact(operation:'inspect', slug:'${item.slug}')` : undefined),
51
+ }
52
+ }
53
+
54
+ function filterArtifactSearchResult(raw, limit) {
41
55
  if (!isObject(raw)) return raw
42
- const cap = Number.isFinite(Number(limit)) ? Number(limit) : 20
43
- const artifacts = normalizeArtifactList(raw.artifacts).filter(isStudioSafeArtifact).slice(0, cap)
44
- const features = normalizeArtifactList(raw.features).filter(isStudioSafeArtifact).slice(0, cap)
56
+ const cap = Number.isFinite(Number(limit)) ? Number(limit) : 3
57
+ const artifacts = normalizeArtifactList(raw.artifacts)
58
+ .filter(isStudioSafeArtifact)
59
+ .slice(0, cap)
60
+ .map(leanArtifact)
45
61
  return {
46
- ...raw,
47
- frontend_only: true,
62
+ ok: raw.ok !== false,
63
+ query: raw.query,
48
64
  results_count: artifacts.length,
49
65
  artifacts,
50
- bases: [],
51
- features,
52
- migration_note: "Studio only exposes frontend/UI artifacts. Backend/database artifacts must be implemented as Flow before Studio install.",
66
+ hint: raw.hint || "Pick one slug, inspect it, then apply only if it fits the current app.",
53
67
  }
54
68
  }
55
69
 
@@ -76,11 +90,20 @@ function manifestFromModuleContract(contract) {
76
90
  const mount = Array.isArray(contract?.mount_contract?.manifest) ? contract.mount_contract.manifest : []
77
91
  const frontendManifest = []
78
92
  const backendManifest = []
93
+ const flowManifest = []
79
94
  const databaseManifest = {}
80
95
  for (const entry of mount) {
81
96
  if (!entry?.source) continue
82
97
  const source = String(entry.source)
83
98
  const role = String(entry.role || "")
99
+ if (role === "flow" || source.includes("/flows/")) {
100
+ flowManifest.push({
101
+ source,
102
+ suggestedTarget: entry.target,
103
+ flowName: entry.export_name || basename(source).replace(/\.flow\.ts$/i, ""),
104
+ })
105
+ continue
106
+ }
84
107
  if (role === "endpoint" || source.includes("/endpoints/")) {
85
108
  backendManifest.push({
86
109
  source,
@@ -128,10 +151,14 @@ function manifestFromModuleContract(contract) {
128
151
  dependencies,
129
152
  frontendManifest,
130
153
  backendManifest,
154
+ flowManifest,
131
155
  databaseManifest,
156
+ flow_ready: contract.flow_ready === true,
157
+ studio_safe: contract.studio_safe === true,
158
+ install_risk: contract.install_risk || null,
132
159
  install: {
133
160
  requiresExecuteSql: Boolean(databaseManifest.schema || databaseManifest.seed),
134
- requiresBackendPublish: backendManifest.length > 0,
161
+ requiresBackendPublish: backendManifest.length > 0 || flowManifest.length > 0,
135
162
  },
136
163
  }
137
164
  }
@@ -160,24 +187,24 @@ function loadArtifactFromDir(artifactDir) {
160
187
  return { dir: artifactDir, manifest }
161
188
  }
162
189
 
163
- async function materializeArtifactFromGithub({ source_repo, source_path, source_ref }) {
190
+ async function materializeArtifactFromGithub({ slug, source_repo, source_path, source_ref }) {
164
191
  const repo = String(source_repo || "").trim()
165
- if (!repo) {
166
- throw new Error("source_repo is required from search_project_artifacts (e.g. dyapps-codes/artifacts).")
167
- }
192
+ const wantedSlug = String(slug || "").trim()
193
+ if (!repo && !wantedSlug) throw new Error("slug is required.")
168
194
  const path = String(source_path || "").trim()
169
195
  const ref = String(source_ref || "main").trim() || "main"
170
196
 
171
- const remote = await proxyToolCall("fetch_project_artifact", {
172
- source_repo: repo,
173
- source_path: path,
174
- source_ref: ref,
175
- })
197
+ const remote = await proxyToolCall(
198
+ "fetch_project_artifact",
199
+ repo
200
+ ? { source_repo: repo, source_path: path, source_ref: ref }
201
+ : { slug: wantedSlug },
202
+ )
176
203
  if (!isObject(remote) || !remote.ok) {
177
204
  throw new Error(isObject(remote) && remote.error ? remote.error : "fetch_project_artifact failed")
178
205
  }
179
206
 
180
- const key = crypto.createHash("sha256").update(`${repo}|${path}|${ref}`).digest("hex")
207
+ const key = crypto.createHash("sha256").update(`${repo || wantedSlug}|${path}|${ref}`).digest("hex")
181
208
  const artifactDir = join(tmpdir(), "dypai-artifact-fetch", key)
182
209
  rmSync(artifactDir, { recursive: true, force: true })
183
210
  mkdirSync(artifactDir, { recursive: true })
@@ -232,29 +259,27 @@ async function lookupArtifactGithubSource(slug) {
232
259
  }
233
260
 
234
261
  async function searchProjectArtifacts(args = {}) {
235
- const studioSafe = isStudioArtifactProfile()
236
- const requestedLimit = Number(args.limit || 20)
237
- const proxyLimit = studioSafe || args.frontend_only ? Math.max(requestedLimit, 50) : requestedLimit
262
+ const requestedLimit = Number(args.limit || 3)
238
263
  const remoteArgs = {
239
264
  ...args,
240
- limit: Math.min(Math.max(proxyLimit, 1), 50),
265
+ limit: Math.min(Math.max(requestedLimit, 1), 5),
241
266
  }
242
267
  delete remoteArgs.frontend_only
243
268
 
244
269
  const raw = await proxyToolCall("search_project_artifacts", remoteArgs)
245
270
  if (!isObject(raw) || raw.ok === false) return raw
246
- if (!studioSafe && !args.frontend_only) return raw
247
- return filterStudioSafeSearchResult(raw, requestedLimit)
271
+ return filterArtifactSearchResult(raw, requestedLimit)
248
272
  }
249
273
 
250
274
  async function resolveArtifactInstallation({ slug, source_repo, source_path, source_ref }) {
251
275
  const resolved = String(source_repo || "").trim()
252
276
  ? {
277
+ slug: String(slug || "").trim(),
253
278
  source_repo: String(source_repo).trim(),
254
279
  source_path: String(source_path || "").trim(),
255
280
  source_ref: String(source_ref || "main").trim() || "main",
256
281
  }
257
- : await lookupArtifactGithubSource(slug)
282
+ : { slug: String(slug || "").trim() }
258
283
  return materializeArtifactFromGithub(resolved)
259
284
  }
260
285
 
@@ -362,6 +387,7 @@ function resolveArtifactAssetPath(artifactDir, source) {
362
387
 
363
388
  function defaultTargetForSource(manifest, source, kind) {
364
389
  if (kind === "frontend") return `src/dypai-kits/${manifest.slug}/${stripFrontendPrefix(source)}`
390
+ if (kind === "flow") return `dypai/flows/${source.split("/").pop()}`
365
391
  if (kind === "backend") return `dypai/endpoints/${manifest.slug}/${source.split("/").pop()}`
366
392
  if (kind === "database") return `dypai/kit-installations/${manifest.slug}/${manifest.version}/${source.split("/").pop()}`
367
393
  return `.dypai/kits/${manifest.slug}/${source.split("/").pop()}`
@@ -525,11 +551,17 @@ async function applyProjectArtifact({
525
551
 
526
552
  const installFrontend = install.frontend !== false
527
553
  const studioSafe = isStudioArtifactProfile()
528
- const hasBackendAssets = (manifest.backendManifest || []).length > 0
554
+ const hasLegacyBackendAssets = (manifest.backendManifest || []).length > 0
555
+ const hasFlowAssets = (manifest.flowManifest || []).length > 0
529
556
  const hasDatabaseAssets = Boolean(manifest.databaseManifest?.schema || manifest.databaseManifest?.seed)
530
- if (studioSafe && (hasBackendAssets || hasDatabaseAssets)) {
557
+ if (studioSafe && (hasLegacyBackendAssets || hasDatabaseAssets)) {
531
558
  throw new Error(
532
- `Artifact "${manifest.slug}" includes backend/database assets. Studio can only apply frontend/UI artifacts; implement backend pieces as Flow first.`,
559
+ `Artifact "${manifest.slug}" includes legacy YAML backend or database assets. Studio can only apply frontend/UI artifacts until backend is Flow-only.`,
560
+ )
561
+ }
562
+ if (studioSafe && hasFlowAssets && manifest.studio_safe !== true) {
563
+ throw new Error(
564
+ `Artifact "${manifest.slug}" has Flow backend but is not marked studio_safe. Complete Flow migration and set studio_safe in module.contract.json.`,
533
565
  )
534
566
  }
535
567
  if (studioSafe && install.backend === true) {
@@ -545,6 +577,7 @@ async function applyProjectArtifact({
545
577
  throw new Error(`Artifact "${manifest.slug}" has no frontend files to install in Studio.`)
546
578
  }
547
579
  const installBackend = studioSafe ? false : install.backend !== false
580
+ const installFlows = studioSafe ? false : install.flows !== false && install.backend !== false
548
581
  const installDatabase = studioSafe ? false : install.database !== false
549
582
 
550
583
  if (installFrontend) {
@@ -579,6 +612,19 @@ async function applyProjectArtifact({
579
612
  }
580
613
  }
581
614
 
615
+ if (installFlows) {
616
+ for (const asset of manifest.flowManifest || []) {
617
+ const sourceAbs = resolveArtifactAssetPath(dir, asset.source)
618
+ let targetRel = asset.suggestedTarget || defaultTargetForSource(manifest, asset.source, "flow")
619
+ if (target.flowDir) {
620
+ targetRel = join(target.flowDir, basename(asset.source)).split(sep).join("/")
621
+ }
622
+ if (writeTemplateFile({ sourceAbs, targetRel, workspaceRoot, manifest, naming, kind: "frontend", overwrite, record, copied, skipped })) {
623
+ copiedByKind.backend.push(targetRel)
624
+ }
625
+ }
626
+ }
627
+
582
628
  if (installDatabase && isObject(manifest.databaseManifest)) {
583
629
  for (const key of ["schema", "seed"]) {
584
630
  const source = manifest.databaseManifest[key]
@@ -604,9 +650,14 @@ async function applyProjectArtifact({
604
650
  return true
605
651
  })
606
652
 
607
- const backendEndpoints = (manifest.backendManifest || []).map((asset) =>
608
- adaptTarget(asset.endpointName || asset.source.split("/").pop().replace(/\.ya?ml$/, ""), manifest, naming),
609
- )
653
+ const backendEndpoints = [
654
+ ...(manifest.backendManifest || []).map((asset) =>
655
+ adaptTarget(asset.endpointName || asset.source.split("/").pop().replace(/\.ya?ml$/, ""), manifest, naming),
656
+ ),
657
+ ...(manifest.flowManifest || []).map((asset) =>
658
+ adaptTarget(asset.flowName || asset.source.split("/").pop().replace(/\.flow\.ts$/, ""), manifest, naming),
659
+ ),
660
+ ]
610
661
  const databaseTables = normalizeList(manifest.databaseManifest?.tables).map((table) =>
611
662
  adaptTarget(table, manifest, naming),
612
663
  )