@neverinfamous/postgres-mcp 1.2.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 +68 -62
- package/dist/adapters/postgresql/PostgresAdapter.d.ts.map +1 -1
- package/dist/adapters/postgresql/PostgresAdapter.js +53 -3
- package/dist/adapters/postgresql/PostgresAdapter.js.map +1 -1
- package/dist/adapters/postgresql/prompts/ltree.js +2 -2
- package/dist/adapters/postgresql/prompts/ltree.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 +53 -19
- package/dist/adapters/postgresql/schemas/core.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/core.js +61 -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 +177 -60
- 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 +5 -4
- package/dist/adapters/postgresql/schemas/partitioning.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/partitioning.js +5 -4
- package/dist/adapters/postgresql/schemas/partitioning.js.map +1 -1
- package/dist/adapters/postgresql/schemas/performance.d.ts +60 -30
- package/dist/adapters/postgresql/schemas/performance.d.ts.map +1 -1
- package/dist/adapters/postgresql/schemas/performance.js +85 -22
- 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 -0
- 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 +11 -4
- 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 +360 -337
- package/dist/adapters/postgresql/tools/backup/dump.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 +78 -11
- 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 +18 -4
- 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 +51 -25
- package/dist/adapters/postgresql/tools/core/schemas.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/core/schemas.js +51 -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 +68 -28
- 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 +278 -246
- 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 +121 -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 +54 -34
- 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 +79 -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 +11 -4
- 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 +132 -19
- 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 +124 -36
- 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 +244 -89
- 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 +285 -222
- 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 +504 -231
- 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 +392 -218
- 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 +18 -0
- package/dist/adapters/postgresql/tools/vector/advanced.js.map +1 -1
- package/dist/adapters/postgresql/tools/vector/basic.d.ts.map +1 -1
- package/dist/adapters/postgresql/tools/vector/basic.js +100 -53
- package/dist/adapters/postgresql/tools/vector/basic.js.map +1 -1
- package/dist/codemode/api.js +1 -1
- 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 +45 -7
- package/dist/constants/ServerInstructions.js.map +1 -1
- package/package.json +9 -5
|
@@ -14,11 +14,10 @@
|
|
|
14
14
|
import { z } from "zod";
|
|
15
15
|
import { readOnly, write, destructive } from "../../../utils/annotations.js";
|
|
16
16
|
import { getToolIcons } from "../../../utils/icons.js";
|
|
17
|
-
import {
|
|
17
|
+
import { formatPostgresError } from "./core/error-helpers.js";
|
|
18
|
+
import { KcacheQueryStatsSchemaBase, KcacheQueryStatsSchema, KcacheTopCpuSchemaBase, KcacheTopIoSchemaBase, KcacheDatabaseStatsSchemaBase, KcacheDatabaseStatsSchema, KcacheResourceAnalysisSchemaBase, KcacheResourceAnalysisSchema,
|
|
18
19
|
// Output schemas
|
|
19
20
|
KcacheCreateExtensionOutputSchema, KcacheQueryStatsOutputSchema, KcacheTopCpuOutputSchema, KcacheTopIoOutputSchema, KcacheDatabaseStatsOutputSchema, KcacheResourceAnalysisOutputSchema, KcacheResetOutputSchema, } from "../schemas/index.js";
|
|
20
|
-
// Helper to handle undefined params (allows tools to be called without {})
|
|
21
|
-
const defaultToEmpty = (val) => val ?? {};
|
|
22
21
|
async function getKcacheColumnNames(adapter) {
|
|
23
22
|
const result = await adapter.executeQuery(`
|
|
24
23
|
SELECT column_name FROM information_schema.columns
|
|
@@ -105,37 +104,52 @@ Joins pg_stat_statements with pg_stat_kcache to show what SQL did AND what syste
|
|
|
105
104
|
|
|
106
105
|
orderBy options: 'total_time' (default), 'cpu_time', 'reads', 'writes'. Use minCalls parameter to filter by call count.`,
|
|
107
106
|
group: "kcache",
|
|
108
|
-
inputSchema:
|
|
107
|
+
inputSchema: KcacheQueryStatsSchemaBase,
|
|
109
108
|
outputSchema: KcacheQueryStatsOutputSchema,
|
|
110
109
|
annotations: readOnly("Kcache Query Stats"),
|
|
111
110
|
icons: getToolIcons("kcache", readOnly("Kcache Query Stats")),
|
|
112
111
|
handler: async (params, _context) => {
|
|
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
|
-
|
|
112
|
+
try {
|
|
113
|
+
const { limit, orderBy, minCalls, queryPreviewLength } = KcacheQueryStatsSchema.parse(params);
|
|
114
|
+
// Validate orderBy inside handler for structured error response
|
|
115
|
+
const VALID_ORDER_BY = [
|
|
116
|
+
"total_time",
|
|
117
|
+
"cpu_time",
|
|
118
|
+
"reads",
|
|
119
|
+
"writes",
|
|
120
|
+
];
|
|
121
|
+
if (orderBy !== undefined &&
|
|
122
|
+
!VALID_ORDER_BY.includes(orderBy)) {
|
|
123
|
+
return {
|
|
124
|
+
success: false,
|
|
125
|
+
error: `Invalid orderBy value "${orderBy}". Valid options: ${VALID_ORDER_BY.join(", ")}`,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const cols = await getKcacheColumnNames(adapter);
|
|
129
|
+
const DEFAULT_LIMIT = 20;
|
|
130
|
+
// limit: 0 means "no limit" (return all rows), undefined means use default
|
|
131
|
+
const limitVal = limit === 0 ? null : (limit ?? DEFAULT_LIMIT);
|
|
132
|
+
// Bound queryPreviewLength: 0 = full query, default 100, max 500
|
|
133
|
+
const previewLen = queryPreviewLength === 0
|
|
134
|
+
? 10000
|
|
135
|
+
: Math.min(queryPreviewLength ?? 100, 500);
|
|
136
|
+
const orderColumn = orderBy === "cpu_time"
|
|
137
|
+
? `(k.${cols.userTime} + k.${cols.systemTime})`
|
|
138
|
+
: orderBy === "reads"
|
|
139
|
+
? `k.${cols.reads}`
|
|
140
|
+
: orderBy === "writes"
|
|
141
|
+
? `k.${cols.writes}`
|
|
142
|
+
: "s.total_exec_time";
|
|
143
|
+
const conditions = [];
|
|
144
|
+
const queryParams = [];
|
|
145
|
+
const paramIndex = 1;
|
|
146
|
+
if (minCalls !== undefined) {
|
|
147
|
+
conditions.push(`s.calls >= $${String(paramIndex)}`);
|
|
148
|
+
queryParams.push(minCalls);
|
|
149
|
+
}
|
|
150
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
151
|
+
// Get total count first for truncation indicator
|
|
152
|
+
const countSql = `
|
|
139
153
|
SELECT COUNT(*) as total
|
|
140
154
|
FROM pg_stat_statements s
|
|
141
155
|
JOIN pg_stat_kcache() k ON s.queryid = k.queryid
|
|
@@ -143,10 +157,10 @@ orderBy options: 'total_time' (default), 'cpu_time', 'reads', 'writes'. Use minC
|
|
|
143
157
|
AND s.dbid = k.dbid
|
|
144
158
|
${whereClause}
|
|
145
159
|
`;
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
160
|
+
const countResult = await adapter.executeQuery(countSql, queryParams);
|
|
161
|
+
const totalRaw = countResult.rows?.[0]?.["total"];
|
|
162
|
+
const totalCount = Number(totalRaw) || 0;
|
|
163
|
+
const sql = `
|
|
150
164
|
SELECT
|
|
151
165
|
s.queryid,
|
|
152
166
|
LEFT(s.query, ${String(previewLen)}) as query_preview,
|
|
@@ -170,18 +184,25 @@ orderBy options: 'total_time' (default), 'cpu_time', 'reads', 'writes'. Use minC
|
|
|
170
184
|
ORDER BY ${orderColumn} DESC
|
|
171
185
|
${limitVal !== null ? `LIMIT ${String(limitVal)}` : ""}
|
|
172
186
|
`;
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
187
|
+
const result = await adapter.executeQuery(sql, queryParams);
|
|
188
|
+
const rowCount = result.rows?.length ?? 0;
|
|
189
|
+
const effectiveTotalCount = Math.max(totalCount, rowCount);
|
|
190
|
+
const truncated = rowCount < effectiveTotalCount;
|
|
191
|
+
const response = {
|
|
192
|
+
queries: result.rows ?? [],
|
|
193
|
+
count: rowCount,
|
|
194
|
+
orderBy: orderBy ?? "total_time",
|
|
195
|
+
truncated,
|
|
196
|
+
totalCount: effectiveTotalCount,
|
|
197
|
+
};
|
|
198
|
+
return response;
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
return {
|
|
202
|
+
success: false,
|
|
203
|
+
error: formatPostgresError(error, { tool: "pg_kcache_query_stats" }),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
185
206
|
},
|
|
186
207
|
};
|
|
187
208
|
}
|
|
@@ -194,36 +215,28 @@ function createKcacheTopCpuTool(adapter) {
|
|
|
194
215
|
description: `Get top CPU-consuming queries. Shows which queries spend the most time
|
|
195
216
|
in user CPU (application code) vs system CPU (kernel operations).`,
|
|
196
217
|
group: "kcache",
|
|
197
|
-
inputSchema:
|
|
198
|
-
limit: z
|
|
199
|
-
.number()
|
|
200
|
-
.optional()
|
|
201
|
-
.describe("Number of top queries to return (default: 10)"),
|
|
202
|
-
queryPreviewLength: z
|
|
203
|
-
.number()
|
|
204
|
-
.optional()
|
|
205
|
-
.describe("Characters for query preview (default: 100, max: 500, 0 for full)"),
|
|
206
|
-
})),
|
|
218
|
+
inputSchema: KcacheTopCpuSchemaBase,
|
|
207
219
|
outputSchema: KcacheTopCpuOutputSchema,
|
|
208
220
|
annotations: readOnly("Kcache Top CPU"),
|
|
209
221
|
icons: getToolIcons("kcache", readOnly("Kcache Top CPU")),
|
|
210
222
|
handler: async (params, _context) => {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
223
|
+
try {
|
|
224
|
+
const parsed = z
|
|
225
|
+
.object({
|
|
226
|
+
limit: z.number().optional(),
|
|
227
|
+
queryPreviewLength: z.number().optional(),
|
|
228
|
+
})
|
|
229
|
+
.parse(params ?? {});
|
|
230
|
+
const DEFAULT_LIMIT = 10;
|
|
231
|
+
// limit: 0 means "no limit" (return all rows), undefined means use default
|
|
232
|
+
const limitVal = parsed.limit === 0 ? null : (parsed.limit ?? DEFAULT_LIMIT);
|
|
233
|
+
// Bound queryPreviewLength: 0 = full query, default 100, max 500
|
|
234
|
+
const previewLen = parsed.queryPreviewLength === 0
|
|
235
|
+
? 10000
|
|
236
|
+
: Math.min(parsed.queryPreviewLength ?? 100, 500);
|
|
237
|
+
const cols = await getKcacheColumnNames(adapter);
|
|
238
|
+
// Get total count first for truncation indicator
|
|
239
|
+
const countSql = `
|
|
227
240
|
SELECT COUNT(*) as total
|
|
228
241
|
FROM pg_stat_statements s
|
|
229
242
|
JOIN pg_stat_kcache() k ON s.queryid = k.queryid
|
|
@@ -231,10 +244,10 @@ in user CPU (application code) vs system CPU (kernel operations).`,
|
|
|
231
244
|
AND s.dbid = k.dbid
|
|
232
245
|
WHERE (k.${cols.userTime} + k.${cols.systemTime}) > 0
|
|
233
246
|
`;
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
247
|
+
const countResult = await adapter.executeQuery(countSql);
|
|
248
|
+
const totalRaw = countResult.rows?.[0]?.["total"];
|
|
249
|
+
const totalCount = Number(totalRaw) || 0;
|
|
250
|
+
const sql = `
|
|
238
251
|
SELECT
|
|
239
252
|
s.queryid,
|
|
240
253
|
LEFT(s.query, ${String(previewLen)}) as query_preview,
|
|
@@ -261,18 +274,25 @@ in user CPU (application code) vs system CPU (kernel operations).`,
|
|
|
261
274
|
ORDER BY (k.${cols.userTime} + k.${cols.systemTime}) DESC
|
|
262
275
|
${limitVal !== null ? `LIMIT ${String(limitVal)}` : ""}
|
|
263
276
|
`;
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
277
|
+
const result = await adapter.executeQuery(sql);
|
|
278
|
+
const rowCount = result.rows?.length ?? 0;
|
|
279
|
+
const effectiveTotalCount = Math.max(totalCount, rowCount);
|
|
280
|
+
const truncated = rowCount < effectiveTotalCount;
|
|
281
|
+
const response = {
|
|
282
|
+
topCpuQueries: result.rows ?? [],
|
|
283
|
+
count: rowCount,
|
|
284
|
+
description: "Queries ranked by total CPU time (user + system)",
|
|
285
|
+
truncated,
|
|
286
|
+
totalCount: effectiveTotalCount,
|
|
287
|
+
};
|
|
288
|
+
return response;
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
return {
|
|
292
|
+
success: false,
|
|
293
|
+
error: formatPostgresError(error, { tool: "pg_kcache_top_cpu" }),
|
|
294
|
+
};
|
|
295
|
+
}
|
|
276
296
|
},
|
|
277
297
|
};
|
|
278
298
|
}
|
|
@@ -285,72 +305,49 @@ function createKcacheTopIoTool(adapter) {
|
|
|
285
305
|
description: `Get top I/O-consuming queries. Shows filesystem-level reads and writes,
|
|
286
306
|
which represent actual disk access (not just shared buffer hits).`,
|
|
287
307
|
group: "kcache",
|
|
288
|
-
inputSchema:
|
|
289
|
-
const obj = defaultToEmpty(input);
|
|
290
|
-
// Alias: ioType -> type
|
|
291
|
-
if (obj["ioType"] !== undefined && obj["type"] === undefined) {
|
|
292
|
-
obj["type"] = obj["ioType"];
|
|
293
|
-
}
|
|
294
|
-
return obj;
|
|
295
|
-
}, z.object({
|
|
296
|
-
type: z
|
|
297
|
-
.enum(["reads", "writes", "both"])
|
|
298
|
-
.optional()
|
|
299
|
-
.describe("I/O type to rank by (default: both)"),
|
|
300
|
-
ioType: z
|
|
301
|
-
.enum(["reads", "writes", "both"])
|
|
302
|
-
.optional()
|
|
303
|
-
.describe("Alias for type"),
|
|
304
|
-
limit: z
|
|
305
|
-
.number()
|
|
306
|
-
.optional()
|
|
307
|
-
.describe("Number of top queries to return (default: 10)"),
|
|
308
|
-
queryPreviewLength: z
|
|
309
|
-
.number()
|
|
310
|
-
.optional()
|
|
311
|
-
.describe("Characters for query preview (default: 100, max: 500, 0 for full)"),
|
|
312
|
-
})),
|
|
308
|
+
inputSchema: KcacheTopIoSchemaBase,
|
|
313
309
|
outputSchema: KcacheTopIoOutputSchema,
|
|
314
310
|
annotations: readOnly("Kcache Top IO"),
|
|
315
311
|
icons: getToolIcons("kcache", readOnly("Kcache Top IO")),
|
|
316
312
|
handler: async (params, _context) => {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
313
|
+
try {
|
|
314
|
+
// Apply the same preprocessing as inputSchema
|
|
315
|
+
const preprocessed = (() => {
|
|
316
|
+
const obj = (params ?? {});
|
|
317
|
+
if (obj["ioType"] !== undefined && obj["type"] === undefined) {
|
|
318
|
+
return { ...obj, type: obj["ioType"] };
|
|
319
|
+
}
|
|
320
|
+
return obj;
|
|
321
|
+
})();
|
|
322
|
+
const parsed = z
|
|
323
|
+
.object({
|
|
324
|
+
type: z.enum(["reads", "writes", "both"]).optional(),
|
|
325
|
+
limit: z.number().optional(),
|
|
326
|
+
queryPreviewLength: z.number().optional(),
|
|
327
|
+
})
|
|
328
|
+
.parse(preprocessed);
|
|
329
|
+
const ioType = parsed.type ?? "both";
|
|
330
|
+
const DEFAULT_LIMIT = 10;
|
|
331
|
+
// limit: 0 means "no limit" (return all rows), undefined means use default
|
|
332
|
+
const limitVal = parsed.limit === 0 ? null : (parsed.limit ?? DEFAULT_LIMIT);
|
|
333
|
+
// Bound queryPreviewLength: 0 = full query, default 100, max 500
|
|
334
|
+
const previewLen = parsed.queryPreviewLength === 0
|
|
335
|
+
? 10000
|
|
336
|
+
: Math.min(parsed.queryPreviewLength ?? 100, 500);
|
|
337
|
+
const cols = await getKcacheColumnNames(adapter);
|
|
338
|
+
const orderColumn = ioType === "reads"
|
|
339
|
+
? `k.${cols.reads}`
|
|
340
|
+
: ioType === "writes"
|
|
341
|
+
? `k.${cols.writes}`
|
|
342
|
+
: `(k.${cols.reads} + k.${cols.writes})`;
|
|
343
|
+
// Filter by the type-specific IO column so 'reads' excludes write-only queries
|
|
344
|
+
const ioFilter = ioType === "reads"
|
|
345
|
+
? `k.${cols.reads} > 0`
|
|
346
|
+
: ioType === "writes"
|
|
347
|
+
? `k.${cols.writes} > 0`
|
|
348
|
+
: `(k.${cols.reads} + k.${cols.writes}) > 0`;
|
|
349
|
+
// Get total count first for truncation indicator
|
|
350
|
+
const countSql = `
|
|
354
351
|
SELECT COUNT(*) as total
|
|
355
352
|
FROM pg_stat_statements s
|
|
356
353
|
JOIN pg_stat_kcache() k ON s.queryid = k.queryid
|
|
@@ -358,10 +355,10 @@ which represent actual disk access (not just shared buffer hits).`,
|
|
|
358
355
|
AND s.dbid = k.dbid
|
|
359
356
|
WHERE ${ioFilter}
|
|
360
357
|
`;
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
358
|
+
const countResult = await adapter.executeQuery(countSql);
|
|
359
|
+
const totalRaw = countResult.rows?.[0]?.["total"];
|
|
360
|
+
const totalCount = Number(totalRaw) || 0;
|
|
361
|
+
const sql = `
|
|
365
362
|
SELECT
|
|
366
363
|
s.queryid,
|
|
367
364
|
LEFT(s.query, ${String(previewLen)}) as query_preview,
|
|
@@ -380,19 +377,26 @@ which represent actual disk access (not just shared buffer hits).`,
|
|
|
380
377
|
ORDER BY ${orderColumn} DESC
|
|
381
378
|
${limitVal !== null ? `LIMIT ${String(limitVal)}` : ""}
|
|
382
379
|
`;
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
380
|
+
const result = await adapter.executeQuery(sql);
|
|
381
|
+
const rowCount = result.rows?.length ?? 0;
|
|
382
|
+
const effectiveTotalCount = Math.max(totalCount, rowCount);
|
|
383
|
+
const truncated = rowCount < effectiveTotalCount;
|
|
384
|
+
const response = {
|
|
385
|
+
topIoQueries: result.rows ?? [],
|
|
386
|
+
count: rowCount,
|
|
387
|
+
ioType,
|
|
388
|
+
description: `Queries ranked by ${ioType === "both" ? "total I/O" : ioType}`,
|
|
389
|
+
truncated,
|
|
390
|
+
totalCount: effectiveTotalCount,
|
|
391
|
+
};
|
|
392
|
+
return response;
|
|
393
|
+
}
|
|
394
|
+
catch (error) {
|
|
395
|
+
return {
|
|
396
|
+
success: false,
|
|
397
|
+
error: formatPostgresError(error, { tool: "pg_kcache_top_io" }),
|
|
398
|
+
};
|
|
399
|
+
}
|
|
396
400
|
},
|
|
397
401
|
};
|
|
398
402
|
}
|
|
@@ -405,17 +409,18 @@ function createKcacheDatabaseStatsTool(adapter) {
|
|
|
405
409
|
description: `Get aggregated OS-level statistics for a database.
|
|
406
410
|
Shows total CPU time, I/O, and page faults across all queries.`,
|
|
407
411
|
group: "kcache",
|
|
408
|
-
inputSchema:
|
|
412
|
+
inputSchema: KcacheDatabaseStatsSchemaBase,
|
|
409
413
|
outputSchema: KcacheDatabaseStatsOutputSchema,
|
|
410
414
|
annotations: readOnly("Kcache Database Stats"),
|
|
411
415
|
icons: getToolIcons("kcache", readOnly("Kcache Database Stats")),
|
|
412
416
|
handler: async (params, _context) => {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
417
|
+
try {
|
|
418
|
+
const { database } = KcacheDatabaseStatsSchema.parse(params);
|
|
419
|
+
const cols = await getKcacheColumnNames(adapter);
|
|
420
|
+
let sql;
|
|
421
|
+
const queryParams = [];
|
|
422
|
+
if (database !== undefined) {
|
|
423
|
+
sql = `
|
|
419
424
|
SELECT
|
|
420
425
|
d.datname as database,
|
|
421
426
|
SUM(k.${cols.userTime}) as total_user_time,
|
|
@@ -433,10 +438,10 @@ Shows total CPU time, I/O, and page faults across all queries.`,
|
|
|
433
438
|
WHERE d.datname = $1
|
|
434
439
|
GROUP BY d.datname
|
|
435
440
|
`;
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
441
|
+
queryParams.push(database);
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
sql = `
|
|
440
445
|
SELECT
|
|
441
446
|
datname as database,
|
|
442
447
|
SUM(${cols.userTime}) as total_user_time,
|
|
@@ -453,12 +458,21 @@ Shows total CPU time, I/O, and page faults across all queries.`,
|
|
|
453
458
|
GROUP BY datname
|
|
454
459
|
ORDER BY SUM(${cols.userTime} + ${cols.systemTime}) DESC
|
|
455
460
|
`;
|
|
461
|
+
}
|
|
462
|
+
const result = await adapter.executeQuery(sql, queryParams);
|
|
463
|
+
return {
|
|
464
|
+
databaseStats: result.rows ?? [],
|
|
465
|
+
count: result.rows?.length ?? 0,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
catch (error) {
|
|
469
|
+
return {
|
|
470
|
+
success: false,
|
|
471
|
+
error: formatPostgresError(error, {
|
|
472
|
+
tool: "pg_kcache_database_stats",
|
|
473
|
+
}),
|
|
474
|
+
};
|
|
456
475
|
}
|
|
457
|
-
const result = await adapter.executeQuery(sql, queryParams);
|
|
458
|
-
return {
|
|
459
|
-
databaseStats: result.rows ?? [],
|
|
460
|
-
count: result.rows?.length ?? 0,
|
|
461
|
-
};
|
|
462
476
|
},
|
|
463
477
|
};
|
|
464
478
|
}
|
|
@@ -471,36 +485,37 @@ function createKcacheResourceAnalysisTool(adapter) {
|
|
|
471
485
|
description: `Analyze queries to classify them as CPU-bound, I/O-bound, or balanced.
|
|
472
486
|
Helps identify the root cause of performance issues - is the query computation-heavy or disk-heavy?`,
|
|
473
487
|
group: "kcache",
|
|
474
|
-
inputSchema:
|
|
488
|
+
inputSchema: KcacheResourceAnalysisSchemaBase,
|
|
475
489
|
outputSchema: KcacheResourceAnalysisOutputSchema,
|
|
476
490
|
annotations: readOnly("Kcache Resource Analysis"),
|
|
477
491
|
icons: getToolIcons("kcache", readOnly("Kcache Resource Analysis")),
|
|
478
492
|
handler: async (params, _context) => {
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
493
|
+
try {
|
|
494
|
+
const { queryId, threshold, limit, minCalls, queryPreviewLength } = KcacheResourceAnalysisSchema.parse(params);
|
|
495
|
+
const thresholdVal = threshold ?? 0.5;
|
|
496
|
+
const DEFAULT_LIMIT = 20;
|
|
497
|
+
// limit: 0 means "no limit" (return all rows), undefined means use default
|
|
498
|
+
const limitVal = limit === 0 ? null : (limit ?? DEFAULT_LIMIT);
|
|
499
|
+
// Bound queryPreviewLength: 0 = full query, default 100, max 500
|
|
500
|
+
const previewLen = queryPreviewLength === 0
|
|
501
|
+
? 10000
|
|
502
|
+
: Math.min(queryPreviewLength ?? 100, 500);
|
|
503
|
+
const cols = await getKcacheColumnNames(adapter);
|
|
504
|
+
const conditions = [];
|
|
505
|
+
const queryParams = [];
|
|
506
|
+
let paramIndex = 1;
|
|
507
|
+
if (queryId !== undefined) {
|
|
508
|
+
conditions.push(`s.queryid::text = $${String(paramIndex++)}`);
|
|
509
|
+
queryParams.push(queryId);
|
|
510
|
+
}
|
|
511
|
+
if (minCalls !== undefined) {
|
|
512
|
+
conditions.push(`s.calls >= $${String(paramIndex)}`);
|
|
513
|
+
queryParams.push(minCalls);
|
|
514
|
+
}
|
|
515
|
+
conditions.push(`(k.${cols.userTime} + k.${cols.systemTime} + k.${cols.reads} + k.${cols.writes}) > 0`);
|
|
516
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
517
|
+
// Get total count first for truncation indicator
|
|
518
|
+
const countSql = `
|
|
504
519
|
SELECT COUNT(*) as total
|
|
505
520
|
FROM pg_stat_statements s
|
|
506
521
|
JOIN pg_stat_kcache() k ON s.queryid = k.queryid
|
|
@@ -508,10 +523,10 @@ Helps identify the root cause of performance issues - is the query computation-h
|
|
|
508
523
|
AND s.dbid = k.dbid
|
|
509
524
|
${whereClause}
|
|
510
525
|
`;
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
526
|
+
const countResult = await adapter.executeQuery(countSql, queryParams);
|
|
527
|
+
const totalRaw = countResult.rows?.[0]?.["total"];
|
|
528
|
+
const totalCount = Number(totalRaw) || 0;
|
|
529
|
+
const sql = `
|
|
515
530
|
WITH query_metrics AS (
|
|
516
531
|
SELECT
|
|
517
532
|
s.queryid,
|
|
@@ -557,33 +572,42 @@ Helps identify the root cause of performance issues - is the query computation-h
|
|
|
557
572
|
ORDER BY total_time_ms DESC
|
|
558
573
|
${limitVal !== null ? `LIMIT ${String(limitVal)}` : ""}
|
|
559
574
|
`;
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
575
|
+
const result = await adapter.executeQuery(sql, queryParams);
|
|
576
|
+
const rows = result.rows ?? [];
|
|
577
|
+
const effectiveTotalCount = Math.max(totalCount, rows.length);
|
|
578
|
+
const truncated = rows.length < effectiveTotalCount;
|
|
579
|
+
const cpuBound = rows.filter((r) => r["resource_classification"] === "CPU-bound").length;
|
|
580
|
+
const ioBound = rows.filter((r) => r["resource_classification"] === "I/O-bound").length;
|
|
581
|
+
const balanced = rows.filter((r) => r["resource_classification"] === "Balanced").length;
|
|
582
|
+
const response = {
|
|
583
|
+
queries: rows,
|
|
584
|
+
count: rows.length,
|
|
585
|
+
summary: {
|
|
586
|
+
cpuBound,
|
|
587
|
+
ioBound,
|
|
588
|
+
balanced,
|
|
589
|
+
threshold: thresholdVal,
|
|
590
|
+
},
|
|
591
|
+
recommendations: [
|
|
592
|
+
cpuBound > ioBound
|
|
593
|
+
? "Most resource-intensive queries are CPU-bound. Consider query optimization or more CPU resources."
|
|
594
|
+
: ioBound > cpuBound
|
|
595
|
+
? "Most resource-intensive queries are I/O-bound. Consider more memory, faster storage, or better indexing."
|
|
596
|
+
: "Resource usage is balanced between CPU and I/O.",
|
|
597
|
+
],
|
|
598
|
+
truncated,
|
|
599
|
+
totalCount: effectiveTotalCount,
|
|
600
|
+
};
|
|
601
|
+
return response;
|
|
602
|
+
}
|
|
603
|
+
catch (error) {
|
|
604
|
+
return {
|
|
605
|
+
success: false,
|
|
606
|
+
error: formatPostgresError(error, {
|
|
607
|
+
tool: "pg_kcache_resource_analysis",
|
|
608
|
+
}),
|
|
609
|
+
};
|
|
610
|
+
}
|
|
587
611
|
},
|
|
588
612
|
};
|
|
589
613
|
}
|
|
@@ -601,12 +625,20 @@ Note: This also resets pg_stat_statements statistics.`,
|
|
|
601
625
|
annotations: destructive("Reset Kcache Stats"),
|
|
602
626
|
icons: getToolIcons("kcache", destructive("Reset Kcache Stats")),
|
|
603
627
|
handler: async (_params, _context) => {
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
628
|
+
try {
|
|
629
|
+
await adapter.executeQuery("SELECT pg_stat_kcache_reset()");
|
|
630
|
+
return {
|
|
631
|
+
success: true,
|
|
632
|
+
message: "pg_stat_kcache statistics reset",
|
|
633
|
+
note: "pg_stat_statements statistics were also reset",
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
catch (error) {
|
|
637
|
+
return {
|
|
638
|
+
success: false,
|
|
639
|
+
error: formatPostgresError(error, { tool: "pg_kcache_reset" }),
|
|
640
|
+
};
|
|
641
|
+
}
|
|
610
642
|
},
|
|
611
643
|
};
|
|
612
644
|
}
|