@neverinfamous/postgres-mcp 1.1.0 → 1.3.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 (243) hide show
  1. package/README.md +95 -81
  2. package/dist/__tests__/mocks/adapter.d.ts.map +1 -1
  3. package/dist/__tests__/mocks/adapter.js +0 -1
  4. package/dist/__tests__/mocks/adapter.js.map +1 -1
  5. package/dist/__tests__/mocks/pool.d.ts.map +1 -1
  6. package/dist/__tests__/mocks/pool.js +0 -1
  7. package/dist/__tests__/mocks/pool.js.map +1 -1
  8. package/dist/adapters/DatabaseAdapter.js +1 -1
  9. package/dist/adapters/DatabaseAdapter.js.map +1 -1
  10. package/dist/adapters/postgresql/PostgresAdapter.d.ts.map +1 -1
  11. package/dist/adapters/postgresql/PostgresAdapter.js +78 -8
  12. package/dist/adapters/postgresql/PostgresAdapter.js.map +1 -1
  13. package/dist/adapters/postgresql/prompts/backup.d.ts.map +1 -1
  14. package/dist/adapters/postgresql/prompts/backup.js +2 -3
  15. package/dist/adapters/postgresql/prompts/backup.js.map +1 -1
  16. package/dist/adapters/postgresql/prompts/citext.d.ts.map +1 -1
  17. package/dist/adapters/postgresql/prompts/citext.js +3 -4
  18. package/dist/adapters/postgresql/prompts/citext.js.map +1 -1
  19. package/dist/adapters/postgresql/prompts/extensionSetup.d.ts.map +1 -1
  20. package/dist/adapters/postgresql/prompts/extensionSetup.js +2 -3
  21. package/dist/adapters/postgresql/prompts/extensionSetup.js.map +1 -1
  22. package/dist/adapters/postgresql/prompts/health.d.ts.map +1 -1
  23. package/dist/adapters/postgresql/prompts/health.js +2 -3
  24. package/dist/adapters/postgresql/prompts/health.js.map +1 -1
  25. package/dist/adapters/postgresql/prompts/index.js +20 -27
  26. package/dist/adapters/postgresql/prompts/index.js.map +1 -1
  27. package/dist/adapters/postgresql/prompts/indexTuning.d.ts.map +1 -1
  28. package/dist/adapters/postgresql/prompts/indexTuning.js +2 -3
  29. package/dist/adapters/postgresql/prompts/indexTuning.js.map +1 -1
  30. package/dist/adapters/postgresql/prompts/kcache.d.ts.map +1 -1
  31. package/dist/adapters/postgresql/prompts/kcache.js +3 -4
  32. package/dist/adapters/postgresql/prompts/kcache.js.map +1 -1
  33. package/dist/adapters/postgresql/prompts/ltree.d.ts.map +1 -1
  34. package/dist/adapters/postgresql/prompts/ltree.js +5 -6
  35. package/dist/adapters/postgresql/prompts/ltree.js.map +1 -1
  36. package/dist/adapters/postgresql/prompts/partman.d.ts.map +1 -1
  37. package/dist/adapters/postgresql/prompts/partman.js +2 -3
  38. package/dist/adapters/postgresql/prompts/partman.js.map +1 -1
  39. package/dist/adapters/postgresql/prompts/pgcron.d.ts.map +1 -1
  40. package/dist/adapters/postgresql/prompts/pgcron.js +2 -3
  41. package/dist/adapters/postgresql/prompts/pgcron.js.map +1 -1
  42. package/dist/adapters/postgresql/prompts/pgcrypto.d.ts.map +1 -1
  43. package/dist/adapters/postgresql/prompts/pgcrypto.js +3 -4
  44. package/dist/adapters/postgresql/prompts/pgcrypto.js.map +1 -1
  45. package/dist/adapters/postgresql/prompts/pgvector.d.ts.map +1 -1
  46. package/dist/adapters/postgresql/prompts/pgvector.js +3 -4
  47. package/dist/adapters/postgresql/prompts/pgvector.js.map +1 -1
  48. package/dist/adapters/postgresql/prompts/postgis.d.ts.map +1 -1
  49. package/dist/adapters/postgresql/prompts/postgis.js +2 -3
  50. package/dist/adapters/postgresql/prompts/postgis.js.map +1 -1
  51. package/dist/adapters/postgresql/schemas/admin.d.ts +10 -5
  52. package/dist/adapters/postgresql/schemas/admin.d.ts.map +1 -1
  53. package/dist/adapters/postgresql/schemas/admin.js +10 -5
  54. package/dist/adapters/postgresql/schemas/admin.js.map +1 -1
  55. package/dist/adapters/postgresql/schemas/backup.d.ts +8 -4
  56. package/dist/adapters/postgresql/schemas/backup.d.ts.map +1 -1
  57. package/dist/adapters/postgresql/schemas/backup.js +11 -4
  58. package/dist/adapters/postgresql/schemas/backup.js.map +1 -1
  59. package/dist/adapters/postgresql/schemas/core.d.ts +54 -19
  60. package/dist/adapters/postgresql/schemas/core.d.ts.map +1 -1
  61. package/dist/adapters/postgresql/schemas/core.js +65 -17
  62. package/dist/adapters/postgresql/schemas/core.js.map +1 -1
  63. package/dist/adapters/postgresql/schemas/cron.d.ts +51 -32
  64. package/dist/adapters/postgresql/schemas/cron.d.ts.map +1 -1
  65. package/dist/adapters/postgresql/schemas/cron.js +64 -44
  66. package/dist/adapters/postgresql/schemas/cron.js.map +1 -1
  67. package/dist/adapters/postgresql/schemas/extensions.d.ts +168 -73
  68. package/dist/adapters/postgresql/schemas/extensions.d.ts.map +1 -1
  69. package/dist/adapters/postgresql/schemas/extensions.js +179 -62
  70. package/dist/adapters/postgresql/schemas/extensions.js.map +1 -1
  71. package/dist/adapters/postgresql/schemas/index.d.ts +5 -5
  72. package/dist/adapters/postgresql/schemas/index.d.ts.map +1 -1
  73. package/dist/adapters/postgresql/schemas/index.js +9 -7
  74. package/dist/adapters/postgresql/schemas/index.js.map +1 -1
  75. package/dist/adapters/postgresql/schemas/jsonb.d.ts +94 -42
  76. package/dist/adapters/postgresql/schemas/jsonb.d.ts.map +1 -1
  77. package/dist/adapters/postgresql/schemas/jsonb.js +101 -30
  78. package/dist/adapters/postgresql/schemas/jsonb.js.map +1 -1
  79. package/dist/adapters/postgresql/schemas/monitoring.d.ts +28 -11
  80. package/dist/adapters/postgresql/schemas/monitoring.d.ts.map +1 -1
  81. package/dist/adapters/postgresql/schemas/monitoring.js +49 -24
  82. package/dist/adapters/postgresql/schemas/monitoring.js.map +1 -1
  83. package/dist/adapters/postgresql/schemas/partitioning.d.ts +15 -11
  84. package/dist/adapters/postgresql/schemas/partitioning.d.ts.map +1 -1
  85. package/dist/adapters/postgresql/schemas/partitioning.js +17 -13
  86. package/dist/adapters/postgresql/schemas/partitioning.js.map +1 -1
  87. package/dist/adapters/postgresql/schemas/performance.d.ts +62 -31
  88. package/dist/adapters/postgresql/schemas/performance.d.ts.map +1 -1
  89. package/dist/adapters/postgresql/schemas/performance.js +86 -24
  90. package/dist/adapters/postgresql/schemas/performance.js.map +1 -1
  91. package/dist/adapters/postgresql/schemas/postgis.d.ts +20 -0
  92. package/dist/adapters/postgresql/schemas/postgis.d.ts.map +1 -1
  93. package/dist/adapters/postgresql/schemas/postgis.js +20 -3
  94. package/dist/adapters/postgresql/schemas/postgis.js.map +1 -1
  95. package/dist/adapters/postgresql/schemas/schema-mgmt.d.ts +35 -23
  96. package/dist/adapters/postgresql/schemas/schema-mgmt.d.ts.map +1 -1
  97. package/dist/adapters/postgresql/schemas/schema-mgmt.js +69 -26
  98. package/dist/adapters/postgresql/schemas/schema-mgmt.js.map +1 -1
  99. package/dist/adapters/postgresql/schemas/stats.d.ts +33 -20
  100. package/dist/adapters/postgresql/schemas/stats.d.ts.map +1 -1
  101. package/dist/adapters/postgresql/schemas/stats.js +36 -20
  102. package/dist/adapters/postgresql/schemas/stats.js.map +1 -1
  103. package/dist/adapters/postgresql/schemas/text-search.d.ts +8 -5
  104. package/dist/adapters/postgresql/schemas/text-search.d.ts.map +1 -1
  105. package/dist/adapters/postgresql/schemas/text-search.js +15 -5
  106. package/dist/adapters/postgresql/schemas/text-search.js.map +1 -1
  107. package/dist/adapters/postgresql/tools/admin.d.ts.map +1 -1
  108. package/dist/adapters/postgresql/tools/admin.js +211 -140
  109. package/dist/adapters/postgresql/tools/admin.js.map +1 -1
  110. package/dist/adapters/postgresql/tools/backup/dump.d.ts.map +1 -1
  111. package/dist/adapters/postgresql/tools/backup/dump.js +410 -387
  112. package/dist/adapters/postgresql/tools/backup/dump.js.map +1 -1
  113. package/dist/adapters/postgresql/tools/backup/planning.d.ts.map +1 -1
  114. package/dist/adapters/postgresql/tools/backup/planning.js +175 -172
  115. package/dist/adapters/postgresql/tools/backup/planning.js.map +1 -1
  116. package/dist/adapters/postgresql/tools/citext.d.ts.map +1 -1
  117. package/dist/adapters/postgresql/tools/citext.js +221 -163
  118. package/dist/adapters/postgresql/tools/citext.js.map +1 -1
  119. package/dist/adapters/postgresql/tools/core/convenience.d.ts +9 -1
  120. package/dist/adapters/postgresql/tools/core/convenience.d.ts.map +1 -1
  121. package/dist/adapters/postgresql/tools/core/convenience.js +96 -9
  122. package/dist/adapters/postgresql/tools/core/convenience.js.map +1 -1
  123. package/dist/adapters/postgresql/tools/core/error-helpers.d.ts +48 -0
  124. package/dist/adapters/postgresql/tools/core/error-helpers.d.ts.map +1 -0
  125. package/dist/adapters/postgresql/tools/core/error-helpers.js +256 -0
  126. package/dist/adapters/postgresql/tools/core/error-helpers.js.map +1 -0
  127. package/dist/adapters/postgresql/tools/core/health.d.ts.map +1 -1
  128. package/dist/adapters/postgresql/tools/core/health.js +23 -6
  129. package/dist/adapters/postgresql/tools/core/health.js.map +1 -1
  130. package/dist/adapters/postgresql/tools/core/indexes.d.ts.map +1 -1
  131. package/dist/adapters/postgresql/tools/core/indexes.js +45 -4
  132. package/dist/adapters/postgresql/tools/core/indexes.js.map +1 -1
  133. package/dist/adapters/postgresql/tools/core/objects.d.ts.map +1 -1
  134. package/dist/adapters/postgresql/tools/core/objects.js +104 -85
  135. package/dist/adapters/postgresql/tools/core/objects.js.map +1 -1
  136. package/dist/adapters/postgresql/tools/core/query.d.ts.map +1 -1
  137. package/dist/adapters/postgresql/tools/core/query.js +100 -42
  138. package/dist/adapters/postgresql/tools/core/query.js.map +1 -1
  139. package/dist/adapters/postgresql/tools/core/schemas.d.ts +52 -25
  140. package/dist/adapters/postgresql/tools/core/schemas.d.ts.map +1 -1
  141. package/dist/adapters/postgresql/tools/core/schemas.js +55 -25
  142. package/dist/adapters/postgresql/tools/core/schemas.js.map +1 -1
  143. package/dist/adapters/postgresql/tools/core/tables.d.ts.map +1 -1
  144. package/dist/adapters/postgresql/tools/core/tables.js +74 -30
  145. package/dist/adapters/postgresql/tools/core/tables.js.map +1 -1
  146. package/dist/adapters/postgresql/tools/cron.d.ts.map +1 -1
  147. package/dist/adapters/postgresql/tools/cron.js +274 -179
  148. package/dist/adapters/postgresql/tools/cron.js.map +1 -1
  149. package/dist/adapters/postgresql/tools/jsonb/advanced.d.ts.map +1 -1
  150. package/dist/adapters/postgresql/tools/jsonb/advanced.js +372 -284
  151. package/dist/adapters/postgresql/tools/jsonb/advanced.js.map +1 -1
  152. package/dist/adapters/postgresql/tools/jsonb/basic.d.ts.map +1 -1
  153. package/dist/adapters/postgresql/tools/jsonb/basic.js +617 -398
  154. package/dist/adapters/postgresql/tools/jsonb/basic.js.map +1 -1
  155. package/dist/adapters/postgresql/tools/kcache.d.ts.map +1 -1
  156. package/dist/adapters/postgresql/tools/kcache.js +282 -220
  157. package/dist/adapters/postgresql/tools/kcache.js.map +1 -1
  158. package/dist/adapters/postgresql/tools/ltree.d.ts.map +1 -1
  159. package/dist/adapters/postgresql/tools/ltree.js +126 -35
  160. package/dist/adapters/postgresql/tools/ltree.js.map +1 -1
  161. package/dist/adapters/postgresql/tools/monitoring.d.ts.map +1 -1
  162. package/dist/adapters/postgresql/tools/monitoring.js +59 -40
  163. package/dist/adapters/postgresql/tools/monitoring.js.map +1 -1
  164. package/dist/adapters/postgresql/tools/partitioning.d.ts.map +1 -1
  165. package/dist/adapters/postgresql/tools/partitioning.js +150 -15
  166. package/dist/adapters/postgresql/tools/partitioning.js.map +1 -1
  167. package/dist/adapters/postgresql/tools/partman/management.d.ts.map +1 -1
  168. package/dist/adapters/postgresql/tools/partman/management.js +12 -5
  169. package/dist/adapters/postgresql/tools/partman/management.js.map +1 -1
  170. package/dist/adapters/postgresql/tools/partman/operations.d.ts.map +1 -1
  171. package/dist/adapters/postgresql/tools/partman/operations.js +135 -22
  172. package/dist/adapters/postgresql/tools/partman/operations.js.map +1 -1
  173. package/dist/adapters/postgresql/tools/performance/analysis.d.ts.map +1 -1
  174. package/dist/adapters/postgresql/tools/performance/analysis.js +264 -160
  175. package/dist/adapters/postgresql/tools/performance/analysis.js.map +1 -1
  176. package/dist/adapters/postgresql/tools/performance/explain.d.ts.map +1 -1
  177. package/dist/adapters/postgresql/tools/performance/explain.js +61 -21
  178. package/dist/adapters/postgresql/tools/performance/explain.js.map +1 -1
  179. package/dist/adapters/postgresql/tools/performance/monitoring.d.ts.map +1 -1
  180. package/dist/adapters/postgresql/tools/performance/monitoring.js +44 -7
  181. package/dist/adapters/postgresql/tools/performance/monitoring.js.map +1 -1
  182. package/dist/adapters/postgresql/tools/performance/optimization.d.ts.map +1 -1
  183. package/dist/adapters/postgresql/tools/performance/optimization.js +92 -81
  184. package/dist/adapters/postgresql/tools/performance/optimization.js.map +1 -1
  185. package/dist/adapters/postgresql/tools/performance/stats.d.ts.map +1 -1
  186. package/dist/adapters/postgresql/tools/performance/stats.js +128 -37
  187. package/dist/adapters/postgresql/tools/performance/stats.js.map +1 -1
  188. package/dist/adapters/postgresql/tools/pgcrypto.d.ts.map +1 -1
  189. package/dist/adapters/postgresql/tools/pgcrypto.js +242 -87
  190. package/dist/adapters/postgresql/tools/pgcrypto.js.map +1 -1
  191. package/dist/adapters/postgresql/tools/postgis/advanced.d.ts.map +1 -1
  192. package/dist/adapters/postgresql/tools/postgis/advanced.js +293 -201
  193. package/dist/adapters/postgresql/tools/postgis/advanced.js.map +1 -1
  194. package/dist/adapters/postgresql/tools/postgis/basic.d.ts.map +1 -1
  195. package/dist/adapters/postgresql/tools/postgis/basic.js +359 -249
  196. package/dist/adapters/postgresql/tools/postgis/basic.js.map +1 -1
  197. package/dist/adapters/postgresql/tools/postgis/standalone.d.ts.map +1 -1
  198. package/dist/adapters/postgresql/tools/postgis/standalone.js +135 -51
  199. package/dist/adapters/postgresql/tools/postgis/standalone.js.map +1 -1
  200. package/dist/adapters/postgresql/tools/schema.d.ts.map +1 -1
  201. package/dist/adapters/postgresql/tools/schema.js +515 -226
  202. package/dist/adapters/postgresql/tools/schema.js.map +1 -1
  203. package/dist/adapters/postgresql/tools/stats/advanced.d.ts.map +1 -1
  204. package/dist/adapters/postgresql/tools/stats/advanced.js +515 -476
  205. package/dist/adapters/postgresql/tools/stats/advanced.js.map +1 -1
  206. package/dist/adapters/postgresql/tools/stats/basic.d.ts.map +1 -1
  207. package/dist/adapters/postgresql/tools/stats/basic.js +302 -293
  208. package/dist/adapters/postgresql/tools/stats/basic.js.map +1 -1
  209. package/dist/adapters/postgresql/tools/text.d.ts.map +1 -1
  210. package/dist/adapters/postgresql/tools/text.js +398 -220
  211. package/dist/adapters/postgresql/tools/text.js.map +1 -1
  212. package/dist/adapters/postgresql/tools/transactions.d.ts.map +1 -1
  213. package/dist/adapters/postgresql/tools/transactions.js +157 -50
  214. package/dist/adapters/postgresql/tools/transactions.js.map +1 -1
  215. package/dist/adapters/postgresql/tools/vector/advanced.d.ts.map +1 -1
  216. package/dist/adapters/postgresql/tools/vector/advanced.js +70 -38
  217. package/dist/adapters/postgresql/tools/vector/advanced.js.map +1 -1
  218. package/dist/adapters/postgresql/tools/vector/basic.d.ts +8 -0
  219. package/dist/adapters/postgresql/tools/vector/basic.d.ts.map +1 -1
  220. package/dist/adapters/postgresql/tools/vector/basic.js +194 -82
  221. package/dist/adapters/postgresql/tools/vector/basic.js.map +1 -1
  222. package/dist/cli/args.d.ts +2 -0
  223. package/dist/cli/args.d.ts.map +1 -1
  224. package/dist/cli/args.js +15 -0
  225. package/dist/cli/args.js.map +1 -1
  226. package/dist/cli.js +7 -6
  227. package/dist/cli.js.map +1 -1
  228. package/dist/codemode/api.d.ts.map +1 -1
  229. package/dist/codemode/api.js +4 -3
  230. package/dist/codemode/api.js.map +1 -1
  231. package/dist/constants/ServerInstructions.d.ts +1 -1
  232. package/dist/constants/ServerInstructions.d.ts.map +1 -1
  233. package/dist/constants/ServerInstructions.js +76 -34
  234. package/dist/constants/ServerInstructions.js.map +1 -1
  235. package/dist/filtering/ToolConstants.d.ts +29 -13
  236. package/dist/filtering/ToolConstants.d.ts.map +1 -1
  237. package/dist/filtering/ToolConstants.js +44 -27
  238. package/dist/filtering/ToolConstants.js.map +1 -1
  239. package/dist/utils/logger.js +2 -2
  240. package/dist/utils/logger.js.map +1 -1
  241. package/dist/utils/progress-utils.js +1 -1
  242. package/dist/utils/progress-utils.js.map +1 -1
  243. package/package.json +13 -9
