@dypai-ai/mcp 1.0.6 → 1.0.8

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.0.6",
3
+ "version": "1.0.8",
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",
@@ -17,8 +17,7 @@
17
17
  "deploy",
18
18
  "backend",
19
19
  "database",
20
- "api",
21
- "cloudflare-pages"
20
+ "api"
22
21
  ],
23
22
  "license": "MIT",
24
23
  "repository": {
package/src/index.js CHANGED
@@ -65,7 +65,8 @@ const FALLBACK_REMOTE_TOOLS = [
65
65
  { name: "get_auth_users", description: "List users.", inputSchema: { type: "object", properties: { project_id: { type: "string" } }, required: [] } },
66
66
  { name: "list_buckets", description: "List storage buckets.", inputSchema: { type: "object", properties: { project_id: { type: "string" } }, required: [] } },
67
67
  { name: "search_docs", description: "Search documentation.", inputSchema: { type: "object", properties: { query: { type: "string" } }, required: ["query"] } },
68
- { name: "search_templates", description: "Search workflow templates.", inputSchema: { type: "object", properties: { query: { type: "string" } }, required: ["query"] } },
68
+ { name: "search_workflow_templates", description: "Search workflow templates.", inputSchema: { type: "object", properties: { query: { type: "string" } }, required: ["query"] } },
69
+ { name: "search_project_templates", description: "Search project starter templates.", inputSchema: { type: "object", properties: { query: { type: "string" } }, required: ["query"] } },
69
70
  { name: "search_nodes", description: "Search workflow nodes.", inputSchema: { type: "object", properties: { query: { type: "string" } }, required: ["query"] } },
70
71
  ]
71
72
 
@@ -74,7 +75,7 @@ const REMOTE_TOOLS = [
74
75
  // ── Project ───────────────────────────────────────────────────────────────
75
76
  { 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: [] } },
76
77
  { 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"] } },
77
- { name: "create_project", description: "Create a new DYPAI project (free plan). Creates a full project with database, API engine, GitHub repo, and frontend hosting. Provisioning takes ~1 minute.", 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: "Optional. Start from a template (e.g. 'clinic', 'gym'). Use search_templates to browse." } }, required: ["name"] } },
78
+ { name: "create_project", description: "Create a new DYPAI project (free plan). Creates a full project with database, API engine, GitHub repo, and frontend hosting. Provisioning takes ~1 minute.", 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: "Optional. Start from a project template (e.g. 'clinic', 'gym', 'blank'). Use search_project_templates to browse." } }, required: ["name"] } },
78
79
  { 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: [] } },
79
80
 
80
81
  // ── Database ──────────────────────────────────────────────────────────────
@@ -105,7 +106,8 @@ const REMOTE_TOOLS = [
105
106
 
106
107
  // ── Knowledge ─────────────────────────────────────────────────────────────
107
108
  { name: "search_docs", description: "Search DYPAI documentation. Use this when unsure about SDK usage, auth patterns, workflow nodes, or platform features. Returns relevant documentation chunks.", inputSchema: { type: "object", properties: { query: { type: "string", description: "What you want to learn about" } }, required: ["query"] } },
108
- { name: "search_templates", description: "Search workflow templates by description. Returns ready-to-use workflow code for common patterns: CRUD operations, payment gateways, email sending, AI chatbots, data pipelines, etc.", inputSchema: { type: "object", properties: { query: { type: "string", description: "What the workflow should do (e.g. 'send email', 'stripe payment')" }, category: { type: "string", description: "Optional: AI, Database, Payments, Communication, Logic, Storage" } }, required: ["query"] } },
109
+ { name: "search_workflow_templates", description: "Search workflow templates by description. Returns ready-to-use workflow code for common patterns: CRUD operations, payment gateways, email sending, AI chatbots, data pipelines, etc.", inputSchema: { type: "object", properties: { query: { type: "string", description: "What the workflow should do (e.g. 'send email', 'stripe payment')" }, category: { type: "string", description: "Optional: AI, Database, Payments, Communication, Logic, Storage" } }, required: ["query"] } },
110
+ { name: "search_project_templates", description: "Search project starter templates by description. Returns template metadata and slugs for starters like clinic, gym, waitlist, blank, auth, or landing.", inputSchema: { type: "object", properties: { query: { type: "string", description: "What kind of project starter you need (e.g. 'gym app', 'landing page', 'auth starter')" }, category: { type: "string", description: "Optional category filter" } }, required: ["query"] } },
109
111
  ]
110
112
 
111
113
  // ── Server Instructions ──────────────────────────────────────────────────────
