@neverinfamous/postgres-mcp 1.2.0 → 2.0.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.
Files changed (293) hide show
  1. package/README.md +202 -148
  2. package/dist/__tests__/benchmarks/codemode.bench.d.ts +10 -0
  3. package/dist/__tests__/benchmarks/codemode.bench.d.ts.map +1 -0
  4. package/dist/__tests__/benchmarks/codemode.bench.js +159 -0
  5. package/dist/__tests__/benchmarks/codemode.bench.js.map +1 -0
  6. package/dist/__tests__/benchmarks/connection-pool.bench.d.ts +10 -0
  7. package/dist/__tests__/benchmarks/connection-pool.bench.d.ts.map +1 -0
  8. package/dist/__tests__/benchmarks/connection-pool.bench.js +123 -0
  9. package/dist/__tests__/benchmarks/connection-pool.bench.js.map +1 -0
  10. package/dist/__tests__/benchmarks/handler-dispatch.bench.d.ts +11 -0
  11. package/dist/__tests__/benchmarks/handler-dispatch.bench.d.ts.map +1 -0
  12. package/dist/__tests__/benchmarks/handler-dispatch.bench.js +199 -0
  13. package/dist/__tests__/benchmarks/handler-dispatch.bench.js.map +1 -0
  14. package/dist/__tests__/benchmarks/logger-sanitization.bench.d.ts +15 -0
  15. package/dist/__tests__/benchmarks/logger-sanitization.bench.d.ts.map +1 -0
  16. package/dist/__tests__/benchmarks/logger-sanitization.bench.js +155 -0
  17. package/dist/__tests__/benchmarks/logger-sanitization.bench.js.map +1 -0
  18. package/dist/__tests__/benchmarks/resource-prompts.bench.d.ts +10 -0
  19. package/dist/__tests__/benchmarks/resource-prompts.bench.d.ts.map +1 -0
  20. package/dist/__tests__/benchmarks/resource-prompts.bench.js +181 -0
  21. package/dist/__tests__/benchmarks/resource-prompts.bench.js.map +1 -0
  22. package/dist/__tests__/benchmarks/schema-parsing.bench.d.ts +11 -0
  23. package/dist/__tests__/benchmarks/schema-parsing.bench.d.ts.map +1 -0
  24. package/dist/__tests__/benchmarks/schema-parsing.bench.js +209 -0
  25. package/dist/__tests__/benchmarks/schema-parsing.bench.js.map +1 -0
  26. package/dist/__tests__/benchmarks/tool-filtering.bench.d.ts +9 -0
  27. package/dist/__tests__/benchmarks/tool-filtering.bench.d.ts.map +1 -0
  28. package/dist/__tests__/benchmarks/tool-filtering.bench.js +83 -0
  29. package/dist/__tests__/benchmarks/tool-filtering.bench.js.map +1 -0
  30. package/dist/__tests__/benchmarks/transport-auth.bench.d.ts +10 -0
  31. package/dist/__tests__/benchmarks/transport-auth.bench.d.ts.map +1 -0
  32. package/dist/__tests__/benchmarks/transport-auth.bench.js +128 -0
  33. package/dist/__tests__/benchmarks/transport-auth.bench.js.map +1 -0
  34. package/dist/__tests__/benchmarks/utilities.bench.d.ts +10 -0
  35. package/dist/__tests__/benchmarks/utilities.bench.d.ts.map +1 -0
  36. package/dist/__tests__/benchmarks/utilities.bench.js +164 -0
  37. package/dist/__tests__/benchmarks/utilities.bench.js.map +1 -0
  38. package/dist/adapters/DatabaseAdapter.d.ts.map +1 -1
  39. package/dist/adapters/DatabaseAdapter.js +12 -0
  40. package/dist/adapters/DatabaseAdapter.js.map +1 -1
  41. package/dist/adapters/postgresql/PostgresAdapter.d.ts.map +1 -1
  42. package/dist/adapters/postgresql/PostgresAdapter.js +56 -3
  43. package/dist/adapters/postgresql/PostgresAdapter.js.map +1 -1
  44. package/dist/adapters/postgresql/prompts/ltree.js +2 -2
  45. package/dist/adapters/postgresql/prompts/ltree.js.map +1 -1
  46. package/dist/adapters/postgresql/schemas/admin.d.ts +10 -5
  47. package/dist/adapters/postgresql/schemas/admin.d.ts.map +1 -1
  48. package/dist/adapters/postgresql/schemas/admin.js +10 -5
  49. package/dist/adapters/postgresql/schemas/admin.js.map +1 -1
  50. package/dist/adapters/postgresql/schemas/backup.d.ts +45 -27
  51. package/dist/adapters/postgresql/schemas/backup.d.ts.map +1 -1
  52. package/dist/adapters/postgresql/schemas/backup.js +64 -26
  53. package/dist/adapters/postgresql/schemas/backup.js.map +1 -1
  54. package/dist/adapters/postgresql/schemas/core.d.ts +53 -19
  55. package/dist/adapters/postgresql/schemas/core.d.ts.map +1 -1
  56. package/dist/adapters/postgresql/schemas/core.js +61 -17
  57. package/dist/adapters/postgresql/schemas/core.js.map +1 -1
  58. package/dist/adapters/postgresql/schemas/cron.d.ts +51 -32
  59. package/dist/adapters/postgresql/schemas/cron.d.ts.map +1 -1
  60. package/dist/adapters/postgresql/schemas/cron.js +64 -44
  61. package/dist/adapters/postgresql/schemas/cron.js.map +1 -1
  62. package/dist/adapters/postgresql/schemas/extensions.d.ts +224 -110
  63. package/dist/adapters/postgresql/schemas/extensions.d.ts.map +1 -1
  64. package/dist/adapters/postgresql/schemas/extensions.js +245 -96
  65. package/dist/adapters/postgresql/schemas/extensions.js.map +1 -1
  66. package/dist/adapters/postgresql/schemas/index.d.ts +7 -6
  67. package/dist/adapters/postgresql/schemas/index.d.ts.map +1 -1
  68. package/dist/adapters/postgresql/schemas/index.js +16 -8
  69. package/dist/adapters/postgresql/schemas/index.js.map +1 -1
  70. package/dist/adapters/postgresql/schemas/introspection.d.ts +445 -0
  71. package/dist/adapters/postgresql/schemas/introspection.d.ts.map +1 -0
  72. package/dist/adapters/postgresql/schemas/introspection.js +478 -0
  73. package/dist/adapters/postgresql/schemas/introspection.js.map +1 -0
  74. package/dist/adapters/postgresql/schemas/jsonb.d.ts +102 -42
  75. package/dist/adapters/postgresql/schemas/jsonb.d.ts.map +1 -1
  76. package/dist/adapters/postgresql/schemas/jsonb.js +125 -30
  77. package/dist/adapters/postgresql/schemas/jsonb.js.map +1 -1
  78. package/dist/adapters/postgresql/schemas/monitoring.d.ts +69 -36
  79. package/dist/adapters/postgresql/schemas/monitoring.d.ts.map +1 -1
  80. package/dist/adapters/postgresql/schemas/monitoring.js +98 -40
  81. package/dist/adapters/postgresql/schemas/monitoring.js.map +1 -1
  82. package/dist/adapters/postgresql/schemas/partitioning.d.ts +21 -24
  83. package/dist/adapters/postgresql/schemas/partitioning.d.ts.map +1 -1
  84. package/dist/adapters/postgresql/schemas/partitioning.js +26 -14
  85. package/dist/adapters/postgresql/schemas/partitioning.js.map +1 -1
  86. package/dist/adapters/postgresql/schemas/partman.d.ts +69 -0
  87. package/dist/adapters/postgresql/schemas/partman.d.ts.map +1 -1
  88. package/dist/adapters/postgresql/schemas/partman.js +46 -33
  89. package/dist/adapters/postgresql/schemas/partman.js.map +1 -1
  90. package/dist/adapters/postgresql/schemas/performance.d.ts +97 -49
  91. package/dist/adapters/postgresql/schemas/performance.d.ts.map +1 -1
  92. package/dist/adapters/postgresql/schemas/performance.js +139 -34
  93. package/dist/adapters/postgresql/schemas/performance.js.map +1 -1
  94. package/dist/adapters/postgresql/schemas/postgis.d.ts +20 -0
  95. package/dist/adapters/postgresql/schemas/postgis.d.ts.map +1 -1
  96. package/dist/adapters/postgresql/schemas/postgis.js +40 -0
  97. package/dist/adapters/postgresql/schemas/postgis.js.map +1 -1
  98. package/dist/adapters/postgresql/schemas/schema-mgmt.d.ts +50 -30
  99. package/dist/adapters/postgresql/schemas/schema-mgmt.d.ts.map +1 -1
  100. package/dist/adapters/postgresql/schemas/schema-mgmt.js +105 -33
  101. package/dist/adapters/postgresql/schemas/schema-mgmt.js.map +1 -1
  102. package/dist/adapters/postgresql/schemas/stats.d.ts +33 -20
  103. package/dist/adapters/postgresql/schemas/stats.d.ts.map +1 -1
  104. package/dist/adapters/postgresql/schemas/stats.js +36 -20
  105. package/dist/adapters/postgresql/schemas/stats.js.map +1 -1
  106. package/dist/adapters/postgresql/schemas/text-search.d.ts +34 -19
  107. package/dist/adapters/postgresql/schemas/text-search.d.ts.map +1 -1
  108. package/dist/adapters/postgresql/schemas/text-search.js +52 -13
  109. package/dist/adapters/postgresql/schemas/text-search.js.map +1 -1
  110. package/dist/adapters/postgresql/tools/admin.d.ts.map +1 -1
  111. package/dist/adapters/postgresql/tools/admin.js +272 -186
  112. package/dist/adapters/postgresql/tools/admin.js.map +1 -1
  113. package/dist/adapters/postgresql/tools/backup/dump.d.ts.map +1 -1
  114. package/dist/adapters/postgresql/tools/backup/dump.js +376 -350
  115. package/dist/adapters/postgresql/tools/backup/dump.js.map +1 -1
  116. package/dist/adapters/postgresql/tools/citext.d.ts.map +1 -1
  117. package/dist/adapters/postgresql/tools/citext.js +333 -243
  118. package/dist/adapters/postgresql/tools/citext.js.map +1 -1
  119. package/dist/adapters/postgresql/tools/codemode/index.d.ts.map +1 -1
  120. package/dist/adapters/postgresql/tools/codemode/index.js +2 -11
  121. package/dist/adapters/postgresql/tools/codemode/index.js.map +1 -1
  122. package/dist/adapters/postgresql/tools/core/convenience.d.ts +9 -1
  123. package/dist/adapters/postgresql/tools/core/convenience.d.ts.map +1 -1
  124. package/dist/adapters/postgresql/tools/core/convenience.js +101 -19
  125. package/dist/adapters/postgresql/tools/core/convenience.js.map +1 -1
  126. package/dist/adapters/postgresql/tools/core/error-helpers.d.ts +48 -0
  127. package/dist/adapters/postgresql/tools/core/error-helpers.d.ts.map +1 -0
  128. package/dist/adapters/postgresql/tools/core/error-helpers.js +256 -0
  129. package/dist/adapters/postgresql/tools/core/error-helpers.js.map +1 -0
  130. package/dist/adapters/postgresql/tools/core/health.d.ts.map +1 -1
  131. package/dist/adapters/postgresql/tools/core/health.js +18 -4
  132. package/dist/adapters/postgresql/tools/core/health.js.map +1 -1
  133. package/dist/adapters/postgresql/tools/core/indexes.d.ts.map +1 -1
  134. package/dist/adapters/postgresql/tools/core/indexes.js +48 -6
  135. package/dist/adapters/postgresql/tools/core/indexes.js.map +1 -1
  136. package/dist/adapters/postgresql/tools/core/objects.d.ts.map +1 -1
  137. package/dist/adapters/postgresql/tools/core/objects.js +104 -85
  138. package/dist/adapters/postgresql/tools/core/objects.js.map +1 -1
  139. package/dist/adapters/postgresql/tools/core/query.d.ts.map +1 -1
  140. package/dist/adapters/postgresql/tools/core/query.js +100 -42
  141. package/dist/adapters/postgresql/tools/core/query.js.map +1 -1
  142. package/dist/adapters/postgresql/tools/core/schemas.d.ts +51 -25
  143. package/dist/adapters/postgresql/tools/core/schemas.d.ts.map +1 -1
  144. package/dist/adapters/postgresql/tools/core/schemas.js +51 -25
  145. package/dist/adapters/postgresql/tools/core/schemas.js.map +1 -1
  146. package/dist/adapters/postgresql/tools/core/tables.d.ts.map +1 -1
  147. package/dist/adapters/postgresql/tools/core/tables.js +72 -32
  148. package/dist/adapters/postgresql/tools/core/tables.js.map +1 -1
  149. package/dist/adapters/postgresql/tools/cron.d.ts.map +1 -1
  150. package/dist/adapters/postgresql/tools/cron.js +333 -206
  151. package/dist/adapters/postgresql/tools/cron.js.map +1 -1
  152. package/dist/adapters/postgresql/tools/introspection.d.ts +15 -0
  153. package/dist/adapters/postgresql/tools/introspection.d.ts.map +1 -0
  154. package/dist/adapters/postgresql/tools/introspection.js +1682 -0
  155. package/dist/adapters/postgresql/tools/introspection.js.map +1 -0
  156. package/dist/adapters/postgresql/tools/jsonb/advanced.d.ts.map +1 -1
  157. package/dist/adapters/postgresql/tools/jsonb/advanced.js +394 -297
  158. package/dist/adapters/postgresql/tools/jsonb/advanced.js.map +1 -1
  159. package/dist/adapters/postgresql/tools/jsonb/basic.d.ts.map +1 -1
  160. package/dist/adapters/postgresql/tools/jsonb/basic.js +686 -398
  161. package/dist/adapters/postgresql/tools/jsonb/basic.js.map +1 -1
  162. package/dist/adapters/postgresql/tools/kcache.d.ts.map +1 -1
  163. package/dist/adapters/postgresql/tools/kcache.js +278 -246
  164. package/dist/adapters/postgresql/tools/kcache.js.map +1 -1
  165. package/dist/adapters/postgresql/tools/ltree.d.ts.map +1 -1
  166. package/dist/adapters/postgresql/tools/ltree.js +137 -38
  167. package/dist/adapters/postgresql/tools/ltree.js.map +1 -1
  168. package/dist/adapters/postgresql/tools/monitoring.d.ts.map +1 -1
  169. package/dist/adapters/postgresql/tools/monitoring.js +86 -55
  170. package/dist/adapters/postgresql/tools/monitoring.js.map +1 -1
  171. package/dist/adapters/postgresql/tools/partitioning.d.ts.map +1 -1
  172. package/dist/adapters/postgresql/tools/partitioning.js +79 -15
  173. package/dist/adapters/postgresql/tools/partitioning.js.map +1 -1
  174. package/dist/adapters/postgresql/tools/partman/management.d.ts.map +1 -1
  175. package/dist/adapters/postgresql/tools/partman/management.js +43 -56
  176. package/dist/adapters/postgresql/tools/partman/management.js.map +1 -1
  177. package/dist/adapters/postgresql/tools/partman/operations.d.ts.map +1 -1
  178. package/dist/adapters/postgresql/tools/partman/operations.js +137 -24
  179. package/dist/adapters/postgresql/tools/partman/operations.js.map +1 -1
  180. package/dist/adapters/postgresql/tools/performance/analysis.d.ts.map +1 -1
  181. package/dist/adapters/postgresql/tools/performance/analysis.js +276 -165
  182. package/dist/adapters/postgresql/tools/performance/analysis.js.map +1 -1
  183. package/dist/adapters/postgresql/tools/performance/explain.d.ts.map +1 -1
  184. package/dist/adapters/postgresql/tools/performance/explain.js +61 -21
  185. package/dist/adapters/postgresql/tools/performance/explain.js.map +1 -1
  186. package/dist/adapters/postgresql/tools/performance/monitoring.d.ts.map +1 -1
  187. package/dist/adapters/postgresql/tools/performance/monitoring.js +52 -12
  188. package/dist/adapters/postgresql/tools/performance/monitoring.js.map +1 -1
  189. package/dist/adapters/postgresql/tools/performance/optimization.d.ts.map +1 -1
  190. package/dist/adapters/postgresql/tools/performance/optimization.js +92 -81
  191. package/dist/adapters/postgresql/tools/performance/optimization.js.map +1 -1
  192. package/dist/adapters/postgresql/tools/performance/stats.d.ts.map +1 -1
  193. package/dist/adapters/postgresql/tools/performance/stats.js +182 -60
  194. package/dist/adapters/postgresql/tools/performance/stats.js.map +1 -1
  195. package/dist/adapters/postgresql/tools/pgcrypto.d.ts.map +1 -1
  196. package/dist/adapters/postgresql/tools/pgcrypto.js +277 -102
  197. package/dist/adapters/postgresql/tools/pgcrypto.js.map +1 -1
  198. package/dist/adapters/postgresql/tools/postgis/advanced.d.ts.map +1 -1
  199. package/dist/adapters/postgresql/tools/postgis/advanced.js +298 -230
  200. package/dist/adapters/postgresql/tools/postgis/advanced.js.map +1 -1
  201. package/dist/adapters/postgresql/tools/postgis/basic.d.ts.map +1 -1
  202. package/dist/adapters/postgresql/tools/postgis/basic.js +370 -251
  203. package/dist/adapters/postgresql/tools/postgis/basic.js.map +1 -1
  204. package/dist/adapters/postgresql/tools/postgis/standalone.d.ts.map +1 -1
  205. package/dist/adapters/postgresql/tools/postgis/standalone.js +135 -51
  206. package/dist/adapters/postgresql/tools/postgis/standalone.js.map +1 -1
  207. package/dist/adapters/postgresql/tools/schema.d.ts.map +1 -1
  208. package/dist/adapters/postgresql/tools/schema.js +580 -233
  209. package/dist/adapters/postgresql/tools/schema.js.map +1 -1
  210. package/dist/adapters/postgresql/tools/stats/advanced.d.ts.map +1 -1
  211. package/dist/adapters/postgresql/tools/stats/advanced.js +567 -506
  212. package/dist/adapters/postgresql/tools/stats/advanced.js.map +1 -1
  213. package/dist/adapters/postgresql/tools/stats/basic.d.ts.map +1 -1
  214. package/dist/adapters/postgresql/tools/stats/basic.js +340 -316
  215. package/dist/adapters/postgresql/tools/stats/basic.js.map +1 -1
  216. package/dist/adapters/postgresql/tools/text.d.ts.map +1 -1
  217. package/dist/adapters/postgresql/tools/text.js +690 -337
  218. package/dist/adapters/postgresql/tools/text.js.map +1 -1
  219. package/dist/adapters/postgresql/tools/transactions.d.ts.map +1 -1
  220. package/dist/adapters/postgresql/tools/transactions.js +157 -50
  221. package/dist/adapters/postgresql/tools/transactions.js.map +1 -1
  222. package/dist/adapters/postgresql/tools/vector/advanced.d.ts.map +1 -1
  223. package/dist/adapters/postgresql/tools/vector/advanced.js +18 -0
  224. package/dist/adapters/postgresql/tools/vector/advanced.js.map +1 -1
  225. package/dist/adapters/postgresql/tools/vector/basic.d.ts.map +1 -1
  226. package/dist/adapters/postgresql/tools/vector/basic.js +100 -53
  227. package/dist/adapters/postgresql/tools/vector/basic.js.map +1 -1
  228. package/dist/auth/auth-context.d.ts +28 -0
  229. package/dist/auth/auth-context.d.ts.map +1 -0
  230. package/dist/auth/auth-context.js +37 -0
  231. package/dist/auth/auth-context.js.map +1 -0
  232. package/dist/auth/scope-map.d.ts +20 -0
  233. package/dist/auth/scope-map.d.ts.map +1 -0
  234. package/dist/auth/scope-map.js +40 -0
  235. package/dist/auth/scope-map.js.map +1 -0
  236. package/dist/auth/scopes.d.ts.map +1 -1
  237. package/dist/auth/scopes.js +2 -0
  238. package/dist/auth/scopes.js.map +1 -1
  239. package/dist/cli.js +1 -1
  240. package/dist/cli.js.map +1 -1
  241. package/dist/codemode/api.d.ts +1 -0
  242. package/dist/codemode/api.d.ts.map +1 -1
  243. package/dist/codemode/api.js +35 -1
  244. package/dist/codemode/api.js.map +1 -1
  245. package/dist/codemode/index.d.ts +0 -2
  246. package/dist/codemode/index.d.ts.map +1 -1
  247. package/dist/codemode/index.js +0 -4
  248. package/dist/codemode/index.js.map +1 -1
  249. package/dist/codemode/sandbox.d.ts +14 -1
  250. package/dist/codemode/sandbox.d.ts.map +1 -1
  251. package/dist/codemode/sandbox.js +58 -19
  252. package/dist/codemode/sandbox.js.map +1 -1
  253. package/dist/codemode/types.d.ts.map +1 -1
  254. package/dist/codemode/types.js +3 -0
  255. package/dist/codemode/types.js.map +1 -1
  256. package/dist/constants/ServerInstructions.d.ts +5 -1
  257. package/dist/constants/ServerInstructions.d.ts.map +1 -1
  258. package/dist/constants/ServerInstructions.js +117 -31
  259. package/dist/constants/ServerInstructions.js.map +1 -1
  260. package/dist/filtering/ToolConstants.d.ts +22 -19
  261. package/dist/filtering/ToolConstants.d.ts.map +1 -1
  262. package/dist/filtering/ToolConstants.js +48 -37
  263. package/dist/filtering/ToolConstants.js.map +1 -1
  264. package/dist/filtering/ToolFilter.d.ts.map +1 -1
  265. package/dist/filtering/ToolFilter.js +10 -13
  266. package/dist/filtering/ToolFilter.js.map +1 -1
  267. package/dist/pool/ConnectionPool.js +1 -1
  268. package/dist/pool/ConnectionPool.js.map +1 -1
  269. package/dist/transports/http.d.ts +1 -0
  270. package/dist/transports/http.d.ts.map +1 -1
  271. package/dist/transports/http.js +75 -21
  272. package/dist/transports/http.js.map +1 -1
  273. package/dist/types/filtering.d.ts +2 -2
  274. package/dist/types/filtering.d.ts.map +1 -1
  275. package/dist/utils/icons.d.ts.map +1 -1
  276. package/dist/utils/icons.js +5 -0
  277. package/dist/utils/icons.js.map +1 -1
  278. package/dist/utils/where-clause.d.ts.map +1 -1
  279. package/dist/utils/where-clause.js +24 -0
  280. package/dist/utils/where-clause.js.map +1 -1
  281. package/package.json +20 -13
  282. package/dist/codemode/sandbox-factory.d.ts +0 -72
  283. package/dist/codemode/sandbox-factory.d.ts.map +0 -1
  284. package/dist/codemode/sandbox-factory.js +0 -88
  285. package/dist/codemode/sandbox-factory.js.map +0 -1
  286. package/dist/codemode/worker-sandbox.d.ts +0 -82
  287. package/dist/codemode/worker-sandbox.d.ts.map +0 -1
  288. package/dist/codemode/worker-sandbox.js +0 -244
  289. package/dist/codemode/worker-sandbox.js.map +0 -1
  290. package/dist/codemode/worker-script.d.ts +0 -8
  291. package/dist/codemode/worker-script.d.ts.map +0 -1
  292. package/dist/codemode/worker-script.js +0 -113
  293. package/dist/codemode/worker-script.js.map +0 -1
