postgresai 0.11.0-alpha.9 → 0.12.0-alpha.13
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 -15
- package/bin/postgres-ai.ts +194 -102
- package/dist/bin/postgres-ai.js +177 -102
- package/dist/bin/postgres-ai.js.map +1 -1
- package/dist/lib/auth-server.js +8 -8
- package/dist/lib/issues.d.ts +7 -0
- package/dist/lib/issues.d.ts.map +1 -0
- package/dist/lib/issues.js +105 -0
- package/dist/lib/issues.js.map +1 -0
- package/dist/lib/mcp-server.d.ts +9 -0
- package/dist/lib/mcp-server.d.ts.map +1 -0
- package/dist/lib/mcp-server.js +114 -0
- package/dist/lib/mcp-server.js.map +1 -0
- package/dist/lib/util.d.ts +27 -0
- package/dist/lib/util.d.ts.map +1 -0
- package/dist/lib/util.js +46 -0
- package/dist/lib/util.js.map +1 -0
- package/dist/package.json +3 -2
- package/lib/auth-server.ts +8 -8
- package/lib/issues.ts +83 -0
- package/lib/mcp-server.ts +98 -0
- package/lib/util.ts +60 -0
- package/package.json +3 -4
- package/tsconfig.json +2 -2
package/lib/auth-server.ts
CHANGED
|
@@ -97,7 +97,7 @@ export function createCallbackServer(
|
|
|
97
97
|
<!DOCTYPE html>
|
|
98
98
|
<html>
|
|
99
99
|
<head>
|
|
100
|
-
<title>Authentication
|
|
100
|
+
<title>Authentication failed</title>
|
|
101
101
|
<style>
|
|
102
102
|
body { font-family: system-ui, -apple-system, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; }
|
|
103
103
|
.error { background: #fee; border: 1px solid #fcc; padding: 20px; border-radius: 8px; }
|
|
@@ -107,7 +107,7 @@ export function createCallbackServer(
|
|
|
107
107
|
</head>
|
|
108
108
|
<body>
|
|
109
109
|
<div class="error">
|
|
110
|
-
<h1>Authentication
|
|
110
|
+
<h1>Authentication failed</h1>
|
|
111
111
|
<p><strong>Error:</strong> ${escapeHtml(error)}</p>
|
|
112
112
|
${errorDescription ? `<p><strong>Description:</strong> ${escapeHtml(errorDescription)}</p>` : ""}
|
|
113
113
|
<p>You can close this window and return to your terminal.</p>
|
|
@@ -130,7 +130,7 @@ export function createCallbackServer(
|
|
|
130
130
|
<!DOCTYPE html>
|
|
131
131
|
<html>
|
|
132
132
|
<head>
|
|
133
|
-
<title>Authentication
|
|
133
|
+
<title>Authentication failed</title>
|
|
134
134
|
<style>
|
|
135
135
|
body { font-family: system-ui, -apple-system, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; }
|
|
136
136
|
.error { background: #fee; border: 1px solid #fcc; padding: 20px; border-radius: 8px; }
|
|
@@ -139,7 +139,7 @@ export function createCallbackServer(
|
|
|
139
139
|
</head>
|
|
140
140
|
<body>
|
|
141
141
|
<div class="error">
|
|
142
|
-
<h1>Authentication
|
|
142
|
+
<h1>Authentication failed</h1>
|
|
143
143
|
<p>Missing required parameters (code or state).</p>
|
|
144
144
|
<p>You can close this window and return to your terminal.</p>
|
|
145
145
|
</div>
|
|
@@ -159,7 +159,7 @@ export function createCallbackServer(
|
|
|
159
159
|
<!DOCTYPE html>
|
|
160
160
|
<html>
|
|
161
161
|
<head>
|
|
162
|
-
<title>Authentication
|
|
162
|
+
<title>Authentication failed</title>
|
|
163
163
|
<style>
|
|
164
164
|
body { font-family: system-ui, -apple-system, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; }
|
|
165
165
|
.error { background: #fee; border: 1px solid #fcc; padding: 20px; border-radius: 8px; }
|
|
@@ -168,7 +168,7 @@ export function createCallbackServer(
|
|
|
168
168
|
</head>
|
|
169
169
|
<body>
|
|
170
170
|
<div class="error">
|
|
171
|
-
<h1>Authentication
|
|
171
|
+
<h1>Authentication failed</h1>
|
|
172
172
|
<p>Invalid state parameter (possible CSRF attack).</p>
|
|
173
173
|
<p>You can close this window and return to your terminal.</p>
|
|
174
174
|
</div>
|
|
@@ -192,7 +192,7 @@ export function createCallbackServer(
|
|
|
192
192
|
<!DOCTYPE html>
|
|
193
193
|
<html>
|
|
194
194
|
<head>
|
|
195
|
-
<title>Authentication
|
|
195
|
+
<title>Authentication successful</title>
|
|
196
196
|
<style>
|
|
197
197
|
body { font-family: system-ui, -apple-system, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; }
|
|
198
198
|
.success { background: #efe; border: 1px solid #cfc; padding: 20px; border-radius: 8px; }
|
|
@@ -201,7 +201,7 @@ export function createCallbackServer(
|
|
|
201
201
|
</head>
|
|
202
202
|
<body>
|
|
203
203
|
<div class="success">
|
|
204
|
-
<h1>Authentication
|
|
204
|
+
<h1>Authentication successful</h1>
|
|
205
205
|
<p>You have successfully authenticated the PostgresAI CLI.</p>
|
|
206
206
|
<p>You can close this window and return to your terminal.</p>
|
|
207
207
|
</div>
|
package/lib/issues.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import * as https from "https";
|
|
2
|
+
import { URL } from "url";
|
|
3
|
+
import { maskSecret, normalizeBaseUrl } from "./util";
|
|
4
|
+
|
|
5
|
+
export interface FetchIssuesParams {
|
|
6
|
+
apiKey: string;
|
|
7
|
+
apiBaseUrl: string;
|
|
8
|
+
debug?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function fetchIssues(params: FetchIssuesParams): Promise<unknown> {
|
|
12
|
+
const { apiKey, apiBaseUrl, debug } = params;
|
|
13
|
+
if (!apiKey) {
|
|
14
|
+
throw new Error("API key is required");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const base = normalizeBaseUrl(apiBaseUrl);
|
|
18
|
+
const url = new URL(`${base}/issues`);
|
|
19
|
+
|
|
20
|
+
const headers: Record<string, string> = {
|
|
21
|
+
"access-token": apiKey,
|
|
22
|
+
"Prefer": "return=representation",
|
|
23
|
+
"Content-Type": "application/json",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
if (debug) {
|
|
27
|
+
const debugHeaders: Record<string, string> = { ...headers, "access-token": maskSecret(apiKey) };
|
|
28
|
+
// eslint-disable-next-line no-console
|
|
29
|
+
console.log(`Debug: Resolved API base URL: ${base}`);
|
|
30
|
+
// eslint-disable-next-line no-console
|
|
31
|
+
console.log(`Debug: GET URL: ${url.toString()}`);
|
|
32
|
+
// eslint-disable-next-line no-console
|
|
33
|
+
console.log(`Debug: Auth scheme: access-token`);
|
|
34
|
+
// eslint-disable-next-line no-console
|
|
35
|
+
console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
const req = https.request(
|
|
40
|
+
url,
|
|
41
|
+
{
|
|
42
|
+
method: "GET",
|
|
43
|
+
headers,
|
|
44
|
+
},
|
|
45
|
+
(res) => {
|
|
46
|
+
let data = "";
|
|
47
|
+
res.on("data", (chunk) => (data += chunk));
|
|
48
|
+
res.on("end", () => {
|
|
49
|
+
if (debug) {
|
|
50
|
+
// eslint-disable-next-line no-console
|
|
51
|
+
console.log(`Debug: Response status: ${res.statusCode}`);
|
|
52
|
+
// eslint-disable-next-line no-console
|
|
53
|
+
console.log(`Debug: Response headers: ${JSON.stringify(res.headers)}`);
|
|
54
|
+
}
|
|
55
|
+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
|
56
|
+
try {
|
|
57
|
+
const parsed = JSON.parse(data);
|
|
58
|
+
resolve(parsed);
|
|
59
|
+
} catch {
|
|
60
|
+
resolve(data);
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
let errMsg = `Failed to fetch issues: HTTP ${res.statusCode}`;
|
|
64
|
+
if (data) {
|
|
65
|
+
try {
|
|
66
|
+
const errObj = JSON.parse(data);
|
|
67
|
+
errMsg += `\n${JSON.stringify(errObj, null, 2)}`;
|
|
68
|
+
} catch {
|
|
69
|
+
errMsg += `\n${data}`;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
reject(new Error(errMsg));
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
req.on("error", (err: Error) => reject(err));
|
|
79
|
+
req.end();
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import * as pkg from "../package.json";
|
|
2
|
+
import * as config from "./config";
|
|
3
|
+
import { fetchIssues } from "./issues";
|
|
4
|
+
import { resolveBaseUrls } from "./util";
|
|
5
|
+
|
|
6
|
+
// MCP SDK imports
|
|
7
|
+
import { Server } from "@modelcontextprotocol/sdk/server";
|
|
8
|
+
import * as path from "path";
|
|
9
|
+
// Types schemas will be loaded dynamically from the SDK's CJS bundle
|
|
10
|
+
|
|
11
|
+
interface RootOptsLike {
|
|
12
|
+
apiKey?: string;
|
|
13
|
+
apiBaseUrl?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function startMcpServer(rootOpts?: RootOptsLike, extra?: { debug?: boolean }): Promise<void> {
|
|
17
|
+
// Resolve stdio transport at runtime to avoid subpath export resolution issues
|
|
18
|
+
const serverEntry = require.resolve("@modelcontextprotocol/sdk/server");
|
|
19
|
+
const stdioPath = path.join(path.dirname(serverEntry), "stdio.js");
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
21
|
+
const { StdioServerTransport } = require(stdioPath);
|
|
22
|
+
// Load schemas dynamically to avoid subpath export resolution issues
|
|
23
|
+
const typesPath = path.resolve(path.dirname(serverEntry), "../types.js");
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
25
|
+
const { CallToolRequestSchema, ListToolsRequestSchema } = require(typesPath);
|
|
26
|
+
|
|
27
|
+
const server = new Server(
|
|
28
|
+
{ name: "postgresai-mcp", version: pkg.version },
|
|
29
|
+
{ capabilities: { tools: {} } }
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
33
|
+
return {
|
|
34
|
+
tools: [
|
|
35
|
+
{
|
|
36
|
+
name: "list_issues",
|
|
37
|
+
description: "List issues from PostgresAI API (same as CLI 'issues list')",
|
|
38
|
+
inputSchema: {
|
|
39
|
+
type: "object",
|
|
40
|
+
properties: {
|
|
41
|
+
debug: { type: "boolean", description: "Enable verbose debug logs" },
|
|
42
|
+
},
|
|
43
|
+
additionalProperties: false,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
server.setRequestHandler(CallToolRequestSchema, async (req: any) => {
|
|
51
|
+
const toolName = req.params.name;
|
|
52
|
+
const args = (req.params.arguments as Record<string, unknown>) || {};
|
|
53
|
+
|
|
54
|
+
if (toolName !== "list_issues") {
|
|
55
|
+
throw new Error(`Unknown tool: ${toolName}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const cfg = config.readConfig();
|
|
59
|
+
const apiKey = (rootOpts?.apiKey || process.env.PGAI_API_KEY || cfg.apiKey || "").toString();
|
|
60
|
+
const { apiBaseUrl } = resolveBaseUrls(rootOpts, cfg);
|
|
61
|
+
|
|
62
|
+
const debug = Boolean(args.debug ?? extra?.debug);
|
|
63
|
+
|
|
64
|
+
if (!apiKey) {
|
|
65
|
+
return {
|
|
66
|
+
content: [
|
|
67
|
+
{
|
|
68
|
+
type: "text",
|
|
69
|
+
text: "API key is required. Run 'pgai auth' or set PGAI_API_KEY.",
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
isError: true,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const result = await fetchIssues({ apiKey, apiBaseUrl, debug });
|
|
78
|
+
return {
|
|
79
|
+
content: [
|
|
80
|
+
{ type: "text", text: JSON.stringify(result, null, 2) },
|
|
81
|
+
],
|
|
82
|
+
};
|
|
83
|
+
} catch (err) {
|
|
84
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
85
|
+
return {
|
|
86
|
+
content: [
|
|
87
|
+
{ type: "text", text: message },
|
|
88
|
+
],
|
|
89
|
+
isError: true,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const transport = new StdioServerTransport();
|
|
95
|
+
await server.connect(transport);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
package/lib/util.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export function maskSecret(secret: string): string {
|
|
2
|
+
if (!secret) return "";
|
|
3
|
+
if (secret.length <= 8) return "****";
|
|
4
|
+
if (secret.length <= 16) return `${secret.slice(0, 4)}${"*".repeat(secret.length - 8)}${secret.slice(-4)}`;
|
|
5
|
+
return `${secret.slice(0, Math.min(12, secret.length - 8))}${"*".repeat(Math.max(4, secret.length - 16))}${secret.slice(-4)}`;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
export interface RootOptsLike {
|
|
10
|
+
apiBaseUrl?: string;
|
|
11
|
+
uiBaseUrl?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ConfigLike {
|
|
15
|
+
baseUrl?: string | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ResolvedBaseUrls {
|
|
19
|
+
apiBaseUrl: string;
|
|
20
|
+
uiBaseUrl: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Normalize a base URL by trimming a single trailing slash and validating.
|
|
25
|
+
* @throws Error if the URL is invalid
|
|
26
|
+
*/
|
|
27
|
+
export function normalizeBaseUrl(value: string): string {
|
|
28
|
+
const trimmed = (value || "").replace(/\/$/, "");
|
|
29
|
+
try {
|
|
30
|
+
// Validate
|
|
31
|
+
// eslint-disable-next-line no-new
|
|
32
|
+
new URL(trimmed);
|
|
33
|
+
} catch {
|
|
34
|
+
throw new Error(`Invalid base URL: ${value}`);
|
|
35
|
+
}
|
|
36
|
+
return trimmed;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Resolve API and UI base URLs using precedence and normalize them.
|
|
41
|
+
* Precedence (API): opts.apiBaseUrl → env.PGAI_API_BASE_URL → cfg.baseUrl → default
|
|
42
|
+
* Precedence (UI): opts.uiBaseUrl → env.PGAI_UI_BASE_URL → default
|
|
43
|
+
*/
|
|
44
|
+
export function resolveBaseUrls(
|
|
45
|
+
opts?: RootOptsLike,
|
|
46
|
+
cfg?: ConfigLike,
|
|
47
|
+
defaults: { apiBaseUrl?: string; uiBaseUrl?: string } = {}
|
|
48
|
+
): ResolvedBaseUrls {
|
|
49
|
+
const defApi = defaults.apiBaseUrl || "https://postgres.ai/api/general/";
|
|
50
|
+
const defUi = defaults.uiBaseUrl || "https://console.postgres.ai";
|
|
51
|
+
|
|
52
|
+
const apiCandidate = (opts?.apiBaseUrl || process.env.PGAI_API_BASE_URL || cfg?.baseUrl || defApi) as string;
|
|
53
|
+
const uiCandidate = (opts?.uiBaseUrl || process.env.PGAI_UI_BASE_URL || defUi) as string;
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
apiBaseUrl: normalizeBaseUrl(apiCandidate),
|
|
57
|
+
uiBaseUrl: normalizeBaseUrl(uiCandidate),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "postgresai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0-alpha.13",
|
|
4
4
|
"description": "postgres_ai CLI (Node.js)",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"private": false,
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"commander": "^12.1.0",
|
|
32
|
-
"js-yaml": "^4.1.0"
|
|
32
|
+
"js-yaml": "^4.1.0",
|
|
33
|
+
"@modelcontextprotocol/sdk": "^1.20.2"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
35
36
|
"@types/js-yaml": "^4.0.9",
|
|
@@ -40,5 +41,3 @@
|
|
|
40
41
|
"access": "public"
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
|
-
|
|
44
|
-
|
package/tsconfig.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
3
|
"target": "ES2020",
|
|
4
|
-
"module": "
|
|
4
|
+
"module": "node16",
|
|
5
5
|
"lib": ["ES2020"],
|
|
6
6
|
"outDir": "./dist",
|
|
7
7
|
"rootDir": "./",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"declaration": true,
|
|
14
14
|
"declarationMap": true,
|
|
15
15
|
"sourceMap": true,
|
|
16
|
-
"moduleResolution": "
|
|
16
|
+
"moduleResolution": "node16",
|
|
17
17
|
"types": ["node"]
|
|
18
18
|
},
|
|
19
19
|
"include": [
|