postgresai 0.15.0-dev.1 → 0.15.0-dev.10

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/lib/issues.ts CHANGED
@@ -130,10 +130,10 @@ export async function fetchIssues(params: FetchIssuesParams): Promise<IssueListI
130
130
 
131
131
  if (debug) {
132
132
  const debugHeaders: Record<string, string> = { ...headers, "access-token": maskSecret(apiKey) };
133
- console.log(`Debug: Resolved API base URL: ${base}`);
134
- console.log(`Debug: GET URL: ${url.toString()}`);
135
- console.log(`Debug: Auth scheme: access-token`);
136
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
133
+ console.error(`Debug: Resolved API base URL: ${base}`);
134
+ console.error(`Debug: GET URL: ${url.toString()}`);
135
+ console.error(`Debug: Auth scheme: access-token`);
136
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
137
137
  }
138
138
 
139
139
  const response = await fetch(url.toString(), {
@@ -142,8 +142,8 @@ export async function fetchIssues(params: FetchIssuesParams): Promise<IssueListI
142
142
  });
143
143
 
144
144
  if (debug) {
145
- console.log(`Debug: Response status: ${response.status}`);
146
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
145
+ console.error(`Debug: Response status: ${response.status}`);
146
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
147
147
  }
148
148
 
149
149
  const data = await response.text();
@@ -188,10 +188,10 @@ export async function fetchIssueComments(params: FetchIssueCommentsParams): Prom
188
188
 
189
189
  if (debug) {
190
190
  const debugHeaders: Record<string, string> = { ...headers, "access-token": maskSecret(apiKey) };
191
- console.log(`Debug: Resolved API base URL: ${base}`);
192
- console.log(`Debug: GET URL: ${url.toString()}`);
193
- console.log(`Debug: Auth scheme: access-token`);
194
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
191
+ console.error(`Debug: Resolved API base URL: ${base}`);
192
+ console.error(`Debug: GET URL: ${url.toString()}`);
193
+ console.error(`Debug: Auth scheme: access-token`);
194
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
195
195
  }
196
196
 
197
197
  const response = await fetch(url.toString(), {
@@ -200,8 +200,8 @@ export async function fetchIssueComments(params: FetchIssueCommentsParams): Prom
200
200
  });
201
201
 
202
202
  if (debug) {
203
- console.log(`Debug: Response status: ${response.status}`);
204
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
203
+ console.error(`Debug: Response status: ${response.status}`);
204
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
205
205
  }
206
206
 
207
207
  const data = await response.text();
@@ -248,10 +248,10 @@ export async function fetchIssue(params: FetchIssueParams): Promise<IssueDetail
248
248
 
249
249
  if (debug) {
250
250
  const debugHeaders: Record<string, string> = { ...headers, "access-token": maskSecret(apiKey) };
251
- console.log(`Debug: Resolved API base URL: ${base}`);
252
- console.log(`Debug: GET URL: ${url.toString()}`);
253
- console.log(`Debug: Auth scheme: access-token`);
254
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
251
+ console.error(`Debug: Resolved API base URL: ${base}`);
252
+ console.error(`Debug: GET URL: ${url.toString()}`);
253
+ console.error(`Debug: Auth scheme: access-token`);
254
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
255
255
  }
256
256
 
257
257
  const response = await fetch(url.toString(), {
@@ -260,8 +260,8 @@ export async function fetchIssue(params: FetchIssueParams): Promise<IssueDetail
260
260
  });
261
261
 
262
262
  if (debug) {
263
- console.log(`Debug: Response status: ${response.status}`);
264
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
263
+ console.error(`Debug: Response status: ${response.status}`);
264
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
265
265
  }
266
266
 
267
267
  const data = await response.text();
@@ -369,11 +369,11 @@ export async function createIssue(params: CreateIssueParams): Promise<CreatedIss
369
369
 
370
370
  if (debug) {
371
371
  const debugHeaders: Record<string, string> = { ...headers, "access-token": maskSecret(apiKey) };
372
- console.log(`Debug: Resolved API base URL: ${base}`);
373
- console.log(`Debug: POST URL: ${url.toString()}`);
374
- console.log(`Debug: Auth scheme: access-token`);
375
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
376
- console.log(`Debug: Request body: ${body}`);
372
+ console.error(`Debug: Resolved API base URL: ${base}`);
373
+ console.error(`Debug: POST URL: ${url.toString()}`);
374
+ console.error(`Debug: Auth scheme: access-token`);
375
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
376
+ console.error(`Debug: Request body: ${body}`);
377
377
  }
