@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 +1 -1
- package/src/index.js +1 -1
- package/src/tools/project-artifacts.js +86 -87
package/package.json
CHANGED
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.
|
|
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
|
|
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) :
|
|
43
|
-
const artifacts = normalizeArtifactList(raw.artifacts)
|
|
44
|
-
|
|
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
|
-
|
|
47
|
-
|
|
62
|
+
ok: raw.ok !== false,
|
|
63
|
+
query: raw.query,
|
|
48
64
|
results_count: artifacts.length,
|
|
49
65
|
artifacts,
|
|
50
|
-
|
|
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
|
|
164
|
-
const
|
|
165
|
-
if (!
|
|
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(`${
|
|
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: "
|
|
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
|
|
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(
|
|
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
|
-
|
|
247
|
-
return filterStudioSafeSearchResult(raw, requestedLimit)
|
|
237
|
+
return filterArtifactSearchResult(raw, requestedLimit)
|
|
248
238
|
}
|
|
249
239
|
|
|
250
|
-
async function resolveArtifactInstallation({ slug
|
|
251
|
-
|
|
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"
|
|
484
|
-
const { dir, manifest,
|
|
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
|
|
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
|
|
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 && (
|
|
510
|
+
if (studioSafe && (hasLegacyBackendAssets || hasDatabaseAssets)) {
|
|
531
511
|
throw new Error(
|
|
532
|
-
`Artifact "${manifest.slug}" includes backend
|
|
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 =
|
|
608
|
-
|
|
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
|
|
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
|
-
|
|
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
|
},
|