@@ -4,9 +4,10 @@
4
4
  * Text processing, FTS, trigrams, and fuzzy matching.
5
5
  * 14 tools total.
6
6
  */
7
- import { z } from "zod";
7
+ import { z, ZodError } from "zod";
8
8
  import { readOnly, write } from "../../../utils/annotations.js";
9
9
  import { getToolIcons } from "../../../utils/icons.js";
10
+ import { formatPostgresError } from "./core/error-helpers.js";
10
11
  import { sanitizeIdentifier, sanitizeIdentifiers, sanitizeTableName, } from "../../../utils/identifiers.js";
11
12
  import { sanitizeFtsConfig } from "../../../utils/fts-config.js";
12
13
  import { sanitizeWhereClause } from "../../../utils/where-clause.js";
@@ -43,43 +44,80 @@ function createTextSearchTool(adapter) {
43
44
  annotations: readOnly("Full-Text Search"),
44
45
  icons: getToolIcons("text", readOnly("Full-Text Search")),
45
46
  handler: async (params, _context) => {
46
- const parsed = TextSearchSchema.parse(params);
47
- const cfg = sanitizeFtsConfig(parsed.config ?? "english");
48
- // Handle both column (string) and columns (array) parameters
49
- // The preprocessor converts column columns, but we handle both for safety
50
- let cols;
51
- if (parsed.columns !== undefined && parsed.columns.length > 0) {
52
- cols = parsed.columns;
53
- }
54
- else if (parsed.column !== undefined) {
55
- cols = [parsed.column];
56
- }
57
- else {
58
- throw new Error("Either 'columns' (array) or 'column' (string) is required");
59
- }
60
- // Build qualified table name with schema support
61
- // The preprocessor guarantees table is set (converts tableName → table)
62
- const resolvedTable = parsed.table ?? parsed.tableName;
63
- if (!resolvedTable) {
64
- throw new Error("Either 'table' or 'tableName' is required");
65
- }
66
- const tableName = sanitizeTableName(resolvedTable, parsed.schema);
67
- const sanitizedCols = sanitizeIdentifiers(cols);
68
- const selectCols = parsed.select !== undefined && parsed.select.length > 0
69
- ? sanitizeIdentifiers(parsed.select).join(", ")
70
- : "*";
71
- const tsvector = sanitizedCols
72
- .map((c) => `coalesce(${c}, '')`)
73
- .join(" || ' ' || ");
74
- const limitClause = parsed.limit !== undefined && parsed.limit > 0
75
- ? ` LIMIT ${String(parsed.limit)}`
76
- : "";
77
- const sql = `SELECT ${selectCols}, ts_rank_cd(to_tsvector('${cfg}', ${tsvector}), plainto_tsquery('${cfg}', $1)) as rank
47
+ try {
48
+ const parsed = TextSearchSchema.parse(params);
49
+ const cfg = sanitizeFtsConfig(parsed.config ?? "english");
50
+ // Handle both column (string) and columns (array) parameters
51
+ // The preprocessor converts column → columns, but we handle both for safety
52
+ let cols;
53
+ if (parsed.columns !== undefined && parsed.columns.length > 0) {
54
+ cols = parsed.columns;
55
+ }
56
+ else if (parsed.column !== undefined) {
57
+ cols = [parsed.column];
58
+ }
59
+ else {
60
+ return {
61
+ success: false,
62
+ error: "Either 'columns' (array) or 'column' (string) is required",
63
+ };
64
+ }
65
+ // Build qualified table name with schema support
66
+ // The preprocessor guarantees table is set (converts tableName → table)
67
+ const resolvedTable = parsed.table ?? parsed.tableName;
68
+ if (!resolvedTable) {
69
+ return {
70
+ success: false,
71
+ error: "Either 'table' or 'tableName' is required",
72
+ };
73
+ }
74
+ const tableName = sanitizeTableName(resolvedTable, parsed.schema);
75
+ const sanitizedCols = sanitizeIdentifiers(cols);
76
+ const selectCols = parsed.select !== undefined && parsed.select.length > 0
77
+ ? sanitizeIdentifiers(parsed.select).join(", ")
78
+ : "*";
79
+ const tsvector = sanitizedCols
80
+ .map((c) => `coalesce(${c}, '')`)
81
+ .join(" || ' ' || ");
82
+ // Default limit to 100 to prevent large payloads; limit: 0 means no limit
83
+ const limitVal = parsed.limit === 0
84
+ ? null
85
+ : parsed.limit !== undefined && parsed.limit > 0
86
+ ? parsed.limit
87
+ : 100;
88
+ const limitClause = limitVal !== null ? ` LIMIT ${String(limitVal)}` : "";
89
+ const sql = `SELECT ${selectCols}, ts_rank_cd(to_tsvector('${cfg}', ${tsvector}), plainto_tsquery('${cfg}', $1)) as rank
78
90
  FROM ${tableName}
79
91
  WHERE to_tsvector('${cfg}', ${tsvector}) @@ plainto_tsquery('${cfg}', $1)
80
92
  ORDER BY rank DESC${limitClause}`;
81
- const result = await adapter.executeQuery(sql, [parsed.query]);
82
- return { rows: result.rows, count: result.rows?.length ?? 0 };
93
+ const result = await adapter.executeQuery(sql, [parsed.query]);
94
+ const count = result.rows?.length ?? 0;
95
+ const truncated = limitVal !== null && count === limitVal;
96
+ return {
97
+ rows: result.rows,
98
+ count,
99
+ ...(truncated
100
+ ? {
101
+ truncated: true,
102
+ hint: `Results limited to ${String(limitVal)}. Use limit: 0 for all rows.`,
103
+ }
104
+ : {}),
105
+ };
106
+ }
107
+ catch (error) {
108
+ if (error instanceof ZodError) {
109
+ return {
110
+ success: false,
111
+ error: `pg_text_search validation error: ${error.issues.map((e) => e.message).join(", ")}`,
112
+ };
113
+ }
114
+ return {
115
+ success: false,
116
+ error: formatPostgresError(error, {
117
+ tool: "pg_text_search",
118
+ }),
119
+ };
120
+ }
83
121
  },
84
122
  };
85
123
  }
@@ -115,42 +153,79 @@ function createTextRankTool(adapter) {
115
153
  annotations: readOnly("Text Rank"),
116
154
  icons: getToolIcons("text", readOnly("Text Rank")),
117
155
  handler: async (params, _context) => {
118
- const parsed = TextRankSchema.parse(params);
119
- const cfg = sanitizeFtsConfig(parsed.config ?? "english");
120
- const norm = parsed.normalization ?? 0;
121
- // Handle both column (string) and columns (array) parameters
122
- let cols;
123
- if (parsed.columns !== undefined && parsed.columns.length > 0) {
124
- cols = parsed.columns;
125
- }
126
- else if (parsed.column !== undefined) {
127
- cols = [parsed.column];
128
- }
129
- else {
130
- throw new Error("Either column or columns parameter is required");
131
- }
132
- // The preprocessor guarantees table is set (converts tableName → table)
133
- const resolvedTable = parsed.table ?? parsed.tableName;
134
- if (!resolvedTable) {
135
- throw new Error("Either 'table' or 'tableName' is required");
136
- }
137
- const tableName = sanitizeTableName(resolvedTable, parsed.schema);
138
- const sanitizedCols = sanitizeIdentifiers(cols);
139
- const selectCols = parsed.select !== undefined && parsed.select.length > 0
140
- ? sanitizeIdentifiers(parsed.select).join(", ")
141
- : "*";
142
- const tsvector = sanitizedCols
143
- .map((c) => `coalesce(${c}, '')`)
144
- .join(" || ' ' || ");
145
- const limitClause = parsed.limit !== undefined && parsed.limit > 0
146
- ? ` LIMIT ${String(parsed.limit)}`
147
- : "";
148
- const sql = `SELECT ${selectCols}, ts_rank_cd(to_tsvector('${cfg}', ${tsvector}), plainto_tsquery('${cfg}', $1), ${String(norm)}) as rank
156
+ try {
157
+ const parsed = TextRankSchema.parse(params);
158
+ const cfg = sanitizeFtsConfig(parsed.config ?? "english");
159
+ const norm = parsed.normalization ?? 0;
160
+ // Handle both column (string) and columns (array) parameters
161
+ let cols;
162
+ if (parsed.columns !== undefined && parsed.columns.length > 0) {
163
+ cols = parsed.columns;
164
+ }
165
+ else if (parsed.column !== undefined) {
166
+ cols = [parsed.column];
167
+ }
168
+ else {
169
+ return {
170
+ success: false,
171
+ error: "Either column or columns parameter is required",
172
+ };
173
+ }
174
+ // The preprocessor guarantees table is set (converts tableName → table)
175
+ const resolvedTable = parsed.table ?? parsed.tableName;
176
+ if (!resolvedTable) {
177
+ return {
178
+ success: false,
179
+ error: "Either 'table' or 'tableName' is required",
180
+ };
181
+ }
182
+ const tableName = sanitizeTableName(resolvedTable, parsed.schema);
183
+ const sanitizedCols = sanitizeIdentifiers(cols);
184
+ const selectCols = parsed.select !== undefined && parsed.select.length > 0
185
+ ? sanitizeIdentifiers(parsed.select).join(", ")
186
+ : "*";
187
+ const tsvector = sanitizedCols
188
+ .map((c) => `coalesce(${c}, '')`)
189
+ .join(" || ' ' || ");
190
+ // Default limit to 100 to prevent large payloads; limit: 0 means no limit
191
+ const limitVal = parsed.limit === 0
192
+ ? null
193
+ : parsed.limit !== undefined && parsed.limit > 0
194
+ ? parsed.limit
195
+ : 100;
196
+ const limitClause = limitVal !== null ? ` LIMIT ${String(limitVal)}` : "";
197
+ const sql = `SELECT ${selectCols}, ts_rank_cd(to_tsvector('${cfg}', ${tsvector}), plainto_tsquery('${cfg}', $1), ${String(norm)}) as rank
149
198
  FROM ${tableName}
150
199
  WHERE to_tsvector('${cfg}', ${tsvector}) @@ plainto_tsquery('${cfg}', $1)
151
200
  ORDER BY rank DESC${limitClause}`;
152
- const result = await adapter.executeQuery(sql, [parsed.query]);
153
- return { rows: result.rows, count: result.rows?.length ?? 0 };
201
+ const result = await adapter.executeQuery(sql, [parsed.query]);
202
+ const count = result.rows?.length ?? 0;
203
+ const truncated = limitVal !== null && count === limitVal;
204
+ return {
205
+ rows: result.rows,
206
+ count,
207
+ ...(truncated
208
+ ? {
209
+ truncated: true,
210
+ hint: `Results limited to ${String(limitVal)}. Use limit: 0 for all rows.`,
211
+ }
212
+ : {}),
213
+ };
214
+ }
215
+ catch (error) {
216
+ if (error instanceof ZodError) {
217
+ return {
218
+ success: false,
219
+ error: `pg_text_rank validation error: ${error.issues.map((e) => e.message).join(", ")}`,
220
+ };
221
+ }
222
+ return {
223
+ success: false,
224
+ error: formatPostgresError(error, {
225
+ tool: "pg_text_rank",
226
+ }),
227
+ };
228
+ }
154
229
  },
155
230
  };
