@gizmodata/gizmosql-mcp 0.1.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.
@@ -0,0 +1,429 @@
1
+ // Read-only classification and result-limit wrapping for SQL text.
2
+ //
3
+ // This is defense in depth, not the security boundary: the GizmoSQL
4
+ // user's database-level role/privileges are the real boundary (see the
5
+ // README). The classifier is deliberately conservative — anything it
6
+ // cannot confidently classify as a read is rejected when writes are
7
+ // disabled.
8
+ /** Leading keywords that are always reads and can be wrapped in a subquery. */
9
+ const WRAPPABLE_READ_KEYWORDS = new Set([
10
+ "SELECT",
11
+ "FROM", // DuckDB FROM-first syntax
12
+ "VALUES",
13
+ "SHOW",
14
+ "DESCRIBE",
15
+ "DESC",
16
+ "SUMMARIZE",
17
+ "TABLE", // `TABLE t` is shorthand for SELECT * FROM t
18
+ ]);
19
+ /** Leading keywords that are reads but cannot be used as a subquery. */
20
+ const UNWRAPPABLE_READ_KEYWORDS = new Set(["EXPLAIN", "PRAGMA"]);
21
+ /** Everything here is a write / side-effecting statement. */
22
+ const WRITE_KEYWORDS = new Set([
23
+ "INSERT",
24
+ "UPDATE",
25
+ "DELETE",
26
+ "MERGE",
27
+ "UPSERT",
28
+ "REPLACE",
29
+ "TRUNCATE",
30
+ "CREATE",
31
+ "DROP",
32
+ "ALTER",
33
+ "RENAME",
34
+ "COPY",
35
+ "EXPORT",
36
+ "IMPORT",
37
+ "ATTACH",
38
+ "DETACH",
39
+ "INSTALL",
40
+ "FORCE", // FORCE INSTALL / FORCE CHECKPOINT
41
+ "LOAD",
42
+ "SET",
43
+ "RESET",
44
+ "USE",
45
+ "CALL",
46
+ "BEGIN",
47
+ "START",
48
+ "COMMIT",
49
+ "ROLLBACK",
50
+ "ABORT",
51
+ "END",
52
+ "CHECKPOINT",
53
+ "VACUUM",
54
+ "ANALYZE",
55
+ "GRANT",
56
+ "REVOKE",
57
+ "KILL",
58
+ "COMMENT",
59
+ "PIVOT",
60
+ "UNPIVOT",
61
+ ]);
62
+ // PIVOT/UNPIVOT are reads in DuckDB, but they are rewritten by the
63
+ // binder into multiple internal statements and cannot be prepared with
64
+ // parameters or reliably wrapped; treat them as unclassifiable rather
65
+ // than as writes.
66
+ WRITE_KEYWORDS.delete("PIVOT");
67
+ WRITE_KEYWORDS.delete("UNPIVOT");
68
+ /**
69
+ * Removes SQL comments (`-- ...`, `/* ... *\/` with nesting) while
70
+ * preserving string literals, quoted identifiers and dollar-quoted
71
+ * strings. Comments are replaced by a single space so offsets stay
72
+ * meaningful for error messages.
73
+ */
74
+ export function stripComments(sql) {
75
+ let out = "";
76
+ let i = 0;
77
+ const n = sql.length;
78
+ while (i < n) {
79
+ const ch = sql[i];
80
+ const next = sql[i + 1];
81
+ if (ch === "'") {
82
+ // single-quoted string with '' escapes
83
+ let j = i + 1;
84
+ while (j < n) {
85
+ if (sql[j] === "'") {
86
+ if (sql[j + 1] === "'") {
87
+ j += 2;
88
+ continue;
89
+ }
90
+ break;
91
+ }
92
+ j++;
93
+ }
94
+ out += sql.slice(i, j + 1);
95
+ i = j + 1;
96
+ continue;
97
+ }
98
+ if (ch === '"') {
99
+ let j = i + 1;
100
+ while (j < n) {
101
+ if (sql[j] === '"') {
102
+ if (sql[j + 1] === '"') {
103
+ j += 2;
104
+ continue;
105
+ }
106
+ break;
107
+ }
108
+ j++;
109
+ }
110
+ out += sql.slice(i, j + 1);
111
+ i = j + 1;
112
+ continue;
113
+ }
114
+ if (ch === "$") {
115
+ // dollar-quoted string: $tag$ ... $tag$
116
+ const m = /^\$([A-Za-z_][A-Za-z0-9_]*)?\$/.exec(sql.slice(i));
117
+ if (m) {
118
+ const delim = m[0];
119
+ const end = sql.indexOf(delim, i + delim.length);
120
+ const stop = end === -1 ? n : end + delim.length;
121
+ out += sql.slice(i, stop);
122
+ i = stop;
123
+ continue;
124
+ }
125
+ }
126
+ if (ch === "-" && next === "-") {
127
+ let j = i + 2;
128
+ while (j < n && sql[j] !== "\n")
129
+ j++;
130
+ out += " ";
131
+ i = j;
132
+ continue;
133
+ }
134
+ if (ch === "/" && next === "*") {
135
+ let depth = 1;
136
+ let j = i + 2;
137
+ while (j < n && depth > 0) {
138
+ if (sql[j] === "/" && sql[j + 1] === "*") {
139
+ depth++;
140
+ j += 2;
141
+ }
142
+ else if (sql[j] === "*" && sql[j + 1] === "/") {
143
+ depth--;
144
+ j += 2;
145
+ }
146
+ else {
147
+ j++;
148
+ }
149
+ }
150
+ out += " ";
151
+ i = j;
152
+ continue;
153
+ }
154
+ out += ch;
155
+ i++;
156
+ }
157
+ return out;
158
+ }
159
+ /** Tokenizes comment-stripped SQL into words, punctuation and literals. */
160
+ function tokenize(sql) {
161
+ const tokens = [];
162
+ let i = 0;
163
+ const n = sql.length;
164
+ while (i < n) {
165
+ const ch = sql[i];
166
+ if (/\s/.test(ch)) {
167
+ i++;
168
+ continue;
169
+ }
170
+ if (ch === "'") {
171
+ let j = i + 1;
172
+ while (j < n) {
173
+ if (sql[j] === "'") {
174
+ if (sql[j + 1] === "'") {
175
+ j += 2;
176
+ continue;
177
+ }
178
+ break;
179
+ }
180
+ j++;
181
+ }
182
+ tokens.push({ text: sql.slice(i, j + 1), kind: "string", start: i });
183
+ i = j + 1;
184
+ continue;
185
+ }
186
+ if (ch === '"') {
187
+ let j = i + 1;
188
+ while (j < n) {
189
+ if (sql[j] === '"') {
190
+ if (sql[j + 1] === '"') {
191
+ j += 2;
192
+ continue;
193
+ }
194
+ break;
195
+ }
196
+ j++;
197
+ }
198
+ tokens.push({ text: sql.slice(i, j + 1), kind: "quoted", start: i });
199
+ i = j + 1;
200
+ continue;
201
+ }
202
+ if (ch === "$") {
203
+ const m = /^\$([A-Za-z_][A-Za-z0-9_]*)?\$/.exec(sql.slice(i));
204
+ if (m) {
205
+ const delim = m[0];
206
+ const end = sql.indexOf(delim, i + delim.length);
207
+ const stop = end === -1 ? n : end + delim.length;
208
+ tokens.push({ text: sql.slice(i, stop), kind: "string", start: i });
209
+ i = stop;
210
+ continue;
211
+ }
212
+ }
213
+ const word = /^[A-Za-z_][A-Za-z0-9_$]*/.exec(sql.slice(i));
214
+ if (word) {
215
+ tokens.push({ text: word[0].toUpperCase(), kind: "word", start: i });
216
+ i += word[0].length;
217
+ continue;
218
+ }
219
+ tokens.push({ text: ch, kind: "punct", start: i });
220
+ i++;
221
+ }
222
+ return tokens;
223
+ }
224
+ /**
225
+ * Splits SQL into top-level statements on semicolons that are outside
226
+ * strings, quoted identifiers and comments. Empty statements (e.g. from a
227
+ * trailing semicolon) are dropped.
228
+ */
229
+ export function splitStatements(sql) {
230
+ const stripped = stripComments(sql);
231
+ const tokens = tokenize(stripped);
232
+ const statements = [];
233
+ let start = 0;
234
+ for (const t of tokens) {
235
+ if (t.kind === "punct" && t.text === ";") {
236
+ const piece = stripped.slice(start, t.start).trim();
237
+ if (piece)
238
+ statements.push(piece);
239
+ start = t.start + 1;
240
+ }
241
+ }
242
+ const tail = stripped.slice(start).trim();
243
+ if (tail)
244
+ statements.push(tail);
245
+ return statements;
246
+ }
247
+ /**
248
+ * Returns the statement with comments removed and any trailing
249
+ * semicolons stripped, ready to be wrapped in a subquery.
250
+ */
251
+ export function normalizeStatement(sql) {
252
+ return stripComments(sql).trim().replace(/;+\s*$/u, "").trim();
253
+ }
254
+ /** Index of the token that closes the parenthesis opened at `open`. */
255
+ function matchParen(tokens, open) {
256
+ let depth = 0;
257
+ for (let i = open; i < tokens.length; i++) {
258
+ const t = tokens[i];
259
+ if (t.kind !== "punct")
260
+ continue;
261
+ if (t.text === "(")
262
+ depth++;
263
+ else if (t.text === ")") {
264
+ depth--;
265
+ if (depth === 0)
266
+ return i;
267
+ }
268
+ }
269
+ return -1;
270
+ }
271
+ /**
272
+ * Given tokens starting at a `WITH`, returns the index of the first token
273
+ * of the main statement that follows the CTE list, or -1 if the CTE list
274
+ * cannot be parsed.
275
+ */
276
+ function skipCteList(tokens, withIndex) {
277
+ let i = withIndex + 1;
278
+ if (tokens[i]?.text === "RECURSIVE")
279
+ i++;
280
+ for (;;) {
281
+ // cte name
282
+ const name = tokens[i];
283
+ if (!name || (name.kind !== "word" && name.kind !== "quoted"))
284
+ return -1;
285
+ i++;
286
+ // optional column list
287
+ if (tokens[i]?.kind === "punct" && tokens[i].text === "(") {
288
+ const close = matchParen(tokens, i);
289
+ if (close === -1)
290
+ return -1;
291
+ i = close + 1;
292
+ }
293
+ if (tokens[i]?.text !== "AS")
294
+ return -1;
295
+ i++;
296
+ if (tokens[i]?.text === "NOT")
297
+ i++;
298
+ if (tokens[i]?.text === "MATERIALIZED")
299
+ i++;
300
+ if (!(tokens[i]?.kind === "punct" && tokens[i].text === "("))
301
+ return -1;
302
+ const close = matchParen(tokens, i);
303
+ if (close === -1)
304
+ return -1;
305
+ i = close + 1;
306
+ if (tokens[i]?.kind === "punct" && tokens[i].text === ",") {
307
+ i++;
308
+ continue;
309
+ }
310
+ return i < tokens.length ? i : -1;
311
+ }
312
+ }
313
+ /**
314
+ * Classifies a single SQL statement by its leading keyword, looking
315
+ * through leading parentheses and CTE lists to find the statement that
316
+ * actually executes.
317
+ */
318
+ export function classifyStatement(sql) {
319
+ const tokens = tokenize(stripComments(sql));
320
+ let i = 0;
321
+ let sawWith = false;
322
+ for (let guard = 0; guard < 64 && i < tokens.length; guard++) {
323
+ const t = tokens[i];
324
+ if (t.kind === "punct" && t.text === "(") {
325
+ // `(SELECT ...) UNION (SELECT ...)` — classify the inner statement.
326
+ i++;
327
+ continue;
328
+ }
329
+ if (t.kind !== "word") {
330
+ return { kind: "unknown", keyword: t.text, wrappable: false };
331
+ }
332
+ if (t.text === "WITH") {
333
+ sawWith = true;
334
+ const main = skipCteList(tokens, i);
335
+ if (main === -1) {
336
+ return { kind: "unknown", keyword: "WITH", wrappable: false };
337
+ }
338
+ i = main;
339
+ continue;
340
+ }
341
+ if (WRAPPABLE_READ_KEYWORDS.has(t.text)) {
342
+ return { kind: "read", keyword: sawWith ? "WITH" : t.text, wrappable: true };
343
+ }
344
+ if (UNWRAPPABLE_READ_KEYWORDS.has(t.text)) {
345
+ if (t.text === "PRAGMA") {
346
+ // `PRAGMA name` / `PRAGMA fn(args)` are reads; `PRAGMA x = y` mutates settings.
347
+ const hasAssignment = tokens
348
+ .slice(i + 1)
349
+ .some((tok) => tok.kind === "punct" && tok.text === "=");
350
+ if (hasAssignment) {
351
+ return { kind: "write", keyword: "PRAGMA", wrappable: false };
352
+ }
353
+ }
354
+ if (t.text === "EXPLAIN") {
355
+ // EXPLAIN of a write is a read (it never executes), but keep it
356
+ // conservative: classify the explained statement instead.
357
+ let j = i + 1;
358
+ if (tokens[j]?.text === "ANALYZE")
359
+ j++;
360
+ if (tokens[j]?.kind === "punct" && tokens[j].text === "(") {
361
+ // EXPLAIN (FORMAT ...) options
362
+ const close = matchParen(tokens, j);
363
+ if (close !== -1)
364
+ j = close + 1;
365
+ }
366
+ const inner = classifyStatement(stripComments(sql).slice(tokens[j]?.start ?? sql.length));
367
+ return { kind: inner.kind, keyword: "EXPLAIN", wrappable: false };
368
+ }
369
+ return { kind: "read", keyword: t.text, wrappable: false };
370
+ }
371
+ if (WRITE_KEYWORDS.has(t.text)) {
372
+ return { kind: "write", keyword: t.text, wrappable: false };
373
+ }
374
+ return { kind: "unknown", keyword: t.text, wrappable: false };
375
+ }
376
+ return { kind: "unknown", keyword: "", wrappable: false };
377
+ }
378
+ export class SqlGuardError extends Error {
379
+ constructor(message) {
380
+ super(message);
381
+ this.name = "SqlGuardError";
382
+ }
383
+ }
384
+ /**
385
+ * Validates that `sql` is exactly one statement and, when writes are
386
+ * disabled, that it is a read. Throws {@link SqlGuardError} otherwise.
387
+ */
388
+ export function guardStatement(sql, allowWrites) {
389
+ if (typeof sql !== "string" || !sql.trim()) {
390
+ throw new SqlGuardError("SQL statement is empty.");
391
+ }
392
+ const statements = splitStatements(sql);
393
+ if (statements.length === 0) {
394
+ throw new SqlGuardError("SQL statement is empty (only comments/semicolons).");
395
+ }
396
+ if (statements.length > 1) {
397
+ throw new SqlGuardError(`Only one SQL statement per call is allowed (found ${statements.length}). ` +
398
+ "Split the statements into separate calls.");
399
+ }
400
+ const single = normalizeStatement(statements[0]);
401
+ const classification = classifyStatement(single);
402
+ if (!allowWrites) {
403
+ if (classification.kind === "write") {
404
+ throw new SqlGuardError(`Statement type ${classification.keyword} is not allowed: this server is read-only ` +
405
+ "(GIZMOSQL_ALLOW_WRITES is not enabled). Only SELECT/WITH/FROM/VALUES/SHOW/" +
406
+ "DESCRIBE/SUMMARIZE/EXPLAIN/PRAGMA (read) statements are permitted.");
407
+ }
408
+ if (classification.kind === "unknown") {
409
+ throw new SqlGuardError(`Unable to classify statement starting with "${classification.keyword || single.slice(0, 20)}" ` +
410
+ "as read-only; this server is read-only (GIZMOSQL_ALLOW_WRITES is not enabled). " +
411
+ "Rewrite it as a SELECT/WITH query.");
412
+ }
413
+ }
414
+ return { sql: single, classification };
415
+ }
416
+ /** Alias used by the query wrapper so it never collides with user aliases. */
417
+ export const WRAPPER_ALIAS = "gizmosql_mcp_q";
418
+ /**
419
+ * Wraps a read statement so the server enforces the row cap:
420
+ * `SELECT * FROM (<sql>) AS gizmosql_mcp_q LIMIT <limit>`.
421
+ * The caller must pass a normalized (comment-free, no trailing `;`) statement.
422
+ */
423
+ export function wrapWithLimit(sql, limit) {
424
+ if (!Number.isInteger(limit) || limit < 1) {
425
+ throw new SqlGuardError(`Row limit must be a positive integer (got ${limit}).`);
426
+ }
427
+ return `SELECT * FROM (\n${sql}\n) AS ${WRAPPER_ALIAS} LIMIT ${limit}`;
428
+ }
429
+ //# sourceMappingURL=sql-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql-guard.js","sourceRoot":"","sources":["../src/sql-guard.ts"],"names":[],"mappings":"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"}
package/dist/sso.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ import type { GizmoConnection } from "./connection.js";
2
+ export interface SsoOptions {
3
+ waitSeconds: number;
4
+ pollIntervalMs?: number;
5
+ /** Test hook: replaces the browser opener. */
6
+ openUrl?: (url: string) => Promise<void>;
7
+ /** Test hook: replaces HTTP GET JSON. */
8
+ getJson?: (url: string, tlsSkipVerify: boolean) => Promise<Record<string, unknown>>;
9
+ /** Test hook: replaces OAuth URL discovery. */
10
+ discover?: () => Promise<string | null>;
11
+ }
12
+ export interface SsoOutcome {
13
+ status: "complete" | "pending" | "unavailable" | "error";
14
+ message: string;
15
+ authUrl?: string;
16
+ }
17
+ /** GET a JSON document (small helper so the flow works without extra dependencies). */
18
+ export declare function httpGetJson(url: string, tlsSkipVerify: boolean): Promise<Record<string, unknown>>;
19
+ /** Opens a URL in the user's default browser without any extra dependency. */
20
+ export declare function openInBrowser(url: string): Promise<void>;
21
+ /** Runs (or resumes) the SSO login flow for the shared connection. */
22
+ export declare function runSsoLogin(connection: GizmoConnection, options: SsoOptions): Promise<SsoOutcome>;
package/dist/sso.js ADDED
@@ -0,0 +1,143 @@
1
+ // Optional OAuth/SSO browser login (enabled with GIZMOSQL_ENABLE_SSO=true).
2
+ //
3
+ // Flow (see the gizmosql-client README and the Go driver's oauth.go):
4
+ // GET {oauthUrl}/oauth/initiate -> { session_uuid, auth_url }
5
+ // open auth_url in the user's browser
6
+ // poll GET {oauthUrl}/oauth/token/{uuid} -> { status: pending|complete|error|not_found, token? }
7
+ // reconnect with username "token" / password <identity token>
8
+ // The token lives in process memory only.
9
+ import { spawn } from "node:child_process";
10
+ import * as http from "node:http";
11
+ import * as https from "node:https";
12
+ const pendingSessions = new WeakMap();
13
+ /** GET a JSON document (small helper so the flow works without extra dependencies). */
14
+ export function httpGetJson(url, tlsSkipVerify) {
15
+ return new Promise((resolve, reject) => {
16
+ const lib = url.startsWith("https") ? https : http;
17
+ const req = lib.get(url, url.startsWith("https") ? { rejectUnauthorized: !tlsSkipVerify, timeout: 10000 } : { timeout: 10000 }, (res) => {
18
+ let body = "";
19
+ res.setEncoding("utf8");
20
+ res.on("data", (chunk) => (body += chunk));
21
+ res.on("end", () => {
22
+ try {
23
+ resolve(JSON.parse(body));
24
+ }
25
+ catch {
26
+ reject(new Error(`Unexpected non-JSON response (HTTP ${res.statusCode}) from ${url}`));
27
+ }
28
+ });
29
+ });
30
+ req.on("timeout", () => {
31
+ req.destroy(new Error(`Timed out contacting ${url}`));
32
+ });
33
+ req.on("error", reject);
34
+ });
35
+ }
36
+ /** Opens a URL in the user's default browser without any extra dependency. */
37
+ export function openInBrowser(url) {
38
+ return new Promise((resolve, reject) => {
39
+ let cmd;
40
+ let args;
41
+ if (process.platform === "darwin") {
42
+ cmd = "open";
43
+ args = [url];
44
+ }
45
+ else if (process.platform === "win32") {
46
+ cmd = "rundll32";
47
+ args = ["url.dll,FileProtocolHandler", url];
48
+ }
49
+ else {
50
+ cmd = "xdg-open";
51
+ args = [url];
52
+ }
53
+ try {
54
+ const child = spawn(cmd, args, { stdio: "ignore", detached: true });
55
+ child.on("error", reject);
56
+ child.on("spawn", () => {
57
+ child.unref();
58
+ resolve();
59
+ });
60
+ }
61
+ catch (err) {
62
+ reject(err);
63
+ }
64
+ });
65
+ }
66
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
67
+ /** Runs (or resumes) the SSO login flow for the shared connection. */
68
+ export async function runSsoLogin(connection, options) {
69
+ const tlsSkipVerify = connection.config.tlsSkipVerify;
70
+ const getJson = options.getJson ?? httpGetJson;
71
+ const openUrl = options.openUrl ?? openInBrowser;
72
+ const pollIntervalMs = options.pollIntervalMs ?? 1000;
73
+ let session = pendingSessions.get(connection);
74
+ if (!session || Date.now() - session.startedAt > 10 * 60 * 1000) {
75
+ const discover = options.discover ?? (() => connection.run((c) => c.discoverOAuthUrl(), { timeoutSeconds: 30 }));
76
+ const baseUrl = await discover();
77
+ if (!baseUrl) {
78
+ return {
79
+ status: "unavailable",
80
+ message: "The GizmoSQL server does not expose OAuth/SSO (no /oauth/initiate endpoint on port " +
81
+ `${connection.config.oauthPort}). Configure username/password or a token instead.`,
82
+ };
83
+ }
84
+ const init = await getJson(`${baseUrl}/oauth/initiate`, tlsSkipVerify);
85
+ const sessionUuid = typeof init.session_uuid === "string" ? init.session_uuid : "";
86
+ const authUrl = typeof init.auth_url === "string" ? init.auth_url : "";
87
+ if (!sessionUuid || !authUrl) {
88
+ return { status: "error", message: `Unexpected response from /oauth/initiate: ${JSON.stringify(init)}` };
89
+ }
90
+ session = { baseUrl, sessionUuid, authUrl, startedAt: Date.now() };
91
+ pendingSessions.set(connection, session);
92
+ try {
93
+ await openUrl(authUrl);
94
+ }
95
+ catch {
96
+ // The URL is returned in the message so the user can open it manually.
97
+ }
98
+ }
99
+ const deadline = Date.now() + options.waitSeconds * 1000;
100
+ const pollUrl = `${session.baseUrl}/oauth/token/${session.sessionUuid}`;
101
+ while (Date.now() < deadline) {
102
+ const data = await getJson(pollUrl, tlsSkipVerify);
103
+ const status = typeof data.status === "string" ? data.status : "";
104
+ if (status === "complete") {
105
+ const token = typeof data.token === "string" ? data.token : "";
106
+ pendingSessions.delete(connection);
107
+ if (!token) {
108
+ return { status: "error", message: "Token poll returned 'complete' but no token." };
109
+ }
110
+ await connection.useCredentials({ username: "token", password: token });
111
+ try {
112
+ await connection.query("SELECT 1");
113
+ }
114
+ catch (err) {
115
+ return {
116
+ status: "error",
117
+ message: `Signed in, but reconnecting with the identity token failed: ${connection.redact(err instanceof Error ? err.message : String(err))}`,
118
+ };
119
+ }
120
+ return { status: "complete", message: "Signed in with SSO. Subsequent queries use your identity token." };
121
+ }
122
+ if (status === "error") {
123
+ pendingSessions.delete(connection);
124
+ return { status: "error", message: `OAuth flow failed: ${String(data.error ?? "unknown error")}` };
125
+ }
126
+ if (status === "not_found") {
127
+ pendingSessions.delete(connection);
128
+ return { status: "error", message: "OAuth session not found (it may have expired). Call login_sso again." };
129
+ }
130
+ if (status !== "pending") {
131
+ pendingSessions.delete(connection);
132
+ return { status: "error", message: `Unexpected token poll status: ${JSON.stringify(status)}` };
133
+ }
134
+ await sleep(pollIntervalMs);
135
+ }
136
+ return {
137
+ status: "pending",
138
+ authUrl: session.authUrl,
139
+ message: "Waiting for you to finish signing in. A browser window should have opened; if not, open this URL: " +
140
+ `${session.authUrl}\nThen call login_sso again to complete the sign-in.`,
141
+ };
142
+ }
143
+ //# sourceMappingURL=sso.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sso.js","sourceRoot":"","sources":["../src/sso.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import * as http from "node:http";
2
+ import { type McpConfig } from "./connection.js";
3
+ /** Runs the server on stdio (Claude Desktop / `claude mcp add`). */
4
+ export declare function startStdio(config: McpConfig): Promise<void>;
5
+ export interface HttpOptions {
6
+ host: string;
7
+ port: number;
8
+ }
9
+ /**
10
+ * Runs the server as stateless Streamable HTTP on `/mcp`. Every request
11
+ * gets its own McpServer + transport (the SDK's stateless pattern) while
12
+ * sharing the single GizmoSQL connection.
13
+ *
14
+ * Authentication: when GIZMOSQL_MCP_BEARER_TOKEN is set every request must
15
+ * carry `Authorization: Bearer <token>`. OAuth is a future extension point:
16
+ * replace `authorize()` with an OAuth token verifier.
17
+ */
18
+ export declare function startHttp(config: McpConfig, options: HttpOptions): Promise<http.Server>;