@eamonboyle/mssql-mcp 1.5.0 → 2.0.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 +379 -361
- package/dist/apps/queryResultsApp.d.ts +18 -0
- package/dist/apps/queryResultsApp.d.ts.map +1 -0
- package/dist/apps/queryResultsApp.js +272 -0
- package/dist/apps/queryResultsApp.js.map +1 -0
- package/dist/checkNodeVersion.d.ts +2 -0
- package/dist/checkNodeVersion.d.ts.map +1 -0
- package/dist/checkNodeVersion.js +9 -0
- package/dist/checkNodeVersion.js.map +1 -0
- package/dist/config.d.ts +58 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +202 -33
- package/dist/config.js.map +1 -1
- package/dist/confirmationMessage.d.ts +2 -0
- package/dist/confirmationMessage.d.ts.map +1 -0
- package/dist/confirmationMessage.js +105 -0
- package/dist/confirmationMessage.js.map +1 -0
- package/dist/db.d.ts +24 -0
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +152 -36
- package/dist/db.js.map +1 -1
- package/dist/filteredRead.js +0 -0
- package/dist/httpServer.d.ts +25 -0
- package/dist/httpServer.d.ts.map +1 -0
- package/dist/httpServer.js +128 -0
- package/dist/httpServer.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +47 -366
- package/dist/index.js.map +1 -1
- package/dist/mcpResults.d.ts +1 -1
- package/dist/mcpResults.d.ts.map +1 -1
- package/dist/mcpResults.js +2 -2
- package/dist/mcpResults.js.map +1 -1
- package/dist/nodeVersion.d.ts +14 -0
- package/dist/nodeVersion.d.ts.map +1 -0
- package/dist/nodeVersion.js +23 -0
- package/dist/nodeVersion.js.map +1 -0
- package/dist/packageInfo.d.ts +3 -0
- package/dist/packageInfo.d.ts.map +1 -0
- package/dist/packageInfo.js +5 -0
- package/dist/packageInfo.js.map +1 -0
- package/dist/promptRegistry.d.ts +1 -1
- package/dist/promptRegistry.d.ts.map +1 -1
- package/dist/promptRegistry.js +1 -1
- package/dist/promptRegistry.js.map +1 -1
- package/dist/resourceRegistry.d.ts +9 -1
- package/dist/resourceRegistry.d.ts.map +1 -1
- package/dist/resourceRegistry.js +9 -2
- package/dist/resourceRegistry.js.map +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +16 -20
- package/dist/schema.js.map +1 -1
- package/dist/server.d.ts +5 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +320 -0
- package/dist/server.js.map +1 -0
- package/dist/serverState.js +2 -4
- package/dist/serverState.js.map +1 -1
- package/dist/sql.js +0 -0
- package/dist/sqlErrors.d.ts +10 -0
- package/dist/sqlErrors.d.ts.map +1 -0
- package/dist/sqlErrors.js +32 -0
- package/dist/sqlErrors.js.map +1 -0
- package/dist/toolRegistry.d.ts +1 -1
- package/dist/toolRegistry.d.ts.map +1 -1
- package/dist/toolRegistry.js +1 -1
- package/dist/toolRegistry.js.map +1 -1
- package/dist/tools/AnalyzeTableTool.js +2 -4
- package/dist/tools/AnalyzeTableTool.js.map +1 -1
- package/dist/tools/CreateIndexTool.js +2 -4
- package/dist/tools/CreateIndexTool.js.map +1 -1
- package/dist/tools/CreateTableTool.js +2 -4
- package/dist/tools/CreateTableTool.js.map +1 -1
- package/dist/tools/DeleteDataTool.js +2 -4
- package/dist/tools/DeleteDataTool.js.map +1 -1
- package/dist/tools/DescribeDependenciesTool.js +2 -4
- package/dist/tools/DescribeDependenciesTool.js.map +1 -1
- package/dist/tools/DescribeObjectTool.js +2 -4
- package/dist/tools/DescribeObjectTool.js.map +1 -1
- package/dist/tools/DescribeRelationshipsTool.js +2 -4
- package/dist/tools/DescribeRelationshipsTool.js.map +1 -1
- package/dist/tools/DescribeTableTool.js +2 -4
- package/dist/tools/DescribeTableTool.js.map +1 -1
- package/dist/tools/DropTableTool.js +2 -4
- package/dist/tools/DropTableTool.js.map +1 -1
- package/dist/tools/ExplainQueryTool.d.ts.map +1 -1
- package/dist/tools/ExplainQueryTool.js +4 -6
- package/dist/tools/ExplainQueryTool.js.map +1 -1
- package/dist/tools/InsertDataTool.js +2 -4
- package/dist/tools/InsertDataTool.js.map +1 -1
- package/dist/tools/ListDatabasesTool.d.ts.map +1 -1
- package/dist/tools/ListDatabasesTool.js +2 -4
- package/dist/tools/ListDatabasesTool.js.map +1 -1
- package/dist/tools/ListForeignKeysTool.js +2 -4
- package/dist/tools/ListForeignKeysTool.js.map +1 -1
- package/dist/tools/ListLargestTablesTool.js +2 -4
- package/dist/tools/ListLargestTablesTool.js.map +1 -1
- package/dist/tools/ListObjectsTool.js +2 -4
- package/dist/tools/ListObjectsTool.js.map +1 -1
- package/dist/tools/ListTableTool.js +2 -4
- package/dist/tools/ListTableTool.js.map +1 -1
- package/dist/tools/PreviewDeleteTool.js +2 -4
- package/dist/tools/PreviewDeleteTool.js.map +1 -1
- package/dist/tools/PreviewUpdateTool.js +2 -4
- package/dist/tools/PreviewUpdateTool.js.map +1 -1
- package/dist/tools/ReadDataTool.d.ts +15 -12
- package/dist/tools/ReadDataTool.d.ts.map +1 -1
- package/dist/tools/ReadDataTool.js +86 -64
- package/dist/tools/ReadDataTool.js.map +1 -1
- package/dist/tools/SearchDataTool.js +2 -4
- package/dist/tools/SearchDataTool.js.map +1 -1
- package/dist/tools/SummarizeSchemaTool.js +2 -4
- package/dist/tools/SummarizeSchemaTool.js.map +1 -1
- package/dist/tools/UpdateDataTool.js +2 -4
- package/dist/tools/UpdateDataTool.js.map +1 -1
- package/dist/validation.d.ts +26 -3
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +237 -85
- package/dist/validation.js.map +1 -1
- package/dist/writePreview.js +0 -0
- package/dist/writePreviewGrant.js +0 -0
- package/dist/writePreviewGrantStore.js +0 -0
- package/dist/writeSafety.js +0 -0
- package/package.json +27 -18
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/server";
|
|
2
|
+
/**
|
|
3
|
+
* MCP Apps (io.modelcontextprotocol/ui) view that renders read_data and
|
|
4
|
+
* search_data results as an interactive grid in hosts that support it
|
|
5
|
+
* (Claude, VS Code, Cursor, …). Hosts without MCP Apps ignore the tool
|
|
6
|
+
* metadata and keep using the text/structured result.
|
|
7
|
+
*/
|
|
8
|
+
export declare const QUERY_RESULTS_APP_URI = "ui://mssql/query-results.html";
|
|
9
|
+
export declare function buildQueryResultsHtml(): string;
|
|
10
|
+
export declare function registerQueryResultsApp(server: McpServer): void;
|
|
11
|
+
/** Tool `_meta` linking a tool to the query-results view (both key styles). */
|
|
12
|
+
export declare const queryResultsToolMeta: {
|
|
13
|
+
ui: {
|
|
14
|
+
resourceUri: string;
|
|
15
|
+
};
|
|
16
|
+
"ui/resourceUri": string;
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=queryResultsApp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queryResultsApp.d.ts","sourceRoot":"","sources":["../../src/apps/queryResultsApp.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAE9D;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,kCAAkC,CAAC;AAqOrE,wBAAgB,qBAAqB,IAAI,MAAM,CAmB9C;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAuB/D;AAED,+EAA+E;AAC/E,eAAO,MAAM,oBAAoB;;;;;CAGhC,CAAC"}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { RESOURCE_MIME_TYPE, registerAppResource, } from "@modelcontextprotocol/ext-apps/server";
|
|
4
|
+
/**
|
|
5
|
+
* MCP Apps (io.modelcontextprotocol/ui) view that renders read_data and
|
|
6
|
+
* search_data results as an interactive grid in hosts that support it
|
|
7
|
+
* (Claude, VS Code, Cursor, …). Hosts without MCP Apps ignore the tool
|
|
8
|
+
* metadata and keep using the text/structured result.
|
|
9
|
+
*/
|
|
10
|
+
export const QUERY_RESULTS_APP_URI = "ui://mssql/query-results.html";
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
/**
|
|
13
|
+
* Loads the self-contained ext-apps View runtime and rewrites its trailing
|
|
14
|
+
* `export{…}` into a local binding so it can be inlined into a single
|
|
15
|
+
* module script (hosts render the view from one HTML document).
|
|
16
|
+
*/
|
|
17
|
+
function loadAppRuntime() {
|
|
18
|
+
const bundle = readFileSync(require.resolve("@modelcontextprotocol/ext-apps/app-with-deps"), "utf8");
|
|
19
|
+
const exportStart = bundle.lastIndexOf("export{");
|
|
20
|
+
if (exportStart === -1) {
|
|
21
|
+
throw new Error("Unexpected ext-apps bundle format: missing export list.");
|
|
22
|
+
}
|
|
23
|
+
const exportEnd = bundle.indexOf("}", exportStart);
|
|
24
|
+
const entries = bundle
|
|
25
|
+
.slice(exportStart + "export{".length, exportEnd)
|
|
26
|
+
.split(",")
|
|
27
|
+
.map((entry) => {
|
|
28
|
+
const [local, exported = local] = entry.trim().split(/\s+as\s+/);
|
|
29
|
+
return `${JSON.stringify(exported)}:${local}`;
|
|
30
|
+
});
|
|
31
|
+
return `${bundle.slice(0, exportStart)}const ExtApps={${entries.join(",")}};${bundle.slice(exportEnd + 1)}`;
|
|
32
|
+
}
|
|
33
|
+
const VIEW_STYLES = `
|
|
34
|
+
:root { color-scheme: light dark; }
|
|
35
|
+
* { box-sizing: border-box; }
|
|
36
|
+
body {
|
|
37
|
+
margin: 0;
|
|
38
|
+
font-family: var(--font-sans, system-ui, sans-serif);
|
|
39
|
+
font-size: var(--font-text-sm-size, 13px);
|
|
40
|
+
color: var(--color-text-primary, CanvasText);
|
|
41
|
+
background: var(--color-background-primary, Canvas);
|
|
42
|
+
}
|
|
43
|
+
header {
|
|
44
|
+
display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
|
|
45
|
+
padding: 8px 10px;
|
|
46
|
+
border-bottom: 1px solid var(--color-border-primary, #8884);
|
|
47
|
+
}
|
|
48
|
+
header .summary { flex: 1; color: var(--color-text-secondary, GrayText); }
|
|
49
|
+
.badge {
|
|
50
|
+
padding: 1px 6px; border-radius: 999px; font-size: 11px;
|
|
51
|
+
background: var(--color-background-warning, #f5a62333);
|
|
52
|
+
color: var(--color-text-warning, inherit);
|
|
53
|
+
}
|
|
54
|
+
input[type="search"] {
|
|
55
|
+
font: inherit; padding: 3px 8px; min-width: 160px;
|
|
56
|
+
color: inherit; background: transparent;
|
|
57
|
+
border: 1px solid var(--color-border-primary, #8886);
|
|
58
|
+
border-radius: var(--border-radius-sm, 4px);
|
|
59
|
+
}
|
|
60
|
+
.grid { overflow: auto; max-height: 480px; }
|
|
61
|
+
table { border-collapse: collapse; width: 100%; font-variant-numeric: tabular-nums; }
|
|
62
|
+
th, td {
|
|
63
|
+
padding: 4px 10px; text-align: left; white-space: nowrap;
|
|
64
|
+
border-bottom: 1px solid var(--color-border-secondary, #8882);
|
|
65
|
+
max-width: 360px; overflow: hidden; text-overflow: ellipsis;
|
|
66
|
+
}
|
|
67
|
+
th {
|
|
68
|
+
position: sticky; top: 0; cursor: pointer; user-select: none;
|
|
69
|
+
background: var(--color-background-secondary, Canvas);
|
|
70
|
+
font-weight: 600;
|
|
71
|
+
}
|
|
72
|
+
th[aria-sort="ascending"]::after { content: " ▲"; }
|
|
73
|
+
th[aria-sort="descending"]::after { content: " ▼"; }
|
|
74
|
+
td.null { color: var(--color-text-tertiary, GrayText); font-style: italic; }
|
|
75
|
+
td.num { text-align: right; }
|
|
76
|
+
.empty, .error { padding: 16px 10px; color: var(--color-text-secondary, GrayText); }
|
|
77
|
+
.error { color: var(--color-text-danger, #d33); }
|
|
78
|
+
`;
|
|
79
|
+
const VIEW_SCRIPT = `
|
|
80
|
+
const { App, applyDocumentTheme, applyHostStyleVariables, applyHostFonts } = ExtApps;
|
|
81
|
+
const MAX_RENDERED_ROWS = 1000;
|
|
82
|
+
const root = document.getElementById("root");
|
|
83
|
+
let rows = [];
|
|
84
|
+
let columns = [];
|
|
85
|
+
let meta = {};
|
|
86
|
+
let sort = { column: null, direction: 1 };
|
|
87
|
+
let filter = "";
|
|
88
|
+
|
|
89
|
+
function extractRows(payload) {
|
|
90
|
+
const data = payload && payload.data;
|
|
91
|
+
if (Array.isArray(data)) return data;
|
|
92
|
+
if (data && Array.isArray(data.rows)) return data.rows;
|
|
93
|
+
if (data && Array.isArray(data.data)) return data.data;
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function formatCell(value) {
|
|
98
|
+
if (value === null || value === undefined) return { text: "NULL", cls: "null" };
|
|
99
|
+
if (typeof value === "number" || typeof value === "bigint") return { text: String(value), cls: "num" };
|
|
100
|
+
if (typeof value === "object") return { text: JSON.stringify(value), cls: "" };
|
|
101
|
+
return { text: String(value), cls: "" };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function el(tag, attrs, children) {
|
|
105
|
+
const node = document.createElement(tag);
|
|
106
|
+
for (const [key, value] of Object.entries(attrs || {})) {
|
|
107
|
+
if (key === "text") node.textContent = value;
|
|
108
|
+
else if (key.startsWith("on")) node.addEventListener(key.slice(2), value);
|
|
109
|
+
else node.setAttribute(key, value);
|
|
110
|
+
}
|
|
111
|
+
for (const child of children || []) node.append(child);
|
|
112
|
+
return node;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function visibleRows() {
|
|
116
|
+
let result = rows;
|
|
117
|
+
if (filter) {
|
|
118
|
+
const needle = filter.toLowerCase();
|
|
119
|
+
result = result.filter((row) =>
|
|
120
|
+
columns.some((column) => String(row[column] ?? "").toLowerCase().includes(needle))
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
if (sort.column !== null) {
|
|
124
|
+
const column = sort.column;
|
|
125
|
+
result = [...result].sort((a, b) => {
|
|
126
|
+
const left = a[column];
|
|
127
|
+
const right = b[column];
|
|
128
|
+
if (left === right) return 0;
|
|
129
|
+
if (left === null || left === undefined) return 1;
|
|
130
|
+
if (right === null || right === undefined) return -1;
|
|
131
|
+
return (left < right ? -1 : 1) * sort.direction;
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function render() {
|
|
138
|
+
root.replaceChildren();
|
|
139
|
+
if (meta.error) {
|
|
140
|
+
root.append(el("div", { class: "error", text: meta.error }));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const shown = visibleRows();
|
|
145
|
+
const rendered = shown.slice(0, MAX_RENDERED_ROWS);
|
|
146
|
+
const summaryParts = [rows.length + " row" + (rows.length === 1 ? "" : "s")];
|
|
147
|
+
if (filter) summaryParts.push(shown.length + " matching");
|
|
148
|
+
if (rendered.length < shown.length) summaryParts.push("showing first " + rendered.length);
|
|
149
|
+
|
|
150
|
+
const header = el("header", {}, [
|
|
151
|
+
el("span", { class: "summary", text: summaryParts.join(" · ") }),
|
|
152
|
+
]);
|
|
153
|
+
if (meta.truncated) {
|
|
154
|
+
header.append(el("span", { class: "badge", text: "Truncated at MAX_ROWS", title: "More rows exist on the server" }));
|
|
155
|
+
}
|
|
156
|
+
header.append(
|
|
157
|
+
el("input", {
|
|
158
|
+
type: "search",
|
|
159
|
+
placeholder: "Filter rows",
|
|
160
|
+
value: filter,
|
|
161
|
+
"aria-label": "Filter rows",
|
|
162
|
+
oninput: (event) => {
|
|
163
|
+
filter = event.target.value;
|
|
164
|
+
render();
|
|
165
|
+
const input = root.querySelector("input[type=search]");
|
|
166
|
+
input.focus();
|
|
167
|
+
input.setSelectionRange(filter.length, filter.length);
|
|
168
|
+
},
|
|
169
|
+
})
|
|
170
|
+
);
|
|
171
|
+
root.append(header);
|
|
172
|
+
|
|
173
|
+
if (rows.length === 0) {
|
|
174
|
+
root.append(el("div", { class: "empty", text: meta.message || "No rows returned." }));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const headRow = el("tr", {}, columns.map((column) => {
|
|
179
|
+
const ariaSort = sort.column === column ? (sort.direction === 1 ? "ascending" : "descending") : "none";
|
|
180
|
+
return el("th", {
|
|
181
|
+
text: column,
|
|
182
|
+
scope: "col",
|
|
183
|
+
"aria-sort": ariaSort,
|
|
184
|
+
onclick: () => {
|
|
185
|
+
sort = sort.column === column ? { column, direction: -sort.direction } : { column, direction: 1 };
|
|
186
|
+
render();
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
}));
|
|
190
|
+
const body = el("tbody", {}, rendered.map((row) =>
|
|
191
|
+
el("tr", {}, columns.map((column) => {
|
|
192
|
+
const cell = formatCell(row[column]);
|
|
193
|
+
return el("td", { class: cell.cls, text: cell.text, title: cell.text });
|
|
194
|
+
}))
|
|
195
|
+
));
|
|
196
|
+
root.append(el("div", { class: "grid" }, [el("table", {}, [el("thead", {}, [headRow]), body])]));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function showResult(result) {
|
|
200
|
+
const payload = result && result.structuredContent;
|
|
201
|
+
if (!payload) {
|
|
202
|
+
meta = { error: "This result has no structured content to display." };
|
|
203
|
+
} else if (payload.success === false) {
|
|
204
|
+
meta = { error: payload.message || "The query failed." };
|
|
205
|
+
} else {
|
|
206
|
+
rows = extractRows(payload);
|
|
207
|
+
columns = rows.length > 0 ? Object.keys(rows[0]) : [];
|
|
208
|
+
meta = { truncated: payload.truncated === true, message: payload.message };
|
|
209
|
+
}
|
|
210
|
+
sort = { column: null, direction: 1 };
|
|
211
|
+
filter = "";
|
|
212
|
+
render();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function applyHostContext(context) {
|
|
216
|
+
if (!context) return;
|
|
217
|
+
if (context.theme) applyDocumentTheme(context.theme);
|
|
218
|
+
if (context.styles && context.styles.variables) applyHostStyleVariables(context.styles.variables);
|
|
219
|
+
if (context.styles && context.styles.css && context.styles.css.fonts) applyHostFonts(context.styles.css.fonts);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const app = new App({ name: "mssql-query-results", version: "1.0.0" }, {}, { autoResize: true });
|
|
223
|
+
app.ontoolresult = showResult;
|
|
224
|
+
app.onhostcontextchanged = applyHostContext;
|
|
225
|
+
root.append(el("div", { class: "empty", text: "Waiting for query results…" }));
|
|
226
|
+
await app.connect();
|
|
227
|
+
applyHostContext(app.getHostContext());
|
|
228
|
+
`;
|
|
229
|
+
let cachedHtml;
|
|
230
|
+
export function buildQueryResultsHtml() {
|
|
231
|
+
cachedHtml ??= `<!doctype html>
|
|
232
|
+
<html lang="en">
|
|
233
|
+
<head>
|
|
234
|
+
<meta charset="utf-8">
|
|
235
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
236
|
+
<title>Query results</title>
|
|
237
|
+
<style>${VIEW_STYLES}</style>
|
|
238
|
+
</head>
|
|
239
|
+
<body>
|
|
240
|
+
<div id="root"></div>
|
|
241
|
+
<script type="module">
|
|
242
|
+
${loadAppRuntime()}
|
|
243
|
+
${VIEW_SCRIPT}
|
|
244
|
+
</script>
|
|
245
|
+
</body>
|
|
246
|
+
</html>
|
|
247
|
+
`;
|
|
248
|
+
return cachedHtml;
|
|
249
|
+
}
|
|
250
|
+
export function registerQueryResultsApp(server) {
|
|
251
|
+
registerAppResource(server, "query_results_app", QUERY_RESULTS_APP_URI, {
|
|
252
|
+
title: "Query Results Grid",
|
|
253
|
+
description: "Interactive, sortable and filterable grid for read_data and search_data results.",
|
|
254
|
+
mimeType: RESOURCE_MIME_TYPE,
|
|
255
|
+
_meta: { ui: { prefersBorder: true } },
|
|
256
|
+
}, async () => ({
|
|
257
|
+
contents: [
|
|
258
|
+
{
|
|
259
|
+
uri: QUERY_RESULTS_APP_URI,
|
|
260
|
+
mimeType: RESOURCE_MIME_TYPE,
|
|
261
|
+
text: buildQueryResultsHtml(),
|
|
262
|
+
_meta: { ui: { prefersBorder: true } },
|
|
263
|
+
},
|
|
264
|
+
],
|
|
265
|
+
}));
|
|
266
|
+
}
|
|
267
|
+
/** Tool `_meta` linking a tool to the query-results view (both key styles). */
|
|
268
|
+
export const queryResultsToolMeta = {
|
|
269
|
+
ui: { resourceUri: QUERY_RESULTS_APP_URI },
|
|
270
|
+
"ui/resourceUri": QUERY_RESULTS_APP_URI,
|
|
271
|
+
};
|
|
272
|
+
//# sourceMappingURL=queryResultsApp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queryResultsApp.js","sourceRoot":"","sources":["../../src/apps/queryResultsApp.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkNodeVersion.d.ts","sourceRoot":"","sources":["../src/checkNodeVersion.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Imported first by index.ts so it runs before any dependency is evaluated.
|
|
2
|
+
// Logs to stderr only: stdout belongs to the stdio JSON-RPC stream.
|
|
3
|
+
import { nodeVersionError } from "./nodeVersion.js";
|
|
4
|
+
const versionError = nodeVersionError();
|
|
5
|
+
if (versionError) {
|
|
6
|
+
console.error(versionError);
|
|
7
|
+
process.exit(1);
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=checkNodeVersion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkNodeVersion.js","sourceRoot":"","sources":["../src/checkNodeVersion.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,oEAAoE;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEpD,MAAM,YAAY,GAAG,gBAAgB,EAAE,CAAC;AACxC,IAAI,YAAY,EAAE,CAAC;IACjB,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAC5B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC"}
|
package/dist/config.d.ts
CHANGED
|
@@ -1,13 +1,64 @@
|
|
|
1
|
+
type Environment = NodeJS.ProcessEnv;
|
|
1
2
|
export type McpTransportMode = "stdio" | "http";
|
|
3
|
+
export declare const SQL_AUTH_TYPES: readonly ["sql", "ntlm", "azure-default", "azure-service-principal", "azure-access-token"];
|
|
4
|
+
export type SqlAuthType = (typeof SQL_AUTH_TYPES)[number];
|
|
5
|
+
export type SqlAuthConfig = {
|
|
6
|
+
type: "sql";
|
|
7
|
+
user: string;
|
|
8
|
+
password: string;
|
|
9
|
+
} | {
|
|
10
|
+
type: "ntlm";
|
|
11
|
+
user: string;
|
|
12
|
+
password: string;
|
|
13
|
+
domain: string;
|
|
14
|
+
} | {
|
|
15
|
+
type: "azure-default";
|
|
16
|
+
clientId?: string;
|
|
17
|
+
} | {
|
|
18
|
+
type: "azure-service-principal";
|
|
19
|
+
clientId: string;
|
|
20
|
+
clientSecret: string;
|
|
21
|
+
tenantId: string;
|
|
22
|
+
} | {
|
|
23
|
+
type: "azure-access-token";
|
|
24
|
+
token: string;
|
|
25
|
+
};
|
|
26
|
+
export interface SqlConnectionConfig {
|
|
27
|
+
serverName: string;
|
|
28
|
+
serverPort?: number;
|
|
29
|
+
auth: SqlAuthConfig;
|
|
30
|
+
encrypt: boolean;
|
|
31
|
+
trustServerCertificate: boolean;
|
|
32
|
+
connectionTimeoutSeconds: number;
|
|
33
|
+
queryTimeoutMs: number;
|
|
34
|
+
}
|
|
35
|
+
export interface EnvironmentConfig extends SqlConnectionConfig {
|
|
36
|
+
databaseName: string;
|
|
37
|
+
databases: string[];
|
|
38
|
+
readOnly: boolean;
|
|
39
|
+
enableDdl: boolean;
|
|
40
|
+
maxRows: number;
|
|
41
|
+
maxWriteRows: number;
|
|
42
|
+
requireWritePreview: boolean;
|
|
43
|
+
mcpTransport: McpTransportMode;
|
|
44
|
+
mcpHttpHost: string;
|
|
45
|
+
mcpHttpPort: number;
|
|
46
|
+
mcpBaseUrl?: string;
|
|
47
|
+
mcpHttpAuthToken?: string;
|
|
48
|
+
mcpHttpAllowUnauthenticated: boolean;
|
|
49
|
+
mcpHttpAllowedHosts: string[];
|
|
50
|
+
}
|
|
51
|
+
export declare function configureRuntimeEnvironment(environment: EnvironmentConfig): void;
|
|
52
|
+
export declare function parseDatabaseList(value: string | undefined): string[];
|
|
53
|
+
export declare function parseSqlAuthConfig(env?: Environment): SqlAuthConfig;
|
|
54
|
+
export declare function parseSqlConnectionConfig(env?: Environment): SqlConnectionConfig;
|
|
55
|
+
export declare function parseEnvironmentConfig(env?: Environment): EnvironmentConfig;
|
|
2
56
|
export declare function getMaxRows(): number;
|
|
3
|
-
export declare function getQueryTimeoutMs(): number;
|
|
4
57
|
export declare function getDefaultSearchLimit(): number;
|
|
5
58
|
export declare function clampRowLimit(limit: unknown, fallback?: number): number;
|
|
6
|
-
export declare function
|
|
7
|
-
|
|
8
|
-
export declare function
|
|
9
|
-
export declare function getMcpBaseUrl(): string | undefined;
|
|
10
|
-
export declare function isDdlEnabled(): boolean;
|
|
59
|
+
export declare function getMcpEndpointUrl(environment: Pick<EnvironmentConfig, "mcpBaseUrl" | "mcpHttpHost" | "mcpHttpPort">): string;
|
|
60
|
+
/** Brackets IPv6 literals (`::1` -> `[::1]`) for use in a URL authority. */
|
|
61
|
+
export declare function formatUrlHost(host: string): string;
|
|
11
62
|
export declare function getMaxWriteRows(): number;
|
|
12
|
-
export
|
|
63
|
+
export {};
|
|
13
64
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAQA,KAAK,WAAW,GAAG,MAAM,CAAC,UAAU,CAAC;AAErC,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,MAAM,CAAC;AAEhD,eAAO,MAAM,cAAc,4FAMjB,CAAC;AAEX,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1D,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAChE;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5C;IACE,IAAI,EAAE,yBAAyB,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;CAClB,GACD;IAAE,IAAI,EAAE,oBAAoB,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAElD,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,aAAa,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,sBAAsB,EAAE,OAAO,CAAC;IAChC,wBAAwB,EAAE,MAAM,CAAC;IACjC,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,iBAAkB,SAAQ,mBAAmB;IAC5D,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,YAAY,EAAE,gBAAgB,CAAC;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,2BAA2B,EAAE,OAAO,CAAC;IACrC,mBAAmB,EAAE,MAAM,EAAE,CAAC;CAC/B;AAID,wBAAgB,2BAA2B,CACzC,WAAW,EAAE,iBAAiB,GAC7B,IAAI,CAKN;AAmHD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CASrE;AAeD,wBAAgB,kBAAkB,CAChC,GAAG,GAAE,WAAyB,GAC7B,aAAa,CAoCf;AAED,wBAAgB,wBAAwB,CACtC,GAAG,GAAE,WAAyB,GAC7B,mBAAmB,CA0BrB;AAED,wBAAgB,sBAAsB,CACpC,GAAG,GAAE,WAAyB,GAC7B,iBAAiB,CAyDnB;AAeD,wBAAgB,UAAU,IAAI,MAAM,CAKnC;AAED,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,SAAe,GAAG,MAAM,CAa7E;AAED,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,IAAI,CACf,iBAAiB,EACjB,YAAY,GAAG,aAAa,GAAG,aAAa,CAC7C,GACA,MAAM,CAKR;AAED,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,wBAAgB,eAAe,IAAI,MAAM,CASxC"}
|
package/dist/config.js
CHANGED
|
@@ -1,37 +1,215 @@
|
|
|
1
1
|
const DEFAULT_MAX_ROWS = 10000;
|
|
2
2
|
const DEFAULT_QUERY_TIMEOUT_MS = 30000;
|
|
3
3
|
const DEFAULT_SEARCH_LIMIT = 100;
|
|
4
|
+
const DEFAULT_CONNECTION_TIMEOUT_SECONDS = 30;
|
|
4
5
|
const DEFAULT_HTTP_PORT = 3333;
|
|
5
6
|
const DEFAULT_HTTP_HOST = "127.0.0.1";
|
|
6
7
|
const DEFAULT_MAX_WRITE_ROWS = 100;
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
export const SQL_AUTH_TYPES = [
|
|
9
|
+
"sql",
|
|
10
|
+
"ntlm",
|
|
11
|
+
"azure-default",
|
|
12
|
+
"azure-service-principal",
|
|
13
|
+
"azure-access-token",
|
|
14
|
+
];
|
|
15
|
+
let runtimeEnvironment;
|
|
16
|
+
export function configureRuntimeEnvironment(environment) {
|
|
17
|
+
runtimeEnvironment = {
|
|
18
|
+
...environment,
|
|
19
|
+
databases: [...environment.databases],
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function parseInteger(name, value, fallback, minimum = 1, maximum) {
|
|
23
|
+
if (value === undefined || value.trim() === "") {
|
|
9
24
|
return fallback;
|
|
10
25
|
}
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
26
|
+
const range = maximum !== undefined
|
|
27
|
+
? `between ${minimum} and ${maximum}`
|
|
28
|
+
: `of at least ${minimum}`;
|
|
29
|
+
const normalized = value.trim();
|
|
30
|
+
if (!/^\d+$/.test(normalized)) {
|
|
31
|
+
throw new Error(`${name} must be a whole number ${range}.`);
|
|
32
|
+
}
|
|
33
|
+
const parsed = Number(normalized);
|
|
34
|
+
if (!Number.isSafeInteger(parsed) ||
|
|
35
|
+
parsed < minimum ||
|
|
36
|
+
(maximum !== undefined && parsed > maximum)) {
|
|
37
|
+
throw new Error(`${name} must be a whole number ${range}.`);
|
|
14
38
|
}
|
|
15
39
|
return parsed;
|
|
16
40
|
}
|
|
17
|
-
function
|
|
18
|
-
|
|
41
|
+
function parseRequiredString(name, value) {
|
|
42
|
+
const normalized = value?.trim();
|
|
43
|
+
if (!normalized) {
|
|
44
|
+
throw new Error(`${name} is required and must not be blank.`);
|
|
45
|
+
}
|
|
46
|
+
return normalized;
|
|
47
|
+
}
|
|
48
|
+
function parseBoolean(name, value, fallback) {
|
|
49
|
+
if ((value === undefined || value.trim() === "") && fallback !== undefined) {
|
|
19
50
|
return fallback;
|
|
20
51
|
}
|
|
21
|
-
const normalized = value
|
|
52
|
+
const normalized = value?.trim().toLowerCase();
|
|
22
53
|
if (normalized === "true") {
|
|
23
54
|
return true;
|
|
24
55
|
}
|
|
25
56
|
if (normalized === "false") {
|
|
26
57
|
return false;
|
|
27
58
|
}
|
|
28
|
-
|
|
59
|
+
const requirement = fallback === undefined ? "is required and must be" : "must be";
|
|
60
|
+
throw new Error(`${name} ${requirement} either "true" or "false".`);
|
|
29
61
|
}
|
|
30
|
-
|
|
31
|
-
|
|
62
|
+
function parseServerPort(value) {
|
|
63
|
+
if (value === undefined || value.trim() === "") {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
const normalized = value.trim();
|
|
67
|
+
if (!/^\d+$/.test(normalized)) {
|
|
68
|
+
throw new Error("SERVER_PORT must be a valid TCP port between 1 and 65535.");
|
|
69
|
+
}
|
|
70
|
+
const port = Number(normalized);
|
|
71
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
72
|
+
throw new Error("SERVER_PORT must be a valid TCP port between 1 and 65535.");
|
|
73
|
+
}
|
|
74
|
+
return port;
|
|
32
75
|
}
|
|
33
|
-
|
|
34
|
-
|
|
76
|
+
function parseMcpBaseUrl(value) {
|
|
77
|
+
const normalized = value?.trim();
|
|
78
|
+
if (!normalized) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
let url;
|
|
82
|
+
try {
|
|
83
|
+
url = new URL(normalized);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
throw new Error("MCP_BASE_URL must be a valid absolute HTTP or HTTPS URL.");
|
|
87
|
+
}
|
|
88
|
+
if ((url.protocol !== "http:" && url.protocol !== "https:") ||
|
|
89
|
+
url.username ||
|
|
90
|
+
url.password ||
|
|
91
|
+
url.search ||
|
|
92
|
+
url.hash) {
|
|
93
|
+
throw new Error("MCP_BASE_URL must be a valid absolute HTTP or HTTPS URL.");
|
|
94
|
+
}
|
|
95
|
+
url.pathname = url.pathname.replace(/\/+$/, "");
|
|
96
|
+
return url.toString().replace(/\/$/, "");
|
|
97
|
+
}
|
|
98
|
+
export function parseDatabaseList(value) {
|
|
99
|
+
if (!value?.trim()) {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
return value
|
|
103
|
+
.split(",")
|
|
104
|
+
.map((database) => database.trim())
|
|
105
|
+
.filter(Boolean);
|
|
106
|
+
}
|
|
107
|
+
function parseSqlAuthType(value) {
|
|
108
|
+
if (value === undefined || value.trim() === "") {
|
|
109
|
+
return "sql";
|
|
110
|
+
}
|
|
111
|
+
const normalized = value.trim().toLowerCase();
|
|
112
|
+
if (SQL_AUTH_TYPES.includes(normalized)) {
|
|
113
|
+
return normalized;
|
|
114
|
+
}
|
|
115
|
+
throw new Error(`SQL_AUTH_TYPE must be one of: ${SQL_AUTH_TYPES.join(", ")}.`);
|
|
116
|
+
}
|
|
117
|
+
export function parseSqlAuthConfig(env = process.env) {
|
|
118
|
+
const type = parseSqlAuthType(env.SQL_AUTH_TYPE);
|
|
119
|
+
switch (type) {
|
|
120
|
+
case "sql":
|
|
121
|
+
return {
|
|
122
|
+
type,
|
|
123
|
+
user: parseRequiredString("DB_USER", env.DB_USER),
|
|
124
|
+
password: parseRequiredString("DB_PASSWORD", env.DB_PASSWORD),
|
|
125
|
+
};
|
|
126
|
+
case "ntlm":
|
|
127
|
+
return {
|
|
128
|
+
type,
|
|
129
|
+
user: parseRequiredString("DB_USER", env.DB_USER),
|
|
130
|
+
password: parseRequiredString("DB_PASSWORD", env.DB_PASSWORD),
|
|
131
|
+
domain: parseRequiredString("DB_DOMAIN", env.DB_DOMAIN),
|
|
132
|
+
};
|
|
133
|
+
case "azure-default": {
|
|
134
|
+
const clientId = env.AZURE_CLIENT_ID?.trim();
|
|
135
|
+
return clientId ? { type, clientId } : { type };
|
|
136
|
+
}
|
|
137
|
+
case "azure-service-principal":
|
|
138
|
+
return {
|
|
139
|
+
type,
|
|
140
|
+
clientId: parseRequiredString("AZURE_CLIENT_ID", env.AZURE_CLIENT_ID),
|
|
141
|
+
clientSecret: parseRequiredString("AZURE_CLIENT_SECRET", env.AZURE_CLIENT_SECRET),
|
|
142
|
+
tenantId: parseRequiredString("AZURE_TENANT_ID", env.AZURE_TENANT_ID),
|
|
143
|
+
};
|
|
144
|
+
case "azure-access-token":
|
|
145
|
+
return {
|
|
146
|
+
type,
|
|
147
|
+
token: parseRequiredString("AZURE_ACCESS_TOKEN", env.AZURE_ACCESS_TOKEN),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
export function parseSqlConnectionConfig(env = process.env) {
|
|
152
|
+
const auth = parseSqlAuthConfig(env);
|
|
153
|
+
const isEntraAuth = auth.type.startsWith("azure-");
|
|
154
|
+
return {
|
|
155
|
+
serverName: env.SERVER_NAME?.trim() || "localhost",
|
|
156
|
+
serverPort: parseServerPort(env.SERVER_PORT),
|
|
157
|
+
auth,
|
|
158
|
+
// Azure SQL requires TLS and presents publicly trusted certificates, so
|
|
159
|
+
// Entra ID auth types default to encrypting and validating the certificate.
|
|
160
|
+
encrypt: parseBoolean("ENCRYPT", env.ENCRYPT, isEntraAuth),
|
|
161
|
+
trustServerCertificate: parseBoolean("TRUST_SERVER_CERTIFICATE", env.TRUST_SERVER_CERTIFICATE, !isEntraAuth),
|
|
162
|
+
connectionTimeoutSeconds: parseInteger("CONNECTION_TIMEOUT", env.CONNECTION_TIMEOUT, DEFAULT_CONNECTION_TIMEOUT_SECONDS),
|
|
163
|
+
queryTimeoutMs: parseInteger("QUERY_TIMEOUT_MS", env.QUERY_TIMEOUT_MS, DEFAULT_QUERY_TIMEOUT_MS),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
export function parseEnvironmentConfig(env = process.env) {
|
|
167
|
+
const explicitDefault = env.DATABASE_NAME?.trim();
|
|
168
|
+
const configuredDatabases = parseDatabaseList(env.DATABASES);
|
|
169
|
+
const databases = configuredDatabases.length > 0
|
|
170
|
+
? configuredDatabases
|
|
171
|
+
: explicitDefault
|
|
172
|
+
? [explicitDefault]
|
|
173
|
+
: [];
|
|
174
|
+
if (databases.length === 0) {
|
|
175
|
+
throw new Error("At least one of DATABASE_NAME or DATABASES is required and must contain a database name.");
|
|
176
|
+
}
|
|
177
|
+
const databaseName = explicitDefault && databases.includes(explicitDefault)
|
|
178
|
+
? explicitDefault
|
|
179
|
+
: databases[0];
|
|
180
|
+
return {
|
|
181
|
+
...parseSqlConnectionConfig(env),
|
|
182
|
+
databaseName,
|
|
183
|
+
databases,
|
|
184
|
+
readOnly: parseBoolean("READONLY", env.READONLY, false),
|
|
185
|
+
enableDdl: parseBoolean("ENABLE_DDL", env.ENABLE_DDL, false),
|
|
186
|
+
maxRows: parseInteger("MAX_ROWS", env.MAX_ROWS, DEFAULT_MAX_ROWS),
|
|
187
|
+
maxWriteRows: parseInteger("MAX_WRITE_ROWS", env.MAX_WRITE_ROWS, DEFAULT_MAX_WRITE_ROWS),
|
|
188
|
+
requireWritePreview: parseBoolean("REQUIRE_WRITE_PREVIEW", env.REQUIRE_WRITE_PREVIEW, true),
|
|
189
|
+
mcpTransport: parseMcpTransport(env.MCP_TRANSPORT),
|
|
190
|
+
mcpHttpHost: env.MCP_HTTP_HOST?.trim() || DEFAULT_HTTP_HOST,
|
|
191
|
+
mcpHttpPort: parseInteger("MCP_HTTP_PORT", env.MCP_HTTP_PORT, DEFAULT_HTTP_PORT, 1, 65535),
|
|
192
|
+
mcpBaseUrl: parseMcpBaseUrl(env.MCP_BASE_URL),
|
|
193
|
+
mcpHttpAuthToken: env.MCP_HTTP_AUTH_TOKEN?.trim() || undefined,
|
|
194
|
+
mcpHttpAllowUnauthenticated: parseBoolean("MCP_HTTP_ALLOW_UNAUTHENTICATED", env.MCP_HTTP_ALLOW_UNAUTHENTICATED, false),
|
|
195
|
+
mcpHttpAllowedHosts: parseDatabaseList(env.MCP_HTTP_ALLOWED_HOSTS).map((host) => host.toLowerCase()),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function parseMcpTransport(value) {
|
|
199
|
+
if (value === undefined || value.trim() === "") {
|
|
200
|
+
return "stdio";
|
|
201
|
+
}
|
|
202
|
+
const normalized = value.trim().toLowerCase();
|
|
203
|
+
if (normalized === "stdio" || normalized === "http") {
|
|
204
|
+
return normalized;
|
|
205
|
+
}
|
|
206
|
+
throw new Error('MCP_TRANSPORT must be either "stdio" or "http".');
|
|
207
|
+
}
|
|
208
|
+
export function getMaxRows() {
|
|
209
|
+
if (runtimeEnvironment) {
|
|
210
|
+
return runtimeEnvironment.maxRows;
|
|
211
|
+
}
|
|
212
|
+
return parseInteger("MAX_ROWS", process.env.MAX_ROWS, DEFAULT_MAX_ROWS);
|
|
35
213
|
}
|
|
36
214
|
export function getDefaultSearchLimit() {
|
|
37
215
|
return Math.min(getMaxRows(), DEFAULT_SEARCH_LIMIT);
|
|
@@ -47,28 +225,19 @@ export function clampRowLimit(limit, fallback = getMaxRows()) {
|
|
|
47
225
|
}
|
|
48
226
|
return Math.min(Math.floor(parsed), getMaxRows());
|
|
49
227
|
}
|
|
50
|
-
export function
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
export function getMcpHttpPort() {
|
|
56
|
-
return parsePositiveInt(process.env.MCP_HTTP_PORT, DEFAULT_HTTP_PORT);
|
|
228
|
+
export function getMcpEndpointUrl(environment) {
|
|
229
|
+
const baseUrl = environment.mcpBaseUrl ??
|
|
230
|
+
`http://${formatUrlHost(environment.mcpHttpHost)}:${environment.mcpHttpPort}`;
|
|
231
|
+
return `${baseUrl.replace(/\/+$/, "")}/mcp`;
|
|
57
232
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
export function getMcpBaseUrl() {
|
|
62
|
-
const value = process.env.MCP_BASE_URL?.trim();
|
|
63
|
-
return value ? value : undefined;
|
|
64
|
-
}
|
|
65
|
-
export function isDdlEnabled() {
|
|
66
|
-
return parseBoolean(process.env.ENABLE_DDL, false);
|
|
233
|
+
/** Brackets IPv6 literals (`::1` -> `[::1]`) for use in a URL authority. */
|
|
234
|
+
export function formatUrlHost(host) {
|
|
235
|
+
return host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
|
|
67
236
|
}
|
|
68
237
|
export function getMaxWriteRows() {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return
|
|
238
|
+
if (runtimeEnvironment) {
|
|
239
|
+
return runtimeEnvironment.maxWriteRows;
|
|
240
|
+
}
|
|
241
|
+
return parseInteger("MAX_WRITE_ROWS", process.env.MAX_WRITE_ROWS, DEFAULT_MAX_WRITE_ROWS);
|
|
73
242
|
}
|
|
74
243
|
//# sourceMappingURL=config.js.map
|