@indiekitai/pg-complete 0.1.0

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/dist/mcp.js ADDED
@@ -0,0 +1,1303 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // src/completer.ts
5
+ var import_pg = require("pg");
6
+
7
+ // src/keywords.ts
8
+ var KEYWORDS = [
9
+ // DML
10
+ "SELECT",
11
+ "FROM",
12
+ "WHERE",
13
+ "AND",
14
+ "OR",
15
+ "NOT",
16
+ "IN",
17
+ "EXISTS",
18
+ "BETWEEN",
19
+ "LIKE",
20
+ "ILIKE",
21
+ "IS",
22
+ "NULL",
23
+ "TRUE",
24
+ "FALSE",
25
+ "AS",
26
+ "ON",
27
+ "USING",
28
+ "JOIN",
29
+ "INNER",
30
+ "LEFT",
31
+ "RIGHT",
32
+ "FULL",
33
+ "OUTER",
34
+ "CROSS",
35
+ "NATURAL",
36
+ "ORDER",
37
+ "BY",
38
+ "ASC",
39
+ "DESC",
40
+ "NULLS",
41
+ "FIRST",
42
+ "LAST",
43
+ "GROUP",
44
+ "HAVING",
45
+ "LIMIT",
46
+ "OFFSET",
47
+ "UNION",
48
+ "ALL",
49
+ "INTERSECT",
50
+ "EXCEPT",
51
+ "DISTINCT",
52
+ "INSERT",
53
+ "INTO",
54
+ "VALUES",
55
+ "DEFAULT",
56
+ "RETURNING",
57
+ "UPDATE",
58
+ "SET",
59
+ "DELETE",
60
+ "WITH",
61
+ "RECURSIVE",
62
+ "CASE",
63
+ "WHEN",
64
+ "THEN",
65
+ "ELSE",
66
+ "END",
67
+ "CAST",
68
+ "COALESCE",
69
+ "NULLIF",
70
+ "GREATEST",
71
+ "LEAST",
72
+ // DDL
73
+ "CREATE",
74
+ "ALTER",
75
+ "DROP",
76
+ "TRUNCATE",
77
+ "TABLE",
78
+ "VIEW",
79
+ "INDEX",
80
+ "SEQUENCE",
81
+ "SCHEMA",
82
+ "DATABASE",
83
+ "FUNCTION",
84
+ "PROCEDURE",
85
+ "TRIGGER",
86
+ "TYPE",
87
+ "EXTENSION",
88
+ "MATERIALIZED",
89
+ "TEMPORARY",
90
+ "TEMP",
91
+ "UNLOGGED",
92
+ "IF",
93
+ "CASCADE",
94
+ "RESTRICT",
95
+ "ADD",
96
+ "COLUMN",
97
+ "RENAME",
98
+ "TO",
99
+ "PRIMARY",
100
+ "KEY",
101
+ "FOREIGN",
102
+ "REFERENCES",
103
+ "UNIQUE",
104
+ "CHECK",
105
+ "CONSTRAINT",
106
+ "NOT NULL",
107
+ // Data types
108
+ "INTEGER",
109
+ "INT",
110
+ "BIGINT",
111
+ "SMALLINT",
112
+ "SERIAL",
113
+ "BIGSERIAL",
114
+ "REAL",
115
+ "DOUBLE",
116
+ "PRECISION",
117
+ "NUMERIC",
118
+ "DECIMAL",
119
+ "TEXT",
120
+ "VARCHAR",
121
+ "CHAR",
122
+ "CHARACTER",
123
+ "VARYING",
124
+ "BOOLEAN",
125
+ "BOOL",
126
+ "DATE",
127
+ "TIME",
128
+ "TIMESTAMP",
129
+ "TIMESTAMPTZ",
130
+ "INTERVAL",
131
+ "JSON",
132
+ "JSONB",
133
+ "UUID",
134
+ "BYTEA",
135
+ "ARRAY",
136
+ // Transaction
137
+ "BEGIN",
138
+ "COMMIT",
139
+ "ROLLBACK",
140
+ "SAVEPOINT",
141
+ // Other
142
+ "GRANT",
143
+ "REVOKE",
144
+ "EXPLAIN",
145
+ "ANALYZE",
146
+ "VERBOSE",
147
+ "COPY",
148
+ "VACUUM",
149
+ "REINDEX",
150
+ "CLUSTER",
151
+ "LATERAL",
152
+ "WINDOW",
153
+ "OVER",
154
+ "PARTITION",
155
+ "ROWS",
156
+ "RANGE",
157
+ "FETCH",
158
+ "NEXT",
159
+ "PRIOR",
160
+ "ABSOLUTE",
161
+ "RELATIVE",
162
+ "FOR",
163
+ "SHARE",
164
+ "NOWAIT",
165
+ "SKIP",
166
+ "LOCKED",
167
+ "ANY",
168
+ "SOME"
169
+ ];
170
+ var KEYWORD_TREE = {
171
+ "SELECT": ["DISTINCT", "ALL", "*"],
172
+ "FROM": [],
173
+ "WHERE": [],
174
+ "ORDER": ["BY"],
175
+ "GROUP": ["BY"],
176
+ "INSERT": ["INTO"],
177
+ "UPDATE": ["SET"],
178
+ "DELETE": ["FROM"],
179
+ "CREATE": [
180
+ "TABLE",
181
+ "VIEW",
182
+ "INDEX",
183
+ "SCHEMA",
184
+ "DATABASE",
185
+ "FUNCTION",
186
+ "PROCEDURE",
187
+ "TRIGGER",
188
+ "TYPE",
189
+ "EXTENSION",
190
+ "MATERIALIZED",
191
+ "TEMPORARY",
192
+ "TEMP",
193
+ "UNLOGGED",
194
+ "UNIQUE",
195
+ "OR"
196
+ ],
197
+ "ALTER": [
198
+ "TABLE",
199
+ "VIEW",
200
+ "INDEX",
201
+ "SCHEMA",
202
+ "DATABASE",
203
+ "FUNCTION",
204
+ "PROCEDURE",
205
+ "TRIGGER",
206
+ "TYPE",
207
+ "EXTENSION",
208
+ "SEQUENCE"
209
+ ],
210
+ "DROP": [
211
+ "TABLE",
212
+ "VIEW",
213
+ "INDEX",
214
+ "SCHEMA",
215
+ "DATABASE",
216
+ "FUNCTION",
217
+ "PROCEDURE",
218
+ "TRIGGER",
219
+ "TYPE",
220
+ "EXTENSION",
221
+ "SEQUENCE",
222
+ "IF"
223
+ ],
224
+ "JOIN": [],
225
+ "INNER": ["JOIN"],
226
+ "LEFT": ["JOIN", "OUTER"],
227
+ "RIGHT": ["JOIN", "OUTER"],
228
+ "FULL": ["JOIN", "OUTER"],
229
+ "CROSS": ["JOIN"],
230
+ "NATURAL": ["JOIN", "LEFT", "RIGHT", "FULL", "INNER"],
231
+ "OUTER": ["JOIN"],
232
+ "IF": ["EXISTS", "NOT"],
233
+ "NOT": ["NULL", "EXISTS", "IN", "BETWEEN", "LIKE", "ILIKE"],
234
+ "IS": ["NULL", "NOT", "TRUE", "FALSE", "DISTINCT"],
235
+ "OR": ["REPLACE"],
236
+ "UNION": ["ALL"],
237
+ "LIMIT": [],
238
+ "OFFSET": [],
239
+ "SET": [],
240
+ "VALUES": [],
241
+ "ON": [],
242
+ "RETURNING": [],
243
+ "INTO": [],
244
+ "GRANT": ["ALL", "SELECT", "INSERT", "UPDATE", "DELETE", "USAGE"],
245
+ "REVOKE": ["ALL", "SELECT", "INSERT", "UPDATE", "DELETE", "USAGE"],
246
+ "WITH": ["RECURSIVE"]
247
+ };
248
+ var BUILTIN_FUNCTIONS = [
249
+ // Aggregate
250
+ "avg",
251
+ "count",
252
+ "max",
253
+ "min",
254
+ "sum",
255
+ "array_agg",
256
+ "string_agg",
257
+ "bool_and",
258
+ "bool_or",
259
+ "json_agg",
260
+ "jsonb_agg",
261
+ "json_object_agg",
262
+ "jsonb_object_agg",
263
+ // String
264
+ "length",
265
+ "lower",
266
+ "upper",
267
+ "trim",
268
+ "ltrim",
269
+ "rtrim",
270
+ "substring",
271
+ "replace",
272
+ "concat",
273
+ "concat_ws",
274
+ "left",
275
+ "right",
276
+ "repeat",
277
+ "reverse",
278
+ "split_part",
279
+ "starts_with",
280
+ "format",
281
+ "regexp_replace",
282
+ "regexp_matches",
283
+ // Numeric
284
+ "abs",
285
+ "ceil",
286
+ "floor",
287
+ "round",
288
+ "trunc",
289
+ "mod",
290
+ "power",
291
+ "sqrt",
292
+ "random",
293
+ "generate_series",
294
+ // Date/Time
295
+ "now",
296
+ "current_date",
297
+ "current_time",
298
+ "current_timestamp",
299
+ "date_trunc",
300
+ "date_part",
301
+ "extract",
302
+ "age",
303
+ "to_char",
304
+ "to_date",
305
+ "to_timestamp",
306
+ "to_number",
307
+ "make_date",
308
+ "make_time",
309
+ "make_timestamp",
310
+ // JSON
311
+ "json_build_object",
312
+ "jsonb_build_object",
313
+ "json_build_array",
314
+ "jsonb_build_array",
315
+ "json_extract_path",
316
+ "jsonb_extract_path",
317
+ "json_extract_path_text",
318
+ "jsonb_extract_path_text",
319
+ "json_array_length",
320
+ "jsonb_array_length",
321
+ "json_each",
322
+ "jsonb_each",
323
+ "json_each_text",
324
+ "jsonb_each_text",
325
+ "json_typeof",
326
+ "jsonb_typeof",
327
+ "row_to_json",
328
+ "to_json",
329
+ "to_jsonb",
330
+ // Array
331
+ "array_length",
332
+ "array_dims",
333
+ "array_lower",
334
+ "array_upper",
335
+ "array_append",
336
+ "array_prepend",
337
+ "array_cat",
338
+ "array_remove",
339
+ "array_position",
340
+ "array_positions",
341
+ "unnest",
342
+ // Type casting
343
+ "cast",
344
+ // Window
345
+ "row_number",
346
+ "rank",
347
+ "dense_rank",
348
+ "percent_rank",
349
+ "cume_dist",
350
+ "ntile",
351
+ "lag",
352
+ "lead",
353
+ "first_value",
354
+ "last_value",
355
+ "nth_value",
356
+ // System
357
+ "current_database",
358
+ "current_schema",
359
+ "current_user",
360
+ "session_user",
361
+ "pg_typeof",
362
+ "version",
363
+ // Conditional
364
+ "coalesce",
365
+ "nullif",
366
+ "greatest",
367
+ "least"
368
+ ];
369
+
370
+ // src/parser.ts
371
+ function suggestType(fullText, textBeforeCursor) {
372
+ const trimmed = textBeforeCursor.trimEnd();
373
+ const wordMatch = textBeforeCursor.match(/([A-Za-z0-9_."$]+)$/);
374
+ const wordBeforeCursor = wordMatch ? wordMatch[1] : "";
375
+ const textForAnalysis = wordBeforeCursor ? textBeforeCursor.slice(0, -wordBeforeCursor.length) : textBeforeCursor;
376
+ const dotMatch = wordBeforeCursor.match(/^([A-Za-z_][A-Za-z0-9_]*)\.(.*)$/);
377
+ const tableRefs = extractTables(fullText);
378
+ const lastKeyword = findLastKeyword(textForAnalysis);
379
+ const lastKw = lastKeyword?.toUpperCase() || "";
380
+ if (dotMatch) {
381
+ const parent = dotMatch[1];
382
+ const matchingTables = tableRefs.filter(
383
+ (t) => t.alias === parent || t.name === parent || t.schema && `${t.schema}.${t.name}` === parent
384
+ );
385
+ const suggestions = [];
386
+ if (matchingTables.length > 0) {
387
+ suggestions.push({ type: "column", tableRefs: matchingTables, qualifiable: false, parent });
388
+ }
389
+ suggestions.push(
390
+ { type: "table", schema: parent },
391
+ { type: "view", schema: parent },
392
+ { type: "function", schema: parent }
393
+ );
394
+ return suggestions;
395
+ }
396
+ if (!lastKw || lastKw === "") {
397
+ return [{ type: "keyword" }];
398
+ }
399
+ if (lastKw === "SELECT" || lastKw === "WHERE" || lastKw === "HAVING" || lastKw === "ORDER BY" || lastKw === "DISTINCT") {
400
+ return [
401
+ { type: "column", tableRefs, qualifiable: tableRefs.length > 1 },
402
+ { type: "function", schema: null },
403
+ { type: "keyword", lastToken: lastKw }
404
+ ];
405
+ }
406
+ if (lastKw === "FROM" || lastKw === "COPY" || lastKw === "UPDATE" || lastKw === "INTO" || lastKw === "INSERT INTO" || lastKw === "TRUNCATE") {
407
+ const isFrom = lastKw === "FROM";
408
+ const suggestions = [{ type: "schema" }];
409
+ if (isFrom) {
410
+ suggestions.push({ type: "from_clause", schema: null, tableRefs });
411
+ } else {
412
+ suggestions.push({ type: "table", schema: null });
413
+ suggestions.push({ type: "view", schema: null });
414
+ }
415
+ return suggestions;
416
+ }
417
+ if (lastKw === "JOIN" || lastKw.endsWith(" JOIN")) {
418
+ const suggestions = [
419
+ { type: "schema" },
420
+ { type: "from_clause", schema: null, tableRefs }
421
+ ];
422
+ if (tableRefs.length > 0) {
423
+ suggestions.push({ type: "join", tableRefs, schema: null });
424
+ }
425
+ return suggestions;
426
+ }
427
+ if (lastKw === "ON") {
428
+ const aliases = tableRefs.map((t) => t.alias || t.name);
429
+ const suggestions = [
430
+ { type: "alias", aliases }
431
+ ];
432
+ if (tableRefs.length >= 2) {
433
+ suggestions.push({ type: "join_condition", tableRefs });
434
+ }
435
+ return suggestions;
436
+ }
437
+ if (lastKw === "SET") {
438
+ return [{ type: "column", tableRefs, qualifiable: false }];
439
+ }
440
+ if (lastKw === "TABLE" || lastKw === "VIEW") {
441
+ return [{ type: "schema" }, { type: lastKw === "TABLE" ? "table" : "view", schema: null }];
442
+ }
443
+ if (lastKw === "FUNCTION") {
444
+ return [{ type: "schema" }, { type: "function", schema: null }];
445
+ }
446
+ if (lastKw === "SCHEMA") {
447
+ return [{ type: "schema" }];
448
+ }
449
+ if (lastKw === "DATABASE") {
450
+ return [{ type: "database" }];
451
+ }
452
+ if (lastKw === "TYPE" || lastKw === "::") {
453
+ return [
454
+ { type: "datatype", schema: null },
455
+ { type: "table", schema: null },
456
+ { type: "schema" }
457
+ ];
458
+ }
459
+ if (["CREATE", "ALTER", "DROP"].includes(lastKw)) {
460
+ return [{ type: "keyword", lastToken: lastKw }];
461
+ }
462
+ if (lastKw === "AND" || lastKw === "OR" || lastKw === "," || lastKw === "=") {
463
+ const prevKw = findPrevSignificantKeyword(textForAnalysis);
464
+ if (prevKw) {
465
+ return suggestType(fullText, textForAnalysis);
466
+ }
467
+ }
468
+ if (lastKw === "AS") {
469
+ return [];
470
+ }
471
+ if (lastKw === "COLUMN") {
472
+ return [{ type: "column", tableRefs, qualifiable: false }];
473
+ }
474
+ return [{ type: "keyword", lastToken: lastKw }];
475
+ }
476
+ function extractTables(sql) {
477
+ const tables = [];
478
+ const normalized = sql.replace(/\s+/g, " ").trim();
479
+ const tableContextRegex = /\b(?:FROM|JOIN|UPDATE|INTO|TABLE)\s+/gi;
480
+ let match;
481
+ while ((match = tableContextRegex.exec(normalized)) !== null) {
482
+ const afterKeyword = normalized.slice(match.index + match[0].length);
483
+ const refs = parseTableList(afterKeyword);
484
+ tables.push(...refs);
485
+ }
486
+ return dedup(tables);
487
+ }
488
+ function parseTableList(text) {
489
+ const tables = [];
490
+ const parts = splitOnCommas(text);
491
+ for (const part of parts) {
492
+ const ref = parseOneTableRef(part.trim());
493
+ if (ref) tables.push(ref);
494
+ }
495
+ return tables;
496
+ }
497
+ function splitOnCommas(text) {
498
+ const parts = [];
499
+ let depth = 0;
500
+ let current = "";
501
+ for (const ch of text) {
502
+ if (ch === "(") depth++;
503
+ else if (ch === ")") depth--;
504
+ if (ch === "," && depth === 0) {
505
+ parts.push(current);
506
+ current = "";
507
+ } else if (depth < 0) {
508
+ parts.push(current);
509
+ break;
510
+ } else if (/[;]/.test(ch) && depth === 0) {
511
+ parts.push(current);
512
+ break;
513
+ } else {
514
+ current += ch;
515
+ }
516
+ }
517
+ if (current.trim()) parts.push(current);
518
+ return parts;
519
+ }
520
+ function parseOneTableRef(text) {
521
+ const stopKeywords = /\b(?:WHERE|SET|ORDER|GROUP|HAVING|LIMIT|OFFSET|UNION|INTERSECT|EXCEPT|ON|USING|RETURNING|VALUES|LEFT|RIGHT|INNER|OUTER|FULL|CROSS|NATURAL|JOIN)\b/i;
522
+ const stopMatch = text.match(stopKeywords);
523
+ if (stopMatch) {
524
+ text = text.slice(0, stopMatch.index).trim();
525
+ }
526
+ if (!text) return null;
527
+ const m = text.match(
528
+ /^(?:"([^"]+)"|([A-Za-z_][A-Za-z0-9_$]*))(?:\.(?:"([^"]+)"|([A-Za-z_][A-Za-z0-9_$]*)))?(?:\s+(?:AS\s+)?(?:"([^"]+)"|([A-Za-z_][A-Za-z0-9_$]*)))?/i
529
+ );
530
+ if (!m) return null;
531
+ const part1 = m[1] || m[2];
532
+ const part2 = m[3] || m[4];
533
+ const alias = m[5] || m[6];
534
+ const aliasUpper = alias?.toUpperCase();
535
+ const keywordAlias = aliasUpper && /^(WHERE|SET|ORDER|GROUP|HAVING|LIMIT|OFFSET|ON|USING|JOIN|LEFT|RIGHT|INNER|OUTER|FULL|CROSS|NATURAL|AS|AND|OR|NOT|RETURNING|VALUES|INTO|FROM|SELECT|INSERT|UPDATE|DELETE|CREATE|ALTER|DROP)$/.test(aliasUpper);
536
+ if (part2) {
537
+ return {
538
+ schema: part1.toLowerCase(),
539
+ name: part2.toLowerCase(),
540
+ alias: keywordAlias ? null : alias?.toLowerCase() || null,
541
+ isFunction: false
542
+ };
543
+ }
544
+ return {
545
+ schema: null,
546
+ name: part1.toLowerCase(),
547
+ alias: keywordAlias ? null : alias?.toLowerCase() || null,
548
+ isFunction: false
549
+ };
550
+ }
551
+ function dedup(tables) {
552
+ const seen = /* @__PURE__ */ new Set();
553
+ return tables.filter((t) => {
554
+ const key = `${t.schema || ""}.${t.name}.${t.alias || ""}`;
555
+ if (seen.has(key)) return false;
556
+ seen.add(key);
557
+ return true;
558
+ });
559
+ }
560
+ function findLastKeyword(text) {
561
+ const trimmed = text.trimEnd();
562
+ const compoundMatch = trimmed.match(/\b(ORDER\s+BY|GROUP\s+BY|INSERT\s+INTO|NOT\s+IN|LEFT\s+(?:OUTER\s+)?JOIN|RIGHT\s+(?:OUTER\s+)?JOIN|FULL\s+(?:OUTER\s+)?JOIN|INNER\s+JOIN|CROSS\s+JOIN|NATURAL\s+JOIN|DISTINCT\s+ON)\s*$/i);
563
+ if (compoundMatch) {
564
+ return compoundMatch[1].replace(/\s+/g, " ").toUpperCase();
565
+ }
566
+ const simpleMatch = trimmed.match(/\b([A-Za-z]+)\s*$/);
567
+ if (simpleMatch) {
568
+ const kw = simpleMatch[1].toUpperCase();
569
+ const knownKeywords = /* @__PURE__ */ new Set([
570
+ "SELECT",
571
+ "FROM",
572
+ "WHERE",
573
+ "JOIN",
574
+ "ON",
575
+ "AND",
576
+ "OR",
577
+ "SET",
578
+ "INTO",
579
+ "UPDATE",
580
+ "INSERT",
581
+ "DELETE",
582
+ "CREATE",
583
+ "ALTER",
584
+ "DROP",
585
+ "TABLE",
586
+ "VIEW",
587
+ "FUNCTION",
588
+ "SCHEMA",
589
+ "DATABASE",
590
+ "AS",
591
+ "HAVING",
592
+ "ORDER",
593
+ "GROUP",
594
+ "DISTINCT",
595
+ "LIMIT",
596
+ "OFFSET",
597
+ "RETURNING",
598
+ "TRUNCATE",
599
+ "COPY",
600
+ "TYPE",
601
+ "COLUMN",
602
+ "VALUES"
603
+ ]);
604
+ if (knownKeywords.has(kw)) return kw;
605
+ }
606
+ if (trimmed.endsWith(",")) return ",";
607
+ if (trimmed.endsWith("=")) return "=";
608
+ if (trimmed.endsWith("::")) return "::";
609
+ return null;
610
+ }
611
+ function findPrevSignificantKeyword(text) {
612
+ const withoutLast = text.replace(/\S+\s*$/, "").trim();
613
+ return findLastKeyword(withoutLast);
614
+ }
615
+ function tableRef(t) {
616
+ return t.alias || t.name;
617
+ }
618
+
619
+ // src/metadata.ts
620
+ async function fetchMetadata(pool) {
621
+ const meta = {
622
+ tables: {},
623
+ views: {},
624
+ functions: {},
625
+ datatypes: {}
626
+ };
627
+ const [schemas, tables, views, tableCols, viewCols, functions, fks, datatypes] = await Promise.all([
628
+ fetchSchemas(pool),
629
+ fetchTables(pool),
630
+ fetchViews(pool),
631
+ fetchColumns(pool, ["r", "p", "f"]),
632
+ fetchColumns(pool, ["v", "m"]),
633
+ fetchFunctions(pool),
634
+ fetchForeignKeys(pool),
635
+ fetchDatatypes(pool)
636
+ ]);
637
+ for (const schema of schemas) {
638
+ meta.tables[schema] = meta.tables[schema] || {};
639
+ meta.views[schema] = meta.views[schema] || {};
640
+ meta.functions[schema] = meta.functions[schema] || {};
641
+ meta.datatypes[schema] = meta.datatypes[schema] || {};
642
+ }
643
+ for (const { schema, name } of tables) {
644
+ if (!meta.tables[schema]) meta.tables[schema] = {};
645
+ meta.tables[schema][name] = [];
646
+ }
647
+ for (const { schema, name } of views) {
648
+ if (!meta.views[schema]) meta.views[schema] = {};
649
+ meta.views[schema][name] = [];
650
+ }
651
+ for (const col of tableCols) {
652
+ const tbl = meta.tables[col.schema]?.[col.table];
653
+ if (tbl) {
654
+ tbl.push({
655
+ name: col.name,
656
+ datatype: col.datatype,
657
+ hasDefault: col.hasDefault,
658
+ default_: col.default_,
659
+ foreignKeys: []
660
+ });
661
+ }
662
+ }
663
+ for (const col of viewCols) {
664
+ const v = meta.views[col.schema]?.[col.view];
665
+ if (v) {
666
+ v.push({
667
+ name: col.name,
668
+ datatype: col.datatype,
669
+ hasDefault: col.hasDefault,
670
+ default_: col.default_,
671
+ foreignKeys: []
672
+ });
673
+ }
674
+ }
675
+ for (const f of functions) {
676
+ if (!meta.functions[f.schemaName]) meta.functions[f.schemaName] = {};
677
+ if (!meta.functions[f.schemaName][f.funcName]) {
678
+ meta.functions[f.schemaName][f.funcName] = [];
679
+ }
680
+ meta.functions[f.schemaName][f.funcName].push(f);
681
+ }
682
+ for (const fk of fks) {
683
+ const childCols = meta.tables[fk.childSchema]?.[fk.childTable];
684
+ if (childCols) {
685
+ const col = childCols.find((c) => c.name === fk.childColumn);
686
+ if (col) col.foreignKeys.push(fk);
687
+ }
688
+ const parentCols = meta.tables[fk.parentSchema]?.[fk.parentTable];
689
+ if (parentCols) {
690
+ const col = parentCols.find((c) => c.name === fk.parentColumn);
691
+ if (col) col.foreignKeys.push(fk);
692
+ }
693
+ }
694
+ for (const dt of datatypes) {
695
+ if (!meta.datatypes[dt.schema]) meta.datatypes[dt.schema] = {};
696
+ meta.datatypes[dt.schema][dt.name] = null;
697
+ }
698
+ return meta;
699
+ }
700
+ async function fetchSchemas(pool) {
701
+ const { rows } = await pool.query(
702
+ `SELECT nspname FROM pg_catalog.pg_namespace ORDER BY 1`
703
+ );
704
+ return rows.map((r) => r.nspname);
705
+ }
706
+ async function fetchTables(pool) {
707
+ const { rows } = await pool.query(
708
+ `SELECT n.nspname AS schema, c.relname AS name
709
+ FROM pg_catalog.pg_class c
710
+ LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
711
+ WHERE c.relkind = ANY($1)
712
+ ORDER BY 1, 2`,
713
+ [["r", "p", "f"]]
714
+ );
715
+ return rows.map((r) => ({ schema: r.schema, name: r.name }));
716
+ }
717
+ async function fetchViews(pool) {
718
+ const { rows } = await pool.query(
719
+ `SELECT n.nspname AS schema, c.relname AS name
720
+ FROM pg_catalog.pg_class c
721
+ LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
722
+ WHERE c.relkind = ANY($1)
723
+ ORDER BY 1, 2`,
724
+ [["v", "m"]]
725
+ );
726
+ return rows.map((r) => ({ schema: r.schema, name: r.name }));
727
+ }
728
+ async function fetchColumns(pool, kinds) {
729
+ const { rows } = await pool.query(
730
+ `SELECT nsp.nspname AS schema,
731
+ cls.relname AS table,
732
+ att.attname AS name,
733
+ att.atttypid::regtype::text AS datatype,
734
+ att.atthasdef AS "hasDefault",
735
+ pg_catalog.pg_get_expr(def.adbin, def.adrelid, true) AS "default_"
736
+ FROM pg_catalog.pg_attribute att
737
+ INNER JOIN pg_catalog.pg_class cls ON att.attrelid = cls.oid
738
+ INNER JOIN pg_catalog.pg_namespace nsp ON cls.relnamespace = nsp.oid
739
+ LEFT OUTER JOIN pg_attrdef def ON def.adrelid = att.attrelid AND def.adnum = att.attnum
740
+ WHERE cls.relkind = ANY($1)
741
+ AND NOT att.attisdropped
742
+ AND att.attnum > 0
743
+ ORDER BY 1, 2, att.attnum`,
744
+ [kinds]
745
+ );
746
+ return rows.map((r) => ({
747
+ schema: r.schema,
748
+ table: r.table,
749
+ view: r.table,
750
+ name: r.name,
751
+ datatype: r.datatype,
752
+ hasDefault: r.hasDefault,
753
+ default_: r.default_
754
+ }));
755
+ }
756
+ async function fetchFunctions(pool) {
757
+ const { rows } = await pool.query(
758
+ `SELECT n.nspname AS "schemaName",
759
+ p.proname AS "funcName",
760
+ p.proargnames AS "argNames",
761
+ COALESCE(proallargtypes::regtype[], proargtypes::regtype[])::text[] AS "argTypes",
762
+ p.proargmodes AS "argModes",
763
+ prorettype::regtype::text AS "returnType",
764
+ p.prokind = 'a' AS "isAggregate",
765
+ p.prokind = 'w' AS "isWindow",
766
+ p.proretset AS "isSetReturning",
767
+ d.deptype = 'e' AS "isExtension"
768
+ FROM pg_catalog.pg_proc p
769
+ INNER JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
770
+ LEFT JOIN pg_depend d ON d.objid = p.oid AND d.deptype = 'e'
771
+ WHERE p.prorettype::regtype != 'trigger'::regtype
772
+ ORDER BY 1, 2`
773
+ );
774
+ return rows.map((r) => ({
775
+ ...r,
776
+ isPublic: r.schemaName === "public",
777
+ isExtension: !!r.isExtension
778
+ }));
779
+ }
780
+ async function fetchForeignKeys(pool) {
781
+ const { rows } = await pool.query(
782
+ `SELECT s_p.nspname AS "parentSchema",
783
+ t_p.relname AS "parentTable",
784
+ unnest((
785
+ SELECT array_agg(attname ORDER BY i)
786
+ FROM (SELECT unnest(confkey) AS attnum, generate_subscripts(confkey, 1) AS i) x
787
+ JOIN pg_catalog.pg_attribute c USING(attnum)
788
+ WHERE c.attrelid = fk.confrelid
789
+ )) AS "parentColumn",
790
+ s_c.nspname AS "childSchema",
791
+ t_c.relname AS "childTable",
792
+ unnest((
793
+ SELECT array_agg(attname ORDER BY i)
794
+ FROM (SELECT unnest(conkey) AS attnum, generate_subscripts(conkey, 1) AS i) x
795
+ JOIN pg_catalog.pg_attribute c USING(attnum)
796
+ WHERE c.attrelid = fk.conrelid
797
+ )) AS "childColumn"
798
+ FROM pg_catalog.pg_constraint fk
799
+ JOIN pg_catalog.pg_class t_p ON t_p.oid = fk.confrelid
800
+ JOIN pg_catalog.pg_namespace s_p ON s_p.oid = t_p.relnamespace
801
+ JOIN pg_catalog.pg_class t_c ON t_c.oid = fk.conrelid
802
+ JOIN pg_catalog.pg_namespace s_c ON s_c.oid = t_c.relnamespace
803
+ WHERE fk.contype = 'f'`
804
+ );
805
+ return rows;
806
+ }
807
+ async function fetchDatatypes(pool) {
808
+ const { rows } = await pool.query(
809
+ `SELECT n.nspname AS schema, t.typname AS name
810
+ FROM pg_catalog.pg_type t
811
+ INNER JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
812
+ WHERE (t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid))
813
+ AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_type el WHERE el.oid = t.typelem AND el.typarray = t.oid)
814
+ AND n.nspname <> 'pg_catalog'
815
+ AND n.nspname <> 'information_schema'
816
+ ORDER BY 1, 2`
817
+ );
818
+ return rows;
819
+ }
820
+ async function fetchSearchPath(pool) {
821
+ const { rows } = await pool.query(
822
+ `SELECT * FROM unnest(current_schemas(true))`
823
+ );
824
+ return rows.map((r) => r.unnest);
825
+ }
826
+
827
+ // src/completer.ts
828
+ var PgCompleter = class {
829
+ pool = null;
830
+ metadata;
831
+ searchPath;
832
+ qualifyColumns;
833
+ connectionString;
834
+ constructor(connectionStringOrOpts) {
835
+ const opts = typeof connectionStringOrOpts === "string" ? { connectionString: connectionStringOrOpts } : connectionStringOrOpts || {};
836
+ this.connectionString = opts.connectionString;
837
+ this.metadata = opts.metadata || { tables: {}, views: {}, functions: {}, datatypes: {} };
838
+ this.searchPath = opts.searchPath || ["pg_catalog", "public"];
839
+ this.qualifyColumns = opts.qualifyColumns || "if_more_than_one_table";
840
+ }
841
+ /** Connect to the database and load schema metadata */
842
+ async refresh() {
843
+ if (!this.connectionString) {
844
+ throw new Error("No connection string provided");
845
+ }
846
+ this.pool = new import_pg.Pool({ connectionString: this.connectionString, max: 2 });
847
+ try {
848
+ const [meta, sp] = await Promise.all([
849
+ fetchMetadata(this.pool),
850
+ fetchSearchPath(this.pool)
851
+ ]);
852
+ this.metadata = meta;
853
+ this.searchPath = sp;
854
+ } finally {
855
+ await this.pool.end();
856
+ this.pool = null;
857
+ }
858
+ }
859
+ /** Close any open connections */
860
+ async close() {
861
+ if (this.pool) {
862
+ await this.pool.end();
863
+ this.pool = null;
864
+ }
865
+ }
866
+ /** Set metadata directly (for testing or pre-loaded scenarios) */
867
+ setMetadata(meta, searchPath) {
868
+ this.metadata = meta;
869
+ if (searchPath) this.searchPath = searchPath;
870
+ }
871
+ /**
872
+ * Get completions for the given SQL text.
873
+ * @param text - The full SQL text typed so far
874
+ * @param cursorPos - Cursor position (defaults to end of text)
875
+ * @returns Array of completion suggestions
876
+ */
877
+ complete(text, cursorPos) {
878
+ const pos = cursorPos ?? text.length;
879
+ const textBeforeCursor = text.slice(0, pos);
880
+ const fullText = text;
881
+ const suggestions = suggestType(fullText, textBeforeCursor);
882
+ const completions = [];
883
+ for (const suggestion of suggestions) {
884
+ switch (suggestion.type) {
885
+ case "keyword":
886
+ completions.push(...this.getKeywordCompletions(suggestion.lastToken));
887
+ break;
888
+ case "table":
889
+ completions.push(...this.getTableCompletions(suggestion.schema));
890
+ break;
891
+ case "view":
892
+ completions.push(...this.getViewCompletions(suggestion.schema));
893
+ break;
894
+ case "column":
895
+ completions.push(...this.getColumnCompletions(
896
+ suggestion.tableRefs,
897
+ suggestion.qualifiable,
898
+ suggestion.parent
899
+ ));
900
+ break;
901
+ case "function":
902
+ completions.push(...this.getFunctionCompletions(suggestion.schema));
903
+ break;
904
+ case "schema":
905
+ completions.push(...this.getSchemaCompletions());
906
+ break;
907
+ case "from_clause":
908
+ completions.push(...this.getTableCompletions(suggestion.schema));
909
+ completions.push(...this.getViewCompletions(suggestion.schema));
910
+ completions.push(...this.getFunctionCompletions(suggestion.schema));
911
+ break;
912
+ case "join":
913
+ completions.push(...this.getJoinCompletions(suggestion.tableRefs, suggestion.schema));
914
+ break;
915
+ case "join_condition":
916
+ completions.push(...this.getJoinConditionCompletions(suggestion.tableRefs, suggestion.parent));
917
+ break;
918
+ case "alias":
919
+ completions.push(...suggestion.aliases.map((a) => ({
920
+ text: a,
921
+ type: "alias"
922
+ })));
923
+ break;
924
+ case "database":
925
+ break;
926
+ case "datatype":
927
+ completions.push(...this.getDatatypeCompletions(suggestion.schema));
928
+ break;
929
+ }
930
+ }
931
+ const wordMatch = textBeforeCursor.match(/([A-Za-z0-9_."$]+)$/);
932
+ const word = wordMatch ? wordMatch[1] : "";
933
+ const dotIdx = word.lastIndexOf(".");
934
+ const filterWord = dotIdx >= 0 ? word.slice(dotIdx + 1) : word;
935
+ if (!filterWord) return this.dedup(completions);
936
+ const lower = filterWord.toLowerCase().replace(/^"/, "");
937
+ const filtered = completions.filter((c) => {
938
+ const ct = c.text.toLowerCase();
939
+ return ct.startsWith(lower) || fuzzyMatch(lower, ct);
940
+ });
941
+ filtered.sort((a, b) => {
942
+ const aExact = a.text.toLowerCase().startsWith(lower) ? 0 : 1;
943
+ const bExact = b.text.toLowerCase().startsWith(lower) ? 0 : 1;
944
+ if (aExact !== bExact) return aExact - bExact;
945
+ return a.text.localeCompare(b.text);
946
+ });
947
+ return this.dedup(filtered);
948
+ }
949
+ dedup(completions) {
950
+ const seen = /* @__PURE__ */ new Set();
951
+ return completions.filter((c) => {
952
+ const key = `${c.type}:${c.text}`;
953
+ if (seen.has(key)) return false;
954
+ seen.add(key);
955
+ return true;
956
+ });
957
+ }
958
+ getKeywordCompletions(lastToken) {
959
+ let kws;
960
+ if (lastToken && KEYWORD_TREE[lastToken]) {
961
+ kws = KEYWORD_TREE[lastToken];
962
+ if (kws.length === 0) kws = KEYWORDS;
963
+ } else {
964
+ kws = KEYWORDS;
965
+ }
966
+ return kws.map((k) => ({ text: k, type: "keyword" }));
967
+ }
968
+ getSchemaCompletions() {
969
+ const schemas = /* @__PURE__ */ new Set();
970
+ for (const kind of ["tables", "views", "functions", "datatypes"]) {
971
+ for (const s of Object.keys(this.metadata[kind])) {
972
+ if (!s.startsWith("pg_")) schemas.add(s);
973
+ }
974
+ }
975
+ return [...schemas].map((s) => ({ text: s, type: "schema" }));
976
+ }
977
+ getTableCompletions(schema) {
978
+ const results = [];
979
+ const schemas = schema ? [schema] : this.getVisibleSchemas();
980
+ for (const s of schemas) {
981
+ const tables = this.metadata.tables[s];
982
+ if (!tables) continue;
983
+ for (const name of Object.keys(tables)) {
984
+ if (!schema && name.startsWith("pg_")) continue;
985
+ const prefix = !schema && !this.searchPath.includes(s) ? `${s}.` : "";
986
+ results.push({ text: prefix + name, type: "table" });
987
+ }
988
+ }
989
+ return results;
990
+ }
991
+ getViewCompletions(schema) {
992
+ const results = [];
993
+ const schemas = schema ? [schema] : this.getVisibleSchemas();
994
+ for (const s of schemas) {
995
+ const views = this.metadata.views[s];
996
+ if (!views) continue;
997
+ for (const name of Object.keys(views)) {
998
+ if (!schema && name.startsWith("pg_")) continue;
999
+ const prefix = !schema && !this.searchPath.includes(s) ? `${s}.` : "";
1000
+ results.push({ text: prefix + name, type: "view" });
1001
+ }
1002
+ }
1003
+ return results;
1004
+ }
1005
+ getColumnCompletions(tableRefs, qualifiable, parent) {
1006
+ const results = [];
1007
+ const doQualify = qualifiable && {
1008
+ always: true,
1009
+ never: false,
1010
+ if_more_than_one_table: tableRefs.length > 1
1011
+ }[this.qualifyColumns];
1012
+ for (const tref of tableRefs) {
1013
+ const cols = this.findColumns(tref);
1014
+ const ref = tableRef(tref);
1015
+ for (const col of cols) {
1016
+ const text = doQualify ? `${ref}.${col.name}` : col.name;
1017
+ results.push({
1018
+ text,
1019
+ type: "column",
1020
+ meta: col.datatype
1021
+ });
1022
+ }
1023
+ }
1024
+ return results;
1025
+ }
1026
+ getFunctionCompletions(schema) {
1027
+ const results = [];
1028
+ const schemas = schema ? [schema] : this.getVisibleSchemas();
1029
+ for (const s of schemas) {
1030
+ const funcs = this.metadata.functions[s];
1031
+ if (!funcs) continue;
1032
+ for (const name of Object.keys(funcs)) {
1033
+ const prefix = schema || !this.searchPath.includes(s) ? `${s}.` : "";
1034
+ results.push({ text: prefix + name, type: "function" });
1035
+ }
1036
+ }
1037
+ if (!schema) {
1038
+ for (const f of BUILTIN_FUNCTIONS) {
1039
+ results.push({ text: f, type: "function" });
1040
+ }
1041
+ }
1042
+ return results;
1043
+ }
1044
+ getJoinCompletions(tableRefs, schema) {
1045
+ const results = [];
1046
+ for (const tref of tableRefs) {
1047
+ const cols = this.findColumns(tref);
1048
+ for (const col of cols) {
1049
+ for (const fk of col.foreignKeys) {
1050
+ const isChild = col.name === fk.childColumn && this.matchesTable(tref, fk.childSchema, fk.childTable);
1051
+ const otherSchema = isChild ? fk.parentSchema : fk.childSchema;
1052
+ const otherTable = isChild ? fk.parentTable : fk.childTable;
1053
+ const otherCol = isChild ? fk.parentColumn : fk.childColumn;
1054
+ const thisCol = isChild ? fk.childColumn : fk.parentColumn;
1055
+ if (schema && otherSchema !== schema) continue;
1056
+ const ref = tableRef(tref);
1057
+ const joinText = `${otherTable} ON ${otherTable}.${otherCol} = ${ref}.${thisCol}`;
1058
+ results.push({ text: joinText, type: "join", meta: "fk join" });
1059
+ }
1060
+ }
1061
+ }
1062
+ return results;
1063
+ }
1064
+ getJoinConditionCompletions(tableRefs, parent) {
1065
+ const results = [];
1066
+ if (tableRefs.length < 2) return results;
1067
+ const lastRef = tableRefs[tableRefs.length - 1];
1068
+ const lastCols = this.findColumns(lastRef);
1069
+ const lastRefName = tableRef(lastRef);
1070
+ for (const col of lastCols) {
1071
+ for (const fk of col.foreignKeys) {
1072
+ for (const otherRef of tableRefs.slice(0, -1)) {
1073
+ const otherRefName = tableRef(otherRef);
1074
+ const isChild = col.name === fk.childColumn && this.matchesTable(lastRef, fk.childSchema, fk.childTable);
1075
+ if (isChild) {
1076
+ if (this.matchesTable(otherRef, fk.parentSchema, fk.parentTable)) {
1077
+ results.push({
1078
+ text: `${lastRefName}.${fk.childColumn} = ${otherRefName}.${fk.parentColumn}`,
1079
+ type: "join",
1080
+ meta: "fk join"
1081
+ });
1082
+ }
1083
+ } else {
1084
+ if (this.matchesTable(otherRef, fk.childSchema, fk.childTable)) {
1085
+ results.push({
1086
+ text: `${lastRefName}.${fk.parentColumn} = ${otherRefName}.${fk.childColumn}`,
1087
+ type: "join",
1088
+ meta: "fk join"
1089
+ });
1090
+ }
1091
+ }
1092
+ }
1093
+ }
1094
+ }
1095
+ for (const col of lastCols) {
1096
+ for (const otherRef of tableRefs.slice(0, -1)) {
1097
+ const otherCols = this.findColumns(otherRef);
1098
+ const match = otherCols.find((c) => c.name === col.name);
1099
+ if (match) {
1100
+ const otherRefName = tableRef(otherRef);
1101
+ const text = `${lastRefName}.${col.name} = ${otherRefName}.${col.name}`;
1102
+ if (!results.some((r) => r.text === text)) {
1103
+ results.push({ text, type: "join", meta: "name join" });
1104
+ }
1105
+ }
1106
+ }
1107
+ }
1108
+ return results;
1109
+ }
1110
+ getDatatypeCompletions(schema) {
1111
+ const results = [];
1112
+ const schemas = schema ? [schema] : this.getVisibleSchemas();
1113
+ for (const s of schemas) {
1114
+ const types = this.metadata.datatypes[s];
1115
+ if (!types) continue;
1116
+ for (const name of Object.keys(types)) {
1117
+ results.push({ text: name, type: "datatype" });
1118
+ }
1119
+ }
1120
+ if (!schema) {
1121
+ for (const t of [
1122
+ "integer",
1123
+ "bigint",
1124
+ "smallint",
1125
+ "serial",
1126
+ "bigserial",
1127
+ "real",
1128
+ "double precision",
1129
+ "numeric",
1130
+ "decimal",
1131
+ "text",
1132
+ "varchar",
1133
+ "char",
1134
+ "boolean",
1135
+ "bool",
1136
+ "date",
1137
+ "time",
1138
+ "timestamp",
1139
+ "timestamptz",
1140
+ "interval",
1141
+ "json",
1142
+ "jsonb",
1143
+ "uuid",
1144
+ "bytea",
1145
+ "xml"
1146
+ ]) {
1147
+ results.push({ text: t, type: "datatype" });
1148
+ }
1149
+ }
1150
+ return results;
1151
+ }
1152
+ /** Find columns for a table reference, searching through schemas */
1153
+ findColumns(tref) {
1154
+ const schemas = tref.schema ? [tref.schema] : this.searchPath;
1155
+ for (const s of schemas) {
1156
+ const cols = this.metadata.tables[s]?.[tref.name] || this.metadata.views[s]?.[tref.name];
1157
+ if (cols) return cols;
1158
+ }
1159
+ return [];
1160
+ }
1161
+ matchesTable(tref, schema, table) {
1162
+ if (tref.schema) return tref.schema === schema && tref.name === table;
1163
+ return tref.name === table && this.searchPath.includes(schema);
1164
+ }
1165
+ getVisibleSchemas() {
1166
+ const all = new Set(this.searchPath);
1167
+ for (const kind of ["tables", "views", "functions", "datatypes"]) {
1168
+ for (const s of Object.keys(this.metadata[kind])) {
1169
+ all.add(s);
1170
+ }
1171
+ }
1172
+ return [...all];
1173
+ }
1174
+ };
1175
+ function fuzzyMatch(pattern, text) {
1176
+ let pi = 0;
1177
+ for (let ti = 0; ti < text.length && pi < pattern.length; ti++) {
1178
+ if (text[ti] === pattern[pi]) pi++;
1179
+ }
1180
+ return pi === pattern.length;
1181
+ }
1182
+
1183
+ // src/mcp.ts
1184
+ var completers = /* @__PURE__ */ new Map();
1185
+ function handleRequest(req) {
1186
+ switch (req.method) {
1187
+ case "initialize":
1188
+ return {
1189
+ jsonrpc: "2.0",
1190
+ id: req.id,
1191
+ result: {
1192
+ protocolVersion: "2024-11-05",
1193
+ capabilities: { tools: {} },
1194
+ serverInfo: { name: "pg-complete", version: "0.1.0" }
1195
+ }
1196
+ };
1197
+ case "tools/list":
1198
+ return {
1199
+ jsonrpc: "2.0",
1200
+ id: req.id,
1201
+ result: {
1202
+ tools: [
1203
+ {
1204
+ name: "pg_complete",
1205
+ description: "Get SQL autocompletions for PostgreSQL queries",
1206
+ inputSchema: {
1207
+ type: "object",
1208
+ properties: {
1209
+ sql: { type: "string", description: "The SQL text to complete" },
1210
+ connectionId: { type: "string", description: "Connection identifier (optional)" },
1211
+ cursorPos: { type: "number", description: "Cursor position (defaults to end)" }
1212
+ },
1213
+ required: ["sql"]
1214
+ }
1215
+ },
1216
+ {
1217
+ name: "pg_connect",
1218
+ description: "Connect to a PostgreSQL database and load schema metadata",
1219
+ inputSchema: {
1220
+ type: "object",
1221
+ properties: {
1222
+ connectionString: { type: "string", description: "PostgreSQL connection string" },
1223
+ connectionId: { type: "string", description: "Identifier for this connection" }
1224
+ },
1225
+ required: ["connectionString"]
1226
+ }
1227
+ }
1228
+ ]
1229
+ }
1230
+ };
1231
+ case "tools/call":
1232
+ return handleToolCall(req);
1233
+ default:
1234
+ return {
1235
+ jsonrpc: "2.0",
1236
+ id: req.id,
1237
+ result: {}
1238
+ };
1239
+ }
1240
+ }
1241
+ function handleToolCall(req) {
1242
+ const { name, arguments: args } = req.params || {};
1243
+ if (name === "pg_complete") {
1244
+ const connId = args?.connectionId || "default";
1245
+ let completer = completers.get(connId);
1246
+ if (!completer) {
1247
+ completer = new PgCompleter();
1248
+ completers.set(connId, completer);
1249
+ }
1250
+ const results = completer.complete(args.sql, args.cursorPos);
1251
+ return {
1252
+ jsonrpc: "2.0",
1253
+ id: req.id,
1254
+ result: {
1255
+ content: [{ type: "text", text: JSON.stringify(results, null, 2) }]
1256
+ }
1257
+ };
1258
+ }
1259
+ if (name === "pg_connect") {
1260
+ const connId = args?.connectionId || "default";
1261
+ const completer = new PgCompleter(args.connectionString);
1262
+ completers.set(connId, completer);
1263
+ return {
1264
+ jsonrpc: "2.0",
1265
+ id: req.id,
1266
+ result: {
1267
+ content: [{ type: "text", text: `Connecting... Use connectionId="${connId}" in pg_complete calls.` }],
1268
+ isAsync: true
1269
+ }
1270
+ };
1271
+ }
1272
+ return {
1273
+ jsonrpc: "2.0",
1274
+ id: req.id,
1275
+ error: { code: -32601, message: `Unknown tool: ${name}` }
1276
+ };
1277
+ }
1278
+ async function main() {
1279
+ process.stdin.setEncoding("utf8");
1280
+ let buffer = "";
1281
+ process.stdin.on("data", (chunk) => {
1282
+ buffer += chunk;
1283
+ const lines = buffer.split("\n");
1284
+ buffer = lines.pop() || "";
1285
+ for (const line of lines) {
1286
+ if (!line.trim()) continue;
1287
+ try {
1288
+ const req = JSON.parse(line);
1289
+ const res = handleRequest(req);
1290
+ process.stdout.write(JSON.stringify(res) + "\n");
1291
+ } catch (e) {
1292
+ const errorRes = {
1293
+ jsonrpc: "2.0",
1294
+ id: 0,
1295
+ error: { code: -32700, message: `Parse error: ${e.message}` }
1296
+ };
1297
+ process.stdout.write(JSON.stringify(errorRes) + "\n");
1298
+ }
1299
+ }
1300
+ });
1301
+ process.stderr.write("pg-complete MCP server started (stdio)\n");
1302
+ }
1303
+ main().catch(console.error);