378
378
 
379
379
  const response = await fetch(url.toString(), {
@@ -383,8 +383,8 @@ export async function createIssue(params: CreateIssueParams): Promise<CreatedIss
383
383
  });
384
384
 
385
385
  if (debug) {
386
- console.log(`Debug: Response status: ${response.status}`);
387
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
386
+ console.error(`Debug: Response status: ${response.status}`);
387
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
388
388
  }
389
389
 
390
390
  const data = await response.text();
@@ -442,11 +442,11 @@ export async function createIssueComment(params: CreateIssueCommentParams): Prom
442
442
 
443
443
  if (debug) {
444
444
  const debugHeaders: Record<string, string> = { ...headers, "access-token": maskSecret(apiKey) };
445
- console.log(`Debug: Resolved API base URL: ${base}`);
446
- console.log(`Debug: POST URL: ${url.toString()}`);
447
- console.log(`Debug: Auth scheme: access-token`);
448
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
449
- console.log(`Debug: Request body: ${body}`);
445
+ console.error(`Debug: Resolved API base URL: ${base}`);
446
+ console.error(`Debug: POST URL: ${url.toString()}`);
447
+ console.error(`Debug: Auth scheme: access-token`);
448
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
449
+ console.error(`Debug: Request body: ${body}`);
450
450
  }
451
451
 
452
452
  const response = await fetch(url.toString(), {
@@ -456,8 +456,8 @@ export async function createIssueComment(params: CreateIssueCommentParams): Prom
456
456
  });
457
457
 
458
458
  if (debug) {
459
- console.log(`Debug: Response status: ${response.status}`);
460
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
459
+ console.error(`Debug: Response status: ${response.status}`);
460
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
461
461
  }
462
462
 
463
463
  const data = await response.text();
@@ -550,11 +550,11 @@ export async function updateIssue(params: UpdateIssueParams): Promise<UpdatedIss
550
550
 
551
551
  if (debug) {
552
552
  const debugHeaders: Record<string, string> = { ...headers, "access-token": maskSecret(apiKey) };
553
- console.log(`Debug: Resolved API base URL: ${base}`);
554
- console.log(`Debug: POST URL: ${url.toString()}`);
555
- console.log(`Debug: Auth scheme: access-token`);
556
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
557
- console.log(`Debug: Request body: ${body}`);
553
+ console.error(`Debug: Resolved API base URL: ${base}`);
554
+ console.error(`Debug: POST URL: ${url.toString()}`);
555
+ console.error(`Debug: Auth scheme: access-token`);
556
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
557
+ console.error(`Debug: Request body: ${body}`);
558
558
  }
559
559
 
560
560
  const response = await fetch(url.toString(), {
@@ -564,8 +564,8 @@ export async function updateIssue(params: UpdateIssueParams): Promise<UpdatedIss
564
564
  });
565
565
 
566
566
  if (debug) {
567
- console.log(`Debug: Response status: ${response.status}`);
568
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
567
+ console.error(`Debug: Response status: ${response.status}`);
568
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
569
569
  }
570
570
 
571
571
  const data = await response.text();
@@ -639,11 +639,11 @@ export async function updateIssueComment(params: UpdateIssueCommentParams): Prom
639
639
 
640
640
  if (debug) {
641
641
  const debugHeaders: Record<string, string> = { ...headers, "access-token": maskSecret(apiKey) };
642
- console.log(`Debug: Resolved API base URL: ${base}`);
643
- console.log(`Debug: POST URL: ${url.toString()}`);
644
- console.log(`Debug: Auth scheme: access-token`);
645
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
646
- console.log(`Debug: Request body: ${body}`);
642
+ console.error(`Debug: Resolved API base URL: ${base}`);
643
+ console.error(`Debug: POST URL: ${url.toString()}`);
644
+ console.error(`Debug: Auth scheme: access-token`);
645
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
646
+ console.error(`Debug: Request body: ${body}`);
647
647
  }
