@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
@@ -3,10 +3,12 @@
3
3
  *
4
4
  * Core spatial tools: extension, geometry_column, point_in_polygon, distance, buffer, intersection, bounding_box, spatial_index.
5
5
  */
6
- import { z } from "zod";
6
+ import { z, ZodError } from "zod";
7
7
  import { readOnly, write } from "../../../../utils/annotations.js";
8
8
  import { getToolIcons } from "../../../../utils/icons.js";
9
+ import { parsePostgresError, formatPostgresError, } from "../core/error-helpers.js";
9
10
  import { sanitizeIdentifier, sanitizeTableName, } from "../../../../utils/identifiers.js";
11
+ import { sanitizeWhereClause } from "../../../../utils/where-clause.js";
10
12
  import { GeometryColumnSchemaBase, GeometryColumnSchema, GeometryDistanceSchemaBase, GeometryDistanceSchema, PointInPolygonSchemaBase, PointInPolygonSchema, SpatialIndexSchemaBase, SpatialIndexSchema, BufferSchemaBase, BufferSchema, IntersectionSchemaBase, IntersectionSchema, BoundingBoxSchemaBase, BoundingBoxSchema,
11
13
  // Output schemas
12
14
  PostgisCreateExtensionOutputSchema, GeometryColumnOutputSchema, PointInPolygonOutputSchema, DistanceOutputSchema, BufferOutputSchema, IntersectionOutputSchema, BoundingBoxOutputSchema, SpatialIndexOutputSchema, } from "../../schemas/index.js";
@@ -80,8 +82,14 @@ export function createGeometryColumnTool(adapter) {
80
82
  suggestion: "Create the table first, then add the geometry column.",
81
83
  };
82
84
  }
