@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.
Files changed (180) hide show
  1. package/README.md +68 -62
  2. package/dist/adapters/postgresql/PostgresAdapter.d.ts.map +1 -1
  3. package/dist/adapters/postgresql/PostgresAdapter.js +53 -3
  4. package/dist/adapters/postgresql/PostgresAdapter.js.map +1 -1
  5. package/dist/adapters/postgresql/prompts/ltree.js +2 -2
  6. package/dist/adapters/postgresql/prompts/ltree.js.map +1 -1
  7. package/dist/adapters/postgresql/schemas/admin.d.ts +10 -5
  8. package/dist/adapters/postgresql/schemas/admin.d.ts.map +1 -1
  9. package/dist/adapters/postgresql/schemas/admin.js +10 -5
  10. package/dist/adapters/postgresql/schemas/admin.js.map +1 -1
  11. package/dist/adapters/postgresql/schemas/backup.d.ts +8 -4
  12. package/dist/adapters/postgresql/schemas/backup.d.ts.map +1 -1
  13. package/dist/adapters/postgresql/schemas/backup.js +11 -4
  14. package/dist/adapters/postgresql/schemas/backup.js.map +1 -1
  15. package/dist/adapters/postgresql/schemas/core.d.ts +53 -19
  16. package/dist/adapters/postgresql/schemas/core.d.ts.map +1 -1
  17. package/dist/adapters/postgresql/schemas/core.js +61 -17
  18. package/dist/adapters/postgresql/schemas/core.js.map +1 -1
  19. package/dist/adapters/postgresql/schemas/cron.d.ts +51 -32
  20. package/dist/adapters/postgresql/schemas/cron.d.ts.map +1 -1
  21. package/dist/adapters/postgresql/schemas/cron.js +64 -44
  22. package/dist/adapters/postgresql/schemas/cron.js.map +1 -1
  23. package/dist/adapters/postgresql/schemas/extensions.d.ts +168 -73
  24. package/dist/adapters/postgresql/schemas/extensions.d.ts.map +1 -1
  25. package/dist/adapters/postgresql/schemas/extensions.js +177 -60
  26. package/dist/adapters/postgresql/schemas/extensions.js.map +1 -1
  27. package/dist/adapters/postgresql/schemas/index.d.ts +5 -5
  28. package/dist/adapters/postgresql/schemas/index.d.ts.map +1 -1
  29. package/dist/adapters/postgresql/schemas/index.js +9 -7
  30. package/dist/adapters/postgresql/schemas/index.js.map +1 -1
  31. package/dist/adapters/postgresql/schemas/jsonb.d.ts +94 -42
  32. package/dist/adapters/postgresql/schemas/jsonb.d.ts.map +1 -1
  33. package/dist/adapters/postgresql/schemas/jsonb.js +101 -30
  34. package/dist/adapters/postgresql/schemas/jsonb.js.map +1 -1
  35. package/dist/adapters/postgresql/schemas/monitoring.d.ts +28 -11
  36. package/dist/adapters/postgresql/schemas/monitoring.d.ts.map +1 -1
  37. package/dist/adapters/postgresql/schemas/monitoring.js +49 -24
  38. package/dist/adapters/postgresql/schemas/monitoring.js.map +1 -1
  39. package/dist/adapters/postgresql/schemas/partitioning.d.ts +5 -4
  40. package/dist/adapters/postgresql/schemas/partitioning.d.ts.map +1 -1
  41. package/dist/adapters/postgresql/schemas/partitioning.js +5 -4
  42. package/dist/adapters/postgresql/schemas/partitioning.js.map +1 -1
  43. package/dist/adapters/postgresql/schemas/performance.d.ts +60 -30
  44. package/dist/adapters/postgresql/schemas/performance.d.ts.map +1 -1
  45. package/dist/adapters/postgresql/schemas/performance.js +85 -22
  46. package/dist/adapters/postgresql/schemas/performance.js.map +1 -1
  47. package/dist/adapters/postgresql/schemas/postgis.d.ts +20 -0
  48. package/dist/adapters/postgresql/schemas/postgis.d.ts.map +1 -1
  49. package/dist/adapters/postgresql/schemas/postgis.js +20 -0
  50. package/dist/adapters/postgresql/schemas/postgis.js.map +1 -1
  51. package/dist/adapters/postgresql/schemas/schema-mgmt.d.ts +35 -23
  52. package/dist/adapters/postgresql/schemas/schema-mgmt.d.ts.map +1 -1
  53. package/dist/adapters/postgresql/schemas/schema-mgmt.js +69 -26
  54. package/dist/adapters/postgresql/schemas/schema-mgmt.js.map +1 -1
  55. package/dist/adapters/postgresql/schemas/stats.d.ts +33 -20
  56. package/dist/adapters/postgresql/schemas/stats.d.ts.map +1 -1
  57. package/dist/adapters/postgresql/schemas/stats.js +36 -20
  58. package/dist/adapters/postgresql/schemas/stats.js.map +1 -1
  59. package/dist/adapters/postgresql/schemas/text-search.d.ts +8 -5
  60. package/dist/adapters/postgresql/schemas/text-search.d.ts.map +1 -1
  61. package/dist/adapters/postgresql/schemas/text-search.js +11 -4
  62. package/dist/adapters/postgresql/schemas/text-search.js.map +1 -1
  63. package/dist/adapters/postgresql/tools/admin.d.ts.map +1 -1
  64. package/dist/adapters/postgresql/tools/admin.js +211 -140
  65. package/dist/adapters/postgresql/tools/admin.js.map +1 -1
  66. package/dist/adapters/postgresql/tools/backup/dump.d.ts.map +1 -1
  67. package/dist/adapters/postgresql/tools/backup/dump.js +360 -337
  68. package/dist/adapters/postgresql/tools/backup/dump.js.map +1 -1
  69. package/dist/adapters/postgresql/tools/citext.d.ts.map +1 -1
  70. package/dist/adapters/postgresql/tools/citext.js +221 -163
  71. package/dist/adapters/postgresql/tools/citext.js.map +1 -1
  72. package/dist/adapters/postgresql/tools/core/convenience.d.ts +9 -1
  73. package/dist/adapters/postgresql/tools/core/convenience.d.ts.map +1 -1
  74. package/dist/adapters/postgresql/tools/core/convenience.js +78 -11
  75. package/dist/adapters/postgresql/tools/core/convenience.js.map +1 -1
  76. package/dist/adapters/postgresql/tools/core/error-helpers.d.ts +48 -0
  77. package/dist/adapters/postgresql/tools/core/error-helpers.d.ts.map +1 -0
  78. package/dist/adapters/postgresql/tools/core/error-helpers.js +256 -0
  79. package/dist/adapters/postgresql/tools/core/error-helpers.js.map +1 -0
  80. package/dist/adapters/postgresql/tools/core/health.d.ts.map +1 -1
  81. package/dist/adapters/postgresql/tools/core/health.js +18 -4
  82. package/dist/adapters/postgresql/tools/core/health.js.map +1 -1
  83. package/dist/adapters/postgresql/tools/core/indexes.d.ts.map +1 -1
  84. package/dist/adapters/postgresql/tools/core/indexes.js +45 -4
  85. package/dist/adapters/postgresql/tools/core/indexes.js.map +1 -1
  86. package/dist/adapters/postgresql/tools/core/objects.d.ts.map +1 -1
  87. package/dist/adapters/postgresql/tools/core/objects.js +104 -85
  88. package/dist/adapters/postgresql/tools/core/objects.js.map +1 -1
  89. package/dist/adapters/postgresql/tools/core/query.d.ts.map +1 -1
  90. package/dist/adapters/postgresql/tools/core/query.js +100 -42
  91. package/dist/adapters/postgresql/tools/core/query.js.map +1 -1
  92. package/dist/adapters/postgresql/tools/core/schemas.d.ts +51 -25
  93. package/dist/adapters/postgresql/tools/core/schemas.d.ts.map +1 -1
  94. package/dist/adapters/postgresql/tools/core/schemas.js +51 -25
  95. package/dist/adapters/postgresql/tools/core/schemas.js.map +1 -1
  96. package/dist/adapters/postgresql/tools/core/tables.d.ts.map +1 -1
  97. package/dist/adapters/postgresql/tools/core/tables.js +68 -28
  98. package/dist/adapters/postgresql/tools/core/tables.js.map +1 -1
  99. package/dist/adapters/postgresql/tools/cron.d.ts.map +1 -1
  100. package/dist/adapters/postgresql/tools/cron.js +274 -179
  101. package/dist/adapters/postgresql/tools/cron.js.map +1 -1
  102. package/dist/adapters/postgresql/tools/jsonb/advanced.d.ts.map +1 -1
  103. package/dist/adapters/postgresql/tools/jsonb/advanced.js +372 -284
  104. package/dist/adapters/postgresql/tools/jsonb/advanced.js.map +1 -1
  105. package/dist/adapters/postgresql/tools/jsonb/basic.d.ts.map +1 -1
  106. package/dist/adapters/postgresql/tools/jsonb/basic.js +617 -398
  107. package/dist/adapters/postgresql/tools/jsonb/basic.js.map +1 -1
  108. package/dist/adapters/postgresql/tools/kcache.d.ts.map +1 -1
  109. package/dist/adapters/postgresql/tools/kcache.js +278 -246
  110. package/dist/adapters/postgresql/tools/kcache.js.map +1 -1
  111. package/dist/adapters/postgresql/tools/ltree.d.ts.map +1 -1
  112. package/dist/adapters/postgresql/tools/ltree.js +121 -35
  113. package/dist/adapters/postgresql/tools/ltree.js.map +1 -1
  114. package/dist/adapters/postgresql/tools/monitoring.d.ts.map +1 -1
  115. package/dist/adapters/postgresql/tools/monitoring.js +54 -34
  116. package/dist/adapters/postgresql/tools/monitoring.js.map +1 -1
  117. package/dist/adapters/postgresql/tools/partitioning.d.ts.map +1 -1
  118. package/dist/adapters/postgresql/tools/partitioning.js +79 -15
  119. package/dist/adapters/postgresql/tools/partitioning.js.map +1 -1
  120. package/dist/adapters/postgresql/tools/partman/management.d.ts.map +1 -1
  121. package/dist/adapters/postgresql/tools/partman/management.js +11 -4
  122. package/dist/adapters/postgresql/tools/partman/management.js.map +1 -1
  123. package/dist/adapters/postgresql/tools/partman/operations.d.ts.map +1 -1
  124. package/dist/adapters/postgresql/tools/partman/operations.js +132 -19
  125. package/dist/adapters/postgresql/tools/partman/operations.js.map +1 -1
  126. package/dist/adapters/postgresql/tools/performance/analysis.d.ts.map +1 -1
  127. package/dist/adapters/postgresql/tools/performance/analysis.js +264 -160
  128. package/dist/adapters/postgresql/tools/performance/analysis.js.map +1 -1
  129. package/dist/adapters/postgresql/tools/performance/explain.d.ts.map +1 -1
  130. package/dist/adapters/postgresql/tools/performance/explain.js +61 -21
  131. package/dist/adapters/postgresql/tools/performance/explain.js.map +1 -1
  132. package/dist/adapters/postgresql/tools/performance/monitoring.d.ts.map +1 -1
  133. package/dist/adapters/postgresql/tools/performance/monitoring.js +44 -7
  134. package/dist/adapters/postgresql/tools/performance/monitoring.js.map +1 -1
  135. package/dist/adapters/postgresql/tools/performance/optimization.d.ts.map +1 -1
  136. package/dist/adapters/postgresql/tools/performance/optimization.js +92 -81
  137. package/dist/adapters/postgresql/tools/performance/optimization.js.map +1 -1
  138. package/dist/adapters/postgresql/tools/performance/stats.d.ts.map +1 -1
  139. package/dist/adapters/postgresql/tools/performance/stats.js +124 -36
  140. package/dist/adapters/postgresql/tools/performance/stats.js.map +1 -1
  141. package/dist/adapters/postgresql/tools/pgcrypto.d.ts.map +1 -1
  142. package/dist/adapters/postgresql/tools/pgcrypto.js +244 -89
  143. package/dist/adapters/postgresql/tools/pgcrypto.js.map +1 -1
  144. package/dist/adapters/postgresql/tools/postgis/advanced.d.ts.map +1 -1
  145. package/dist/adapters/postgresql/tools/postgis/advanced.js +285 -222
  146. package/dist/adapters/postgresql/tools/postgis/advanced.js.map +1 -1
  147. package/dist/adapters/postgresql/tools/postgis/basic.d.ts.map +1 -1
  148. package/dist/adapters/postgresql/tools/postgis/basic.js +359 -249
  149. package/dist/adapters/postgresql/tools/postgis/basic.js.map +1 -1
  150. package/dist/adapters/postgresql/tools/postgis/standalone.d.ts.map +1 -1
  151. package/dist/adapters/postgresql/tools/postgis/standalone.js +135 -51
  152. package/dist/adapters/postgresql/tools/postgis/standalone.js.map +1 -1
  153. package/dist/adapters/postgresql/tools/schema.d.ts.map +1 -1
  154. package/dist/adapters/postgresql/tools/schema.js +504 -231
  155. package/dist/adapters/postgresql/tools/schema.js.map +1 -1
  156. package/dist/adapters/postgresql/tools/stats/advanced.d.ts.map +1 -1
  157. package/dist/adapters/postgresql/tools/stats/advanced.js +515 -476
  158. package/dist/adapters/postgresql/tools/stats/advanced.js.map +1 -1
  159. package/dist/adapters/postgresql/tools/stats/basic.d.ts.map +1 -1
  160. package/dist/adapters/postgresql/tools/stats/basic.js +302 -293
  161. package/dist/adapters/postgresql/tools/stats/basic.js.map +1 -1
  162. package/dist/adapters/postgresql/tools/text.d.ts.map +1 -1
  163. package/dist/adapters/postgresql/tools/text.js +392 -218
  164. package/dist/adapters/postgresql/tools/text.js.map +1 -1
  165. package/dist/adapters/postgresql/tools/transactions.d.ts.map +1 -1
  166. package/dist/adapters/postgresql/tools/transactions.js +157 -50
  167. package/dist/adapters/postgresql/tools/transactions.js.map +1 -1
  168. package/dist/adapters/postgresql/tools/vector/advanced.d.ts.map +1 -1
  169. package/dist/adapters/postgresql/tools/vector/advanced.js +18 -0
  170. package/dist/adapters/postgresql/tools/vector/advanced.js.map +1 -1
  171. package/dist/adapters/postgresql/tools/vector/basic.d.ts.map +1 -1
  172. package/dist/adapters/postgresql/tools/vector/basic.js +100 -53
  173. package/dist/adapters/postgresql/tools/vector/basic.js.map +1 -1
  174. package/dist/codemode/api.js +1 -1
  175. package/dist/codemode/api.js.map +1 -1
  176. package/dist/constants/ServerInstructions.d.ts +1 -1
  177. package/dist/constants/ServerInstructions.d.ts.map +1 -1
  178. package/dist/constants/ServerInstructions.js +45 -7
  179. package/dist/constants/ServerInstructions.js.map +1 -1
  180. 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 { KcacheQueryStatsSchema, KcacheDatabaseStatsSchema, KcacheResourceAnalysisSchema,
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: KcacheQueryStatsSchema,
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
- const { limit, orderBy, minCalls, queryPreviewLength } = KcacheQueryStatsSchema.parse(params);
114
- const cols = await getKcacheColumnNames(adapter);
115
- const DEFAULT_LIMIT = 20;
116
- // limit: 0 means "no limit" (return all rows), undefined means use default
117
- const limitVal = limit === 0 ? null : (limit ?? DEFAULT_LIMIT);
118
- // Bound queryPreviewLength: 0 = full query, default 100, max 500
119
- const previewLen = queryPreviewLength === 0
120
- ? 10000
121
- : Math.min(queryPreviewLength ?? 100, 500);
122
- const orderColumn = orderBy === "cpu_time"
123
- ? `(k.${cols.userTime} + k.${cols.systemTime})`
124
- : orderBy === "reads"
125
- ? `k.${cols.reads}`
126
- : orderBy === "writes"
127
- ? `k.${cols.writes}`
128
- : "s.total_exec_time";
129
- const conditions = [];
130
- const queryParams = [];
131
- let paramIndex = 1;
132
- if (minCalls !== undefined) {
133
- conditions.push(`s.calls >= $${String(paramIndex++)}`);
134
- queryParams.push(minCalls);
135
- }
136
- const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
137
- // Get total count first for truncation indicator
138
- const countSql = `
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
- const countResult = await adapter.executeQuery(countSql, queryParams);
147
- const totalRaw = countResult.rows?.[0]?.["total"];
148
- const totalCount = Number(totalRaw) || 0;
149
- const sql = `
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
- const result = await adapter.executeQuery(sql, queryParams);
174
- const rowCount = result.rows?.length ?? 0;
175
- const effectiveTotalCount = Math.max(totalCount, rowCount);
176
- const truncated = rowCount < effectiveTotalCount;
177
- const response = {
178
- queries: result.rows ?? [],
179
- count: rowCount,
180
- orderBy: orderBy ?? "total_time",
181
- truncated,
182
- totalCount: effectiveTotalCount,
183
- };
184
- return response;
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: z.preprocess(defaultToEmpty, z.object({
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
- const parsed = z
212
- .object({
213
- limit: z.number().optional(),
214
- queryPreviewLength: z.number().optional(),
215
- })
216
- .parse(params ?? {});
217
- const DEFAULT_LIMIT = 10;
218
- // limit: 0 means "no limit" (return all rows), undefined means use default
219
- const limitVal = parsed.limit === 0 ? null : (parsed.limit ?? DEFAULT_LIMIT);
220
- // Bound queryPreviewLength: 0 = full query, default 100, max 500
221
- const previewLen = parsed.queryPreviewLength === 0
222
- ? 10000
223
- : Math.min(parsed.queryPreviewLength ?? 100, 500);
224
- const cols = await getKcacheColumnNames(adapter);
225
- // Get total count first for truncation indicator
226
- const countSql = `
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
- const countResult = await adapter.executeQuery(countSql);
235
- const totalRaw = countResult.rows?.[0]?.["total"];
236
- const totalCount = Number(totalRaw) || 0;
237
- const sql = `
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
- const result = await adapter.executeQuery(sql);
265
- const rowCount = result.rows?.length ?? 0;
266
- const effectiveTotalCount = Math.max(totalCount, rowCount);
267
- const truncated = rowCount < effectiveTotalCount;
268
- const response = {
269
- topCpuQueries: result.rows ?? [],
270
- count: rowCount,
271
- description: "Queries ranked by total CPU time (user + system)",
272
- truncated,
273
- totalCount: effectiveTotalCount,
274
- };
275
- return response;
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: z.preprocess((input) => {
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
- // Apply the same preprocessing as inputSchema
318
- const preprocessed = (() => {
319
- const obj = (params ?? {});
320
- if (obj["ioType"] !== undefined && obj["type"] === undefined) {
321
- return { ...obj, type: obj["ioType"] };
322
- }
323
- return obj;
324
- })();
325
- const parsed = z
326
- .object({
327
- type: z.enum(["reads", "writes", "both"]).optional(),
328
- limit: z.number().optional(),
329
- queryPreviewLength: z.number().optional(),
330
- })
331
- .parse(preprocessed);
332
- const ioType = parsed.type ?? "both";
333
- const DEFAULT_LIMIT = 10;
334
- // limit: 0 means "no limit" (return all rows), undefined means use default
335
- const limitVal = parsed.limit === 0 ? null : (parsed.limit ?? DEFAULT_LIMIT);
336
- // Bound queryPreviewLength: 0 = full query, default 100, max 500
337
- const previewLen = parsed.queryPreviewLength === 0
338
- ? 10000
339
- : Math.min(parsed.queryPreviewLength ?? 100, 500);
340
- const cols = await getKcacheColumnNames(adapter);
341
- const orderColumn = ioType === "reads"
342
- ? `k.${cols.reads}`
343
- : ioType === "writes"
344
- ? `k.${cols.writes}`
345
- : `(k.${cols.reads} + k.${cols.writes})`;
346
- // Filter by the type-specific IO column so 'reads' excludes write-only queries
347
- const ioFilter = ioType === "reads"
348
- ? `k.${cols.reads} > 0`
349
- : ioType === "writes"
350
- ? `k.${cols.writes} > 0`
351
- : `(k.${cols.reads} + k.${cols.writes}) > 0`;
352
- // Get total count first for truncation indicator
353
- const countSql = `
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
- const countResult = await adapter.executeQuery(countSql);
362
- const totalRaw = countResult.rows?.[0]?.["total"];
363
- const totalCount = Number(totalRaw) || 0;
364
- const sql = `
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
- const result = await adapter.executeQuery(sql);
384
- const rowCount = result.rows?.length ?? 0;
385
- const effectiveTotalCount = Math.max(totalCount, rowCount);
386
- const truncated = rowCount < effectiveTotalCount;
387
- const response = {
388
- topIoQueries: result.rows ?? [],
389
- count: rowCount,
390
- ioType,
391
- description: `Queries ranked by ${ioType === "both" ? "total I/O" : ioType}`,
392
- truncated,
393
- totalCount: effectiveTotalCount,
394
- };
395
- return response;
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: KcacheDatabaseStatsSchema,
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
- const { database } = KcacheDatabaseStatsSchema.parse(params);
414
- const cols = await getKcacheColumnNames(adapter);
415
- let sql;
416
- const queryParams = [];
417
- if (database !== undefined) {
418
- sql = `
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
- queryParams.push(database);
437
- }
438
- else {
439
- sql = `
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: KcacheResourceAnalysisSchema,
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
- const { queryId, threshold, limit, minCalls, queryPreviewLength } = KcacheResourceAnalysisSchema.parse(params);
480
- const thresholdVal = threshold ?? 0.5;
481
- const DEFAULT_LIMIT = 20;
482
- // limit: 0 means "no limit" (return all rows), undefined means use default
483
- const limitVal = limit === 0 ? null : (limit ?? DEFAULT_LIMIT);
484
- // Bound queryPreviewLength: 0 = full query, default 100, max 500
485
- const previewLen = queryPreviewLength === 0
486
- ? 10000
487
- : Math.min(queryPreviewLength ?? 100, 500);
488
- const cols = await getKcacheColumnNames(adapter);
489
- const conditions = [];
490
- const queryParams = [];
491
- let paramIndex = 1;
492
- if (queryId !== undefined) {
493
- conditions.push(`s.queryid::text = $${String(paramIndex++)}`);
494
- queryParams.push(queryId);
495
- }
496
- if (minCalls !== undefined) {
497
- conditions.push(`s.calls >= $${String(paramIndex++)}`);
498
- queryParams.push(minCalls);
499
- }
500
- conditions.push(`(k.${cols.userTime} + k.${cols.systemTime} + k.${cols.reads} + k.${cols.writes}) > 0`);
501
- const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
502
- // Get total count first for truncation indicator
503
- const countSql = `
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
- const countResult = await adapter.executeQuery(countSql, queryParams);
512
- const totalRaw = countResult.rows?.[0]?.["total"];
513
- const totalCount = Number(totalRaw) || 0;
514
- const sql = `
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
- const result = await adapter.executeQuery(sql, queryParams);
561
- const rows = result.rows ?? [];
562
- const effectiveTotalCount = Math.max(totalCount, rows.length);
563
- const truncated = rows.length < effectiveTotalCount;
564
- const cpuBound = rows.filter((r) => r["resource_classification"] === "CPU-bound").length;
565
- const ioBound = rows.filter((r) => r["resource_classification"] === "I/O-bound").length;
566
- const balanced = rows.filter((r) => r["resource_classification"] === "Balanced").length;
567
- const response = {
568
- queries: rows,
569
- count: rows.length,
570
- summary: {
571
- cpuBound,
572
- ioBound,
573
- balanced,
574
- threshold: thresholdVal,
575
- },
576
- recommendations: [
577
- cpuBound > ioBound
578
- ? "Most resource-intensive queries are CPU-bound. Consider query optimization or more CPU resources."
579
- : ioBound > cpuBound
580
- ? "Most resource-intensive queries are I/O-bound. Consider more memory, faster storage, or better indexing."
581
- : "Resource usage is balanced between CPU and I/O.",
582
- ],
583
- truncated,
584
- totalCount: effectiveTotalCount,
585
- };
586
- return response;
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
- await adapter.executeQuery("SELECT pg_stat_kcache_reset()");
605
- return {
606
- success: true,
607
- message: "pg_stat_kcache statistics reset",
608
- note: "pg_stat_statements statistics were also reset",
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
  }