@dypai-ai/mcp 1.5.16 → 1.5.17

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.5.16",
3
+ "version": "1.5.17",
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",
@@ -316,6 +316,53 @@ async function readSchemaColumns(rootDir) {
316
316
  }
317
317
 
318
318
  /** Extract referenced table names from a SQL string: `FROM public.X`, `JOIN public.X`, `INTO public.X`, `UPDATE public.X`. */
319
+ function skipSqlBalancedParens(sql, start) {
320
+ let depth = 0
321
+ for (let i = start; i < sql.length; i++) {
322
+ const ch = sql[i]
323
+ if (ch === "\"") {
324
+ i++
325
+ while (i < sql.length && sql[i] !== "\"") i++
326
+ continue
327
+ }
328
+ if (ch === "(") depth++
329
+ else if (ch === ")") {
330
+ depth--
331
+ if (depth === 0) return i + 1
332
+ }
333
+ }
334
+ return sql.length
335
+ }
336
+
337
+ function extractSqlCteNames(cleanSql) {
338
+ const names = new Set()
339
+ const withMatch = /^\s*WITH\s+(?:RECURSIVE\s+)?/i.exec(cleanSql)
340
+ if (!withMatch) return names
341
+ let i = withMatch[0].length
342
+ while (i < cleanSql.length) {
343
+ while (/\s|,/.test(cleanSql[i] || "")) i++
344
+ const nameMatch = /^"?([A-Za-z_][A-Za-z0-9_]*)"?/.exec(cleanSql.slice(i))
345
+ if (!nameMatch) break
346
+ const cteName = nameMatch[1]
347
+ if (SQL_KEYWORDS_AFTER_FROM.has(cteName.toUpperCase())) break
348
+ i += nameMatch[0].length
349
+ while (/\s/.test(cleanSql[i] || "")) i++
350
+ if (cleanSql[i] === "(") {
351
+ i = skipSqlBalancedParens(cleanSql, i)
352
+ while (/\s/.test(cleanSql[i] || "")) i++
353
+ }
354
+ if (!/^AS\b/i.test(cleanSql.slice(i))) break
355
+ i += 2
356
+ while (/\s/.test(cleanSql[i] || "")) i++
357
+ if (cleanSql[i] !== "(") break
358
+ names.add(cteName)
359
+ i = skipSqlBalancedParens(cleanSql, i)
360
+ while (/\s/.test(cleanSql[i] || "")) i++
361
+ if (cleanSql[i] !== ",") break
362
+ }
363
+ return names
364
+ }
365
+
319
366
  function extractSqlTables(sql) {
320
367
  const tables = new Set()
321
368
  if (typeof sql !== "string" || sql.length === 0) return tables
@@ -325,6 +372,7 @@ function extractSqlTables(sql) {
325
372
  .replace(/--[^\n]*/g, " ")
326
373
  .replace(/\/\*[\s\S]*?\*\//g, " ")
327
374
  .replace(/'(?:[^']|'')*'/g, "''")
375
+ const cteNames = extractSqlCteNames(clean)
328
376
  // Single regex that captures BOTH a possible schema and the table name,
329
377
  // optionally preceded by `ONLY` (Postgres inheritance modifier). Splitting
330
378
  // schema/table into separate groups makes filtering by schema trivial.
@@ -339,6 +387,7 @@ function extractSqlTables(sql) {
339
387
  // when the regex was greedy enough (defensive — the optional `ONLY` above
340
388
  // already handles the common case).
341
389
  if (SQL_KEYWORDS_AFTER_FROM.has(tableName.toUpperCase())) continue
390
+ if (cteNames.has(tableName)) continue
342
391
  // Only validate tables in the user-managed `public` schema. System
343
392
  // schemas (auth, system, ext, pg_catalog, information_schema) are
344
393
  // managed by the engine and not present in dypai/schema.sql.