@@ -3,9 +3,10 @@
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";
10
11
  import { GeometryColumnSchemaBase, GeometryColumnSchema, GeometryDistanceSchemaBase, GeometryDistanceSchema, PointInPolygonSchemaBase, PointInPolygonSchema, SpatialIndexSchemaBase, SpatialIndexSchema, BufferSchemaBase, BufferSchema, IntersectionSchemaBase, IntersectionSchema, BoundingBoxSchemaBase, BoundingBoxSchema,
11
12
  // Output schemas
@@ -102,47 +103,65 @@ export function createPointInPolygonTool(adapter) {
102
103
  annotations: readOnly("Point in Polygon"),
103
104
  icons: getToolIcons("postgis", readOnly("Point in Polygon")),
104
105
  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.`;
106
+ try {
107
+ const { table, column, point, schema } = PointInPolygonSchema.parse(params ?? {});
108
+ const schemaName = schema ?? "public";
109
+ const tableName = sanitizeTableName(table, schemaName !== "public" ? schemaName : undefined);
110
+ const columnName = sanitizeIdentifier(column);
111
+ // Check geometry type and warn if not polygon
112
+ const typeCheckSql = `SELECT DISTINCT GeometryType(${columnName}) as geom_type FROM ${tableName} WHERE ${columnName} IS NOT NULL LIMIT 1`;
113
+ const typeResult = await adapter.executeQuery(typeCheckSql);
114
+ const geomType = typeResult.rows?.[0]?.["geom_type"];
115
+ const isPolygonType = geomType?.toUpperCase()?.includes("POLYGON") ?? false;
116
+ // Get non-geometry columns to avoid returning raw WKB
117
+ const colQuery = `
118
+ SELECT column_name FROM information_schema.columns
119
+ WHERE table_schema = $1 AND table_name = $2
120
+ AND udt_name NOT IN ('geometry', 'geography')
121
+ ORDER BY ordinal_position
122
+ `;
123
+ const colResult = await adapter.executeQuery(colQuery, [
124
+ schemaName,
125
+ table,
126
+ ]);
127
+ const nonGeomCols = (colResult.rows ?? [])
128
+ .map((row) => sanitizeIdentifier(String(row["column_name"])))
129
+ .join(", ");
130
+ // Select non-geometry columns + readable geometry representation
131
+ const selectCols = nonGeomCols.length > 0
132
+ ? `${nonGeomCols}, ST_AsText(${columnName}) as geometry_text`
133
+ : `ST_AsText(${columnName}) as geometry_text`;
134
+ const sql = `SELECT ${selectCols}
135
+ FROM ${tableName}
136
+ WHERE ST_Contains(${columnName}, ST_SetSRID(ST_MakePoint($1, $2), 4326))`;
137
+ const result = await adapter.executeQuery(sql, [point.lng, point.lat]);
138
+ const response = {
139
+ containingPolygons: result.rows,
140
+ count: result.rows?.length ?? 0,
141
+ };
142
+ // Add warning if geometry type is not polygon
143
+ if (!isPolygonType && geomType !== undefined) {
144
+ response["warning"] =
145
+ `Column "${column}" contains ${geomType} geometries, not polygons. ST_Contains requires polygons to produce meaningful results.`;
146
+ }
147
+ return response;
148
+ }
149
+ catch (error) {
150
+ if (error instanceof ZodError) {
151
+ return {
152
+ success: false,
153
+ error: error.issues.map((i) => i.message).join("; "),
154
+ };
155
+ }
156
+ return {
157
+ success: false,
158
+ error: formatPostgresError(error, {
159
+ tool: "pg_point_in_polygon",
160
+ table: params?.["table"] ??
161
+ undefined,
162
+ }),
163
+ };
144
164
  }
145
- return response;
146
165
  },
147
166
  };
148
167
  }
@@ -156,34 +175,35 @@ export function createDistanceTool(adapter) {
156
175
  annotations: readOnly("Distance Search"),
157
176
  icons: getToolIcons("postgis", readOnly("Distance Search")),
158
177
  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 (
178
+ try {
179
+ const { table, column, point, limit, maxDistance, schema } = GeometryDistanceSchema.parse(params);
180
+ const schemaName = schema ?? "public";
181
+ const tableName = sanitizeTableName(table, schemaName !== "public" ? schemaName : undefined);
182
+ const columnName = sanitizeIdentifier(column);
183
+ const limitVal = limit ?? 10;
184
+ const distanceFilter = maxDistance !== undefined && maxDistance > 0
185
+ ? `WHERE distance_meters <= ${String(maxDistance)}`
186
+ : "";
187
+ // Get non-geometry columns to avoid returning raw WKB
188
+ const colQuery = `
189
+ SELECT column_name FROM information_schema.columns
190
+ WHERE table_schema = $1 AND table_name = $2
191
+ AND udt_name NOT IN ('geometry', 'geography')
192
+ ORDER BY ordinal_position
193
+ `;
194
+ const colResult = await adapter.executeQuery(colQuery, [
195
+ schemaName,
196
+ table,
197
+ ]);
198
+ const nonGeomCols = (colResult.rows ?? [])
199
+ .map((row) => sanitizeIdentifier(String(row["column_name"])))
200
+ .join(", ");
201
+ // Select non-geometry columns + readable geometry representation + distance
202
+ const selectCols = nonGeomCols.length > 0
203
+ ? `${nonGeomCols}, ST_AsText(${columnName}) as geometry_text, ST_Distance(${columnName}::geography, ST_SetSRID(ST_MakePoint($1, $2), 4326)::geography) as distance_meters`
204
+ : `ST_AsText(${columnName}) as geometry_text, ST_Distance(${columnName}::geography, ST_SetSRID(ST_MakePoint($1, $2), 4326)::geography) as distance_meters`;
205
+ // Use CTE for consistent distance calculation and filtering
206
+ const sql = `WITH distances AS (
187
207
  SELECT ${selectCols}
188
208
  FROM ${tableName}
189
209
  )
@@ -191,8 +211,25 @@ export function createDistanceTool(adapter) {
191
211
  ${distanceFilter}
192
212
  ORDER BY distance_meters
193
213
  LIMIT ${String(limitVal)}`;
194
- const result = await adapter.executeQuery(sql, [point.lng, point.lat]);
195
- return { results: result.rows, count: result.rows?.length ?? 0 };
214
+ const result = await adapter.executeQuery(sql, [point.lng, point.lat]);
215
+ return { results: result.rows, count: result.rows?.length ?? 0 };
216
+ }
217
+ catch (error) {
218
+ if (error instanceof ZodError) {
219
+ return {
220
+ success: false,
221
+ error: error.issues.map((i) => i.message).join("; "),
222
+ };
223
+ }
224
+ return {
225
+ success: false,
226
+ error: formatPostgresError(error, {
227
+ tool: "pg_distance",
228
+ table: params?.["table"] ??
229
+ undefined,
230
+ }),
231
+ };
232
+ }
196
233
  },
197
234
  };
198
235
  }
@@ -206,85 +243,14 @@ export function createBufferTool(adapter) {
206
243
  annotations: readOnly("Buffer Zone"),
207
244
  icons: getToolIcons("postgis", readOnly("Buffer Zone")),
208
245
  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
- // When using default limit, check if more rows exist
249
- if (parsed.limit === undefined && 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;
257
- }
258
- }
259
- // Add simplify indicator if simplification was applied
260
- if (effectiveSimplify > 0) {
261
- response["simplified"] = true;
262
- response["simplifyTolerance"] = effectiveSimplify;
263
- }
264
- return response;
265
- },
266
- };
267
- }
268
- export function createIntersectionTool(adapter) {
269
- return {
270
- name: "pg_intersection",
271
- description: "Find geometries that intersect with a given geometry. Auto-detects SRID from target column if not specified.",
272
- group: "postgis",
273
- inputSchema: IntersectionSchemaBase, // Base schema for MCP visibility
274
- outputSchema: IntersectionOutputSchema,
275
- annotations: readOnly("Intersection Search"),
276
- icons: getToolIcons("postgis", readOnly("Intersection Search")),
277
- 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 {
246
+ try {
247
+ const parsed = BufferSchema.parse(params ?? {});
248
+ const whereClause = parsed.where !== undefined ? ` WHERE ${parsed.where}` : "";
249
+ const schemaName = parsed.schema ?? "public";
250
+ const qualifiedTable = sanitizeTableName(parsed.table, schemaName !== "public" ? schemaName : undefined);
251
+ const columnName = sanitizeIdentifier(parsed.column);
252
+ // Default limit of 50 to prevent large payloads, use limit: 0 for all
253
+ const effectiveLimit = parsed.limit ?? 50;
288
254
  // Get non-geometry columns to avoid returning raw WKB
289
255
  const colQuery = `
290
256
  SELECT column_name FROM information_schema.columns
@@ -299,54 +265,163 @@ export function createIntersectionTool(adapter) {
299
265
  const nonGeomCols = (colResult.rows ?? [])
300
266
  .map((row) => sanitizeIdentifier(String(row["column_name"])))
301
267
  .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);
268
+ // Default simplify of 10m reduces polygon points for LLM-friendly payloads
269
+ // User can set simplify: 0 to disable or higher values for more aggressive reduction
270
+ const effectiveSimplify = parsed.simplify ?? 10;
271
+ // Build buffer expression with simplification (applied by default)
272
+ let bufferExpr = `ST_Buffer(${columnName}::geography, $1)::geometry`;
273
+ if (effectiveSimplify > 0) {
274
+ // SimplifyPreserveTopology maintains valid geometries
275
+ bufferExpr = `ST_SimplifyPreserveTopology(${bufferExpr}, ${String(effectiveSimplify)})`;
276
+ }
277
+ // Select non-geometry columns + readable geometry representations
278
+ const selectCols = nonGeomCols.length > 0
279
+ ? `${nonGeomCols}, ST_AsText(${columnName}) as geometry_text, ST_AsGeoJSON(${bufferExpr}) as buffer_geojson`
280
+ : `ST_AsText(${columnName}) as geometry_text, ST_AsGeoJSON(${bufferExpr}) as buffer_geojson`;
281
+ const limitClause = effectiveLimit > 0 ? ` LIMIT ${String(effectiveLimit)}` : "";
282
+ const sql = `SELECT ${selectCols} FROM ${qualifiedTable}${whereClause}${limitClause}`;
283
+ const result = await adapter.executeQuery(sql, [parsed.distance]);
284
+ // Build response with truncation indicators if default limit was applied
285
+ const response = { results: result.rows };
286
+ // Check if results were truncated (works for both default and explicit limits)
287
+ if (effectiveLimit > 0) {
288
+ const countSql = `SELECT COUNT(*) as cnt FROM ${qualifiedTable}${whereClause}`;
289
+ const countResult = await adapter.executeQuery(countSql);
290
+ const totalCount = Number(countResult.rows?.[0]?.["cnt"] ?? 0);
291
+ if (totalCount > effectiveLimit) {
292
+ response["truncated"] = true;
293
+ response["totalCount"] = totalCount;
294
+ response["limit"] = effectiveLimit;
295
+ }
296
+ }
297
+ // Add simplify indicator if simplification was applied
298
+ if (effectiveSimplify > 0) {
299
+ response["simplified"] = true;
300
+ response["simplifyTolerance"] = effectiveSimplify;
328
301
  }
302
+ return response;
329
303
  }
330
- // Build geometry expression with SRID if available
331
- let geomExpr;
332
- if (isGeoJson) {
333
- geomExpr = `ST_GeomFromGeoJSON($1)`;
304
+ catch (error) {
305
+ if (error instanceof ZodError) {
306
+ return {
307
+ success: false,
308
+ error: error.issues.map((i) => i.message).join("; "),
309
+ };
310
+ }
311
+ return {
312
+ success: false,
313
+ error: formatPostgresError(error, {
314
+ tool: "pg_buffer",
315
+ table: params?.["table"] ??
316
+ undefined,
317
+ }),
318
+ };
334
319
  }
335
- else if (srid !== undefined) {
336
- geomExpr = `ST_SetSRID(ST_GeomFromText($1), ${String(srid)})`;
320
+ },
321
+ };
322
+ }
323
+ export function createIntersectionTool(adapter) {
324
+ return {
325
+ name: "pg_intersection",
326
+ description: "Find geometries that intersect with a given geometry. Auto-detects SRID from target column if not specified.",
327
+ group: "postgis",
328
+ inputSchema: IntersectionSchemaBase, // Base schema for MCP visibility
329
+ outputSchema: IntersectionOutputSchema,
330
+ annotations: readOnly("Intersection Search"),
331
+ icons: getToolIcons("postgis", readOnly("Intersection Search")),
332
+ handler: async (params, _context) => {
333
+ try {
334
+ const parsed = IntersectionSchema.parse(params ?? {});
335
+ const schemaName = parsed.schema ?? "public";
336
+ const qualifiedTable = sanitizeTableName(parsed.table, schemaName !== "public" ? schemaName : undefined);
337
+ const columnName = sanitizeIdentifier(parsed.column);
338
+ // Build select columns - user-specified or non-geometry columns to avoid raw WKB
339
+ let selectCols;
340
+ if (parsed.select !== undefined && parsed.select.length > 0) {
341
+ selectCols = parsed.select
342
+ .map((c) => sanitizeIdentifier(c))
343
+ .join(", ");
344
+ }
345
+ else {
346
+ // Get non-geometry columns to avoid returning raw WKB
347
+ const colQuery = `
348
+ SELECT column_name FROM information_schema.columns
349
+ WHERE table_schema = $1 AND table_name = $2
350
+ AND udt_name NOT IN ('geometry', 'geography')
351
+ ORDER BY ordinal_position
352
+ `;
353
+ const colResult = await adapter.executeQuery(colQuery, [
354
+ schemaName,
355
+ parsed.table,
356
+ ]);
357
+ const nonGeomCols = (colResult.rows ?? [])
358
+ .map((row) => sanitizeIdentifier(String(row["column_name"])))
359
+ .join(", ");
360
+ selectCols =
361
+ nonGeomCols.length > 0
362
+ ? `${nonGeomCols}, ST_AsText(${columnName}) as geometry_text`
363
+ : `ST_AsText(${columnName}) as geometry_text`;
364
+ }
365
+ const isGeoJson = parsed.geometry.trim().startsWith("{");
366
+ // Auto-detect SRID from column if not provided and using WKT
367
+ let srid = parsed.srid;
368
+ if (!isGeoJson && srid === undefined) {
369
+ // Query the column's SRID from geometry_columns or geography_columns
370
+ const sridQuery = `
371
+ SELECT srid FROM geometry_columns
372
+ WHERE f_table_schema = $1 AND f_table_name = $2 AND f_geometry_column = $3
373
+ UNION
374
+ SELECT srid FROM geography_columns
375
+ WHERE f_table_schema = $1 AND f_table_name = $2 AND f_geography_column = $3
376
+ LIMIT 1
377
+ `;
378
+ const sridResult = await adapter.executeQuery(sridQuery, [
379
+ schemaName,
380
+ parsed.table,
381
+ parsed.column,
382
+ ]);
383
+ const sridValue = sridResult.rows?.[0]?.["srid"];
384
+ if (sridValue !== undefined && sridValue !== null) {
385
+ srid = Number(sridValue);
386
+ }
387
+ }
388
+ // Build geometry expression with SRID if available
389
+ let geomExpr;
390
+ if (isGeoJson) {
391
+ geomExpr = `ST_GeomFromGeoJSON($1)`;
392
+ }
393
+ else if (srid !== undefined) {
394
+ geomExpr = `ST_SetSRID(ST_GeomFromText($1), ${String(srid)})`;
395
+ }
396
+ else {
397
+ geomExpr = `ST_GeomFromText($1)`;
398
+ }
399
+ const sql = `SELECT ${selectCols}
400
+ FROM ${qualifiedTable}
401
+ WHERE ST_Intersects(${columnName}, ${geomExpr})`;
402
+ const result = await adapter.executeQuery(sql, [parsed.geometry]);
403
+ return {
404
+ intersecting: result.rows,
405
+ count: result.rows?.length ?? 0,
406
+ sridUsed: srid ?? "none (explicit SRID in geometry or GeoJSON)",
407
+ };
337
408
  }
338
- else {
339
- geomExpr = `ST_GeomFromText($1)`;
409
+ catch (error) {
410
+ if (error instanceof ZodError) {
411
+ return {
412
+ success: false,
413
+ error: error.issues.map((i) => i.message).join("; "),
414
+ };
415
+ }
416
+ return {
417
+ success: false,
418
+ error: formatPostgresError(error, {
419
+ tool: "pg_intersection",
420
+ table: params?.["table"] ??
421
+ undefined,
422
+ }),
423
+ };
340
424
  }
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
425
  },
351
426
  };
352
427
  }
@@ -360,64 +435,91 @@ export function createBoundingBoxTool(adapter) {
360
435
  annotations: readOnly("Bounding Box Search"),
361
436
  icons: getToolIcons("postgis", readOnly("Bounding Box Search")),
362
437
  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,
438
+ try {
439
+ const parsed = BoundingBoxSchema.parse(params ?? {});
440
+ const schemaName = parsed.schema ?? "public";
441
+ const qualifiedTable = sanitizeTableName(parsed.table, schemaName !== "public" ? schemaName : undefined);
442
+ const columnName = sanitizeIdentifier(parsed.column);
443
+ // Build select columns - user-specified or non-geometry columns to avoid raw WKB
444
+ let selectCols;
445
+ if (parsed.select !== undefined && parsed.select.length > 0) {
446
+ selectCols = parsed.select
447
+ .map((c) => sanitizeIdentifier(c))
448
+ .join(", ");
449
+ }
450
+ else {
451
+ // Get non-geometry columns to avoid returning raw WKB
452
+ const colQuery = `
453
+ SELECT column_name FROM information_schema.columns
454
+ WHERE table_schema = $1 AND table_name = $2
455
+ AND udt_name NOT IN ('geometry', 'geography')
456
+ ORDER BY ordinal_position
457
+ `;
458
+ const colResult = await adapter.executeQuery(colQuery, [
459
+ schemaName,
460
+ parsed.table,
461
+ ]);
462
+ selectCols = (colResult.rows ?? [])
463
+ .map((row) => sanitizeIdentifier(String(row["column_name"])))
464
+ .join(", ");
465
+ // If no columns found, table likely doesn't exist
466
+ if (selectCols.length === 0) {
467
+ return {
468
+ success: false,
469
+ error: `Table or view '${parsed.table}' not found in schema '${schemaName}'. Use pg_list_tables to see available tables.`,
470
+ };
471
+ }
472
+ }
473
+ // Auto-correct swapped bounds
474
+ const corrections = [];
475
+ let actualMinLng = parsed.minLng;
476
+ let actualMaxLng = parsed.maxLng;
477
+ let actualMinLat = parsed.minLat;
478
+ let actualMaxLat = parsed.maxLat;
479
+ if (parsed.minLng > parsed.maxLng) {
480
+ actualMinLng = parsed.maxLng;
481
+ actualMaxLng = parsed.minLng;
482
+ corrections.push("minLng/maxLng were swapped");
483
+ }
484
+ if (parsed.minLat > parsed.maxLat) {
485
+ actualMinLat = parsed.maxLat;
486
+ actualMaxLat = parsed.minLat;
487
+ corrections.push("minLat/maxLat were swapped");
488
+ }
489
+ const sql = `SELECT ${selectCols}, ST_AsText(${columnName}) as geometry_text
490
+ FROM ${qualifiedTable}
491
+ WHERE ${columnName} && ST_MakeEnvelope($1, $2, $3, $4, 4326)`;
492
+ const result = await adapter.executeQuery(sql, [
493
+ actualMinLng,
494
+ actualMinLat,
495
+ actualMaxLng,
496
+ actualMaxLat,
383
497
  ]);
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");
498
+ const response = {
499
+ results: result.rows,
500
+ count: result.rows?.length ?? 0,
501
+ };
502
+ if (corrections.length > 0) {
503
+ response["note"] = `Auto-corrected: ${corrections.join(", ")}`;
504
+ }
505
+ return response;
403
506
  }
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(", ")}`;
507
+ catch (error) {
508
+ if (error instanceof ZodError) {
509
+ return {
510
+ success: false,
511
+ error: error.issues.map((i) => i.message).join("; "),
512
+ };
513
+ }
514
+ return {
515
+ success: false,
516
+ error: formatPostgresError(error, {
517
+ tool: "pg_bounding_box",
518
+ table: params?.["table"] ??
519
+ undefined,
520
+ }),
521
+ };
419
522
  }
420
- return response;
421
523
  },
422
524
  };
423
525
  }
@@ -481,7 +583,15 @@ export function createSpatialIndexTool(adapter) {
481
583
  }
482
584
  // Always use IF NOT EXISTS to prevent unclear PostgreSQL errors
483
585
  const sql = `CREATE INDEX IF NOT EXISTS ${indexName} ON ${qualifiedTable} USING GIST (${columnName})`;
484
- await adapter.executeQuery(sql);
586
+ try {
587
+ await adapter.executeQuery(sql);
588
+ }
589
+ catch (error) {
590
+ throw parsePostgresError(error, {
591
+ tool: "pg_spatial_index",
592
+ table,
593
+ });
594
+ }
485
595
  return { success: true, index: indexNameRaw, table, column };
486
596
  },
487
597
  };