156
231
  }
@@ -164,29 +239,64 @@ function createTrigramSimilarityTool(adapter) {
164
239
  annotations: readOnly("Trigram Similarity"),
165
240
  icons: getToolIcons("text", readOnly("Trigram Similarity")),
166
241
  handler: async (params, _context) => {
167
- const parsed = TrigramSimilaritySchema.parse(params);
168
- const thresh = parsed.threshold ?? 0.3;
169
- // Default limit to 100 to prevent large payloads
170
- const limitVal = parsed.limit !== undefined && parsed.limit > 0 ? parsed.limit : 100;
171
- // The preprocessor guarantees table is set (converts tableName → table)
172
- const resolvedTable = parsed.table ?? parsed.tableName;
173
- if (!resolvedTable) {
174
- throw new Error("Either 'table' or 'tableName' is required");
175
- }
176
- const tableName = sanitizeTableName(resolvedTable, parsed.schema);
177
- const columnName = sanitizeIdentifier(parsed.column);
178
- const selectCols = parsed.select !== undefined && parsed.select.length > 0
179
- ? sanitizeIdentifiers(parsed.select).join(", ")
180
- : "*";
181
- const additionalWhere = parsed.where
182
- ? ` AND (${sanitizeWhereClause(parsed.where)})`
183
- : "";
184
- const sql = `SELECT ${selectCols}, similarity(${columnName}, $1) as similarity
242
+ try {
243
+ const parsed = TrigramSimilaritySchema.parse(params);
244
+ const thresh = parsed.threshold ?? 0.3;
245
+ // Default limit to 100 to prevent large payloads; limit: 0 means no limit
246
+ const limitVal = parsed.limit === 0
247
+ ? null
248
+ : parsed.limit !== undefined && parsed.limit > 0
249
+ ? parsed.limit
250
+ : 100;
251
+ const limitClause = limitVal !== null ? ` LIMIT ${String(limitVal)}` : "";
252
+ // The preprocessor guarantees table is set (converts tableName → table)
253
+ const resolvedTable = parsed.table ?? parsed.tableName;
254
+ if (!resolvedTable) {
255
+ return {
256
+ success: false,
257
+ error: "Either 'table' or 'tableName' is required",
258
+ };
259
+ }
260
+ const tableName = sanitizeTableName(resolvedTable, parsed.schema);
261
+ const columnName = sanitizeIdentifier(parsed.column);
262
+ const selectCols = parsed.select !== undefined && parsed.select.length > 0
263
+ ? sanitizeIdentifiers(parsed.select).join(", ")
264
+ : "*";
265
+ const additionalWhere = parsed.where
266
+ ? ` AND (${sanitizeWhereClause(parsed.where)})`
267
+ : "";
268
+ const sql = `SELECT ${selectCols}, similarity(${columnName}, $1) as similarity
185
269
  FROM ${tableName}
186
270
  WHERE similarity(${columnName}, $1) > ${String(thresh)}${additionalWhere}
187
- ORDER BY similarity DESC LIMIT ${String(limitVal)}`;
188
- const result = await adapter.executeQuery(sql, [parsed.value]);
189
- return { rows: result.rows, count: result.rows?.length ?? 0 };
271
+ ORDER BY similarity DESC${limitClause}`;
272
+ const result = await adapter.executeQuery(sql, [parsed.value]);
273
+ const count = result.rows?.length ?? 0;
274
+ const truncated = limitVal !== null && count === limitVal;
275
+ return {
276
+ rows: result.rows,
277
+ count,
278
+ ...(truncated
279
+ ? {
280
+ truncated: true,
281
+ hint: `Results limited to ${String(limitVal)}. Use limit: 0 for all rows.`,
282
+ }
283
+ : {}),
284
+ };
285
+ }
286
+ catch (error) {
287
+ if (error instanceof ZodError) {
288
+ return {
289
+ success: false,
290
+ error: `pg_trigram_similarity validation error: ${error.issues.map((e) => e.message).join(", ")}`,
291
+ };
292
+ }
293
+ return {
294
+ success: false,
295
+ error: formatPostgresError(error, {
296
+ tool: "pg_trigram_similarity",
297
+ }),
298
+ };
299
+ }
190
300
  },
