@dypai-ai/mcp 1.6.5 → 1.6.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.6.5",
3
+ "version": "1.6.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
@@ -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.7" },
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,16 @@ function loadArtifactFromDir(artifactDir) {
160
187
  return { dir: artifactDir, manifest }
161
188
  }
162
189
 
163
- async function materializeArtifactFromGithub({ source_repo, source_path, source_ref }) {
164
- 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
- }
168
- const path = String(source_path || "").trim()
169
- const ref = String(source_ref || "main").trim() || "main"
190
+ async function materializeArtifactFromCatalog({ slug }) {
191
+ const wantedSlug = String(slug || "").trim()
192
+ if (!wantedSlug) throw new Error("slug is required.")
170
193
 
171
- const remote = await proxyToolCall("fetch_project_artifact", {
172
- source_repo: repo,
173
- source_path: path,
174
- source_ref: ref,
175
- })
194
+ const remote = await proxyToolCall("fetch_project_artifact", { slug: wantedSlug })
176
195
  if (!isObject(remote) || !remote.ok) {
177
196
  throw new Error(isObject(remote) && remote.error ? remote.error : "fetch_project_artifact failed")
178
197
  }
179
198
 
180
- const key = crypto.createHash("sha256").update(`${repo}|${path}|${ref}`).digest("hex")
199
+ const key = crypto.createHash("sha256").update(`${wantedSlug}|${remote.content_hash || remote.version || "latest"}`).digest("hex")
181
200
  const artifactDir = join(tmpdir(), "dypai-artifact-fetch", key)
182
201
  rmSync(artifactDir, { recursive: true, force: true })
183
202
  mkdirSync(artifactDir, { recursive: true })
@@ -199,63 +218,27 @@ async function materializeArtifactFromGithub({ source_repo, source_path, source_
199
218
  const loaded = loadArtifactFromDir(artifactDir)
200
219
  return {
201
220
  ...loaded,
202
- source: "github",
203
- source_repo: repo,
204
- source_path: path,
205
- source_ref: ref,
221
+ source: remote.source || "catalog",
206
222
  agentNotes: typeof remote.agent_notes === "string" ? remote.agent_notes : undefined,
207
223
  checklist: typeof remote.checklist === "string" ? remote.checklist : undefined,
208
224
  }
209
225
  }
210
226
 
211
- async function lookupArtifactGithubSource(slug) {
212
- const wanted = String(slug || "").trim()
213
- if (!wanted) throw new Error("slug is required.")
214
-
215
- const remote = await proxyToolCall("search_project_artifacts", { query: wanted, limit: 20 })
216
- if (!isObject(remote) || remote.ok === false) {
217
- throw new Error(isObject(remote) && remote.error ? remote.error : "search_project_artifacts failed")
218
- }
219
-
220
- const pools = [remote.artifacts, remote.features, remote.bases].filter(Array.isArray)
221
- for (const pool of pools) {
222
- const hit = pool.find((item) => isObject(item) && String(item.slug || "").trim() === wanted)
223
- if (hit?.source_repo) {
224
- return {
225
- source_repo: String(hit.source_repo).trim(),
226
- source_path: String(hit.source_path || "").trim(),
227
- source_ref: String(hit.source_ref || "main").trim() || "main",
228
- }
229
- }
230
- }
231
- throw new Error(`Artifact "${wanted}" not found in catalog. Call search_project_artifacts first.`)
232
- }
233
-
234
227
  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
228
+ const requestedLimit = Number(args.limit || 3)
238
229
  const remoteArgs = {
239
230
  ...args,
240
- limit: Math.min(Math.max(proxyLimit, 1), 50),
231
+ limit: Math.min(Math.max(requestedLimit, 1), 5),
241
232
  }
242
233
  delete remoteArgs.frontend_only
243
234
 
244
235
  const raw = await proxyToolCall("search_project_artifacts", remoteArgs)
245
236
  if (!isObject(raw) || raw.ok === false) return raw
246
- if (!studioSafe && !args.frontend_only) return raw
247
- return filterStudioSafeSearchResult(raw, requestedLimit)
237
+ return filterArtifactSearchResult(raw, requestedLimit)
248
238
  }
249
239
 
250
- async function resolveArtifactInstallation({ slug, source_repo, source_path, source_ref }) {
251
- const resolved = String(source_repo || "").trim()
252
- ? {
253
- source_repo: String(source_repo).trim(),
254
- source_path: String(source_path || "").trim(),
255
- source_ref: String(source_ref || "main").trim() || "main",
256
- }
257
- : await lookupArtifactGithubSource(slug)
258
- return materializeArtifactFromGithub(resolved)
240
+ async function resolveArtifactInstallation({ slug }) {
241
+ return materializeArtifactFromCatalog({ slug: String(slug || "").trim() })
259
242
  }
260
243
 
261
244
  function resolveWorkspaceRoot(inputRoot) {
@@ -362,6 +345,7 @@ function resolveArtifactAssetPath(artifactDir, source) {
362
345
 
363
346
  function defaultTargetForSource(manifest, source, kind) {
364
347
  if (kind === "frontend") return `src/dypai-kits/${manifest.slug}/${stripFrontendPrefix(source)}`
348
+ if (kind === "flow") return `dypai/flows/${source.split("/").pop()}`
365
349
  if (kind === "backend") return `dypai/endpoints/${manifest.slug}/${source.split("/").pop()}`
366
350
  if (kind === "database") return `dypai/kit-installations/${manifest.slug}/${manifest.version}/${source.split("/").pop()}`
367
351
  return `.dypai/kits/${manifest.slug}/${source.split("/").pop()}`
@@ -480,13 +464,11 @@ function writeTemplateFile({ sourceAbs, targetRel, workspaceRoot, manifest, nami
480
464
  return true
481
465
  }
482
466
 
483
- async function inspectProjectArtifact({ slug, version = "1.0.0", source_repo, source_path, source_ref }) {
484
- const { dir, manifest, source, agentNotes, checklist } = await resolveArtifactInstallation({ slug, source_repo, source_path, source_ref })
467
+ async function inspectProjectArtifact({ slug, version = "1.0.0" }) {
468
+ const { dir, manifest, agentNotes, checklist } = await resolveArtifactInstallation({ slug })
485
469
  return {
486
470
  ok: true,
487
471
  operation: "inspect",
488
- source,
489
- artifactRoot: dir,
490
472
  artifact: manifest,
491
473
  assets: assetIndex(dir),
492
474
  agentNotes: agentNotes || (existsSync(join(dir, "agent.md")) ? readFileSync(join(dir, "agent.md"), "utf8") : undefined),
@@ -505,11 +487,8 @@ async function applyProjectArtifact({
505
487
  target = {},
506
488
  overwrite = "skip",
507
489
  workspace_root,
508
- source_repo,
509
- source_path,
510
- source_ref,
511
490
  }) {
512
- const { dir, manifest, source } = await resolveArtifactInstallation({ slug, source_repo, source_path, source_ref })
491
+ const { dir, manifest } = await resolveArtifactInstallation({ slug })
513
492
  const workspaceRoot = resolveWorkspaceRoot(workspace_root)
514
493
  const record = readInstallRecord(workspaceRoot)
515
494
  const previousEntries = (record.artifacts || []).filter((item) => item.slug === manifest.slug && item.version === manifest.version)
@@ -525,11 +504,17 @@ async function applyProjectArtifact({
525
504
 
526
505
  const installFrontend = install.frontend !== false
527
506
  const studioSafe = isStudioArtifactProfile()
528
- const hasBackendAssets = (manifest.backendManifest || []).length > 0
507
+ const hasLegacyBackendAssets = (manifest.backendManifest || []).length > 0
508
+ const hasFlowAssets = (manifest.flowManifest || []).length > 0
529
509
  const hasDatabaseAssets = Boolean(manifest.databaseManifest?.schema || manifest.databaseManifest?.seed)
530
- if (studioSafe && (hasBackendAssets || hasDatabaseAssets)) {
510
+ if (studioSafe && (hasLegacyBackendAssets || hasDatabaseAssets)) {
531
511
  throw new Error(
532
- `Artifact "${manifest.slug}" includes backend/database assets. Studio can only apply frontend/UI artifacts; implement backend pieces as Flow first.`,
512
+ `Artifact "${manifest.slug}" includes legacy YAML backend or database assets. Studio can only apply frontend/UI artifacts until backend is Flow-only.`,
513
+ )
514
+ }
515
+ if (studioSafe && hasFlowAssets && manifest.studio_safe !== true) {
516
+ throw new Error(
517
+ `Artifact "${manifest.slug}" has Flow backend but is not marked studio_safe. Complete Flow migration and set studio_safe in module.contract.json.`,
533
518
  )
534
519
  }
535
520
  if (studioSafe && install.backend === true) {
@@ -545,6 +530,7 @@ async function applyProjectArtifact({
545
530
  throw new Error(`Artifact "${manifest.slug}" has no frontend files to install in Studio.`)
546
531
  }
547
532
  const installBackend = studioSafe ? false : install.backend !== false
533
+ const installFlows = studioSafe ? false : install.flows !== false && install.backend !== false
548
534
  const installDatabase = studioSafe ? false : install.database !== false
549
535
 
550
536
  if (installFrontend) {
@@ -579,6 +565,19 @@ async function applyProjectArtifact({
579
565
  }
580
566
  }
581
567
 
568
+ if (installFlows) {
569
+ for (const asset of manifest.flowManifest || []) {
570
+ const sourceAbs = resolveArtifactAssetPath(dir, asset.source)
571
+ let targetRel = asset.suggestedTarget || defaultTargetForSource(manifest, asset.source, "flow")
572
+ if (target.flowDir) {
573
+ targetRel = join(target.flowDir, basename(asset.source)).split(sep).join("/")
574
+ }
575
+ if (writeTemplateFile({ sourceAbs, targetRel, workspaceRoot, manifest, naming, kind: "frontend", overwrite, record, copied, skipped })) {
576
+ copiedByKind.backend.push(targetRel)
577
+ }
578
+ }
579
+ }
580
+
582
581
  if (installDatabase && isObject(manifest.databaseManifest)) {
583
582
  for (const key of ["schema", "seed"]) {
584
583
  const source = manifest.databaseManifest[key]
@@ -604,9 +603,14 @@ async function applyProjectArtifact({
604
603
  return true
605
604
  })
606
605
 
607
- const backendEndpoints = (manifest.backendManifest || []).map((asset) =>
608
- adaptTarget(asset.endpointName || asset.source.split("/").pop().replace(/\.ya?ml$/, ""), manifest, naming),
609
- )
606
+ const backendEndpoints = [
607
+ ...(manifest.backendManifest || []).map((asset) =>
608
+ adaptTarget(asset.endpointName || asset.source.split("/").pop().replace(/\.ya?ml$/, ""), manifest, naming),
609
+ ),
610
+ ...(manifest.flowManifest || []).map((asset) =>
611
+ adaptTarget(asset.flowName || asset.source.split("/").pop().replace(/\.flow\.ts$/, ""), manifest, naming),
612
+ ),
613
+ ]
610
614
  const databaseTables = normalizeList(manifest.databaseManifest?.tables).map((table) =>
611
615
  adaptTarget(table, manifest, naming),
612
616
  )
@@ -646,7 +650,6 @@ async function applyProjectArtifact({
646
650
  return {
647
651
  ok: true,
648
652
  operation: "apply",
649
- source,
650
653
  slug: manifest.slug,
651
654
  version: manifest.version,
652
655
  workspaceRoot,
@@ -689,7 +692,7 @@ async function applyProjectArtifact({
689
692
 
690
693
  export const manageProjectArtifactTool = {
691
694
  name: "manage_project_artifact",
692
- description: "Inspect or install a project artifact into the workspace. Call search_project_artifacts first, then pass the exact slug; the platform resolves GitHub source server-side. Apply never executes SQL, publishes backend, deploys frontend, or installs npm packages.",
695
+ description: "Inspect or install a project artifact into the workspace. Call search_project_artifacts first, then pass the exact slug. Apply copies files into the workspace; it never executes SQL, publishes backend, deploys frontend, or installs npm packages.",
693
696
  inputSchema: {
694
697
  type: "object",
695
698
  properties: {
@@ -741,11 +744,7 @@ export const searchProjectArtifactsTool = {
741
744
  type: "object",
742
745
  properties: {
743
746
  query: { type: "string", description: "Natural language need, e.g. 'hero section for a florist landing'." },
744
- surface: { type: "string", enum: ["public", "private", "mixed", "agnostic"], description: "Optional surface filter." },
745
- kind: { type: "string", enum: ["shell", "feature"], description: "Optional kind filter. Studio normally uses feature artifacts." },
746
- compatible_shell: { type: "string", description: "Optional shell slug compatibility filter." },
747
- frontend_only: { type: "boolean", default: true, description: "When true, return only frontend/UI artifacts safe to install in Studio." },
748
- limit: { type: "integer", default: 20, minimum: 1, maximum: 50 },
747
+ limit: { type: "integer", default: 3, minimum: 1, maximum: 5 },
749
748
  },
750
749
  required: ["query"],
751
750
  },