@@ -122,9 +124,10 @@ const SERVER_INSTRUCTIONS = `You are building full-stack applications on the DYP
122
124
  1. Create tables with execute_sql (check get_app_tables first to avoid duplicates)
123
125
  2. Create endpoints with create_endpoint using workflow_code
124
126
  3. Test with test_workflow immediately after creating/updating
125
- 4. Use search_templates to find ready-made workflow patterns
126
- 5. Use search_nodes to discover available node types
127
- 6. Use search_docs when unsure about patterns
127
+ 4. Use search_workflow_templates to find ready-made workflow patterns
128
+ 5. Use search_project_templates when the user wants a starter app or template-based project
129
+ 6. Use search_nodes to discover available node types
130
+ 7. Use search_docs when unsure about patterns
128
131
 
129
132
  ## Build Frontend
130
133
  - SDK client is already configured at src/lib/dypai.ts — just import it:
@@ -138,10 +141,13 @@ const SERVER_INSTRUCTIONS = `You are building full-stack applications on the DYP
138
141
 
139
142
  ## Deploy Frontend
140
143
  - Use deploy_frontend tool with the project's source directory
141
- - Cloudflare Pages builds automatically (Vite, Next.js, Astro, etc.)
142
- - Site goes live at https://{slug}.dypai.app within ~1 minute
143
- - Check build progress with get_build_status
144
- - View build logs with get_deployment_logs if something fails
144
+ - The tool pushes source, then waits for the build to finish (~1-2 min)
145
+ - If the build succeeds, the response includes the live URL
146
+ - If the build FAILS, the response includes the error logs — read them carefully, fix the code, and redeploy
147
+ - Common build failures: missing dependencies (add to package.json), TypeScript errors, import path issues
148
+ - Supports: Vite, React, Next.js, Astro, SvelteKit, Nuxt, Remix, Angular, Vinext, CRA, and more
149
+ - Framework is auto-detected from package.json — correct build config is set automatically
150
+ - If build times out (>2 min), use get_build_status / get_deployment_logs to check manually
145
151
 
146
152
  ## Import Data
147
153
  - Use bulk_upsert to import CSV or JSON files into database tables
@@ -11,14 +11,51 @@ import { join, relative } from "path"
11
11
  import { api } from "../api.js"
12
12
 
13
13
  const MAX_SOURCE_SIZE = 50 * 1024 * 1024
14
- const IGNORE_DIRS = new Set(["node_modules", "dist", "build", "out", ".git", ".next", ".cache", ".turbo", ".vercel", ".output", "coverage"])
15
- const SOURCE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".js", ".cjs", ".css", ".scss", ".less", ".html", ".json", ".toml", ".yaml", ".yml", ".md", ".mdx", ".txt", ".svg", ".ico", ".webmanifest"])
14
+
15
+ // Directories to skip build outputs, caches, package managers
16
+ const IGNORE_DIRS = new Set([
17
+ "node_modules", ".git",
18
+ // Build outputs
19
+ "dist", "build", "out", ".output", ".vercel", ".netlify",
20
+ // Framework caches
21
+ ".next", ".nuxt", ".svelte-kit", ".astro", ".angular", ".docusaurus",
22
+ ".cache", ".turbo", ".vite", ".parcel-cache", ".wrangler",
23
+ // Test / misc
24
+ "coverage", "storybook-static", "__pycache__", ".idea", ".vscode",
25
+ ])
26
+
27
+ // Source file extensions to include
28
+ const SOURCE_EXTS = new Set([
29
+ // JavaScript / TypeScript
30
+ ".ts", ".tsx", ".js", ".jsx", ".mjs", ".mts", ".cjs", ".cts",
31
+ // Styles
32
+ ".css", ".scss", ".less", ".sass", ".styl",
33
+ // Markup / templates
34
+ ".html", ".htm",
35
+ // Framework-specific SFCs
36
+ ".vue", ".svelte", ".astro",
37
+ // Data / config
38
+ ".json", ".toml", ".yaml", ".yml",
39
+ // Content
40
+ ".md", ".mdx", ".txt",
41
+ // Assets metadata
42
+ ".svg", ".ico", ".webmanifest",
43
+ // Schema / query
44
+ ".graphql", ".gql", ".prisma",
45
+ // Misc
46
+ ".xml", ".csv",
47
+ ])
48
+
16
49
  const IMAGE_EXTS = new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".avif", ".ico", ".svg"])
17
50
  const BLOCKED = new Set([".env", ".env.local", ".env.production", ".env.development", ".DS_Store", "Thumbs.db"])
18
51
  const MAX_IMAGE = 2 * 1024 * 1024
