@conte777/db-view-mcp 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -250
- package/config.example.json +3 -1
- package/dist/config/types.d.ts +14 -4
- package/dist/config/types.js +5 -10
- package/dist/config/types.js.map +1 -1
- package/dist/connectors/clickhouse.d.ts +1 -1
- package/dist/connectors/clickhouse.js +11 -3
- package/dist/connectors/clickhouse.js.map +1 -1
- package/dist/connectors/manager.d.ts +7 -2
- package/dist/connectors/manager.js +38 -35
- package/dist/connectors/manager.js.map +1 -1
- package/dist/connectors/postgresql.d.ts +1 -1
- package/dist/connectors/postgresql.js +61 -11
- package/dist/connectors/postgresql.js.map +1 -1
- package/dist/index.js +0 -0
- package/dist/server.d.ts +1 -1
- package/dist/server.js +6 -2
- package/dist/server.js.map +1 -1
- package/dist/tools/readonly/describe-table.d.ts +1 -3
- package/dist/tools/readonly/describe-table.js +5 -5
- package/dist/tools/readonly/describe-table.js.map +1 -1
- package/dist/tools/readonly/explain.d.ts +1 -3
- package/dist/tools/readonly/explain.js +6 -6
- package/dist/tools/readonly/explain.js.map +1 -1
- package/dist/tools/readonly/list-tables.d.ts +1 -3
- package/dist/tools/readonly/list-tables.js +5 -5
- package/dist/tools/readonly/list-tables.js.map +1 -1
- package/dist/tools/readonly/performance.d.ts +1 -3
- package/dist/tools/readonly/performance.js +9 -6
- package/dist/tools/readonly/performance.js.map +1 -1
- package/dist/tools/readonly/query.d.ts +1 -3
- package/dist/tools/readonly/query.js +14 -7
- package/dist/tools/readonly/query.js.map +1 -1
- package/dist/tools/readonly/schema.d.ts +1 -3
- package/dist/tools/readonly/schema.js +5 -5
- package/dist/tools/readonly/schema.js.map +1 -1
- package/dist/tools/write/execute.d.ts +5 -3
- package/dist/tools/write/execute.js +14 -6
- package/dist/tools/write/execute.js.map +1 -1
- package/dist/tools/write/transaction.d.ts +3 -4
- package/dist/tools/write/transaction.js +33 -24
- package/dist/tools/write/transaction.js.map +1 -1
- package/dist/transport/http.d.ts +2 -2
- package/dist/transport/http.js +23 -9
- package/dist/transport/http.js.map +1 -1
- package/dist/utils/logger.js +1 -1
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/resolve-db.d.ts +5 -0
- package/dist/utils/resolve-db.js +61 -0
- package/dist/utils/resolve-db.js.map +1 -0
- package/dist/utils/response.d.ts +12 -0
- package/dist/utils/response.js +130 -3
- package/dist/utils/response.js.map +1 -1
- package/dist/utils/sql-validator.d.ts +16 -0
- package/dist/utils/sql-validator.js +157 -4
- package/dist/utils/sql-validator.js.map +1 -1
- package/package.json +9 -1
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { formatCaughtError, formatSuccess } from "../../utils/response.js";
|
|
3
|
+
export function capRows(rows, max) {
|
|
4
|
+
if (rows.length <= max)
|
|
5
|
+
return { rows, truncated: false };
|
|
6
|
+
return { rows: rows.slice(0, max), truncated: true };
|
|
7
|
+
}
|
|
3
8
|
export function createExecuteParams(dbIds) {
|
|
4
9
|
return {
|
|
5
|
-
database: z.
|
|
10
|
+
database: z.string().describe(`Database ID. Available: ${dbIds.join(", ")}`),
|
|
6
11
|
statement: z.string().describe("SQL statement to execute (INSERT, UPDATE, DELETE, DDL, etc.)"),
|
|
7
12
|
params: z.array(z.string()).optional().describe("Query parameters"),
|
|
8
13
|
};
|
|
@@ -10,16 +15,19 @@ export function createExecuteParams(dbIds) {
|
|
|
10
15
|
export function executeHandler(manager) {
|
|
11
16
|
return async (params) => {
|
|
12
17
|
try {
|
|
13
|
-
const connector = await manager.
|
|
18
|
+
const { id: database, connector } = await manager.acquire(params.database);
|
|
14
19
|
const result = await connector.execute(params.statement, params.params);
|
|
20
|
+
const maxRows = manager.getConfig(database).maxRows;
|
|
21
|
+
const { rows, truncated } = capRows(result.rows, maxRows);
|
|
15
22
|
return formatSuccess({
|
|
16
|
-
rows
|
|
23
|
+
rows,
|
|
17
24
|
count: result.rowCount,
|
|
18
|
-
database
|
|
25
|
+
database,
|
|
26
|
+
...(truncated ? { truncatedAt: maxRows } : {}),
|
|
19
27
|
});
|
|
20
28
|
}
|
|
21
29
|
catch (err) {
|
|
22
|
-
return
|
|
30
|
+
return formatCaughtError(err);
|
|
23
31
|
}
|
|
24
32
|
};
|
|
25
33
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"execute.js","sourceRoot":"","sources":["../../../src/tools/write/execute.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"execute.js","sourceRoot":"","sources":["../../../src/tools/write/execute.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE3E,MAAM,UAAU,OAAO,CAAI,IAAS,EAAE,GAAW;IAC/C,IAAI,IAAI,CAAC,MAAM,IAAI,GAAG;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;IAC1D,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,KAAe;IACjD,OAAO;QACL,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,2BAA2B,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5E,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,8DAA8D,CAAC;QAC9F,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,kBAAkB,CAAC;KACpE,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,OAAyB;IACtD,OAAO,KAAK,EAAE,MAAkE,EAAE,EAAE;QAClF,IAAI,CAAC;YACH,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAC3E,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;YACxE,MAAM,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAE,CAAC,OAAO,CAAC;YACrD,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAC1D,OAAO,aAAa,CAAC;gBACnB,IAAI;gBACJ,KAAK,EAAE,MAAM,CAAC,QAAQ;gBACtB,QAAQ;gBACR,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC/C,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,iBAAiB,CAAC,GAAG,CAAC,CAAC;QAChC,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import type { ConnectorManager } from "../../connectors/manager.js";
|
|
3
2
|
import type { TransactionHandle } from "../../connectors/interface.js";
|
|
3
|
+
import type { ConnectorManager } from "../../connectors/manager.js";
|
|
4
4
|
interface TransactionEntry {
|
|
5
5
|
handle: TransactionHandle;
|
|
6
6
|
database: string;
|
|
@@ -12,15 +12,14 @@ export declare class TransactionStore {
|
|
|
12
12
|
constructor(ttlMs?: number);
|
|
13
13
|
add(handle: TransactionHandle, database: string): void;
|
|
14
14
|
get(id: string): TransactionEntry | undefined;
|
|
15
|
+
take(id: string): TransactionEntry | undefined;
|
|
15
16
|
remove(id: string): void;
|
|
16
17
|
cleanupAll(): Promise<void>;
|
|
17
18
|
private autoRollback;
|
|
18
19
|
}
|
|
19
20
|
export declare const transactionStore: TransactionStore;
|
|
20
21
|
export declare function createTransactionParams(dbIds: string[]): {
|
|
21
|
-
database: z.
|
|
22
|
-
[x: string]: string;
|
|
23
|
-
}>;
|
|
22
|
+
database: z.ZodString;
|
|
24
23
|
action: z.ZodEnum<{
|
|
25
24
|
execute: "execute";
|
|
26
25
|
commit: "commit";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { formatSuccess, formatError } from "../../utils/response.js";
|
|
3
2
|
import { getLogger } from "../../utils/logger.js";
|
|
3
|
+
import { formatCaughtError, formatError, formatSuccess } from "../../utils/response.js";
|
|
4
|
+
import { capRows } from "./execute.js";
|
|
4
5
|
const DEFAULT_TX_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
5
6
|
export class TransactionStore {
|
|
6
7
|
entries = new Map();
|
|
@@ -18,12 +19,20 @@ export class TransactionStore {
|
|
|
18
19
|
get(id) {
|
|
19
20
|
return this.entries.get(id);
|
|
20
21
|
}
|
|
21
|
-
|
|
22
|
+
// Synchronous claim: Map.delete happens with no `await` in between, so whichever
|
|
23
|
+
// caller (handler or TTL callback) reaches this first is the only one that gets
|
|
24
|
+
// to finalize the transaction. The old get()...await...delete() sequence let the
|
|
25
|
+
// TTL callback interleave during the await and finalize the same handle twice.
|
|
26
|
+
take(id) {
|
|
22
27
|
const entry = this.entries.get(id);
|
|
23
|
-
if (entry)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
if (!entry)
|
|
29
|
+
return undefined;
|
|
30
|
+
clearTimeout(entry.timer);
|
|
31
|
+
this.entries.delete(id);
|
|
32
|
+
return entry;
|
|
33
|
+
}
|
|
34
|
+
remove(id) {
|
|
35
|
+
this.take(id);
|
|
27
36
|
}
|
|
28
37
|
async cleanupAll() {
|
|
29
38
|
const logger = getLogger();
|
|
@@ -34,7 +43,7 @@ export class TransactionStore {
|
|
|
34
43
|
logger.info("All transactions cleaned up", { count: ids.length });
|
|
35
44
|
}
|
|
36
45
|
async autoRollback(id) {
|
|
37
|
-
const entry = this.
|
|
46
|
+
const entry = this.take(id);
|
|
38
47
|
if (!entry)
|
|
39
48
|
return;
|
|
40
49
|
const logger = getLogger();
|
|
@@ -51,16 +60,12 @@ export class TransactionStore {
|
|
|
51
60
|
error: String(err),
|
|
52
61
|
});
|
|
53
62
|
}
|
|
54
|
-
finally {
|
|
55
|
-
clearTimeout(entry.timer);
|
|
56
|
-
this.entries.delete(id);
|
|
57
|
-
}
|
|
58
63
|
}
|
|
59
64
|
}
|
|
60
65
|
export const transactionStore = new TransactionStore();
|
|
61
66
|
export function createTransactionParams(dbIds) {
|
|
62
67
|
return {
|
|
63
|
-
database: z.
|
|
68
|
+
database: z.string().describe(`Database ID. Available: ${dbIds.join(", ")}`),
|
|
64
69
|
action: z.enum(["begin", "commit", "rollback", "execute"]).describe("Transaction action"),
|
|
65
70
|
transactionId: z.string().optional().describe("Transaction ID (required for commit, rollback, execute)"),
|
|
66
71
|
statement: z.string().optional().describe("SQL statement (required for execute)"),
|
|
@@ -72,12 +77,12 @@ export function transactionHandler(manager) {
|
|
|
72
77
|
try {
|
|
73
78
|
switch (params.action) {
|
|
74
79
|
case "begin": {
|
|
75
|
-
const connector = await manager.
|
|
80
|
+
const { id: database, connector } = await manager.acquire(params.database);
|
|
76
81
|
const tx = await connector.beginTransaction();
|
|
77
|
-
transactionStore.add(tx,
|
|
82
|
+
transactionStore.add(tx, database);
|
|
78
83
|
return formatSuccess({
|
|
79
84
|
data: { transactionId: tx.id, message: "Transaction started" },
|
|
80
|
-
database
|
|
85
|
+
database,
|
|
81
86
|
});
|
|
82
87
|
}
|
|
83
88
|
case "execute": {
|
|
@@ -85,40 +90,44 @@ export function transactionHandler(manager) {
|
|
|
85
90
|
return formatError("transactionId is required for execute");
|
|
86
91
|
if (!params.statement)
|
|
87
92
|
return formatError("statement is required for execute");
|
|
93
|
+
// Non-finalizing lookup: a tx claimed (and finalized) by commit/rollback/TTL
|
|
94
|
+
// concurrently will simply fail at the driver level with a clear error.
|
|
88
95
|
const entry = transactionStore.get(params.transactionId);
|
|
89
96
|
if (!entry)
|
|
90
97
|
return formatError(`Transaction not found: ${params.transactionId}`, "TX_NOT_FOUND");
|
|
91
98
|
const result = await entry.handle.execute(params.statement, params.params);
|
|
99
|
+
const maxRows = manager.getConfig(entry.database).maxRows;
|
|
100
|
+
const { rows, truncated } = capRows(result.rows, maxRows);
|
|
92
101
|
return formatSuccess({
|
|
93
|
-
rows
|
|
102
|
+
rows,
|
|
94
103
|
count: result.rowCount,
|
|
95
|
-
database:
|
|
104
|
+
database: entry.database,
|
|
105
|
+
...(truncated ? { truncatedAt: maxRows } : {}),
|
|
96
106
|
});
|
|
97
107
|
}
|
|
98
108
|
case "commit": {
|
|
99
109
|
if (!params.transactionId)
|
|
100
110
|
return formatError("transactionId is required for commit");
|
|
101
|
-
|
|
111
|
+
// Claim first: if the TTL fires concurrently it will find nothing to take.
|
|
112
|
+
const entry = transactionStore.take(params.transactionId);
|
|
102
113
|
if (!entry)
|
|
103
114
|
return formatError(`Transaction not found: ${params.transactionId}`, "TX_NOT_FOUND");
|
|
104
115
|
await entry.handle.commit();
|
|
105
|
-
transactionStore.remove(params.transactionId);
|
|
106
116
|
return formatSuccess({
|
|
107
117
|
data: { message: "Transaction committed" },
|
|
108
|
-
database:
|
|
118
|
+
database: entry.database,
|
|
109
119
|
});
|
|
110
120
|
}
|
|
111
121
|
case "rollback": {
|
|
112
122
|
if (!params.transactionId)
|
|
113
123
|
return formatError("transactionId is required for rollback");
|
|
114
|
-
const entry = transactionStore.
|
|
124
|
+
const entry = transactionStore.take(params.transactionId);
|
|
115
125
|
if (!entry)
|
|
116
126
|
return formatError(`Transaction not found: ${params.transactionId}`, "TX_NOT_FOUND");
|
|
117
127
|
await entry.handle.rollback();
|
|
118
|
-
transactionStore.remove(params.transactionId);
|
|
119
128
|
return formatSuccess({
|
|
120
129
|
data: { message: "Transaction rolled back" },
|
|
121
|
-
database:
|
|
130
|
+
database: entry.database,
|
|
122
131
|
});
|
|
123
132
|
}
|
|
124
133
|
default:
|
|
@@ -126,7 +135,7 @@ export function transactionHandler(manager) {
|
|
|
126
135
|
}
|
|
127
136
|
}
|
|
128
137
|
catch (err) {
|
|
129
|
-
return
|
|
138
|
+
return formatCaughtError(err);
|
|
130
139
|
}
|
|
131
140
|
};
|
|
132
141
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transaction.js","sourceRoot":"","sources":["../../../src/tools/write/transaction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"transaction.js","sourceRoot":"","sources":["../../../src/tools/write/transaction.ts"],"names":[],"mappings":"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"}
|
package/dist/transport/http.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Server } from "node:http";
|
|
2
|
-
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
3
2
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
-
import
|
|
3
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
5
4
|
import type { AppConfig, HttpTransportConfig } from "../config/types.js";
|
|
5
|
+
import type { ConnectorManager } from "../connectors/manager.js";
|
|
6
6
|
interface SessionEntry {
|
|
7
7
|
transport: StreamableHTTPServerTransport;
|
|
8
8
|
server: McpServer;
|
package/dist/transport/http.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { randomUUID, timingSafeEqual } from "node:crypto";
|
|
2
|
-
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
3
2
|
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
|
|
3
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
4
4
|
import { createConnectorManager, createMcpServerInstance } from "../server.js";
|
|
5
5
|
import { getLogger } from "../utils/logger.js";
|
|
6
|
+
const LOOPBACK_HOSTS = new Set(["127.0.0.1", "::1", "localhost"]);
|
|
7
|
+
function isAuthorized(req, expectedBuf) {
|
|
8
|
+
const authHeader = req.headers.authorization ?? "";
|
|
9
|
+
const actualBuf = Buffer.from(authHeader);
|
|
10
|
+
return actualBuf.length === expectedBuf.length && timingSafeEqual(actualBuf, expectedBuf);
|
|
11
|
+
}
|
|
6
12
|
export async function startHttpTransport(config, transportConfig) {
|
|
7
13
|
const logger = getLogger();
|
|
8
14
|
const manager = createConnectorManager(config);
|
|
@@ -10,13 +16,17 @@ export async function startHttpTransport(config, transportConfig) {
|
|
|
10
16
|
const sessions = new Map();
|
|
11
17
|
const { host, port } = transportConfig;
|
|
12
18
|
const app = createMcpExpressApp({ host });
|
|
19
|
+
if (!LOOPBACK_HOSTS.has(host) && !transportConfig.auth) {
|
|
20
|
+
logger.warn(`HTTP transport is bound to non-loopback host "${host}" with no auth configured — ` +
|
|
21
|
+
"the server is reachable from the network without authentication. " +
|
|
22
|
+
"Set transport.auth (bearer token) or bind to a loopback host.");
|
|
23
|
+
}
|
|
24
|
+
let authBuf;
|
|
13
25
|
if (transportConfig.auth) {
|
|
14
|
-
|
|
15
|
-
const expectedBuf =
|
|
26
|
+
authBuf = Buffer.from(`Bearer ${transportConfig.auth.token}`);
|
|
27
|
+
const expectedBuf = authBuf;
|
|
16
28
|
app.use("/mcp", (req, res, next) => {
|
|
17
|
-
|
|
18
|
-
const actualBuf = Buffer.from(authHeader);
|
|
19
|
-
if (actualBuf.length !== expectedBuf.length || !timingSafeEqual(actualBuf, expectedBuf)) {
|
|
29
|
+
if (!isAuthorized(req, expectedBuf)) {
|
|
20
30
|
res.status(401).json({ error: "Unauthorized" });
|
|
21
31
|
return;
|
|
22
32
|
}
|
|
@@ -43,7 +53,7 @@ export async function startHttpTransport(config, transportConfig) {
|
|
|
43
53
|
}, 60_000);
|
|
44
54
|
cleanupInterval.unref();
|
|
45
55
|
}
|
|
46
|
-
setupHealthEndpoint(app, manager, sessions);
|
|
56
|
+
setupHealthEndpoint(app, manager, sessions, authBuf);
|
|
47
57
|
const httpServer = await new Promise((resolve) => {
|
|
48
58
|
const server = app.listen(port, host, () => {
|
|
49
59
|
resolve(server);
|
|
@@ -98,8 +108,12 @@ function setupStatefulRoutes(app, manager, config, sessions) {
|
|
|
98
108
|
await transport.handleRequest(req, res, req.body);
|
|
99
109
|
});
|
|
100
110
|
}
|
|
101
|
-
function setupHealthEndpoint(app, manager, sessions) {
|
|
102
|
-
app.get("/health", (
|
|
111
|
+
function setupHealthEndpoint(app, manager, sessions, authBuf) {
|
|
112
|
+
app.get("/health", (req, res) => {
|
|
113
|
+
if (authBuf && !isAuthorized(req, authBuf)) {
|
|
114
|
+
res.json({ status: "ok" });
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
103
117
|
res.json({
|
|
104
118
|
status: "ok",
|
|
105
119
|
activeSessions: sessions.size,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/transport/http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/transport/http.ts"],"names":[],"mappings":"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"}
|
package/dist/utils/logger.js
CHANGED
package/dist/utils/logger.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,GAA6B;IAC3C,KAAK,EAAE,CAAC;IACR,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;CACT,CAAC;AASF,MAAM,OAAO,MAAM;IACT,KAAK,CAAS;IACd,OAAO,CAA0B;IAEzC,YAAY,QAAkB,MAAM,EAAE,UAAmC,EAAE;QACzE,IAAI,CAAC,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;QAC/B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,OAAgC;QACpC,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,OAAO,EAAE,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;QACnE,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,IAA8B;QACnD,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IACnC,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,IAA8B;QAClD,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAClC,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,IAA8B;QAClD,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,IAA8B;QACnD,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IACnC,CAAC;IAEO,GAAG,CAAC,KAAe,EAAE,OAAe,EAAE,IAA8B;QAC1E,IAAI,UAAU,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,KAAK;YAAE,OAAO;QAE3C,MAAM,KAAK,GAAa;YACtB,KAAK;YACL,OAAO;YACP,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,GAAG,IAAI,CAAC,OAAO;YACf,GAAG,IAAI;SACR,CAAC;QAEF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,GAA6B;IAC3C,KAAK,EAAE,CAAC;IACR,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;CACT,CAAC;AASF,MAAM,OAAO,MAAM;IACT,KAAK,CAAS;IACd,OAAO,CAA0B;IAEzC,YAAY,QAAkB,MAAM,EAAE,UAAmC,EAAE;QACzE,IAAI,CAAC,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;QAC/B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,OAAgC;QACpC,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,OAAO,EAAE,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;QACnE,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,IAA8B;QACnD,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IACnC,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,IAA8B;QAClD,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAClC,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,IAA8B;QAClD,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,IAA8B;QACnD,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IACnC,CAAC;IAEO,GAAG,CAAC,KAAe,EAAE,OAAe,EAAE,IAA8B;QAC1E,IAAI,UAAU,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,KAAK;YAAE,OAAO;QAE3C,MAAM,KAAK,GAAa;YACtB,KAAK;YACL,OAAO;YACP,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,GAAG,IAAI,CAAC,OAAO;YACf,GAAG,IAAI;SACR,CAAC;QAEF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACrD,CAAC;CACF;AAED,IAAI,YAAY,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC;AAEtC,MAAM,UAAU,UAAU,CAAC,KAAe;IACxC,YAAY,GAAG,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC;IACjC,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,OAAO,YAAY,CAAC;AACtB,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Models often pass an inexact database id ("api" for "api_go", "API-GO"…).
|
|
2
|
+
// Resolve leniently instead of hard-failing: exact -> case-insensitive ->
|
|
3
|
+
// normalized (dashes/underscores stripped) -> unique abbreviation/superset match.
|
|
4
|
+
// Both fuzzy directions — input contains an id ("analytics_db" -> "analytics") and id
|
|
5
|
+
// contains input ("api" -> "api_go") — only match at token boundaries and require the
|
|
6
|
+
// matched id to be >= 3 chars, so a short input like "ch"/"pg" can't silently resolve to
|
|
7
|
+
// an unrelated longer id that merely contains it as a substring ("search", "gpg", …).
|
|
8
|
+
export class DbResolutionError extends Error {
|
|
9
|
+
code;
|
|
10
|
+
constructor(message, code) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.code = code;
|
|
13
|
+
this.name = "DbResolutionError";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function isTokenAlignedSubstring(tokens, target) {
|
|
17
|
+
for (let start = 0; start < tokens.length; start++) {
|
|
18
|
+
let acc = "";
|
|
19
|
+
for (let end = start; end < tokens.length; end++) {
|
|
20
|
+
acc += tokens[end];
|
|
21
|
+
if (acc === target)
|
|
22
|
+
return true;
|
|
23
|
+
if (acc.length > target.length)
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
export function resolveDbId(dbIds, input) {
|
|
30
|
+
if (dbIds.includes(input))
|
|
31
|
+
return input;
|
|
32
|
+
const norm = (s) => s.toLowerCase().replace(/[-_]/g, "");
|
|
33
|
+
const tokenize = (s) => s
|
|
34
|
+
.toLowerCase()
|
|
35
|
+
.split(/[-_\s]+/)
|
|
36
|
+
.filter(Boolean);
|
|
37
|
+
const lower = input.toLowerCase();
|
|
38
|
+
const nInput = norm(input);
|
|
39
|
+
const inputTokens = tokenize(input);
|
|
40
|
+
for (const match of [
|
|
41
|
+
dbIds.filter((id) => id.toLowerCase() === lower),
|
|
42
|
+
dbIds.filter((id) => norm(id) === nInput),
|
|
43
|
+
nInput.length >= 3
|
|
44
|
+
? dbIds.filter((id) => {
|
|
45
|
+
if (norm(id).length < 3)
|
|
46
|
+
return false;
|
|
47
|
+
// id contains input (abbreviation) OR input contains id (superset) — both only at
|
|
48
|
+
// token boundaries, never an arbitrary mid-token substring.
|
|
49
|
+
return isTokenAlignedSubstring(tokenize(id), nInput) || isTokenAlignedSubstring(inputTokens, norm(id));
|
|
50
|
+
})
|
|
51
|
+
: [],
|
|
52
|
+
]) {
|
|
53
|
+
if (match.length === 1)
|
|
54
|
+
return match[0];
|
|
55
|
+
if (match.length > 1) {
|
|
56
|
+
throw new DbResolutionError(`Ambiguous database "${input}": matches ${match.join(", ")}. Valid IDs: ${dbIds.join(", ")}`, "DB_AMBIGUOUS");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
throw new DbResolutionError(`Unknown database: "${input}". Valid IDs: ${dbIds.join(", ")}`, "DB_NOT_FOUND");
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=resolve-db.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-db.js","sourceRoot":"","sources":["../../src/utils/resolve-db.ts"],"names":[],"mappings":"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"}
|
package/dist/utils/response.d.ts
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
export interface SuccessResponse {
|
|
2
2
|
success: true;
|
|
3
3
|
rows?: Record<string, unknown>[];
|
|
4
|
+
rowsTable?: string;
|
|
4
5
|
count?: number;
|
|
5
6
|
database?: string;
|
|
6
7
|
data?: unknown;
|
|
8
|
+
truncated?: boolean;
|
|
9
|
+
returnedRows?: number;
|
|
10
|
+
truncatedAt?: number;
|
|
7
11
|
}
|
|
12
|
+
export declare function setRowFormat(format: "json" | "table"): void;
|
|
8
13
|
export interface ErrorResponse {
|
|
9
14
|
success: false;
|
|
10
15
|
error: string;
|
|
@@ -23,6 +28,13 @@ export declare function formatError(error: string, code?: string): {
|
|
|
23
28
|
}[];
|
|
24
29
|
isError: true;
|
|
25
30
|
};
|
|
31
|
+
export declare function formatCaughtError(err: unknown): {
|
|
32
|
+
content: {
|
|
33
|
+
type: "text";
|
|
34
|
+
text: string;
|
|
35
|
+
}[];
|
|
36
|
+
isError: true;
|
|
37
|
+
};
|
|
26
38
|
export declare function formatRows(rows: Record<string, unknown>[], database: string): {
|
|
27
39
|
content: {
|
|
28
40
|
type: "text";
|
package/dist/utils/response.js
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
|
+
import { DbResolutionError } from "./resolve-db.js";
|
|
2
|
+
const MAX_CELL_CHARS = 10_000;
|
|
3
|
+
const MAX_PAYLOAD_CHARS = 1_000_000;
|
|
4
|
+
const BINARY_PREVIEW_BYTES = 16;
|
|
5
|
+
// Opt-in pipe-table rendering for row payloads, set once at server startup from config.
|
|
6
|
+
let rowFormat = "json";
|
|
7
|
+
export function setRowFormat(format) {
|
|
8
|
+
rowFormat = format;
|
|
9
|
+
}
|
|
1
10
|
export function formatSuccess(data) {
|
|
11
|
+
// Rows can carry attacker-influenced cells (e.g. a write with RETURNING binary/text), so apply
|
|
12
|
+
// the same per-cell and total-payload caps the read path uses. `data`/other fields are left as-is.
|
|
13
|
+
const payload = data.rows
|
|
14
|
+
? { success: true, ...data, rows: data.rows.map(sanitizeRow) }
|
|
15
|
+
: { success: true, ...data };
|
|
2
16
|
return {
|
|
3
17
|
content: [
|
|
4
18
|
{
|
|
5
19
|
type: "text",
|
|
6
|
-
text:
|
|
20
|
+
text: serializeWithinCap(payload),
|
|
7
21
|
},
|
|
8
22
|
],
|
|
9
23
|
};
|
|
@@ -13,13 +27,126 @@ export function formatError(error, code) {
|
|
|
13
27
|
content: [
|
|
14
28
|
{
|
|
15
29
|
type: "text",
|
|
16
|
-
text: JSON.stringify({ success: false, error, code }
|
|
30
|
+
text: JSON.stringify({ success: false, error, code }),
|
|
17
31
|
},
|
|
18
32
|
],
|
|
19
33
|
isError: true,
|
|
20
34
|
};
|
|
21
35
|
}
|
|
36
|
+
// Surfaces a caught error to the client, carrying DbResolutionError's DB_NOT_FOUND/DB_AMBIGUOUS
|
|
37
|
+
// code through to the response so clients can distinguish it from a generic failure.
|
|
38
|
+
export function formatCaughtError(err) {
|
|
39
|
+
if (err instanceof DbResolutionError) {
|
|
40
|
+
return formatError(err.message, err.code);
|
|
41
|
+
}
|
|
42
|
+
return formatError(String(err));
|
|
43
|
+
}
|
|
44
|
+
function sanitizeCell(value) {
|
|
45
|
+
if (Buffer.isBuffer(value) || value instanceof Uint8Array) {
|
|
46
|
+
const hexPreview = Buffer.from(value).subarray(0, BINARY_PREVIEW_BYTES).toString("hex");
|
|
47
|
+
return `<binary ${value.length} bytes: ${hexPreview}...>`;
|
|
48
|
+
}
|
|
49
|
+
if (typeof value === "string" && value.length > MAX_CELL_CHARS) {
|
|
50
|
+
return `${value.slice(0, MAX_CELL_CHARS)}... [truncated, ${value.length} chars total]`;
|
|
51
|
+
}
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
function sanitizeRow(row) {
|
|
55
|
+
const sanitized = {};
|
|
56
|
+
for (const [key, value] of Object.entries(row)) {
|
|
57
|
+
sanitized[key] = sanitizeCell(value);
|
|
58
|
+
}
|
|
59
|
+
return sanitized;
|
|
60
|
+
}
|
|
61
|
+
// Escapes chars that would break the one-cell-per-pipe/one-row-per-line invariant. Order matters:
|
|
62
|
+
// backslash must go first so it doesn't double-escape the backslashes introduced by later steps.
|
|
63
|
+
function escapeCell(text) {
|
|
64
|
+
return text.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\n/g, "\\n").replace(/\r/g, "\\r");
|
|
65
|
+
}
|
|
66
|
+
// Renders a single cell value per the table convention, ahead of escaping. Runs after sanitizeCell,
|
|
67
|
+
// so Buffers/oversized strings already arrive as plain placeholder strings.
|
|
68
|
+
function cellRawText(value) {
|
|
69
|
+
if (value === null || value === undefined)
|
|
70
|
+
return "NULL";
|
|
71
|
+
if (typeof value === "string") {
|
|
72
|
+
if (value === "")
|
|
73
|
+
return "";
|
|
74
|
+
if (value === "NULL")
|
|
75
|
+
return '"NULL"';
|
|
76
|
+
return value;
|
|
77
|
+
}
|
|
78
|
+
if (value instanceof Date)
|
|
79
|
+
return value.toISOString();
|
|
80
|
+
// JSON mode coerces NaN/Infinity to null via JSON.stringify; mirror that so toggling
|
|
81
|
+
// rowFormat never changes what a cell means.
|
|
82
|
+
if (typeof value === "number" && !Number.isFinite(value))
|
|
83
|
+
return "NULL";
|
|
84
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint")
|
|
85
|
+
return String(value);
|
|
86
|
+
return JSON.stringify(value);
|
|
87
|
+
}
|
|
88
|
+
function renderCell(value) {
|
|
89
|
+
const escaped = escapeCell(cellRawText(value));
|
|
90
|
+
// Quotes are escaped only in genuine string cells, so a string that happens to look like
|
|
91
|
+
// the quoted-NULL marker or a serialized object stays distinguishable from the real thing.
|
|
92
|
+
// Runs after escapeCell: the backslashes it introduces must not be doubled by it.
|
|
93
|
+
if (typeof value === "string" && value !== "" && value !== "NULL") {
|
|
94
|
+
return escaped.replace(/"/g, '\\"');
|
|
95
|
+
}
|
|
96
|
+
return escaped;
|
|
97
|
+
}
|
|
98
|
+
// Header = union of row keys in first-appearance order (rows from a single query are normally
|
|
99
|
+
// uniform; the union is just a safety net for heterogeneous shapes).
|
|
100
|
+
function buildTable(rows) {
|
|
101
|
+
const columns = [];
|
|
102
|
+
const seen = new Set();
|
|
103
|
+
for (const row of rows) {
|
|
104
|
+
for (const key of Object.keys(row)) {
|
|
105
|
+
if (!seen.has(key)) {
|
|
106
|
+
seen.add(key);
|
|
107
|
+
columns.push(key);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const lines = [columns.map(escapeCell).join("|")];
|
|
112
|
+
for (const row of rows) {
|
|
113
|
+
lines.push(columns.map((col) => renderCell(row[col])).join("|"));
|
|
114
|
+
}
|
|
115
|
+
return lines.join("\n");
|
|
116
|
+
}
|
|
117
|
+
// Builds the final payload for a given row slice: table mode swaps `rows` for a rendered
|
|
118
|
+
// `rowsTable` string (skipped for an empty slice, where there's nothing to tabulate).
|
|
119
|
+
function buildPayload(payload, rows, extra) {
|
|
120
|
+
if (rowFormat === "table" && rows.length > 0) {
|
|
121
|
+
const { rows: _omit, ...rest } = payload;
|
|
122
|
+
return { ...rest, rowsTable: buildTable(rows), ...extra };
|
|
123
|
+
}
|
|
124
|
+
return { ...payload, rows, ...extra };
|
|
125
|
+
}
|
|
126
|
+
// Serializes the response, and if it blows the payload cap and carries rows, halves the row count
|
|
127
|
+
// until it fits (rows are typically similar in size, so this converges in a handful of iterations)
|
|
128
|
+
// while flagging the truncation. Callers pass rows already sanitized per-cell.
|
|
129
|
+
function serializeWithinCap(payload) {
|
|
130
|
+
const rows = payload.rows;
|
|
131
|
+
let text = JSON.stringify(rows ? buildPayload(payload, rows) : payload);
|
|
132
|
+
if (text.length <= MAX_PAYLOAD_CHARS || !rows)
|
|
133
|
+
return text;
|
|
134
|
+
let n = rows.length;
|
|
135
|
+
while (text.length > MAX_PAYLOAD_CHARS && n > 0) {
|
|
136
|
+
n = Math.floor(n / 2);
|
|
137
|
+
text = JSON.stringify(buildPayload(payload, rows.slice(0, n), { truncated: true, returnedRows: n }));
|
|
138
|
+
}
|
|
139
|
+
return text;
|
|
140
|
+
}
|
|
22
141
|
export function formatRows(rows, database) {
|
|
23
|
-
|
|
142
|
+
const sanitized = rows.map(sanitizeRow);
|
|
143
|
+
return {
|
|
144
|
+
content: [
|
|
145
|
+
{
|
|
146
|
+
type: "text",
|
|
147
|
+
text: serializeWithinCap({ success: true, rows: sanitized, count: sanitized.length, database }),
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
};
|
|
24
151
|
}
|
|
25
152
|
//# sourceMappingURL=response.js.map
|