@dypai-ai/mcp 1.5.12 → 1.5.13
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/sync/validate.js +217 -3
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1121,7 +1121,7 @@ async function handleRequest(msg) {
|
|
|
1121
1121
|
return makeResponse(id, {
|
|
1122
1122
|
protocolVersion: "2024-11-05",
|
|
1123
1123
|
capabilities: { tools: {} },
|
|
1124
|
-
serverInfo: { name: "dypai", version: "1.5.
|
|
1124
|
+
serverInfo: { name: "dypai", version: "1.5.13" },
|
|
1125
1125
|
instructions: SERVER_INSTRUCTIONS,
|
|
1126
1126
|
})
|
|
1127
1127
|
}
|
|
@@ -397,6 +397,46 @@ function isWordAt(sql, index, word) {
|
|
|
397
397
|
return !/[A-Za-z0-9_]/.test(before) && !/[A-Za-z0-9_]/.test(after)
|
|
398
398
|
}
|
|
399
399
|
|
|
400
|
+
function skipDoubleQuotedIdentifier(sql, index) {
|
|
401
|
+
if (sql[index] !== "\"") return index
|
|
402
|
+
for (let i = index + 1; i < sql.length; i++) {
|
|
403
|
+
if (sql[i] !== "\"") continue
|
|
404
|
+
if (sql[i + 1] === "\"") {
|
|
405
|
+
i++
|
|
406
|
+
continue
|
|
407
|
+
}
|
|
408
|
+
return i
|
|
409
|
+
}
|
|
410
|
+
return sql.length - 1
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function skipSqlSpaces(sql, index) {
|
|
414
|
+
let i = index
|
|
415
|
+
while (i < sql.length && /\s/.test(sql[i])) i++
|
|
416
|
+
return i
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function findMatchingParen(sql, openIndex) {
|
|
420
|
+
if (sql[openIndex] !== "(") return -1
|
|
421
|
+
let depth = 0
|
|
422
|
+
for (let i = openIndex; i < sql.length; i++) {
|
|
423
|
+
const ch = sql[i]
|
|
424
|
+
if (ch === "\"") {
|
|
425
|
+
i = skipDoubleQuotedIdentifier(sql, i)
|
|
426
|
+
continue
|
|
427
|
+
}
|
|
428
|
+
if (ch === "(") {
|
|
429
|
+
depth++
|
|
430
|
+
continue
|
|
431
|
+
}
|
|
432
|
+
if (ch === ")") {
|
|
433
|
+
depth--
|
|
434
|
+
if (depth === 0) return i
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return -1
|
|
438
|
+
}
|
|
439
|
+
|
|
400
440
|
function findOuterSelectList(sqlText) {
|
|
401
441
|
const sql = stripSqlForInference(sqlText)
|
|
402
442
|
if (!sql) return null
|
|
@@ -404,6 +444,10 @@ function findOuterSelectList(sqlText) {
|
|
|
404
444
|
let selectStart = -1
|
|
405
445
|
for (let i = 0; i < sql.length; i++) {
|
|
406
446
|
const ch = sql[i]
|
|
447
|
+
if (ch === "\"") {
|
|
448
|
+
i = skipDoubleQuotedIdentifier(sql, i)
|
|
449
|
+
continue
|
|
450
|
+
}
|
|
407
451
|
if (ch === "(") depth++
|
|
408
452
|
else if (ch === ")") depth = Math.max(0, depth - 1)
|
|
409
453
|
|
|
@@ -417,15 +461,183 @@ function findOuterSelectList(sqlText) {
|
|
|
417
461
|
return sql.slice(selectStart, i).trim()
|
|
418
462
|
}
|
|
419
463
|
}
|
|
464
|
+
if (selectStart >= 0) return sql.slice(selectStart).trim()
|
|
420
465
|
return null
|
|
421
466
|
}
|
|
422
467
|
|
|
468
|
+
function hasTopLevelLimitOne(sqlText) {
|
|
469
|
+
const sql = stripSqlForInference(sqlText)
|
|
470
|
+
let depth = 0
|
|
471
|
+
for (let i = 0; i < sql.length; i++) {
|
|
472
|
+
const ch = sql[i]
|
|
473
|
+
if (ch === "\"") {
|
|
474
|
+
i = skipDoubleQuotedIdentifier(sql, i)
|
|
475
|
+
continue
|
|
476
|
+
}
|
|
477
|
+
if (ch === "(") {
|
|
478
|
+
depth++
|
|
479
|
+
continue
|
|
480
|
+
}
|
|
481
|
+
if (ch === ")") {
|
|
482
|
+
depth = Math.max(0, depth - 1)
|
|
483
|
+
continue
|
|
484
|
+
}
|
|
485
|
+
if (depth !== 0 || !isWordAt(sql, i, "limit")) continue
|
|
486
|
+
|
|
487
|
+
const tail = sql.slice(skipSqlSpaces(sql, i + "limit".length))
|
|
488
|
+
return /^1(?:\D|$)/.test(tail)
|
|
489
|
+
}
|
|
490
|
+
return false
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function hasTopLevelFetchOne(sqlText) {
|
|
494
|
+
const sql = stripSqlForInference(sqlText)
|
|
495
|
+
let depth = 0
|
|
496
|
+
for (let i = 0; i < sql.length; i++) {
|
|
497
|
+
const ch = sql[i]
|
|
498
|
+
if (ch === "\"") {
|
|
499
|
+
i = skipDoubleQuotedIdentifier(sql, i)
|
|
500
|
+
continue
|
|
501
|
+
}
|
|
502
|
+
if (ch === "(") {
|
|
503
|
+
depth++
|
|
504
|
+
continue
|
|
505
|
+
}
|
|
506
|
+
if (ch === ")") {
|
|
507
|
+
depth = Math.max(0, depth - 1)
|
|
508
|
+
continue
|
|
509
|
+
}
|
|
510
|
+
if (depth !== 0 || !isWordAt(sql, i, "fetch")) continue
|
|
511
|
+
|
|
512
|
+
let cursor = skipSqlSpaces(sql, i + "fetch".length)
|
|
513
|
+
if (isWordAt(sql, cursor, "first")) cursor += "first".length
|
|
514
|
+
else if (isWordAt(sql, cursor, "next")) cursor += "next".length
|
|
515
|
+
else continue
|
|
516
|
+
cursor = skipSqlSpaces(sql, cursor)
|
|
517
|
+
return /^1(?:\D|$)/.test(sql.slice(cursor))
|
|
518
|
+
}
|
|
519
|
+
return false
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function hasTopLevelGroupBy(sqlText) {
|
|
523
|
+
const sql = stripSqlForInference(sqlText)
|
|
524
|
+
let depth = 0
|
|
525
|
+
for (let i = 0; i < sql.length; i++) {
|
|
526
|
+
const ch = sql[i]
|
|
527
|
+
if (ch === "\"") {
|
|
528
|
+
i = skipDoubleQuotedIdentifier(sql, i)
|
|
529
|
+
continue
|
|
530
|
+
}
|
|
531
|
+
if (ch === "(") {
|
|
532
|
+
depth++
|
|
533
|
+
continue
|
|
534
|
+
}
|
|
535
|
+
if (ch === ")") {
|
|
536
|
+
depth = Math.max(0, depth - 1)
|
|
537
|
+
continue
|
|
538
|
+
}
|
|
539
|
+
if (depth !== 0 || !isWordAt(sql, i, "group")) continue
|
|
540
|
+
|
|
541
|
+
const restStart = i + "group".length
|
|
542
|
+
const rest = sql.slice(restStart)
|
|
543
|
+
const byOffset = rest.search(/\S/)
|
|
544
|
+
if (byOffset >= 0 && isWordAt(sql, restStart + byOffset, "by")) return true
|
|
545
|
+
}
|
|
546
|
+
return false
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function hasTopLevelSetOperation(sqlText) {
|
|
550
|
+
const sql = stripSqlForInference(sqlText)
|
|
551
|
+
let depth = 0
|
|
552
|
+
for (let i = 0; i < sql.length; i++) {
|
|
553
|
+
const ch = sql[i]
|
|
554
|
+
if (ch === "\"") {
|
|
555
|
+
i = skipDoubleQuotedIdentifier(sql, i)
|
|
556
|
+
continue
|
|
557
|
+
}
|
|
558
|
+
if (ch === "(") {
|
|
559
|
+
depth++
|
|
560
|
+
continue
|
|
561
|
+
}
|
|
562
|
+
if (ch === ")") {
|
|
563
|
+
depth = Math.max(0, depth - 1)
|
|
564
|
+
continue
|
|
565
|
+
}
|
|
566
|
+
if (depth !== 0) continue
|
|
567
|
+
if (isWordAt(sql, i, "union") || isWordAt(sql, i, "intersect") || isWordAt(sql, i, "except")) {
|
|
568
|
+
return true
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
return false
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
const AGGREGATE_FUNCTIONS = ["count", "sum", "avg", "min", "max", "json_agg", "jsonb_agg", "array_agg", "string_agg"]
|
|
575
|
+
|
|
576
|
+
function isWindowAggregateCall(sql, openIndex) {
|
|
577
|
+
const closeIndex = findMatchingParen(sql, openIndex)
|
|
578
|
+
if (closeIndex < 0) return false
|
|
579
|
+
|
|
580
|
+
let cursor = skipSqlSpaces(sql, closeIndex + 1)
|
|
581
|
+
if (isWordAt(sql, cursor, "filter")) {
|
|
582
|
+
cursor = skipSqlSpaces(sql, cursor + "filter".length)
|
|
583
|
+
if (sql[cursor] === "(") {
|
|
584
|
+
const filterClose = findMatchingParen(sql, cursor)
|
|
585
|
+
if (filterClose >= 0) cursor = skipSqlSpaces(sql, filterClose + 1)
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
return isWordAt(sql, cursor, "over")
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function hasOuterAggregateFunction(selectList) {
|
|
593
|
+
const sql = String(selectList || "")
|
|
594
|
+
let depth = 0
|
|
595
|
+
const activeSubqueryDepths = []
|
|
596
|
+
|
|
597
|
+
const insideSubquery = () => activeSubqueryDepths.some(d => d <= depth)
|
|
598
|
+
|
|
599
|
+
for (let i = 0; i < sql.length; i++) {
|
|
600
|
+
const ch = sql[i]
|
|
601
|
+
if (ch === "\"") {
|
|
602
|
+
i = skipDoubleQuotedIdentifier(sql, i)
|
|
603
|
+
continue
|
|
604
|
+
}
|
|
605
|
+
if (ch === "(") {
|
|
606
|
+
depth++
|
|
607
|
+
continue
|
|
608
|
+
}
|
|
609
|
+
if (ch === ")") {
|
|
610
|
+
activeSubqueryDepths.splice(0, activeSubqueryDepths.length, ...activeSubqueryDepths.filter(d => d < depth))
|
|
611
|
+
depth = Math.max(0, depth - 1)
|
|
612
|
+
continue
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (depth > 0 && isWordAt(sql, i, "select")) {
|
|
616
|
+
activeSubqueryDepths.push(depth)
|
|
617
|
+
i += "select".length - 1
|
|
618
|
+
continue
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
for (const fn of AGGREGATE_FUNCTIONS) {
|
|
622
|
+
if (!isWordAt(sql, i, fn)) continue
|
|
623
|
+
const openIndex = skipSqlSpaces(sql, i + fn.length)
|
|
624
|
+
if (sql[openIndex] === "(" && !insideSubquery() && !isWindowAggregateCall(sql, openIndex)) return true
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
return false
|
|
629
|
+
}
|
|
630
|
+
|
|
423
631
|
function splitTopLevelComma(list) {
|
|
424
632
|
const parts = []
|
|
425
633
|
let depth = 0
|
|
426
634
|
let start = 0
|
|
427
635
|
for (let i = 0; i < list.length; i++) {
|
|
428
636
|
const ch = list[i]
|
|
637
|
+
if (ch === "\"") {
|
|
638
|
+
i = skipDoubleQuotedIdentifier(list, i)
|
|
639
|
+
continue
|
|
640
|
+
}
|
|
429
641
|
if (ch === "(") depth++
|
|
430
642
|
else if (ch === ")") depth = Math.max(0, depth - 1)
|
|
431
643
|
else if (ch === "," && depth === 0) {
|
|
@@ -478,10 +690,12 @@ function inferSelectOutputColumns(sqlText) {
|
|
|
478
690
|
function inferSqlCardinality(sqlText) {
|
|
479
691
|
const sql = stripSqlForInference(sqlText)
|
|
480
692
|
if (!sql) return null
|
|
481
|
-
if (
|
|
693
|
+
if (hasTopLevelLimitOne(sql) || hasTopLevelFetchOne(sql)) return "single"
|
|
482
694
|
const startsLikeRead = /^(select|with)\b/i.test(sql)
|
|
483
|
-
|
|
484
|
-
const
|
|
695
|
+
if (startsLikeRead && hasTopLevelSetOperation(sql)) return "many"
|
|
696
|
+
const outerSelectList = findOuterSelectList(sql)
|
|
697
|
+
const hasAggregate = outerSelectList ? hasOuterAggregateFunction(outerSelectList) : false
|
|
698
|
+
const hasGroupBy = hasTopLevelGroupBy(sql)
|
|
485
699
|
if (startsLikeRead && hasAggregate && !hasGroupBy) return "single"
|
|
486
700
|
return "many"
|
|
487
701
|
}
|