19
52
 
53
+ // Root-level config files to always include (regex)
54
+ const ROOT_CONFIG_RE = /^(package\.json|package-lock\.json|pnpm-lock\.yaml|bun\.lockb|yarn\.lock|vite\.config|vitest\.config|tsconfig|tailwind\.config|postcss\.config|next\.config|vinext\.config|astro\.config|nuxt\.config|svelte\.config|remix\.config|gatsby-config|angular\.json|docusaurus\.config|wrangler\.toml|wrangler\.json|vercel\.json|netlify\.toml|turbo\.json|components\.json|uno\.config|eslint\.config|prettier\.config|jest\.config|playwright\.config|\.prettierrc|\.eslintrc|\.browserslistrc|\.nvmrc|\.node-version|index\.html)/
55
+
20
56
  function collectSource(dir) {
21
57
  const files = []
58
+ const skipped = []
22
59
  let total = 0
23
60
 
24
61
  function walk(d, rel) {
@@ -35,36 +72,60 @@ function collectSource(dir) {
35
72
  if (!stat.isFile()) continue
36
73
  const ext = entry.includes(".") ? `.${entry.split(".").pop().toLowerCase()}` : ""
37
74
  let ok = SOURCE_EXTS.has(ext)
38
- if (!rel && /^(package\.json|package-lock\.json|bun\.lockb|vite\.config|tsconfig|tailwind\.config|postcss\.config|next\.config|astro\.config|nuxt\.config)/.test(entry)) ok = true
75
+ if (!rel && ROOT_CONFIG_RE.test(entry)) ok = true
39
76
  if (IMAGE_EXTS.has(ext) && stat.size <= MAX_IMAGE) ok = true
40
- if (ok) {
41
- const content = readFileSync(full)
42
- if (total + content.length > MAX_SOURCE_SIZE) return
43
- total += content.length
44
- files.push({ path, content: content.toString("base64") })
77
+
78
+ // Track skipped files so the user knows what was excluded
79
+ if (!ok) {
80
+ if (IMAGE_EXTS.has(ext) && stat.size > MAX_IMAGE) {
81
+ skipped.push({ path, reason: `Image too large (${(stat.size / (1024 * 1024)).toFixed(1)}MB, max ${MAX_IMAGE / (1024 * 1024)}MB)` })
82
+ } else if (ext && ![".lock", ".log"].includes(ext)) {
83
+ skipped.push({ path, reason: `Unsupported file type (${ext})` })
84
+ }
85
+ continue
86
+ }
87
+
88
+ const content = readFileSync(full)
89
+ if (total + content.length > MAX_SOURCE_SIZE) {
90
+ skipped.push({ path, reason: "Total upload size limit reached" })
91
+ return
45
92
  }
93
+ total += content.length
94
+ files.push({ path, content: content.toString("base64") })
46
95
  } catch {}
47
96
  }
48
97
  }
49
98
 
50
99
  walk(dir, "")
51
- return { files, total }
100
+ return { files, total, skipped }
52
101
  }
53
102
 
103
+ /**
104
+ * Detect framework for display AND for API build config.
105
+ * Returns { label, id } where id matches pages_service.py FRAMEWORK_PRESETS.
106
+ */
54
107
  function detectFramework(dir) {
55
108
  const pkgPath = join(dir, "package.json")
56
109
  if (!existsSync(pkgPath)) return null
57
110
  try {
58
111
  const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"))
59
112
  const deps = { ...pkg.dependencies, ...pkg.devDependencies }
60
- if (deps["next"]) return "Next.js"
61
- if (deps["astro"]) return "Astro"
62
- if (deps["nuxt"]) return "Nuxt"
63
- if (deps["@sveltejs/kit"]) return "SvelteKit"
64
- if (deps["vite"]) return "Vite"
65
- if (deps["react-scripts"]) return "Create React App"
66
- if (deps["react"]) return "React"
67
- return "Node.js"
113
+ if (deps["vinext"]) return { label: "Vinext", id: "vinext" }
114
+ if (deps["next"]) return { label: "Next.js", id: "next" }
115
+ if (deps["@sveltejs/kit"]) return { label: "SvelteKit", id: "sveltekit" }
116
+ if (deps["nuxt"]) return { label: "Nuxt", id: "nuxt" }
117
+ if (deps["@remix-run/react"])return { label: "Remix", id: "remix" }
118
+ if (deps["astro"]) return { label: "Astro", id: "astro" }
119
+ if (deps["gatsby"]) return { label: "Gatsby", id: "gatsby" }
120
+ if (deps["@angular/core"]) return { label: "Angular", id: "angular" }
121
+ if (deps["@docusaurus/core"])return { label: "Docusaurus", id: "docusaurus" }
122
+ if (deps["preact"]) return { label: "Preact", id: "preact" }
123
+ if (deps["solid-js"]) return { label: "Solid", id: "solid" }
124
+ if (deps["react-scripts"]) return { label: "Create React App",id: "cra" }
125
+ if (deps["vite"]) return { label: "Vite", id: "vite" }
126
+ if (deps["vue"]) return { label: "Vue", id: "vue" }
127
+ if (deps["react"]) return { label: "React", id: "react" }
128
+ return { label: "Node.js", id: "vite" }
68
129
  } catch { return null }
69
130
  }
