@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.
- package/README.md +95 -81
- package/dist/__tests__/mocks/adapter.d.ts.map +1 -1
- package/dist/__tests__/mocks/adapter.js +0 -1
- package/dist/__tests__/mocks/adapter.js.map +1 -1
- package/dist/__tests__/mocks/pool.d.ts.map +1 -1
- package/dist/__tests__/mocks/pool.js +0 -1
- package/dist/__tests__/mocks/pool.js.map +1 -1
- package/dist/adapters/DatabaseAdapter.js +1 -1
- package/dist/adapters/DatabaseAdapter.js.map +1 -1
- package/dist/adapters/postgresql/PostgresAdapter.d.ts.map +1 -1
- package/dist/adapters/postgresql/PostgresAdapter.js +78 -8
- package/dist/adapters/postgresql/PostgresAdapter.js.map +1 -1
- package/dist/adapters/postgresql/prompts/backup.d.ts.map +1 -1
- package/dist/adapters/postgresql/prompts/backup.js +2 -3
- package/dist/adapters/postgresql/prompts/backup.js.map +1 -1
- package/dist/adapters/postgresql/prompts/citext.d.ts.map +1 -1
- package/dist/adapters/postgresql/prompts/citext.js +3 -4
- package/dist/adapters/postgresql/prompts/citext.js.map +1 -1
- package/dist/adapters/postgresql/prompts/extensionSetup.d.ts.map +1 -1
- package/dist/adapters/postgresql/prompts/extensionSetup.js +2 -3
- package/dist/adapters/postgresql/prompts/extensionSetup.js.map +1 -1
- package/dist/adapters/postgresql/prompts/health.d.ts.map +1 -1
- package/dist/adapters/postgresql/prompts/health.js +2 -3
- package/dist/adapters/postgresql/prompts/health.js.map +1 -1
- package/dist/adapters/postgresql/prompts/index.js +20 -27
- package/dist/adapters/postgresql/prompts/index.js.map +1 -1
- package/dist/adapters/postgresql/prompts/indexTuning.d.ts.map +1 -1
- package/dist/adapters/postgresql/prompts/indexTuning.js +2 -3
- package/dist/adapters/postgresql/prompts/indexTuning.js.map +1 -1
- package/dist/adapters/postgresql/prompts/kcache.d.ts.map +1 -1
- package/dist/adapters/postgresql/prompts/kcache.js +3 -4
- package/dist/adapters/postgresql/prompts/kcache.js.map +1 -1
- package/dist/adapters/postgresql/prompts/ltree.d.ts.map +1 -1
- package/dist/adapters/postgresql/prompts/ltree.js +5 -6
- package/dist/adapters/postgresql/prompts/ltree.js.map +1 -1
- package/dist/adapters/postgresql/prompts/partman.d.ts.map +1 -1
- package/dist/adapters/postgresql/prompts/partman.js +2 -3
- package/dist/adapters/postgresql/prompts/partman.js.map +1 -1
- package/dist/adapters/postgresql/prompts/pgcron.d.ts.map +1 -1
- package/dist/adapters/postgresql/prompts/pgcron.js +2 -3
- package/dist/adapters/postgresql/prompts/pgcron.js.map +1 -1
- package/dist/adapters/postgresql/prompts/pgcrypto.d.ts.map +1 -1
- package/dist/adapters/postgresql/prompts/pgcrypto.js +3 -4
- package/dist/adapters/postgresql/prompts/pgcrypto.js.map +1 -1
- package/dist/adapters/postgresql/prompts/pgvector.d.ts.map +1 -1
- package/dist/adapters/postgresql/prompts/pgvector.js +3 -4
- package/dist/adapters/postgresql/prompts/pgvector.js.map +1 -1
- package/dist/adapters/postgresql/prompts/postgis.d.ts.map +1 -1
- package/dist/adapters/postgresql/prompts/postgis.js +2 -3
- package/dist/adapters/postgresql/prompts/postgis.js.map +1 -1
- package/dist/adapters/postgresql/schemas/admin.d.ts +10 -5
- package/dist/adapters/postgresql/schemas/admin.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/admin.js +10 -5
- package/dist/adapters/postgresql/schemas/admin.js.map +1 -1
- package/dist/adapters/postgresql/schemas/backup.d.ts +8 -4
- package/dist/adapters/postgresql/schemas/backup.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/backup.js +11 -4
- package/dist/adapters/postgresql/schemas/backup.js.map +1 -1
- package/dist/adapters/postgresql/schemas/core.d.ts +54 -19
- package/dist/adapters/postgresql/schemas/core.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/core.js +65 -17
- package/dist/adapters/postgresql/schemas/core.js.map +1 -1
- package/dist/adapters/postgresql/schemas/cron.d.ts +51 -32
- package/dist/adapters/postgresql/schemas/cron.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/cron.js +64 -44
- package/dist/adapters/postgresql/schemas/cron.js.map +1 -1
- package/dist/adapters/postgresql/schemas/extensions.d.ts +168 -73
- package/dist/adapters/postgresql/schemas/extensions.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/extensions.js +179 -62
- package/dist/adapters/postgresql/schemas/extensions.js.map +1 -1
- package/dist/adapters/postgresql/schemas/index.d.ts +5 -5
- package/dist/adapters/postgresql/schemas/index.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/index.js +9 -7
- package/dist/adapters/postgresql/schemas/index.js.map +1 -1
- package/dist/adapters/postgresql/schemas/jsonb.d.ts +94 -42
- package/dist/adapters/postgresql/schemas/jsonb.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/jsonb.js +101 -30
- package/dist/adapters/postgresql/schemas/jsonb.js.map +1 -1
- package/dist/adapters/postgresql/schemas/monitoring.d.ts +28 -11
- package/dist/adapters/postgresql/schemas/monitoring.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/monitoring.js +49 -24
- package/dist/adapters/postgresql/schemas/monitoring.js.map +1 -1
- package/dist/adapters/postgresql/schemas/partitioning.d.ts +15 -11
- package/dist/adapters/postgresql/schemas/partitioning.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/partitioning.js +17 -13
- package/dist/adapters/postgresql/schemas/partitioning.js.map +1 -1
- package/dist/adapters/postgresql/schemas/performance.d.ts +62 -31
- package/dist/adapters/postgresql/schemas/performance.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/performance.js +86 -24
- package/dist/adapters/postgresql/schemas/performance.js.map +1 -1
- package/dist/adapters/postgresql/schemas/postgis.d.ts +20 -0
- package/dist/adapters/postgresql/schemas/postgis.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/postgis.js +20 -3
- package/dist/adapters/postgresql/schemas/postgis.js.map +1 -1
- package/dist/adapters/postgresql/schemas/schema-mgmt.d.ts +35 -23
- package/dist/adapters/postgresql/schemas/schema-mgmt.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/schema-mgmt.js +69 -26
- package/dist/adapters/postgresql/schemas/schema-mgmt.js.map +1 -1
- package/dist/adapters/postgresql/schemas/stats.d.ts +33 -20
- package/dist/adapters/postgresql/schemas/stats.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/stats.js +36 -20
- package/dist/adapters/postgresql/schemas/stats.js.map +1 -1
- package/dist/adapters/postgresql/schemas/text-search.d.ts +8 -5
- package/dist/adapters/postgresql/schemas/text-search.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/text-search.js +15 -5
- package/dist/adapters/postgresql/schemas/text-search.js.map +1 -1
- package/dist/adapters/postgresql/tools/admin.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/admin.js +211 -140
- package/dist/adapters/postgresql/tools/admin.js.map +1 -1
- package/dist/adapters/postgresql/tools/backup/dump.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/backup/dump.js +410 -387
- package/dist/adapters/postgresql/tools/backup/dump.js.map +1 -1
- package/dist/adapters/postgresql/tools/backup/planning.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/backup/planning.js +175 -172
- package/dist/adapters/postgresql/tools/backup/planning.js.map +1 -1
- package/dist/adapters/postgresql/tools/citext.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/citext.js +221 -163
- package/dist/adapters/postgresql/tools/citext.js.map +1 -1
- package/dist/adapters/postgresql/tools/core/convenience.d.ts +9 -1
- package/dist/adapters/postgresql/tools/core/convenience.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/core/convenience.js +96 -9
- package/dist/adapters/postgresql/tools/core/convenience.js.map +1 -1
- package/dist/adapters/postgresql/tools/core/error-helpers.d.ts +48 -0
- package/dist/adapters/postgresql/tools/core/error-helpers.d.ts.map +1 -0
- package/dist/adapters/postgresql/tools/core/error-helpers.js +256 -0
- package/dist/adapters/postgresql/tools/core/error-helpers.js.map +1 -0
- package/dist/adapters/postgresql/tools/core/health.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/core/health.js +23 -6
- package/dist/adapters/postgresql/tools/core/health.js.map +1 -1
- package/dist/adapters/postgresql/tools/core/indexes.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/core/indexes.js +45 -4
- package/dist/adapters/postgresql/tools/core/indexes.js.map +1 -1
- package/dist/adapters/postgresql/tools/core/objects.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/core/objects.js +104 -85
- package/dist/adapters/postgresql/tools/core/objects.js.map +1 -1
- package/dist/adapters/postgresql/tools/core/query.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/core/query.js +100 -42
- package/dist/adapters/postgresql/tools/core/query.js.map +1 -1
- package/dist/adapters/postgresql/tools/core/schemas.d.ts +52 -25
- package/dist/adapters/postgresql/tools/core/schemas.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/core/schemas.js +55 -25
- package/dist/adapters/postgresql/tools/core/schemas.js.map +1 -1
- package/dist/adapters/postgresql/tools/core/tables.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/core/tables.js +74 -30
- package/dist/adapters/postgresql/tools/core/tables.js.map +1 -1
- package/dist/adapters/postgresql/tools/cron.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/cron.js +274 -179
- package/dist/adapters/postgresql/tools/cron.js.map +1 -1
- package/dist/adapters/postgresql/tools/jsonb/advanced.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/jsonb/advanced.js +372 -284
- package/dist/adapters/postgresql/tools/jsonb/advanced.js.map +1 -1
- package/dist/adapters/postgresql/tools/jsonb/basic.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/jsonb/basic.js +617 -398
- package/dist/adapters/postgresql/tools/jsonb/basic.js.map +1 -1
- package/dist/adapters/postgresql/tools/kcache.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/kcache.js +282 -220
- package/dist/adapters/postgresql/tools/kcache.js.map +1 -1
- package/dist/adapters/postgresql/tools/ltree.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/ltree.js +126 -35
- package/dist/adapters/postgresql/tools/ltree.js.map +1 -1
- package/dist/adapters/postgresql/tools/monitoring.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/monitoring.js +59 -40
- package/dist/adapters/postgresql/tools/monitoring.js.map +1 -1
- package/dist/adapters/postgresql/tools/partitioning.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/partitioning.js +150 -15
- package/dist/adapters/postgresql/tools/partitioning.js.map +1 -1
- package/dist/adapters/postgresql/tools/partman/management.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/partman/management.js +12 -5
- package/dist/adapters/postgresql/tools/partman/management.js.map +1 -1
- package/dist/adapters/postgresql/tools/partman/operations.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/partman/operations.js +135 -22
- package/dist/adapters/postgresql/tools/partman/operations.js.map +1 -1
- package/dist/adapters/postgresql/tools/performance/analysis.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/performance/analysis.js +264 -160
- package/dist/adapters/postgresql/tools/performance/analysis.js.map +1 -1
- package/dist/adapters/postgresql/tools/performance/explain.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/performance/explain.js +61 -21
- package/dist/adapters/postgresql/tools/performance/explain.js.map +1 -1
- package/dist/adapters/postgresql/tools/performance/monitoring.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/performance/monitoring.js +44 -7
- package/dist/adapters/postgresql/tools/performance/monitoring.js.map +1 -1
- package/dist/adapters/postgresql/tools/performance/optimization.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/performance/optimization.js +92 -81
- package/dist/adapters/postgresql/tools/performance/optimization.js.map +1 -1
- package/dist/adapters/postgresql/tools/performance/stats.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/performance/stats.js +128 -37
- package/dist/adapters/postgresql/tools/performance/stats.js.map +1 -1
- package/dist/adapters/postgresql/tools/pgcrypto.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/pgcrypto.js +242 -87
- package/dist/adapters/postgresql/tools/pgcrypto.js.map +1 -1
- package/dist/adapters/postgresql/tools/postgis/advanced.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/postgis/advanced.js +293 -201
- package/dist/adapters/postgresql/tools/postgis/advanced.js.map +1 -1
- package/dist/adapters/postgresql/tools/postgis/basic.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/postgis/basic.js +359 -249
- package/dist/adapters/postgresql/tools/postgis/basic.js.map +1 -1
- package/dist/adapters/postgresql/tools/postgis/standalone.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/postgis/standalone.js +135 -51
- package/dist/adapters/postgresql/tools/postgis/standalone.js.map +1 -1
- package/dist/adapters/postgresql/tools/schema.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/schema.js +515 -226
- package/dist/adapters/postgresql/tools/schema.js.map +1 -1
- package/dist/adapters/postgresql/tools/stats/advanced.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/stats/advanced.js +515 -476
- package/dist/adapters/postgresql/tools/stats/advanced.js.map +1 -1
- package/dist/adapters/postgresql/tools/stats/basic.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/stats/basic.js +302 -293
- package/dist/adapters/postgresql/tools/stats/basic.js.map +1 -1
- package/dist/adapters/postgresql/tools/text.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/text.js +398 -220
- package/dist/adapters/postgresql/tools/text.js.map +1 -1
- package/dist/adapters/postgresql/tools/transactions.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/transactions.js +157 -50
- package/dist/adapters/postgresql/tools/transactions.js.map +1 -1
- package/dist/adapters/postgresql/tools/vector/advanced.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/vector/advanced.js +70 -38
- package/dist/adapters/postgresql/tools/vector/advanced.js.map +1 -1
- package/dist/adapters/postgresql/tools/vector/basic.d.ts +8 -0
- package/dist/adapters/postgresql/tools/vector/basic.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/vector/basic.js +194 -82
- package/dist/adapters/postgresql/tools/vector/basic.js.map +1 -1
- package/dist/cli/args.d.ts +2 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +15 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli.js +7 -6
- package/dist/cli.js.map +1 -1
- package/dist/codemode/api.d.ts.map +1 -1
- package/dist/codemode/api.js +4 -3
- package/dist/codemode/api.js.map +1 -1
- package/dist/constants/ServerInstructions.d.ts +1 -1
- package/dist/constants/ServerInstructions.d.ts.map +1 -1
- package/dist/constants/ServerInstructions.js +76 -34
- package/dist/constants/ServerInstructions.js.map +1 -1
- package/dist/filtering/ToolConstants.d.ts +29 -13
- package/dist/filtering/ToolConstants.d.ts.map +1 -1
- package/dist/filtering/ToolConstants.js +44 -27
- package/dist/filtering/ToolConstants.js.map +1 -1
- package/dist/utils/logger.js +2 -2
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/progress-utils.js +1 -1
- package/dist/utils/progress-utils.js.map +1 -1
- 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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
195
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
//
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
-
|
|
336
|
-
|
|
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
|
-
|
|
339
|
-
|
|
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
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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
|
-
|
|
385
|
-
.
|
|
386
|
-
.
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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
|
-
|
|
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
|
};
|