648
648
 
649
649
  const response = await fetch(url.toString(), {
@@ -653,8 +653,8 @@ export async function updateIssueComment(params: UpdateIssueCommentParams): Prom
653
653
  });
654
654
 
655
655
  if (debug) {
656
- console.log(`Debug: Response status: ${response.status}`);
657
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
656
+ console.error(`Debug: Response status: ${response.status}`);
657
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
658
658
  }
659
659
 
660
660
  const data = await response.text();
@@ -736,10 +736,10 @@ export async function fetchActionItem(params: FetchActionItemParams): Promise<Is
736
736
 
737
737
  if (debug) {
738
738
  const debugHeaders: Record<string, string> = { ...headers, "access-token": maskSecret(apiKey) };
739
- console.log(`Debug: Resolved API base URL: ${base}`);
740
- console.log(`Debug: GET URL: ${url.toString()}`);
741
- console.log(`Debug: Auth scheme: access-token`);
742
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
739
+ console.error(`Debug: Resolved API base URL: ${base}`);
740
+ console.error(`Debug: GET URL: ${url.toString()}`);
741
+ console.error(`Debug: Auth scheme: access-token`);
742
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
743
743
  }
744
744
 
745
745
  const response = await fetch(url.toString(), {
@@ -748,8 +748,8 @@ export async function fetchActionItem(params: FetchActionItemParams): Promise<Is
748
748
  });
749
749
 
750
750
  if (debug) {
751
- console.log(`Debug: Response status: ${response.status}`);
752
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
751
+ console.error(`Debug: Response status: ${response.status}`);
752
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
753
753
  }
754
754
 
755
755
  const data = await response.text();
@@ -814,10 +814,10 @@ export async function fetchActionItems(params: FetchActionItemsParams): Promise<
814
814
 
815
815
  if (debug) {
816
816
  const debugHeaders: Record<string, string> = { ...headers, "access-token": maskSecret(apiKey) };
817
- console.log(`Debug: Resolved API base URL: ${base}`);
818
- console.log(`Debug: GET URL: ${url.toString()}`);
819
- console.log(`Debug: Auth scheme: access-token`);
820
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
817
+ console.error(`Debug: Resolved API base URL: ${base}`);
818
+ console.error(`Debug: GET URL: ${url.toString()}`);
819
+ console.error(`Debug: Auth scheme: access-token`);
820
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
821
821
  }
822
822
 
823
823
  const response = await fetch(url.toString(), {
@@ -826,8 +826,8 @@ export async function fetchActionItems(params: FetchActionItemsParams): Promise<
826
826
  });
827
827
 
828
828
  if (debug) {
829
- console.log(`Debug: Response status: ${response.status}`);
830
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
829
+ console.error(`Debug: Response status: ${response.status}`);
830
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
831
831
  }
832
832
 
833
833
  const data = await response.text();
@@ -913,11 +913,11 @@ export async function createActionItem(params: CreateActionItemParams): Promise<
913
913
 
914
914
  if (debug) {
915
915
  const debugHeaders: Record<string, string> = { ...headers, "access-token": maskSecret(apiKey) };
916
- console.log(`Debug: Resolved API base URL: ${base}`);
917
- console.log(`Debug: POST URL: ${url.toString()}`);
918
- console.log(`Debug: Auth scheme: access-token`);
919
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
920
- console.log(`Debug: Request body: ${body}`);
916
+ console.error(`Debug: Resolved API base URL: ${base}`);
917
+ console.error(`Debug: POST URL: ${url.toString()}`);
918
+ console.error(`Debug: Auth scheme: access-token`);
919
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
920
+ console.error(`Debug: Request body: ${body}`);
921
921
  }
922
922
 
923
923
  const response = await fetch(url.toString(), {
@@ -927,8 +927,8 @@ export async function createActionItem(params: CreateActionItemParams): Promise<
927
927
  });
928
928
 
929
929
  if (debug) {
930
- console.log(`Debug: Response status: ${response.status}`);
931
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
930
+ console.error(`Debug: Response status: ${response.status}`);
931
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
932
932
  }
933
933
 
934
934
  const data = await response.text();