191
301
  };
192
302
  }
@@ -198,7 +308,10 @@ function createFuzzyMatchTool(adapter) {
198
308
  tableName: z.string().optional().describe("Table name (alias for table)"),
199
309
  column: z.string(),
200
310
  value: z.string(),
201
- method: z.enum(["soundex", "levenshtein", "metaphone"]).optional(),
311
+ method: z
312
+ .string()
313
+ .optional()
314
+ .describe("Fuzzy match method (default: levenshtein). Valid: soundex, levenshtein, metaphone"),
202
315
  maxDistance: z
203
316
  .number()
204
317
  .optional()
@@ -225,37 +338,84 @@ function createFuzzyMatchTool(adapter) {
225
338
  annotations: readOnly("Fuzzy Match"),
226
339
  icons: getToolIcons("text", readOnly("Fuzzy Match")),
227
340
  handler: async (params, _context) => {
228
- const parsed = FuzzyMatchSchema.parse(params);
229
- // Method is already validated by zod enum, default to levenshtein if not provided
230
- const method = parsed.method ?? "levenshtein";
231
- const maxDist = parsed.maxDistance ?? 3;
232
- // Default limit to 100 to prevent large payloads
233
- const limitVal = parsed.limit !== undefined && parsed.limit > 0 ? parsed.limit : 100;
234
- // The preprocessor guarantees table is set (converts tableName → table)
235
- const resolvedTable = parsed.table ?? parsed.tableName;
236
- if (!resolvedTable) {
237
- throw new Error("Either 'table' or 'tableName' is required");
341
+ try {
342
+ const parsed = FuzzyMatchSchema.parse(params);
343
+ // Validate method (moved from z.enum to handler for structured error)
344
+ const VALID_METHODS = [
345
+ "levenshtein",
346
+ "soundex",
347
+ "metaphone",
348
+ ];
349
+ const rawMethod = parsed.method ?? "levenshtein";
350
+ if (!VALID_METHODS.includes(rawMethod)) {
351
+ return {
352
+ success: false,
353
+ error: `Invalid method "${rawMethod}". Valid methods: ${VALID_METHODS.join(", ")}`,
354
+ };
355
+ }
356
+ const method = rawMethod;
357
+ const maxDist = parsed.maxDistance ?? 3;
358
+ // Default limit to 100 to prevent large payloads; limit: 0 means no limit
359
+ const limitVal = parsed.limit === 0
360
+ ? null
361
+ : parsed.limit !== undefined && parsed.limit > 0
362
+ ? parsed.limit
363
+ : 100;
364
+ const limitClause = limitVal !== null ? ` LIMIT ${String(limitVal)}` : "";
365
+ // The preprocessor guarantees table is set (converts tableName → table)
366
+ const resolvedTable = parsed.table ?? parsed.tableName;
367
+ if (!resolvedTable) {
368
+ return {
369
+ success: false,
370
+ error: "Either 'table' or 'tableName' is required",
371
+ };
372
+ }
373
+ const tableName = sanitizeTableName(resolvedTable, parsed.schema);
374
+ const columnName = sanitizeIdentifier(parsed.column);
375
+ const selectCols = parsed.select !== undefined && parsed.select.length > 0
376
+ ? sanitizeIdentifiers(parsed.select).join(", ")
377
+ : "*";
378
+ const additionalWhere = parsed.where
379
+ ? ` AND (${sanitizeWhereClause(parsed.where)})`
380
+ : "";
381
+ let sql;
382
+ if (method === "soundex") {
383
+ sql = `SELECT ${selectCols}, soundex(${columnName}) as code FROM ${tableName} WHERE soundex(${columnName}) = soundex($1)${additionalWhere}${limitClause}`;
384
+ }
385
+ else if (method === "metaphone") {
386
+ sql = `SELECT ${selectCols}, metaphone(${columnName}, 10) as code FROM ${tableName} WHERE metaphone(${columnName}, 10) = metaphone($1, 10)${additionalWhere}${limitClause}`;
387
+ }
388
+ else {
389
+ sql = `SELECT ${selectCols}, levenshtein(${columnName}, $1) as distance FROM ${tableName} WHERE levenshtein(${columnName}, $1) <= ${String(maxDist)}${additionalWhere} ORDER BY distance${limitClause}`;
390
+ }
391
+ const result = await adapter.executeQuery(sql, [parsed.value]);
392
+ const count = result.rows?.length ?? 0;
393
+ const truncated = limitVal !== null && count === limitVal;
394
+ return {
395
+ rows: result.rows,
396
+ count,
397
+ ...(truncated
398
+ ? {
399
+ truncated: true,
400
+ hint: `Results limited to ${String(limitVal)}. Use limit: 0 for all rows.`,
401
+ }
402
+ : {}),
403
+ };
238
404
  }
239
- const tableName = sanitizeTableName(resolvedTable, parsed.schema);
240
- const columnName = sanitizeIdentifier(parsed.column);
241
- const selectCols = parsed.select !== undefined && parsed.select.length > 0
242
- ? sanitizeIdentifiers(parsed.select).join(", ")
243
- : "*";
244
- const additionalWhere = parsed.where
245
- ? ` AND (${sanitizeWhereClause(parsed.where)})`
246
- : "";
247
- let sql;
248
- if (method === "soundex") {
249
- sql = `SELECT ${selectCols}, soundex(${columnName}) as code FROM ${tableName} WHERE soundex(${columnName}) = soundex($1)${additionalWhere} LIMIT ${String(limitVal)}`;
405
+ catch (error) {
406
+ if (error instanceof ZodError) {
407
+ return {
408
+ success: false,
409
+ error: `pg_fuzzy_match validation error: ${error.issues.map((e) => e.message).join(", ")}`,
410
+ };
411
+ }
412
+ return {
413
+ success: false,
414
+ error: formatPostgresError(error, {
415
+ tool: "pg_fuzzy_match",
416
+ }),
417
+ };
250
418
  }
251
- else if (method === "metaphone") {
252
- sql = `SELECT ${selectCols}, metaphone(${columnName}, 10) as code FROM ${tableName} WHERE metaphone(${columnName}, 10) = metaphone($1, 10)${additionalWhere} LIMIT ${String(limitVal)}`;
253
- }
254
- else {
255
- sql = `SELECT ${selectCols}, levenshtein(${columnName}, $1) as distance FROM ${tableName} WHERE levenshtein(${columnName}, $1) <= ${String(maxDist)}${additionalWhere} ORDER BY distance LIMIT ${String(limitVal)}`;
256
- }
257
- const result = await adapter.executeQuery(sql, [parsed.value]);
258
- return { rows: result.rows, count: result.rows?.length ?? 0 };
259
419
  },
260
420
  };
261
421
  }
@@ -269,27 +429,61 @@ function createRegexpMatchTool(adapter) {
269
429
  annotations: readOnly("Regexp Match"),
270
430
  icons: getToolIcons("text", readOnly("Regexp Match")),
271
431
  handler: async (params, _context) => {
272
- const parsed = RegexpMatchSchema.parse(params);
273
- // The preprocessor guarantees table is set (converts tableName → table)
274
- const resolvedTable = parsed.table ?? parsed.tableName;
275
- if (!resolvedTable) {
276
- throw new Error("Either 'table' or 'tableName' is required");
432
+ try {
433
+ const parsed = RegexpMatchSchema.parse(params);
434
+ // The preprocessor guarantees table is set (converts tableName → table)
435
+ const resolvedTable = parsed.table ?? parsed.tableName;
436
+ if (!resolvedTable) {
437
+ return {
438
+ success: false,
439
+ error: "Either 'table' or 'tableName' is required",
440
+ };
441
+ }
442
+ const tableName = sanitizeTableName(resolvedTable, parsed.schema);
443
+ const columnName = sanitizeIdentifier(parsed.column);
444
+ const selectCols = parsed.select !== undefined && parsed.select.length > 0
445
+ ? sanitizeIdentifiers(parsed.select).join(", ")
446
+ : "*";
447
+ const op = parsed.flags?.includes("i") ? "~*" : "~";
448
+ const additionalWhere = parsed.where
449
+ ? ` AND (${sanitizeWhereClause(parsed.where)})`
450
+ : "";
451
+ // Default limit to 100 to prevent large payloads; limit: 0 means no limit
452
+ const limitVal = parsed.limit === 0
453
+ ? null
454
+ : parsed.limit !== undefined && parsed.limit > 0
455
+ ? parsed.limit
456
+ : 100;
457
+ const limitClause = limitVal !== null ? ` LIMIT ${String(limitVal)}` : "";
458
+ const sql = `SELECT ${selectCols} FROM ${tableName} WHERE ${columnName} ${op} $1${additionalWhere}${limitClause}`;
459
+ const result = await adapter.executeQuery(sql, [parsed.pattern]);
460
+ const count = result.rows?.length ?? 0;
461
+ const truncated = limitVal !== null && count === limitVal;
462
+ return {
463
+ rows: result.rows,
464
+ count,
465
+ ...(truncated
466
+ ? {
467
+ truncated: true,
468
+ hint: `Results limited to ${String(limitVal)}. Use limit: 0 for all rows.`,
469
+ }
470
+ : {}),
471
+ };
472
+ }
473
+ catch (error) {
474
+ if (error instanceof ZodError) {
475
+ return {
476
+ success: false,
477
+ error: `pg_regexp_match validation error: ${error.issues.map((e) => e.message).join(", ")}`,
478
+ };
479
+ }
480
+ return {
481
+ success: false,
482
+ error: formatPostgresError(error, {
483
+ tool: "pg_regexp_match",
484
+ }),
485
+ };
277
486
  }
278
- const tableName = sanitizeTableName(resolvedTable, parsed.schema);
279
- const columnName = sanitizeIdentifier(parsed.column);
280
- const selectCols = parsed.select !== undefined && parsed.select.length > 0
281
- ? sanitizeIdentifiers(parsed.select).join(", ")
282
- : "*";
283
- const op = parsed.flags?.includes("i") ? "~*" : "~";
284
- const additionalWhere = parsed.where
285
- ? ` AND (${sanitizeWhereClause(parsed.where)})`
286
- : "";
287
- // Default limit to 100 to prevent large payloads
288
- const limitVal = parsed.limit !== undefined && parsed.limit > 0 ? parsed.limit : 100;
289
- const limitClause = ` LIMIT ${String(limitVal)}`;
290
- const sql = `SELECT ${selectCols} FROM ${tableName} WHERE ${columnName} ${op} $1${additionalWhere}${limitClause}`;
291
- const result = await adapter.executeQuery(sql, [parsed.pattern]);
292
- return { rows: result.rows, count: result.rows?.length ?? 0 };
293
487
  },
294
488
  };
295
489
  }
@@ -327,27 +521,61 @@ function createLikeSearchTool(adapter) {
327
521
  annotations: readOnly("LIKE Search"),
328
522
  icons: getToolIcons("text", readOnly("LIKE Search")),
329
523
  handler: async (params, _context) => {
330
- const parsed = LikeSearchSchema.parse(params);
331
- // The preprocessor guarantees table is set (converts tableName → table)
332
- const resolvedTable = parsed.table ?? parsed.tableName;
333
- if (!resolvedTable) {
334
- throw new Error("Either 'table' or 'tableName' is required");
524
+ try {
525
+ const parsed = LikeSearchSchema.parse(params);
526
+ // The preprocessor guarantees table is set (converts tableName → table)
527
+ const resolvedTable = parsed.table ?? parsed.tableName;
528
+ if (!resolvedTable) {
529
+ return {
530
+ success: false,
531
+ error: "Either 'table' or 'tableName' is required",
532
+ };
533
+ }
534
+ const tableName = sanitizeTableName(resolvedTable, parsed.schema);
535
+ const columnName = sanitizeIdentifier(parsed.column);
536
+ const selectCols = parsed.select !== undefined && parsed.select.length > 0
537
+ ? sanitizeIdentifiers(parsed.select).join(", ")
538
+ : "*";
539
+ const op = parsed.caseSensitive === true ? "LIKE" : "ILIKE";
540
+ const additionalWhere = parsed.where
541
+ ? ` AND (${sanitizeWhereClause(parsed.where)})`
542
+ : "";
543
+ // Default limit to 100 to prevent large payloads; limit: 0 means no limit
544
+ const limitVal = parsed.limit === 0
545
+ ? null
546
+ : parsed.limit !== undefined && parsed.limit > 0
547
+ ? parsed.limit
548
+ : 100;
549
+ const limitClause = limitVal !== null ? ` LIMIT ${String(limitVal)}` : "";
550
+ const sql = `SELECT ${selectCols} FROM ${tableName} WHERE ${columnName} ${op} $1${additionalWhere}${limitClause}`;
551
+ const result = await adapter.executeQuery(sql, [parsed.pattern]);
552
+ const count = result.rows?.length ?? 0;
553
+ const truncated = limitVal !== null && count === limitVal;
554
+ return {
555
+ rows: result.rows,
556
+ count,
557
+ ...(truncated
558
+ ? {
559
+ truncated: true,
560
+ hint: `Results limited to ${String(limitVal)}. Use limit: 0 for all rows.`,
561
+ }
562
+ : {}),
563
+ };
564
+ }
565
+ catch (error) {
566
+ if (error instanceof ZodError) {
567
+ return {
568
+ success: false,
569
+ error: `pg_like_search validation error: ${error.issues.map((e) => e.message).join(", ")}`,
570
+ };
571
+ }
572
+ return {
573
+ success: false,
574
+ error: formatPostgresError(error, {
575
+ tool: "pg_like_search",
576
+ }),
577
+ };
335
578
  }
336
- const tableName = sanitizeTableName(resolvedTable, parsed.schema);
337
- const columnName = sanitizeIdentifier(parsed.column);
338
- const selectCols = parsed.select !== undefined && parsed.select.length > 0
339
- ? sanitizeIdentifiers(parsed.select).join(", ")
340
- : "*";
341
- const op = parsed.caseSensitive === true ? "LIKE" : "ILIKE";
342
- const additionalWhere = parsed.where
343
- ? ` AND (${sanitizeWhereClause(parsed.where)})`
344
- : "";
345
- // Default limit to 100 to prevent large payloads
346
- const limitVal = parsed.limit !== undefined && parsed.limit > 0 ? parsed.limit : 100;
347
- const limitClause = ` LIMIT ${String(limitVal)}`;
348
- const sql = `SELECT ${selectCols} FROM ${tableName} WHERE ${columnName} ${op} $1${additionalWhere}${limitClause}`;
349
- const result = await adapter.executeQuery(sql, [parsed.pattern]);
350
- return { rows: result.rows, count: result.rows?.length ?? 0 };
351
579
  },
352
580
  };
353
581
  }
@@ -395,40 +623,74 @@ function createTextHeadlineTool(adapter) {
395
623
  annotations: readOnly("Text Headline"),
396
624
  icons: getToolIcons("text", readOnly("Text Headline")),
397
625
  handler: async (params, _context) => {
398
- const parsed = HeadlineSchema.parse(params);
399
- const cfg = sanitizeFtsConfig(parsed.config ?? "english");
400
- // Build options string from individual params or use provided options
401
- let opts;
402
- if (parsed.options) {
403
- opts = parsed.options;
404
- }
405
- else {
406
- const optParts = [];
407
- optParts.push(`StartSel=${parsed.startSel ?? "<b>"}`);
408
- optParts.push(`StopSel=${parsed.stopSel ?? "</b>"}`);
409
- optParts.push(`MaxWords=${String(parsed.maxWords ?? 35)}`);
410
- optParts.push(`MinWords=${String(parsed.minWords ?? 15)}`);
411
- opts = optParts.join(", ");
412
- }
413
- // The preprocessor guarantees table is set (converts tableName → table)
414
- const resolvedTable = parsed.table ?? parsed.tableName;
415
- if (!resolvedTable) {
416
- throw new Error("Either 'table' or 'tableName' is required");
417
- }
418
- const tableName = sanitizeTableName(resolvedTable, parsed.schema);
419
- const columnName = sanitizeIdentifier(parsed.column);
420
- // Use provided select columns, or default to * (user should specify PK for stable identification)
421
- const selectCols = parsed.select !== undefined && parsed.select.length > 0
422
- ? sanitizeIdentifiers(parsed.select).join(", ") + ", "
423
- : "";
424
- const limitClause = parsed.limit !== undefined && parsed.limit > 0
425
- ? ` LIMIT ${String(parsed.limit)}`
426
- : "";
427
- const sql = `SELECT ${selectCols}ts_headline('${cfg}', ${columnName}, plainto_tsquery('${cfg}', $1), '${opts}') as headline
626
+ try {
627
+ const parsed = HeadlineSchema.parse(params);
628
+ const cfg = sanitizeFtsConfig(parsed.config ?? "english");
629
+ // Build options string from individual params or use provided options
630
+ let opts;
631
+ if (parsed.options) {
632
+ opts = parsed.options;
633
+ }
634
+ else {
635
+ const optParts = [];
636
+ optParts.push(`StartSel=${parsed.startSel ?? "<b>"}`);
637
+ optParts.push(`StopSel=${parsed.stopSel ?? "</b>"}`);
638
+ optParts.push(`MaxWords=${String(parsed.maxWords ?? 35)}`);
639
+ optParts.push(`MinWords=${String(parsed.minWords ?? 15)}`);
640
+ opts = optParts.join(", ");
641
+ }
642
+ // The preprocessor guarantees table is set (converts tableName → table)
643
+ const resolvedTable = parsed.table ?? parsed.tableName;
644
+ if (!resolvedTable) {
645
+ return {
646
+ success: false,
647
+ error: "Either 'table' or 'tableName' is required",
648
+ };
649
+ }
650
+ const tableName = sanitizeTableName(resolvedTable, parsed.schema);
651
+ const columnName = sanitizeIdentifier(parsed.column);
652
+ // Use provided select columns, or default to * (user should specify PK for stable identification)
653
+ const selectCols = parsed.select !== undefined && parsed.select.length > 0
654
+ ? sanitizeIdentifiers(parsed.select).join(", ") + ", "
655
+ : "";
656
+ // Default limit to 100 to prevent large payloads; limit: 0 means no limit
657
+ const limitVal = parsed.limit === 0
658
+ ? null
659
+ : parsed.limit !== undefined && parsed.limit > 0
660
+ ? parsed.limit
661
+ : 100;
662
+ const limitClause = limitVal !== null ? ` LIMIT ${String(limitVal)}` : "";
663
+ const sql = `SELECT ${selectCols}ts_headline('${cfg}', ${columnName}, plainto_tsquery('${cfg}', $1), '${opts}') as headline
428
664
  FROM ${tableName}
429
665
  WHERE to_tsvector('${cfg}', ${columnName}) @@ plainto_tsquery('${cfg}', $1)${limitClause}`;
430
- const result = await adapter.executeQuery(sql, [parsed.query]);
431
- return { rows: result.rows, count: result.rows?.length ?? 0 };
666
+ const result = await adapter.executeQuery(sql, [parsed.query]);
667
+ const count = result.rows?.length ?? 0;
668
+ const truncated = limitVal !== null && count === limitVal;
669
+ return {
670
+ rows: result.rows,
671
+ count,
672
+ ...(truncated
673
+ ? {
674
+ truncated: true,
675
+ hint: `Results limited to ${String(limitVal)}. Use limit: 0 for all rows.`,
676
+ }
677
+ : {}),
678
+ };
679
+ }
680
+ catch (error) {
681
+ if (error instanceof ZodError) {
682
+ return {
683
+ success: false,
684
+ error: `pg_text_headline validation error: ${error.issues.map((e) => e.message).join(", ")}`,
685
+ };
686
+ }
687
+ return {
688
+ success: false,
689
+ error: formatPostgresError(error, {
690
+ tool: "pg_text_headline",
691
+ }),
692
+ };
693
+ }
432
694
  },
433
695
  };
434
696
  }
@@ -461,36 +723,55 @@ function createFtsIndexTool(adapter) {
461
723
  annotations: write("Create FTS Index"),
462
724
  icons: getToolIcons("text", write("Create FTS Index")),
463
725
  handler: async (params, _context) => {
464
- const parsed = FtsIndexSchema.parse(params);
465
- const cfg = sanitizeFtsConfig(parsed.config ?? "english");
466
- // The preprocessor guarantees table is set (converts tableName → table)
467
- const resolvedTable = parsed.table ?? parsed.tableName;
468
- if (!resolvedTable) {
469
- throw new Error("Either 'table' or 'tableName' is required");
726
+ try {
727
+ const parsed = FtsIndexSchema.parse(params);
728
+ const cfg = sanitizeFtsConfig(parsed.config ?? "english");
729
+ // The preprocessor guarantees table is set (converts tableName → table)
730
+ const resolvedTable = parsed.table ?? parsed.tableName;
731
+ if (!resolvedTable) {
732
+ return {
733
+ success: false,
734
+ error: "Either 'table' or 'tableName' is required",
735
+ };
736
+ }
737
+ const defaultIndexName = `idx_${resolvedTable}_${parsed.column}_fts`;
738
+ const resolvedIndexName = parsed.name ?? defaultIndexName;
739
+ const indexName = sanitizeIdentifier(resolvedIndexName);
740
+ // Default to IF NOT EXISTS for safer operation (skip existing indexes)
741
+ const useIfNotExists = parsed.ifNotExists !== false;
742
+ const ifNotExists = useIfNotExists ? "IF NOT EXISTS " : "";
743
+ // Build qualified table name with schema support
744
+ const tableName = sanitizeTableName(resolvedTable, parsed.schema);
745
+ const columnName = sanitizeIdentifier(parsed.column);
746
+ // Check if index exists before creation (to accurately report 'skipped')
747
+ let existedBefore = false;
748
+ if (useIfNotExists) {
749
+ const checkResult = await adapter.executeQuery(`SELECT 1 FROM pg_indexes WHERE indexname = $1 LIMIT 1`, [resolvedIndexName]);
750
+ existedBefore = (checkResult.rows?.length ?? 0) > 0;
751
+ }
752
+ const sql = `CREATE INDEX ${ifNotExists}${indexName} ON ${tableName} USING gin(to_tsvector('${cfg}', ${columnName}))`;
753
+ await adapter.executeQuery(sql);
754
+ return {
755
+ success: true,
756
+ index: resolvedIndexName,
757
+ config: cfg,
758
+ skipped: existedBefore,
759
+ };
470
760
  }
471
- const defaultIndexName = `idx_${resolvedTable}_${parsed.column}_fts`;
472
- const resolvedIndexName = parsed.name ?? defaultIndexName;
473
- const indexName = sanitizeIdentifier(resolvedIndexName);
474
- // Default to IF NOT EXISTS for safer operation (skip existing indexes)
475
- const useIfNotExists = parsed.ifNotExists !== false;
476
- const ifNotExists = useIfNotExists ? "IF NOT EXISTS " : "";
477
- // Build qualified table name with schema support
478
- const tableName = sanitizeTableName(resolvedTable, parsed.schema);
479
- const columnName = sanitizeIdentifier(parsed.column);
480
- // Check if index exists before creation (to accurately report 'skipped')
481
- let existedBefore = false;
482
- if (useIfNotExists) {
483
- const checkResult = await adapter.executeQuery(`SELECT 1 FROM pg_indexes WHERE indexname = $1 LIMIT 1`, [resolvedIndexName]);
484
- existedBefore = (checkResult.rows?.length ?? 0) > 0;
761
+ catch (error) {
762
+ if (error instanceof ZodError) {
763
+ return {
764
+ success: false,
765
+ error: `pg_create_fts_index validation error: ${error.issues.map((e) => e.message).join(", ")}`,
766
+ };
767
+ }
768
+ return {
769
+ success: false,
770
+ error: formatPostgresError(error, {
771
+ tool: "pg_create_fts_index",
772
+ }),
773
+ };
485
774
  }
486
- const sql = `CREATE INDEX ${ifNotExists}${indexName} ON ${tableName} USING gin(to_tsvector('${cfg}', ${columnName}))`;
487
- await adapter.executeQuery(sql);
488
- return {
489
- success: true,
490
- index: resolvedIndexName,
491
- config: cfg,
492
- skipped: existedBefore,
493
- };
494
775
  },
495
776
  };
496
777
  }
@@ -507,11 +788,27 @@ function createTextNormalizeTool(adapter) {
507
788
  annotations: readOnly("Text Normalize"),
508
789
  icons: getToolIcons("text", readOnly("Text Normalize")),
509
790
  handler: async (params, _context) => {
510
- const parsed = NormalizeSchema.parse(params ?? {});
511
- // Ensure unaccent extension is available
512
- await adapter.executeQuery("CREATE EXTENSION IF NOT EXISTS unaccent");
513
- const result = await adapter.executeQuery(`SELECT unaccent($1) as normalized`, [parsed.text]);
514
- return { normalized: result.rows?.[0]?.["normalized"] };
791
+ try {
792
+ const parsed = NormalizeSchema.parse(params ?? {});
793
+ // Ensure unaccent extension is available
794
+ await adapter.executeQuery("CREATE EXTENSION IF NOT EXISTS unaccent");
795
+ const result = await adapter.executeQuery(`SELECT unaccent($1) as normalized`, [parsed.text]);
796
+ return { normalized: result.rows?.[0]?.["normalized"] };
797
+ }
798
+ catch (error) {
799
+ if (error instanceof ZodError) {
800
+ return {
801
+ success: false,
802
+ error: `pg_text_normalize validation error: ${error.issues.map((e) => e.message).join(", ")}`,
803
+ };
804
+ }
805
+ return {
806
+ success: false,
807
+ error: formatPostgresError(error, {
808
+ tool: "pg_text_normalize",
809
+ }),
810
+ };
811
+ }
515
812
  },
516
813
  };
517
814
  }
@@ -535,95 +832,109 @@ function createTextSentimentTool(_adapter) {
535
832
  annotations: readOnly("Text Sentiment"),
536
833
  icons: getToolIcons("text", readOnly("Text Sentiment")),
537
834
  handler: (params, _context) => {
538
- const parsed = SentimentSchema.parse(params ?? {});
539
- const text = parsed.text.toLowerCase();
540
- const positiveWords = [
541
- "good",
542
- "great",
543
- "excellent",
544
- "amazing",
545
- "wonderful",
546
- "fantastic",
547
- "love",
548
- "happy",
549
- "positive",
550
- "best",
551
- "beautiful",
552
- "awesome",
553
- "perfect",
554
- "nice",
555
- "helpful",
556
- "thank",
557
- "thanks",
558
- "pleased",
559
- "satisfied",
560
- "recommend",
561
- "enjoy",
562
- "impressive",
563
- "brilliant",
564
- ];
565
- const negativeWords = [
566
- "bad",
567
- "terrible",
568
- "awful",
569
- "horrible",
570
- "worst",
571
- "hate",
572
- "angry",
573
- "disappointed",
574
- "poor",
575
- "wrong",
576
- "problem",
577
- "issue",
578
- "fail",
579
- "failed",
580
- "broken",
581
- "useless",
582
- "waste",
583
- "frustrating",
584
- "annoyed",
585
- "unhappy",
586
- "negative",
587
- "complaint",
588
- "slow",
589
- ];
590
- const words = text.split(/\s+/);
591
- const matchedPositive = words
592
- .map((w) => w.replace(/[^a-z]/g, ""))
593
- .filter((w) => positiveWords.includes(w));
594
- const matchedNegative = words
595
- .map((w) => w.replace(/[^a-z]/g, ""))
596
- .filter((w) => negativeWords.includes(w));
597
- const positiveScore = matchedPositive.length;
598
- const negativeScore = matchedNegative.length;
599
- const totalScore = positiveScore - negativeScore;
600
- let sentiment;
601
- if (totalScore > 2)
602
- sentiment = "very_positive";
603
- else if (totalScore > 0)
604
- sentiment = "positive";
605
- else if (totalScore < -2)
606
- sentiment = "very_negative";
607
- else if (totalScore < 0)
608
- sentiment = "negative";
609
- else
610
- sentiment = "neutral";
611
- const result = {
612
- sentiment,
613
- score: totalScore,
614
- positiveCount: positiveScore,
615
- negativeCount: negativeScore,
616
- confidence: positiveScore + negativeScore > 3
617
- ? "high"
618
- : positiveScore + negativeScore > 1
619
- ? "medium"
620
- : "low",
621
- };
622
- if (parsed.returnWords) {
623
- result.matchedPositive = matchedPositive;
624
- result.matchedNegative = matchedNegative;
835
+ try {
836
+ const parsed = SentimentSchema.parse(params ?? {});
837
+ const text = parsed.text.toLowerCase();
838
+ const positiveWords = [
839
+ "good",
840
+ "great",
841
+ "excellent",
842
+ "amazing",
843
+ "wonderful",
844
+ "fantastic",
845
+ "love",
846
+ "happy",
847
+ "positive",
848
+ "best",
849
+ "beautiful",
850
+ "awesome",
851
+ "perfect",
852
+ "nice",
853
+ "helpful",
854
+ "thank",
855
+ "thanks",
856
+ "pleased",
857
+ "satisfied",
858
+ "recommend",
859
+ "enjoy",
860
+ "impressive",
861
+ "brilliant",
862
+ ];
863
+ const negativeWords = [
864
+ "bad",
865
+ "terrible",
866
+ "awful",
867
+ "horrible",
868
+ "worst",
869
+ "hate",
870
+ "angry",
871
+ "disappointed",
872
+ "poor",
873
+ "wrong",
874
+ "problem",
875
+ "issue",
876
+ "fail",
877
+ "failed",
878
+ "broken",
879
+ "useless",
880
+ "waste",
881
+ "frustrating",
882
+ "annoyed",
883
+ "unhappy",
884
+ "negative",
885
+ "complaint",
886
+ "slow",
887
+ ];
888
+ const words = text.split(/\s+/);
889
+ const matchedPositive = words
890
+ .map((w) => w.replace(/[^a-z]/g, ""))
891
+ .filter((w) => positiveWords.includes(w));
892
+ const matchedNegative = words
893
+ .map((w) => w.replace(/[^a-z]/g, ""))
894
+ .filter((w) => negativeWords.includes(w));
895
+ const positiveScore = matchedPositive.length;
896
+ const negativeScore = matchedNegative.length;
897
+ const totalScore = positiveScore - negativeScore;
898
+ let sentiment;
899
+ if (totalScore > 2)
900
+ sentiment = "very_positive";
901
+ else if (totalScore > 0)
902
+ sentiment = "positive";
903
+ else if (totalScore < -2)
904
+ sentiment = "very_negative";
905
+ else if (totalScore < 0)
906
+ sentiment = "negative";
907
+ else
908
+ sentiment = "neutral";
909
+ const result = {
910
+ sentiment,
911
+ score: totalScore,
912
+ positiveCount: positiveScore,
913
+ negativeCount: negativeScore,
914
+ confidence: positiveScore + negativeScore > 3
915
+ ? "high"
916
+ : positiveScore + negativeScore > 1
917
+ ? "medium"
918
+ : "low",
919
+ };
920
+ if (parsed.returnWords) {
921
+ result.matchedPositive = matchedPositive;
922
+ result.matchedNegative = matchedNegative;
923
+ }
924
+ return Promise.resolve(result);
925
+ }
926
+ catch (error) {
927
+ if (error instanceof ZodError) {
928
+ return Promise.resolve({
929
+ success: false,
930
+ error: `pg_text_sentiment validation error: ${error.issues.map((e) => e.message).join(", ")}`,
931
+ });
932
+ }
933
+ return Promise.resolve({
934
+ success: false,
935
+ error: error instanceof Error ? error.message : "Unknown error occurred",
936
+ });
625
937
  }
626
- return Promise.resolve(result);
627
938
  },
628
939
  };
629
940
  }
@@ -647,10 +958,26 @@ function createTextToVectorTool(adapter) {
647
958
  annotations: readOnly("Text to Vector"),
648
959
  icons: getToolIcons("text", readOnly("Text to Vector")),
649
960
  handler: async (params, _context) => {
650
- const parsed = ToVectorSchema.parse(params ?? {});
651
- const cfg = parsed.config ?? "english";
652
- const result = await adapter.executeQuery(`SELECT to_tsvector($1, $2) as vector`, [cfg, parsed.text]);
653
- return { vector: result.rows?.[0]?.["vector"] };
961
+ try {
962
+ const parsed = ToVectorSchema.parse(params ?? {});
963
+ const cfg = parsed.config ?? "english";
964
+ const result = await adapter.executeQuery(`SELECT to_tsvector($1, $2) as vector`, [cfg, parsed.text]);
965
+ return { vector: result.rows?.[0]?.["vector"] };
966
+ }
967
+ catch (error) {
968
+ if (error instanceof ZodError) {
969
+ return {
970
+ success: false,
971
+ error: `pg_text_to_vector validation error: ${error.issues.map((e) => e.message).join(", ")}`,
972
+ };
973
+ }
974
+ return {
975
+ success: false,
976
+ error: formatPostgresError(error, {
977
+ tool: "pg_text_to_vector",
978
+ }),
979
+ };
980
+ }
654
981
  },
655
982
  };
656
983
  }
@@ -678,22 +1005,38 @@ function createTextToQueryTool(adapter) {
678
1005
  annotations: readOnly("Text to Query"),
679
1006
  icons: getToolIcons("text", readOnly("Text to Query")),
680
1007
  handler: async (params, _context) => {
681
- const parsed = ToQuerySchema.parse(params ?? {});
682
- const cfg = parsed.config ?? "english";
683
- const mode = parsed.mode ?? "plain";
684
- let fn;
685
- switch (mode) {
686
- case "phrase":
687
- fn = "phraseto_tsquery";
688
- break;
689
- case "websearch":
690
- fn = "websearch_to_tsquery";
691
- break;
692
- default:
693
- fn = "plainto_tsquery";
1008
+ try {
1009
+ const parsed = ToQuerySchema.parse(params ?? {});
1010
+ const cfg = parsed.config ?? "english";
1011
+ const mode = parsed.mode ?? "plain";
1012
+ let fn;
1013
+ switch (mode) {
1014
+ case "phrase":
1015
+ fn = "phraseto_tsquery";
1016
+ break;
1017
+ case "websearch":
1018
+ fn = "websearch_to_tsquery";
1019
+ break;
1020
+ default:
1021
+ fn = "plainto_tsquery";
1022
+ }
1023
+ const result = await adapter.executeQuery(`SELECT ${fn}($1, $2) as query`, [cfg, parsed.text]);
1024
+ return { query: result.rows?.[0]?.["query"], mode };
1025
+ }
1026
+ catch (error) {
1027
+ if (error instanceof ZodError) {
1028
+ return {
1029
+ success: false,
1030
+ error: `pg_text_to_query validation error: ${error.issues.map((e) => e.message).join(", ")}`,
1031
+ };
1032
+ }
1033
+ return {
1034
+ success: false,
1035
+ error: formatPostgresError(error, {
1036
+ tool: "pg_text_to_query",
1037
+ }),
1038
+ };
694
1039
  }
695
- const result = await adapter.executeQuery(`SELECT ${fn}($1, $2) as query`, [cfg, parsed.text]);
696
- return { query: result.rows?.[0]?.["query"], mode };
697
1040
  },
698
1041
  };
699
1042
  }
@@ -710,8 +1053,9 @@ function createTextSearchConfigTool(adapter) {
710
1053
  annotations: readOnly("Search Configurations"),
711
1054
  icons: getToolIcons("text", readOnly("Search Configurations")),
712
1055
  handler: async (_params, _context) => {
713
- const result = await adapter.executeQuery(`
714
- SELECT
1056
+ try {
1057
+ const result = await adapter.executeQuery(`
1058
+ SELECT
715
1059
  c.cfgname as name,
716
1060
  n.nspname as schema,
717
1061
  obj_description(c.oid, 'pg_ts_config') as description
@@ -719,10 +1063,19 @@ function createTextSearchConfigTool(adapter) {
719
1063
  JOIN pg_namespace n ON n.oid = c.cfgnamespace
720
1064
  ORDER BY c.cfgname
721
1065
  `);
722
- return {
723
- configs: result.rows ?? [],
724
- count: result.rows?.length ?? 0,
725
- };
1066
+ return {
1067
+ configs: result.rows ?? [],
1068
+ count: result.rows?.length ?? 0,
1069
+ };
1070
+ }
1071
+ catch (error) {
1072
+ return {
1073
+ success: false,
1074
+ error: formatPostgresError(error, {
1075
+ tool: "pg_text_search_config",
1076
+ }),
1077
+ };
1078
+ }
726
1079
  },
727
1080
  };
728
1081
  }