83
- const sql = `SELECT AddGeometryColumn('${schemaName}', '${parsed.table}', '${parsed.column}', ${String(srid)}, '${geomType}', 2)`;
84
- await adapter.executeQuery(sql);
85
+ const sql = `SELECT AddGeometryColumn($1, $2, $3, $4, $5, 2)`;
86
+ await adapter.executeQuery(sql, [
87
+ schemaName,
88
+ parsed.table,
89
+ parsed.column,
90
+ srid,
91
+ geomType,
92
+ ]);
85
93
  return {
86
94
  success: true,
87
95
  table: parsed.table,
@@ -102,47 +110,65 @@ export function createPointInPolygonTool(adapter) {
102
110
  annotations: readOnly("Point in Polygon"),
103
111
  icons: getToolIcons("postgis", readOnly("Point in Polygon")),
104
112
  handler: async (params, _context) => {
105
- const { table, column, point, schema } = PointInPolygonSchema.parse(params ?? {});
106
- const schemaName = schema ?? "public";
107
- const tableName = sanitizeTableName(table, schemaName !== "public" ? schemaName : undefined);
108
- const columnName = sanitizeIdentifier(column);
109
- // Check geometry type and warn if not polygon
110
- const typeCheckSql = `SELECT DISTINCT GeometryType(${columnName}) as geom_type FROM ${tableName} WHERE ${columnName} IS NOT NULL LIMIT 1`;
111
- const typeResult = await adapter.executeQuery(typeCheckSql);
112
- const geomType = typeResult.rows?.[0]?.["geom_type"];
113
- const isPolygonType = geomType?.toUpperCase()?.includes("POLYGON") ?? false;
114
- // Get non-geometry columns to avoid returning raw WKB
115
- const colQuery = `
116
- SELECT column_name FROM information_schema.columns
117
- WHERE table_schema = $1 AND table_name = $2
118
- AND udt_name NOT IN ('geometry', 'geography')
119
- ORDER BY ordinal_position
120
- `;
121
- const colResult = await adapter.executeQuery(colQuery, [
122
- schemaName,
123
- table,
124
- ]);
125
- const nonGeomCols = (colResult.rows ?? [])
126
- .map((row) => sanitizeIdentifier(String(row["column_name"])))
127
- .join(", ");
128
- // Select non-geometry columns + readable geometry representation
129
- const selectCols = nonGeomCols.length > 0
130
- ? `${nonGeomCols}, ST_AsText(${columnName}) as geometry_text`
131
- : `ST_AsText(${columnName}) as geometry_text`;
132
- const sql = `SELECT ${selectCols}
133
- FROM ${tableName}
134
- WHERE ST_Contains(${columnName}, ST_SetSRID(ST_MakePoint($1, $2), 4326))`;
135
- const result = await adapter.executeQuery(sql, [point.lng, point.lat]);
136
- const response = {
137
- containingPolygons: result.rows,
138
- count: result.rows?.length ?? 0,
139
- };
140
- // Add warning if geometry type is not polygon
141
- if (!isPolygonType && geomType !== undefined) {
142
- response["warning"] =
143
- `Column "${column}" contains ${geomType} geometries, not polygons. ST_Contains requires polygons to produce meaningful results.`;
113
+ try {
114
+ const { table, column, point, schema } = PointInPolygonSchema.parse(params ?? {});
115
+ const schemaName = schema ?? "public";
116
+ const tableName = sanitizeTableName(table, schemaName !== "public" ? schemaName : undefined);
117
+ const columnName = sanitizeIdentifier(column);
118
+ // Check geometry type and warn if not polygon
119
+ const typeCheckSql = `SELECT DISTINCT GeometryType(${columnName}) as geom_type FROM ${tableName} WHERE ${columnName} IS NOT NULL LIMIT 1`;
120
+ const typeResult = await adapter.executeQuery(typeCheckSql);
121
+ const geomType = typeResult.rows?.[0]?.["geom_type"];
122
+ const isPolygonType = geomType?.toUpperCase()?.includes("POLYGON") ?? false;
123
+ // Get non-geometry columns to avoid returning raw WKB
124
+ const colQuery = `
125
+ SELECT column_name FROM information_schema.columns
126
+ WHERE table_schema = $1 AND table_name = $2
127
+ AND udt_name NOT IN ('geometry', 'geography')
128
+ ORDER BY ordinal_position
129
+ `;
130
+ const colResult = await adapter.executeQuery(colQuery, [
131
+ schemaName,
132
+ table,
133
+ ]);
134
+ const nonGeomCols = (colResult.rows ?? [])
135
+ .map((row) => sanitizeIdentifier(String(row["column_name"])))
136
+ .join(", ");
137
+ // Select non-geometry columns + readable geometry representation
138
+ const selectCols = nonGeomCols.length > 0
139
+ ? `${nonGeomCols}, ST_AsText(${columnName}) as geometry_text`
140
+ : `ST_AsText(${columnName}) as geometry_text`;
141
+ const sql = `SELECT ${selectCols}
142
+ FROM ${tableName}
143
+ WHERE ST_Contains(${columnName}, ST_SetSRID(ST_MakePoint($1, $2), 4326))`;
144
+ const result = await adapter.executeQuery(sql, [point.lng, point.lat]);
145
+ const response = {
146
+ containingPolygons: result.rows,
147
+ count: result.rows?.length ?? 0,
148
+ };
149
+ // Add warning if geometry type is not polygon
150
+ if (!isPolygonType && geomType !== undefined) {
151
+ response["warning"] =
152
+ `Column "${column}" contains ${geomType} geometries, not polygons. ST_Contains requires polygons to produce meaningful results.`;
153
+ }
154
+ return response;
155
+ }
156
+ catch (error) {
157
+ if (error instanceof ZodError) {
158
+ return {
159
+ success: false,
160
+ error: error.issues.map((i) => i.message).join("; "),
161
+ };
162
+ }
163
+ return {
164
+ success: false,
165
+ error: formatPostgresError(error, {
166
+ tool: "pg_point_in_polygon",
167
+ table: params?.["table"] ??
168
+ undefined,
169
+ }),
170
+ };
144
171
  }
145
- return response;
146
172
  },
147
173
  };
148
174
  }
@@ -156,34 +182,35 @@ export function createDistanceTool(adapter) {
156
182
  annotations: readOnly("Distance Search"),
157
183
  icons: getToolIcons("postgis", readOnly("Distance Search")),
158
184
  handler: async (params, _context) => {
159
- const { table, column, point, limit, maxDistance, schema } = GeometryDistanceSchema.parse(params);
160
- const schemaName = schema ?? "public";
161
- const tableName = sanitizeTableName(table, schemaName !== "public" ? schemaName : undefined);
162
- const columnName = sanitizeIdentifier(column);
163
- const limitVal = limit ?? 10;
164
- const distanceFilter = maxDistance !== undefined && maxDistance > 0
165
- ? `WHERE distance_meters <= ${String(maxDistance)}`
166
- : "";
167
- // Get non-geometry columns to avoid returning raw WKB
168
- const colQuery = `
169
- SELECT column_name FROM information_schema.columns
170
- WHERE table_schema = $1 AND table_name = $2
171
- AND udt_name NOT IN ('geometry', 'geography')
172
- ORDER BY ordinal_position
173
- `;
174
- const colResult = await adapter.executeQuery(colQuery, [
175
- schemaName,
176
- table,
177
- ]);
178
- const nonGeomCols = (colResult.rows ?? [])
179
- .map((row) => sanitizeIdentifier(String(row["column_name"])))
180
- .join(", ");
181
- // Select non-geometry columns + readable geometry representation + distance
182
- const selectCols = nonGeomCols.length > 0
183
- ? `${nonGeomCols}, ST_AsText(${columnName}) as geometry_text, ST_Distance(${columnName}::geography, ST_SetSRID(ST_MakePoint($1, $2), 4326)::geography) as distance_meters`
184
- : `ST_AsText(${columnName}) as geometry_text, ST_Distance(${columnName}::geography, ST_SetSRID(ST_MakePoint($1, $2), 4326)::geography) as distance_meters`;
185
- // Use CTE for consistent distance calculation and filtering
186
- const sql = `WITH distances AS (
185
+ try {
186
+ const { table, column, point, limit, maxDistance, schema } = GeometryDistanceSchema.parse(params);
187
+ const schemaName = schema ?? "public";
188
+ const tableName = sanitizeTableName(table, schemaName !== "public" ? schemaName : undefined);
189
+ const columnName = sanitizeIdentifier(column);
190
+ const limitVal = limit ?? 10;
191
+ const distanceFilter = maxDistance !== undefined && maxDistance > 0
192
+ ? `WHERE distance_meters <= ${String(maxDistance)}`
193
+ : "";
194
+ // Get non-geometry columns to avoid returning raw WKB
195
+ const colQuery = `
196
+ SELECT column_name FROM information_schema.columns
197
+ WHERE table_schema = $1 AND table_name = $2
198
+ AND udt_name NOT IN ('geometry', 'geography')
199
+ ORDER BY ordinal_position
200
+ `;
201
+ const colResult = await adapter.executeQuery(colQuery, [
202
+ schemaName,
203
+ table,
204
+ ]);
205
+ const nonGeomCols = (colResult.rows ?? [])
206
+ .map((row) => sanitizeIdentifier(String(row["column_name"])))
207
+ .join(", ");
208
+ // Select non-geometry columns + readable geometry representation + distance
209
+ const selectCols = nonGeomCols.length > 0
210
+ ? `${nonGeomCols}, ST_AsText(${columnName}) as geometry_text, ST_Distance(${columnName}::geography, ST_SetSRID(ST_MakePoint($1, $2), 4326)::geography) as distance_meters`
211
+ : `ST_AsText(${columnName}) as geometry_text, ST_Distance(${columnName}::geography, ST_SetSRID(ST_MakePoint($1, $2), 4326)::geography) as distance_meters`;
212
+ // Use CTE for consistent distance calculation and filtering
213
+ const sql = `WITH distances AS (
187
214
  SELECT ${selectCols}
188
215
  FROM ${tableName}
189
216
  )
@@ -191,8 +218,25 @@ export function createDistanceTool(adapter) {
191
218
  ${distanceFilter}
192
219
  ORDER BY distance_meters
193
220
  LIMIT ${String(limitVal)}`;
194
- const result = await adapter.executeQuery(sql, [point.lng, point.lat]);
195
- return { results: result.rows, count: result.rows?.length ?? 0 };
221
+ const result = await adapter.executeQuery(sql, [point.lng, point.lat]);
222
+ return { results: result.rows, count: result.rows?.length ?? 0 };
223
+ }
224
+ catch (error) {
225
+ if (error instanceof ZodError) {
226
+ return {
227
+ success: false,
228
+ error: error.issues.map((i) => i.message).join("; "),
229
+ };
230
+ }
231
+ return {
232
+ success: false,
233
+ error: formatPostgresError(error, {
234
+ tool: "pg_distance",
235
+ table: params?.["table"] ??
236
+ undefined,
237
+ }),
238
+ };
239
+ }
196
240
  },
197
241
  };
198
242
  }
@@ -206,62 +250,82 @@ export function createBufferTool(adapter) {
206
250
  annotations: readOnly("Buffer Zone"),
207
251
  icons: getToolIcons("postgis", readOnly("Buffer Zone")),
208
252
  handler: async (params, _context) => {
209
- const parsed = BufferSchema.parse(params ?? {});
210
- const whereClause = parsed.where !== undefined ? ` WHERE ${parsed.where}` : "";
211
- const schemaName = parsed.schema ?? "public";
212
- const qualifiedTable = sanitizeTableName(parsed.table, schemaName !== "public" ? schemaName : undefined);
213
- const columnName = sanitizeIdentifier(parsed.column);
214
- // Default limit of 50 to prevent large payloads, use limit: 0 for all
215
- const effectiveLimit = parsed.limit ?? 50;
216
- // Get non-geometry columns to avoid returning raw WKB
217
- const colQuery = `
218
- SELECT column_name FROM information_schema.columns
219
- WHERE table_schema = $1 AND table_name = $2
220
- AND udt_name NOT IN ('geometry', 'geography')
221
- ORDER BY ordinal_position
222
- `;
223
- const colResult = await adapter.executeQuery(colQuery, [
224
- schemaName,
225
- parsed.table,
226
- ]);
227
- const nonGeomCols = (colResult.rows ?? [])
228
- .map((row) => sanitizeIdentifier(String(row["column_name"])))
229
- .join(", ");
230
- // Default simplify of 10m reduces polygon points for LLM-friendly payloads
231
- // User can set simplify: 0 to disable or higher values for more aggressive reduction
232
- const effectiveSimplify = parsed.simplify ?? 10;
233
- // Build buffer expression with simplification (applied by default)
234
- let bufferExpr = `ST_Buffer(${columnName}::geography, $1)::geometry`;
235
- if (effectiveSimplify > 0) {
236
- // SimplifyPreserveTopology maintains valid geometries
237
- bufferExpr = `ST_SimplifyPreserveTopology(${bufferExpr}, ${String(effectiveSimplify)})`;
238
- }
239
- // Select non-geometry columns + readable geometry representations
240
- const selectCols = nonGeomCols.length > 0
241
- ? `${nonGeomCols}, ST_AsText(${columnName}) as geometry_text, ST_AsGeoJSON(${bufferExpr}) as buffer_geojson`
242
- : `ST_AsText(${columnName}) as geometry_text, ST_AsGeoJSON(${bufferExpr}) as buffer_geojson`;
243
- const limitClause = effectiveLimit > 0 ? ` LIMIT ${String(effectiveLimit)}` : "";
244
- const sql = `SELECT ${selectCols} FROM ${qualifiedTable}${whereClause}${limitClause}`;
245
- const result = await adapter.executeQuery(sql, [parsed.distance]);
246
- // Build response with truncation indicators if default limit was applied
247
- const response = { results: result.rows };
248
- // Check if results were truncated (works for both default and explicit limits)
249
- if (effectiveLimit > 0) {
250
- const countSql = `SELECT COUNT(*) as cnt FROM ${qualifiedTable}${whereClause}`;
251
- const countResult = await adapter.executeQuery(countSql);
252
- const totalCount = Number(countResult.rows?.[0]?.["cnt"] ?? 0);
253
- if (totalCount > effectiveLimit) {
254
- response["truncated"] = true;
255
- response["totalCount"] = totalCount;
256
- response["limit"] = effectiveLimit;
253
+ try {
254
+ const parsed = BufferSchema.parse(params ?? {});
255
+ const whereClause = parsed.where !== undefined
256
+ ? ` WHERE ${sanitizeWhereClause(parsed.where)}`
257
+ : "";
258
+ const schemaName = parsed.schema ?? "public";
259
+ const qualifiedTable = sanitizeTableName(parsed.table, schemaName !== "public" ? schemaName : undefined);
260
+ const columnName = sanitizeIdentifier(parsed.column);
261
+ // Default limit of 50 to prevent large payloads, use limit: 0 for all
262
+ const effectiveLimit = parsed.limit ?? 50;
263
+ // Get non-geometry columns to avoid returning raw WKB
264
+ const colQuery = `
265
+ SELECT column_name FROM information_schema.columns
266
+ WHERE table_schema = $1 AND table_name = $2
267
+ AND udt_name NOT IN ('geometry', 'geography')
268
+ ORDER BY ordinal_position
269
+ `;
270
+ const colResult = await adapter.executeQuery(colQuery, [
271
+ schemaName,
272
+ parsed.table,
273
+ ]);
274
+ const nonGeomCols = (colResult.rows ?? [])
275
+ .map((row) => sanitizeIdentifier(String(row["column_name"])))
276
+ .join(", ");
277
+ // Default simplify of 10m reduces polygon points for LLM-friendly payloads
278
+ // User can set simplify: 0 to disable or higher values for more aggressive reduction
279
+ const effectiveSimplify = parsed.simplify ?? 10;
280
+ // Build buffer expression with simplification (applied by default)
281
+ let bufferExpr = `ST_Buffer(${columnName}::geography, $1)::geometry`;
282
+ if (effectiveSimplify > 0) {
283
+ // SimplifyPreserveTopology maintains valid geometries
284
+ bufferExpr = `ST_SimplifyPreserveTopology(${bufferExpr}, ${String(effectiveSimplify)})`;
257
285
  }
286
+ // Select non-geometry columns + readable geometry representations
287
+ const selectCols = nonGeomCols.length > 0
288
+ ? `${nonGeomCols}, ST_AsText(${columnName}) as geometry_text, ST_AsGeoJSON(${bufferExpr}) as buffer_geojson`
289
+ : `ST_AsText(${columnName}) as geometry_text, ST_AsGeoJSON(${bufferExpr}) as buffer_geojson`;
290
+ const limitClause = effectiveLimit > 0 ? ` LIMIT ${String(effectiveLimit)}` : "";
291
+ const sql = `SELECT ${selectCols} FROM ${qualifiedTable}${whereClause}${limitClause}`;
292
+ const result = await adapter.executeQuery(sql, [parsed.distance]);
293
+ // Build response with truncation indicators if default limit was applied
294
+ const response = { results: result.rows };
295
+ // Check if results were truncated (works for both default and explicit limits)
296
+ if (effectiveLimit > 0) {
297
+ const countSql = `SELECT COUNT(*) as cnt FROM ${qualifiedTable}${whereClause}`;
298
+ const countResult = await adapter.executeQuery(countSql);
299
+ const totalCount = Number(countResult.rows?.[0]?.["cnt"] ?? 0);
300
+ if (totalCount > effectiveLimit) {
301
+ response["truncated"] = true;
302
+ response["totalCount"] = totalCount;
303
+ response["limit"] = effectiveLimit;
304
+ }
305
+ }
306
+ // Add simplify indicator if simplification was applied
307
+ if (effectiveSimplify > 0) {
308
+ response["simplified"] = true;
309
+ response["simplifyTolerance"] = effectiveSimplify;
310
+ }
311
+ return response;
258
312
  }
259
- // Add simplify indicator if simplification was applied
260
- if (effectiveSimplify > 0) {
261
- response["simplified"] = true;
262
- response["simplifyTolerance"] = effectiveSimplify;
313
+ catch (error) {
314
+ if (error instanceof ZodError) {
315
+ return {
316
+ success: false,
317
+ error: error.issues.map((i) => i.message).join("; "),
318
+ };
319
+ }
320
+ return {
321
+ success: false,
322
+ error: formatPostgresError(error, {
323
+ tool: "pg_buffer",
324
+ table: params?.["table"] ??
325
+ undefined,
326
+ }),
327
+ };
263
328
  }
264
- return response;
265
329
  },
266
330
  };
267
331
  }
@@ -275,78 +339,98 @@ export function createIntersectionTool(adapter) {
275
339
  annotations: readOnly("Intersection Search"),
276
340
  icons: getToolIcons("postgis", readOnly("Intersection Search")),
277
341
  handler: async (params, _context) => {
278
- const parsed = IntersectionSchema.parse(params ?? {});
279
- const schemaName = parsed.schema ?? "public";
280
- const qualifiedTable = sanitizeTableName(parsed.table, schemaName !== "public" ? schemaName : undefined);
281
- const columnName = sanitizeIdentifier(parsed.column);
282
- // Build select columns - user-specified or non-geometry columns to avoid raw WKB
283
- let selectCols;
284
- if (parsed.select !== undefined && parsed.select.length > 0) {
285
- selectCols = parsed.select.map((c) => sanitizeIdentifier(c)).join(", ");
286
- }
287
- else {
288
- // Get non-geometry columns to avoid returning raw WKB
289
- const colQuery = `
290
- SELECT column_name FROM information_schema.columns
291
- WHERE table_schema = $1 AND table_name = $2
292
- AND udt_name NOT IN ('geometry', 'geography')
293
- ORDER BY ordinal_position
294
- `;
295
- const colResult = await adapter.executeQuery(colQuery, [
296
- schemaName,
297
- parsed.table,
298
- ]);
299
- const nonGeomCols = (colResult.rows ?? [])
300
- .map((row) => sanitizeIdentifier(String(row["column_name"])))
301
- .join(", ");
302
- selectCols =
303
- nonGeomCols.length > 0
304
- ? `${nonGeomCols}, ST_AsText(${columnName}) as geometry_text`
305
- : `ST_AsText(${columnName}) as geometry_text`;
306
- }
307
- const isGeoJson = parsed.geometry.trim().startsWith("{");
308
- // Auto-detect SRID from column if not provided and using WKT
309
- let srid = parsed.srid;
310
- if (!isGeoJson && srid === undefined) {
311
- // Query the column's SRID from geometry_columns or geography_columns
312
- const sridQuery = `
313
- SELECT srid FROM geometry_columns
314
- WHERE f_table_schema = $1 AND f_table_name = $2 AND f_geometry_column = $3
315
- UNION
316
- SELECT srid FROM geography_columns
317
- WHERE f_table_schema = $1 AND f_table_name = $2 AND f_geography_column = $3
318
- LIMIT 1
319
- `;
320
- const sridResult = await adapter.executeQuery(sridQuery, [
321
- schemaName,
322
- parsed.table,
323
- parsed.column,
324
- ]);
325
- const sridValue = sridResult.rows?.[0]?.["srid"];
326
- if (sridValue !== undefined && sridValue !== null) {
327
- srid = Number(sridValue);
342
+ try {
343
+ const parsed = IntersectionSchema.parse(params ?? {});
344
+ const schemaName = parsed.schema ?? "public";
345
+ const qualifiedTable = sanitizeTableName(parsed.table, schemaName !== "public" ? schemaName : undefined);
346
+ const columnName = sanitizeIdentifier(parsed.column);
347
+ // Build select columns - user-specified or non-geometry columns to avoid raw WKB
348
+ let selectCols;
349
+ if (parsed.select !== undefined && parsed.select.length > 0) {
350
+ selectCols = parsed.select
351
+ .map((c) => sanitizeIdentifier(c))
352
+ .join(", ");
328
353
  }
354
+ else {
355
+ // Get non-geometry columns to avoid returning raw WKB
356
+ const colQuery = `
357
+ SELECT column_name FROM information_schema.columns
358
+ WHERE table_schema = $1 AND table_name = $2
359
+ AND udt_name NOT IN ('geometry', 'geography')
360
+ ORDER BY ordinal_position
361
+ `;
362
+ const colResult = await adapter.executeQuery(colQuery, [
363
+ schemaName,
364
+ parsed.table,
365
+ ]);
366
+ const nonGeomCols = (colResult.rows ?? [])
367
+ .map((row) => sanitizeIdentifier(String(row["column_name"])))
368
+ .join(", ");
369
+ selectCols =
370
+ nonGeomCols.length > 0
371
+ ? `${nonGeomCols}, ST_AsText(${columnName}) as geometry_text`
372
+ : `ST_AsText(${columnName}) as geometry_text`;
373
+ }
374
+ const isGeoJson = parsed.geometry.trim().startsWith("{");
375
+ // Auto-detect SRID from column if not provided and using WKT
376
+ let srid = parsed.srid;
377
+ if (!isGeoJson && srid === undefined) {
378
+ // Query the column's SRID from geometry_columns or geography_columns
379
+ const sridQuery = `
380
+ SELECT srid FROM geometry_columns
381
+ WHERE f_table_schema = $1 AND f_table_name = $2 AND f_geometry_column = $3
382
+ UNION
383
+ SELECT srid FROM geography_columns
384
+ WHERE f_table_schema = $1 AND f_table_name = $2 AND f_geography_column = $3
385
+ LIMIT 1
386
+ `;
387
+ const sridResult = await adapter.executeQuery(sridQuery, [
388
+ schemaName,
389
+ parsed.table,
390
+ parsed.column,
391
+ ]);
392
+ const sridValue = sridResult.rows?.[0]?.["srid"];
393
+ if (sridValue !== undefined && sridValue !== null) {
394
+ srid = Number(sridValue);
395
+ }
396
+ }
397
+ // Build geometry expression with SRID if available
398
+ let geomExpr;
399
+ if (isGeoJson) {
400
+ geomExpr = `ST_GeomFromGeoJSON($1)`;
401
+ }
402
+ else if (srid !== undefined) {
403
+ geomExpr = `ST_SetSRID(ST_GeomFromText($1), ${String(srid)})`;
404
+ }
405
+ else {
406
+ geomExpr = `ST_GeomFromText($1)`;
407
+ }
408
+ const sql = `SELECT ${selectCols}
409
+ FROM ${qualifiedTable}
410
+ WHERE ST_Intersects(${columnName}, ${geomExpr})`;
411
+ const result = await adapter.executeQuery(sql, [parsed.geometry]);
412
+ return {
413
+ intersecting: result.rows,
414
+ count: result.rows?.length ?? 0,
415
+ sridUsed: srid ?? "none (explicit SRID in geometry or GeoJSON)",
416
+ };
329
417
  }
330
- // Build geometry expression with SRID if available
331
- let geomExpr;
332
- if (isGeoJson) {
333
- geomExpr = `ST_GeomFromGeoJSON($1)`;
334
- }
335
- else if (srid !== undefined) {
336
- geomExpr = `ST_SetSRID(ST_GeomFromText($1), ${String(srid)})`;
337
- }
338
- else {
339
- geomExpr = `ST_GeomFromText($1)`;
418
+ catch (error) {
419
+ if (error instanceof ZodError) {
420
+ return {
421
+ success: false,
422
+ error: error.issues.map((i) => i.message).join("; "),
423
+ };
424
+ }
425
+ return {
426
+ success: false,
427
+ error: formatPostgresError(error, {
428
+ tool: "pg_intersection",
429
+ table: params?.["table"] ??
430
+ undefined,
431
+ }),
432
+ };
340
433
  }
341
- const sql = `SELECT ${selectCols}
342
- FROM ${qualifiedTable}
343
- WHERE ST_Intersects(${columnName}, ${geomExpr})`;
344
- const result = await adapter.executeQuery(sql, [parsed.geometry]);
345
- return {
346
- intersecting: result.rows,
347
- count: result.rows?.length ?? 0,
348
- sridUsed: srid ?? "none (explicit SRID in geometry or GeoJSON)",
349
- };
350
434
  },
351
435
  };
352
436
  }
@@ -360,64 +444,91 @@ export function createBoundingBoxTool(adapter) {
360
444
  annotations: readOnly("Bounding Box Search"),
361
445
  icons: getToolIcons("postgis", readOnly("Bounding Box Search")),
362
446
  handler: async (params, _context) => {
363
- const parsed = BoundingBoxSchema.parse(params ?? {});
364
- const schemaName = parsed.schema ?? "public";
365
- const qualifiedTable = sanitizeTableName(parsed.table, schemaName !== "public" ? schemaName : undefined);
366
- const columnName = sanitizeIdentifier(parsed.column);
367
- // Build select columns - user-specified or non-geometry columns to avoid raw WKB
368
- let selectCols;
369
- if (parsed.select !== undefined && parsed.select.length > 0) {
370
- selectCols = parsed.select.map((c) => sanitizeIdentifier(c)).join(", ");
371
- }
372
- else {
373
- // Get non-geometry columns to avoid returning raw WKB
374
- const colQuery = `
375
- SELECT column_name FROM information_schema.columns
376
- WHERE table_schema = $1 AND table_name = $2
377
- AND udt_name NOT IN ('geometry', 'geography')
378
- ORDER BY ordinal_position
379
- `;
380
- const colResult = await adapter.executeQuery(colQuery, [
381
- schemaName,
382
- parsed.table,
447
+ try {
448
+ const parsed = BoundingBoxSchema.parse(params ?? {});
449
+ const schemaName = parsed.schema ?? "public";
450
+ const qualifiedTable = sanitizeTableName(parsed.table, schemaName !== "public" ? schemaName : undefined);
451
+ const columnName = sanitizeIdentifier(parsed.column);
452
+ // Build select columns - user-specified or non-geometry columns to avoid raw WKB
453
+ let selectCols;
454
+ if (parsed.select !== undefined && parsed.select.length > 0) {
455
+ selectCols = parsed.select
456
+ .map((c) => sanitizeIdentifier(c))
457
+ .join(", ");
458
+ }
459
+ else {
460
+ // Get non-geometry columns to avoid returning raw WKB
461
+ const colQuery = `
462
+ SELECT column_name FROM information_schema.columns
463
+ WHERE table_schema = $1 AND table_name = $2
464
+ AND udt_name NOT IN ('geometry', 'geography')
465
+ ORDER BY ordinal_position
466
+ `;
467
+ const colResult = await adapter.executeQuery(colQuery, [
468
+ schemaName,
469
+ parsed.table,
470
+ ]);
471
+ selectCols = (colResult.rows ?? [])
472
+ .map((row) => sanitizeIdentifier(String(row["column_name"])))
473
+ .join(", ");
474
+ // If no columns found, table likely doesn't exist
475
+ if (selectCols.length === 0) {
476
+ return {
477
+ success: false,
478
+ error: `Table or view '${parsed.table}' not found in schema '${schemaName}'. Use pg_list_tables to see available tables.`,
479
+ };
480
+ }
481
+ }
482
+ // Auto-correct swapped bounds
483
+ const corrections = [];
484
+ let actualMinLng = parsed.minLng;
485
+ let actualMaxLng = parsed.maxLng;
486
+ let actualMinLat = parsed.minLat;
487
+ let actualMaxLat = parsed.maxLat;
488
+ if (parsed.minLng > parsed.maxLng) {
489
+ actualMinLng = parsed.maxLng;
490
+ actualMaxLng = parsed.minLng;
491
+ corrections.push("minLng/maxLng were swapped");
492
+ }
493
+ if (parsed.minLat > parsed.maxLat) {
494
+ actualMinLat = parsed.maxLat;
495
+ actualMaxLat = parsed.minLat;
496
+ corrections.push("minLat/maxLat were swapped");
497
+ }
498
+ const sql = `SELECT ${selectCols}, ST_AsText(${columnName}) as geometry_text
499
+ FROM ${qualifiedTable}
500
+ WHERE ${columnName} && ST_MakeEnvelope($1, $2, $3, $4, 4326)`;
501
+ const result = await adapter.executeQuery(sql, [
502
+ actualMinLng,
503
+ actualMinLat,
504
+ actualMaxLng,
505
+ actualMaxLat,
383
506
  ]);
384
- selectCols = (colResult.rows ?? [])
385
- .map((row) => sanitizeIdentifier(String(row["column_name"])))
386
- .join(", ");
387
- }
388
- // Auto-correct swapped bounds
389
- const corrections = [];
390
- let actualMinLng = parsed.minLng;
391
- let actualMaxLng = parsed.maxLng;
392
- let actualMinLat = parsed.minLat;
393
- let actualMaxLat = parsed.maxLat;
394
- if (parsed.minLng > parsed.maxLng) {
395
- actualMinLng = parsed.maxLng;
396
- actualMaxLng = parsed.minLng;
397
- corrections.push("minLng/maxLng were swapped");
398
- }
399
- if (parsed.minLat > parsed.maxLat) {
400
- actualMinLat = parsed.maxLat;
401
- actualMaxLat = parsed.minLat;
402
- corrections.push("minLat/maxLat were swapped");
507
+ const response = {
508
+ results: result.rows,
509
+ count: result.rows?.length ?? 0,
510
+ };
511
+ if (corrections.length > 0) {
512
+ response["note"] = `Auto-corrected: ${corrections.join(", ")}`;
513
+ }
514
+ return response;
403
515
  }
404
- const sql = `SELECT ${selectCols}, ST_AsText(${columnName}) as geometry_text
405
- FROM ${qualifiedTable}
406
- WHERE ${columnName} && ST_MakeEnvelope($1, $2, $3, $4, 4326)`;
407
- const result = await adapter.executeQuery(sql, [
408
- actualMinLng,
409
- actualMinLat,
410
- actualMaxLng,
411
- actualMaxLat,
412
- ]);
413
- const response = {
414
- results: result.rows,
415
- count: result.rows?.length ?? 0,
416
- };
417
- if (corrections.length > 0) {
418
- response["note"] = `Auto-corrected: ${corrections.join(", ")}`;
516
+ catch (error) {
517
+ if (error instanceof ZodError) {
518
+ return {
519
+ success: false,
520
+ error: error.issues.map((i) => i.message).join("; "),
521
+ };
522
+ }
523
+ return {
524
+ success: false,
525
+ error: formatPostgresError(error, {
526
+ tool: "pg_bounding_box",
527
+ table: params?.["table"] ??
528
+ undefined,
529
+ }),
530
+ };
419
531
  }
420
- return response;
421
532
  },
422
533
  };
423
534
  }
@@ -481,7 +592,15 @@ export function createSpatialIndexTool(adapter) {
481
592
  }
482
593
  // Always use IF NOT EXISTS to prevent unclear PostgreSQL errors
483
594
  const sql = `CREATE INDEX IF NOT EXISTS ${indexName} ON ${qualifiedTable} USING GIST (${columnName})`;
484
- await adapter.executeQuery(sql);
595
+ try {
596
+ await adapter.executeQuery(sql);
597
+ }
598
+ catch (error) {
599
+ throw parsePostgresError(error, {
600
+ tool: "pg_spatial_index",
601
+ table,
602
+ });
603
+ }
485
604
  return { success: true, index: indexNameRaw, table, column };
486
605
  },
487
606
  };