@@ -1035,11 +1035,11 @@ export async function updateActionItem(params: UpdateActionItemParams): Promise<
1035
1035
 
1036
1036
  if (debug) {
1037
1037
  const debugHeaders: Record<string, string> = { ...headers, "access-token": maskSecret(apiKey) };
1038
- console.log(`Debug: Resolved API base URL: ${base}`);
1039
- console.log(`Debug: POST URL: ${url.toString()}`);
1040
- console.log(`Debug: Auth scheme: access-token`);
1041
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
1042
- console.log(`Debug: Request body: ${body}`);
1038
+ console.error(`Debug: Resolved API base URL: ${base}`);
1039
+ console.error(`Debug: POST URL: ${url.toString()}`);
1040
+ console.error(`Debug: Auth scheme: access-token`);
1041
+ console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
1042
+ console.error(`Debug: Request body: ${body}`);
1043
1043
  }
1044
1044
 
1045
1045
  const response = await fetch(url.toString(), {
@@ -1049,8 +1049,8 @@ export async function updateActionItem(params: UpdateActionItemParams): Promise<
1049
1049
  });
1050
1050
 
1051
1051
  if (debug) {
1052
- console.log(`Debug: Response status: ${response.status}`);
1053
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
1052
+ console.error(`Debug: Response status: ${response.status}`);
1053
+ console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
1054
1054
  }
1055
1055
 
1056
1056
  if (!response.ok) {
package/lib/mcp-server.ts CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  updateActionItem,
15
15
  type ConfigChange,
16
16
  } from "./issues";
17
+ import { fetchReports, fetchAllReports, fetchReportFiles, fetchReportFileData, parseFlexibleDate } from "./reports";
17
18
  import { resolveBaseUrls } from "./util";
18
19
 
19
20
  // MCP SDK imports - Bun handles these directly
@@ -250,6 +251,50 @@ export async function handleToolCall(
250
251
  return { content: [{ type: "text", text: JSON.stringify({ success: true }, null, 2) }] };
251
252
  }
252
253
 
254
+ // Reports Tools
255
+ if (toolName === "list_reports") {
256
+ const projectId = args.project_id !== undefined ? Number(args.project_id) : undefined;
257
+ const status = args.status ? String(args.status) : undefined;
258
+ const limit = args.limit !== undefined ? Number(args.limit) : undefined;
259
+ const beforeDate = args.before_date ? parseFlexibleDate(String(args.before_date)) : undefined;
260
+ const all = args.all === true;
261
+ let reports;
262
+ if (all) {
263
+ reports = await fetchAllReports({ apiKey, apiBaseUrl, projectId, status, limit, debug });
264
+ } else {
265
+ reports = await fetchReports({ apiKey, apiBaseUrl, projectId, status, limit, beforeDate, debug });
266
+ }
267
+ return { content: [{ type: "text", text: JSON.stringify(reports, null, 2) }] };
268
+ }
269
+
270
+ if (toolName === "list_report_files") {
271
+ const reportId = args.report_id !== undefined ? Number(args.report_id) : undefined;
272
+ if (reportId !== undefined && isNaN(reportId)) {
273
+ return { content: [{ type: "text", text: "report_id must be a number" }], isError: true };
274
+ }
275
+ const type = args.type ? String(args.type) as "json" | "md" : undefined;
276
+ const checkId = args.check_id ? String(args.check_id) : undefined;
277
+ if (reportId === undefined && !checkId) {
278
+ return { content: [{ type: "text", text: "Either report_id or check_id is required" }], isError: true };
279
+ }
280
+ const files = await fetchReportFiles({ apiKey, apiBaseUrl, reportId, type, checkId, debug });
281
+ return { content: [{ type: "text", text: JSON.stringify(files, null, 2) }] };
282
+ }
283
+
284
+ if (toolName === "get_report_data") {
285
+ const reportId = args.report_id !== undefined ? Number(args.report_id) : undefined;
286
+ if (reportId !== undefined && isNaN(reportId)) {
287
+ return { content: [{ type: "text", text: "report_id must be a number" }], isError: true };
288
+ }
289
+ const type = args.type ? String(args.type) as "json" | "md" : undefined;
290
+ const checkId = args.check_id ? String(args.check_id) : undefined;
291
+ if (reportId === undefined && !checkId) {
292
+ return { content: [{ type: "text", text: "Either report_id or check_id is required" }], isError: true };
293
+ }
294
+ const files = await fetchReportFileData({ apiKey, apiBaseUrl, reportId, type, checkId, debug });
295
+ return { content: [{ type: "text", text: JSON.stringify(files, null, 2) }] };
296
+ }
297
+
253
298
  throw new Error(`Unknown tool: ${toolName}`);