70
131
 
@@ -72,13 +133,13 @@ export const deployTool = {
72
133
  name: "deploy_frontend",
73
134
  description: `Deploy frontend source code from a local directory.
74
135
 
75
- Reads all source files from the specified directory, uploads them to DYPAI,
76
- and Cloudflare Pages automatically builds and deploys the project.
77
-
78
- Supports: React, Vite, Next.js, Astro, SvelteKit, Nuxt, CRA, and more.
136
+ Reads all source files from the specified directory and uploads them to DYPAI.
79
137
  The build runs in the cloud — no local Node.js or npm required.
80
138
 
81
- After deploying, the site is live at https://{slug}.dypai.app within ~1 minute.`,
139
+ Supports: React, Vite, Next.js, Astro, SvelteKit, Nuxt, Vue, CRA, and more.
140
+ DYPAI auto-detects the framework and routes to the optimal backend.
141
+
142
+ After deploying, the site is live at https://{slug}.dypai.app within ~30s.`,
82
143
 
83
144
  inputSchema: {
84
145
  type: "object",
@@ -105,7 +166,7 @@ After deploying, the site is live at https://{slug}.dypai.app within ~1 minute.`
105
166
  }
106
167
 
107
168
  const framework = detectFramework(sourceDirectory)
108
- const { files, total } = collectSource(sourceDirectory)
169
+ const { files, total, skipped } = collectSource(sourceDirectory)
109
170
 
110
171
  if (!files.length) {
111
172
  return { error: "No source files found." }
@@ -114,17 +175,90 @@ After deploying, the site is live at https://{slug}.dypai.app within ~1 minute.`
114
175
  try {
115
176
  const result = await api.post(
116
177
  `/api/engine/${project_id}/frontend/deploy/source`,
117
- { files }
178
+ { files, framework: framework?.id ?? null }
118
179
  )
119
180
 
181
+ const label = framework?.label ?? "Project"
182
+
183
+ // ── Wait for CF Pages build (poll up to ~2 min) ──────────────────────
184
+ let buildResult = null
185
+ const maxAttempts = 12 // 12 × 10s = 2 min max
186
+ const pollInterval = 10_000 // 10 seconds
187
+
188
+ for (let i = 0; i < maxAttempts; i++) {
189
+ await new Promise(r => setTimeout(r, pollInterval))
190
+ try {
191
+ const status = await api.get(`/api/engine/${project_id}/frontend/build-status`)
192
+ const s = status?.status
193
+ if (s === "success") {
194
+ buildResult = { status: "success", url: result.url, duration: status.duration_seconds }
195
+ break
196
+ }
197
+ if (s === "failure") {
198
+ // Fetch build logs for error context
199
+ let logs = status.build_error || null
200
+ if (!logs) {
201
+ try {
202
+ const deployments = await api.get(`/api/engine/${project_id}/frontend/deployments?limit=1`)
203
+ const depId = deployments?.deployments?.[0]?.id
204
+ if (depId) {
205
+ const logRes = await api.get(`/api/engine/${project_id}/frontend/deployments/${depId}/logs`)
206
+ if (logRes?.logs) {
207
+ const lines = logRes.logs.split("\n")
208
+ logs = lines.slice(-30).join("\n")
209
+ }
210
+ }
211
+ } catch {}
212
+ }
213
+ buildResult = { status: "failure", error: logs || "Build failed — check logs with get_deployment_logs" }
214
+ break
215
+ }
216
+ // still queued/building — keep polling
217
+ } catch {
218
+ // build-status not available yet, keep polling
219
+ }
220
+ }
221
+
222
+ if (!buildResult) {
223
+ // Timed out — build still in progress
224
+ return {
225
+ success: true,
226
+ url: result.url,
227
+ files_pushed: files.length,
228
+ size_bytes: total,
229
+ framework: label,
230
+ build_status: "building",
231
+ message: `Deployed ${files.length} files (${Math.round(total / 1024)} KB). ${label} build still in progress at ${result.url} — use get_build_status to check progress.`,
232
+ }
233
+ }
234
+
235
+ if (buildResult.status === "failure") {
236
+ return {
237
+ success: false,
238
+ url: result.url,
239
+ files_pushed: files.length,
240
+ framework: label,
241
+ build_status: "failure",
242
+ build_error: buildResult.error,
243
+ message: `Deploy pushed ${files.length} files but the ${label} build FAILED. Build error:\n${buildResult.error}`,
244
+ }
245
+ }
246
+
247
+ const skippedMsg = skipped.length > 0
248
+ ? `\n\nSkipped ${skipped.length} file(s):\n${skipped.map(s => ` - ${s.path}: ${s.reason}`).join("\n")}`
249
+ : ""
250
+
120
251
  return {
121
252
  success: true,
122
253
  url: result.url,
123
254
  files_pushed: files.length,
255
+ files_skipped: skipped.length,
256
+ skipped_files: skipped.length > 0 ? skipped : undefined,
124
257
  size_bytes: total,
125
- framework: framework,
126
- build: "cloudflare_pages",
127
- message: `Deployed ${files.length} files (${Math.round(total / 1024)} KB). ${framework || "Project"} building in the cloud. Live in ~1 minute at ${result.url}`,
258
+ framework: label,
259
+ build_status: "success",
260
+ build_duration: buildResult.duration,
261
+ message: `Deployed ${files.length} files (${Math.round(total / 1024)} KB). ${label} build succeeded${buildResult.duration ? ` in ${buildResult.duration}s` : ""}. Live at ${result.url}${skippedMsg}`,
128
262
  }
129
263
  } catch (e) {
130
264
  return { error: `Deploy failed: ${e.message}` }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * GitHub repo link/unlink tools.
3
+ */
4
+
5
+ import { api } from "../api.js"
6
+
7
+ export const linkRepoTool = {
8
+ name: "link_repo",
9
+ description: `Link an existing GitHub repo to the project.
10
+
11
+ After linking, pushes to that repo auto-deploy to Cloudflare Pages.
12
+ The MCP deploy tool will also push to this repo.
13
+ Requires the user to have GitHub connected (DYPAI App installed on their account).`,
14
+
15
+ inputSchema: {
16
+ type: "object",
17
+ properties: {
18
+ project_id: { type: "string", description: "Project UUID." },
19
+ repo_url: { type: "string", description: "GitHub repo URL (e.g. https://github.com/owner/repo)" },
20
+ branch: { type: "string", description: "Branch to deploy from (default: main)" },
21
+ },
22
+ required: ["project_id", "repo_url"],
23
+ },
24
+
25
+ async execute({ project_id, repo_url, branch }) {
26
+ try {
27
+ return await api.post(`/api/engine/${project_id}/github/link`, {
28
+ repo_url,
29
+ branch: branch || "main",
30
+ })
31
+ } catch (e) {
32
+ return { error: e.message }
33
+ }
34
+ },
35
+ }
36
+
37
+ export const unlinkRepoTool = {
38
+ name: "unlink_repo",
39
+ description: `Unlink the user's GitHub repo and revert to a DYPAI-managed repo.
40
+ The user's repo is NOT deleted — only the connection is removed.`,
41
+
42
+ inputSchema: {
43
+ type: "object",
44
+ properties: {
45
+ project_id: { type: "string", description: "Project UUID." },
46
+ },
47
+ required: ["project_id"],
48
+ },
49
+
50
+ async execute({ project_id }) {
51
+ try {
52
+ return await api.post(`/api/engine/${project_id}/github/unlink`, {})
53
+ } catch (e) {
54
+ return { error: e.message }
55
+ }
56
+ },
57
+ }
@@ -19,7 +19,7 @@ Scaffolds a project directory with:
19
19
  - MCP config for your IDE
20
20
  - .env with engine URL
21
21
 
22
- Use search_templates first to find available templates, then pass the template slug here.
22
+ Use search_project_templates first to find available templates, then pass the template slug here.
23
23
  Or use "blank" for an empty starter project.`,
24
24
 
25
25
  inputSchema: {
@@ -35,7 +35,7 @@ Or use "blank" for an empty starter project.`,
35
35
  },
36
36
  template: {
37
37
  type: "string",
38
- description: 'Template slug (e.g. "clinic", "gym", "blank"). Use search_templates to find available templates.',
38
+ description: 'Template slug (e.g. "clinic", "gym", "blank"). Use search_project_templates to find available templates.',
39
39
  default: "blank",
40
40
  },
41
41
  },