254
299
  } catch (err) {
255
300
  const message = err instanceof Error ? err.message : String(err);
@@ -442,6 +487,51 @@ export async function startMcpServer(rootOpts?: RootOptsLike, extra?: { debug?:
442
487
  additionalProperties: false,
443
488
  },
444
489
  },
490
+ // Reports Tools
491
+ {
492
+ name: "list_reports",
493
+ description: "List checkup reports. Returns report metadata: id, project, status, timestamps. Use get_report_data to fetch actual report content. Supports date-based filtering with before_date.",
494
+ inputSchema: {
495
+ type: "object",
496
+ properties: {
497
+ project_id: { type: "number", description: "Filter by project ID" },
498
+ status: { type: "string", description: "Filter by status (e.g., 'completed')" },
499
+ limit: { type: "number", description: "Max number of reports to return (default: 20)" },
500
+ before_date: { type: "string", description: "Show reports created before this date (YYYY-MM-DD, DD.MM.YYYY, YYYY-MM-DD HH:mm, etc.)" },
501
+ all: { type: "boolean", description: "Fetch all reports (paginated automatically)" },
502
+ debug: { type: "boolean", description: "Enable verbose debug logs" },
503
+ },
504
+ additionalProperties: false,
505
+ },
506
+ },
507
+ {
508
+ name: "list_report_files",
509
+ description: "List files in a checkup report (metadata only, no content). Each report contains json (raw data) and md (markdown analysis) files per check. Either report_id or check_id must be provided.",
510
+ inputSchema: {
511
+ type: "object",
512
+ properties: {
513
+ report_id: { type: "number", description: "Checkup report ID (optional if check_id is provided)" },
514
+ type: { type: "string", description: "Filter by file type: 'json' or 'md'" },
515
+ check_id: { type: "string", description: "Filter by check ID (e.g., 'H002', 'F004')" },
516
+ debug: { type: "boolean", description: "Enable verbose debug logs" },
517
+ },
518
+ additionalProperties: false,
519
+ },
520
+ },
521
+ {
522
+ name: "get_report_data",
523
+ description: "Get checkup report file content. Returns files with a 'data' field containing the actual content: markdown analysis or JSON raw data. Use type='md' for human-readable analysis with recommendations, type='json' for raw check data. Either report_id or check_id must be provided.",
524
+ inputSchema: {
525
+ type: "object",
526
+ properties: {
527
+ report_id: { type: "number", description: "Checkup report ID (optional if check_id is provided)" },
528
+ type: { type: "string", description: "Filter by file type: 'json' for raw data, 'md' for markdown analysis" },
529
+ check_id: { type: "string", description: "Filter by check ID (e.g., 'H002', 'F004')" },
530
+ debug: { type: "boolean", description: "Enable verbose debug logs" },
531
+ },
532
+ additionalProperties: false,
533
+ },
534
+ },
445
535
  ],
446
536
  };
447
537
  });
@@ -63,7 +63,7 @@ export function listMetricNames(): string[] {
63
63
  export const METRIC_NAMES = {
64
64
  // Index health checks
65
65
  H001: "pg_invalid_indexes",
66
- H002: "unused_indexes",
66
+ H002: "unused_indexes",
67
67
  H004: "redundant_indexes",
68
68
  // Bloat estimation
69
69
  F004: "pg_table_bloat",
@@ -75,6 +75,8 @@ export const METRIC_NAMES = {
75
75
  dbSize: "db_size",
76
76
  // Stats reset info (H002)
77
77
  statsReset: "stats_reset",
78
+ // I/O statistics (I001) - PostgreSQL 16+
79
+ I001: "pg_stat_io",
78
80
  } as const;
79
81
 
80
82
  /**