@mgsoftwarebv/mg-dashboard-mcp 7.4.18 → 7.4.20
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/dist/index.js +32 -157
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from 'child_process';
|
|
3
3
|
import { readFileSync, existsSync, statSync, createReadStream } from 'fs';
|
|
4
|
-
import {
|
|
4
|
+
import { dirname, sep, join, isAbsolute } from 'path';
|
|
5
5
|
import { Client as Client$1 } from '@modelcontextprotocol/sdk/client/index.js';
|
|
6
6
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
7
7
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
@@ -13,6 +13,7 @@ import crypto, { randomUUID, createHash, randomBytes, createCipheriv, createDeci
|
|
|
13
13
|
import { readFile, mkdtemp, writeFile, rm } from 'fs/promises';
|
|
14
14
|
import { createServer } from 'http';
|
|
15
15
|
import { tmpdir } from 'os';
|
|
16
|
+
import { fileURLToPath } from 'url';
|
|
16
17
|
import { AsyncLocalStorage } from 'async_hooks';
|
|
17
18
|
import { drizzle } from 'drizzle-orm/postgres-js';
|
|
18
19
|
import postgres from 'postgres';
|
|
@@ -21,7 +22,6 @@ import { sql } from 'drizzle-orm';
|
|
|
21
22
|
import { once } from 'events';
|
|
22
23
|
import { lookup } from 'dns/promises';
|
|
23
24
|
import { connect } from 'tls';
|
|
24
|
-
import { fileURLToPath } from 'url';
|
|
25
25
|
import { pgTable, timestamp, uuid, jsonb, boolean, text, customType, integer, date, real, index, uniqueIndex, pgEnum, check, bigint, doublePrecision, primaryKey, foreignKey } from 'drizzle-orm/pg-core';
|
|
26
26
|
import { HeadObjectCommand, ListObjectsV2Command, DeleteObjectsCommand, S3Client, DeleteObjectCommand, CreateMultipartUploadCommand, UploadPartCommand, CompleteMultipartUploadCommand, AbortMultipartUploadCommand, PutObjectCommand, GetObjectCommand, CopyObjectCommand } from '@aws-sdk/client-s3';
|
|
27
27
|
|
|
@@ -1316,151 +1316,6 @@ async function proxyJson(ctx, route, body) {
|
|
|
1316
1316
|
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
1317
1317
|
};
|
|
1318
1318
|
}
|
|
1319
|
-
var MCP_NPM_PACKAGE = "@mgsoftwarebv/mg-dashboard-mcp";
|
|
1320
|
-
var MCP_SKIP_SELF_UPDATE_ENV = "MG_MCP_SKIP_SELF_UPDATE";
|
|
1321
|
-
var MCP_SELF_UPDATE_TIMEOUT_MS = 2500;
|
|
1322
|
-
var MCP_REQUIRED_VERSION_PATH = "/api/mcp/required-version";
|
|
1323
|
-
var MG_DASHBOARD_ORIGIN_DEFAULT = "https://dashboard.mgsoftware.nl";
|
|
1324
|
-
function npmLatestUrl(pkg = MCP_NPM_PACKAGE) {
|
|
1325
|
-
const slash = pkg.indexOf("/");
|
|
1326
|
-
if (slash < 0) return `https://registry.npmjs.org/${pkg}/latest`;
|
|
1327
|
-
return `https://registry.npmjs.org/${pkg.slice(0, slash)}%2f${pkg.slice(slash + 1)}/latest`;
|
|
1328
|
-
}
|
|
1329
|
-
function parseNpmVersion(value) {
|
|
1330
|
-
const match = /^(\d+)\.(\d+)\.(\d+)/.exec(value?.trim() ?? "");
|
|
1331
|
-
if (!match) return null;
|
|
1332
|
-
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
1333
|
-
}
|
|
1334
|
-
function isNpmVersionBehind(installed, latest) {
|
|
1335
|
-
const a = parseNpmVersion(installed);
|
|
1336
|
-
const b = parseNpmVersion(latest);
|
|
1337
|
-
if (!a || !b) return false;
|
|
1338
|
-
if (a[0] !== b[0]) return a[0] < b[0];
|
|
1339
|
-
if (a[1] !== b[1]) return a[1] < b[1];
|
|
1340
|
-
return a[2] < b[2];
|
|
1341
|
-
}
|
|
1342
|
-
function packageRootFromModuleUrl(moduleUrl) {
|
|
1343
|
-
const here = dirname(fileURLToPath(moduleUrl));
|
|
1344
|
-
const base = here.split(sep).pop();
|
|
1345
|
-
if (base === "dist" || base === "src") return join(here, "..");
|
|
1346
|
-
return here;
|
|
1347
|
-
}
|
|
1348
|
-
function isLocalMonorepoCheckout(packageRoot) {
|
|
1349
|
-
return existsSync(join(packageRoot, "../../apps/backoffice/package.json"));
|
|
1350
|
-
}
|
|
1351
|
-
function readInstalledMcpVersion(packageRoot = packageRootFromModuleUrl(import.meta.url)) {
|
|
1352
|
-
try {
|
|
1353
|
-
const raw = readFileSync(join(packageRoot, "package.json"), "utf8");
|
|
1354
|
-
const parsed = JSON.parse(raw);
|
|
1355
|
-
return typeof parsed.version === "string" ? parsed.version : null;
|
|
1356
|
-
} catch {
|
|
1357
|
-
return null;
|
|
1358
|
-
}
|
|
1359
|
-
}
|
|
1360
|
-
function mcpRequiredVersionUrl(origin = MG_DASHBOARD_ORIGIN_DEFAULT) {
|
|
1361
|
-
return `${origin.replace(/\/$/, "")}${MCP_REQUIRED_VERSION_PATH}`;
|
|
1362
|
-
}
|
|
1363
|
-
function parseDashboardRequiredVersion(json) {
|
|
1364
|
-
if (!json || typeof json !== "object") return null;
|
|
1365
|
-
const version = json.version;
|
|
1366
|
-
if (typeof version !== "string") return null;
|
|
1367
|
-
return parseNpmVersion(version) ? version.trim() : null;
|
|
1368
|
-
}
|
|
1369
|
-
async function fetchDashboardRequiredVersion(fetchFn, origin, timeoutMs = MCP_SELF_UPDATE_TIMEOUT_MS) {
|
|
1370
|
-
const controller = new AbortController();
|
|
1371
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1372
|
-
try {
|
|
1373
|
-
const response = await fetchFn(mcpRequiredVersionUrl(origin), {
|
|
1374
|
-
signal: controller.signal,
|
|
1375
|
-
headers: { Accept: "application/json", "cache-control": "no-store" }
|
|
1376
|
-
});
|
|
1377
|
-
if (!response.ok) return null;
|
|
1378
|
-
return parseDashboardRequiredVersion(await response.json());
|
|
1379
|
-
} catch {
|
|
1380
|
-
return null;
|
|
1381
|
-
} finally {
|
|
1382
|
-
clearTimeout(timer);
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
async function fetchLatestNpmVersion(fetchFn, pkg = MCP_NPM_PACKAGE, timeoutMs = MCP_SELF_UPDATE_TIMEOUT_MS) {
|
|
1386
|
-
const controller = new AbortController();
|
|
1387
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1388
|
-
try {
|
|
1389
|
-
const response = await fetchFn(npmLatestUrl(pkg), {
|
|
1390
|
-
signal: controller.signal,
|
|
1391
|
-
headers: { Accept: "application/json" }
|
|
1392
|
-
});
|
|
1393
|
-
if (!response.ok) return null;
|
|
1394
|
-
const json = await response.json();
|
|
1395
|
-
return typeof json.version === "string" ? json.version : null;
|
|
1396
|
-
} catch {
|
|
1397
|
-
return null;
|
|
1398
|
-
} finally {
|
|
1399
|
-
clearTimeout(timer);
|
|
1400
|
-
}
|
|
1401
|
-
}
|
|
1402
|
-
function shouldSkipMcpSelfUpdate(io = {}) {
|
|
1403
|
-
const env = io.env ?? process.env;
|
|
1404
|
-
if (env[MCP_SKIP_SELF_UPDATE_ENV] === "1") return true;
|
|
1405
|
-
const packageRoot = packageRootFromModuleUrl(io.moduleUrl ?? import.meta.url);
|
|
1406
|
-
const monorepo = io.monorepo ?? isLocalMonorepoCheckout(packageRoot);
|
|
1407
|
-
return monorepo;
|
|
1408
|
-
}
|
|
1409
|
-
function buildNpxSelfUpdateArgs(latest, argv) {
|
|
1410
|
-
const command = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
1411
|
-
return {
|
|
1412
|
-
command,
|
|
1413
|
-
args: ["-y", "--prefer-online", `${MCP_NPM_PACKAGE}@${latest}`, ...argv]
|
|
1414
|
-
};
|
|
1415
|
-
}
|
|
1416
|
-
function waitForSpawnedChild(child) {
|
|
1417
|
-
return new Promise((resolve) => {
|
|
1418
|
-
child.on("error", () => resolve(1));
|
|
1419
|
-
child.on("close", (code) => resolve(code ?? 1));
|
|
1420
|
-
});
|
|
1421
|
-
}
|
|
1422
|
-
async function maybeReexecLatestMcp(io = {}) {
|
|
1423
|
-
if (shouldSkipMcpSelfUpdate(io)) return false;
|
|
1424
|
-
const packageRoot = packageRootFromModuleUrl(io.moduleUrl ?? import.meta.url);
|
|
1425
|
-
const installed = io.installedVersion !== void 0 ? io.installedVersion : readInstalledMcpVersion(packageRoot);
|
|
1426
|
-
if (!installed) return false;
|
|
1427
|
-
const fetchFn = io.fetchFn ?? fetch;
|
|
1428
|
-
const origin = (io.dashboardBaseUrl || io.env?.MG_DASHBOARD_BASE_URL || MG_DASHBOARD_ORIGIN_DEFAULT).replace(/\/$/, "");
|
|
1429
|
-
const dashboardVersion = await fetchDashboardRequiredVersion(fetchFn, origin);
|
|
1430
|
-
const npmVersion = dashboardVersion ? null : await fetchLatestNpmVersion(fetchFn);
|
|
1431
|
-
const latest = dashboardVersion ?? npmVersion;
|
|
1432
|
-
if (!latest || !isNpmVersionBehind(installed, latest)) return false;
|
|
1433
|
-
const argv = io.argv ?? process.argv.slice(2);
|
|
1434
|
-
const { command, args: args2 } = buildNpxSelfUpdateArgs(latest, argv);
|
|
1435
|
-
const log = io.log ?? ((message) => console.error(message));
|
|
1436
|
-
log(
|
|
1437
|
-
`[mcp] ${MCP_NPM_PACKAGE}@${installed} is behind npm ${latest}; restarting via npx --prefer-online`
|
|
1438
|
-
);
|
|
1439
|
-
const spawnFn = io.spawnFn ?? spawn;
|
|
1440
|
-
let spawnFailed = false;
|
|
1441
|
-
const child = spawnFn(command, args2, {
|
|
1442
|
-
stdio: "inherit",
|
|
1443
|
-
env: {
|
|
1444
|
-
...process.env,
|
|
1445
|
-
[MCP_SKIP_SELF_UPDATE_ENV]: "1"
|
|
1446
|
-
},
|
|
1447
|
-
shell: process.platform === "win32",
|
|
1448
|
-
windowsHide: true
|
|
1449
|
-
});
|
|
1450
|
-
child.on?.("error", () => {
|
|
1451
|
-
spawnFailed = true;
|
|
1452
|
-
});
|
|
1453
|
-
const wait = io.waitForChild ?? waitForSpawnedChild;
|
|
1454
|
-
const code = await wait(child);
|
|
1455
|
-
if (spawnFailed) {
|
|
1456
|
-
log(
|
|
1457
|
-
`[mcp] self-update spawn failed; continuing on ${MCP_NPM_PACKAGE}@${installed}`
|
|
1458
|
-
);
|
|
1459
|
-
return false;
|
|
1460
|
-
}
|
|
1461
|
-
if (code !== 0) process.exitCode = code;
|
|
1462
|
-
return true;
|
|
1463
|
-
}
|
|
1464
1319
|
var ALGORITHM = "aes-256-gcm";
|
|
1465
1320
|
var IV_LENGTH = 16;
|
|
1466
1321
|
var AUTH_TAG_LENGTH = 16;
|
|
@@ -9174,6 +9029,11 @@ function normalizeAllowedTools(value) {
|
|
|
9174
9029
|
const tools = value.filter((t) => typeof t === "string").map((t) => t.trim()).filter(Boolean);
|
|
9175
9030
|
return tools.length > 0 ? tools : null;
|
|
9176
9031
|
}
|
|
9032
|
+
function isMcpToolAllowed(name, allowed, denied) {
|
|
9033
|
+
if (denied?.includes(name)) return false;
|
|
9034
|
+
if (allowed && allowed.length > 0) return allowed.includes(name);
|
|
9035
|
+
return true;
|
|
9036
|
+
}
|
|
9177
9037
|
var authContext = null;
|
|
9178
9038
|
var currentClientIp;
|
|
9179
9039
|
function extractClientIp(req) {
|
|
@@ -9202,7 +9062,7 @@ async function validateApiKey(key) {
|
|
|
9202
9062
|
return null;
|
|
9203
9063
|
}
|
|
9204
9064
|
const apiKeyRows = await db.execute(sql`
|
|
9205
|
-
SELECT id, name, created_by, allowed_server_ids, allowed_tools, is_active, expires_at
|
|
9065
|
+
SELECT id, name, created_by, allowed_server_ids, allowed_tools, denied_tools, is_active, expires_at
|
|
9206
9066
|
FROM dashboard_mcp_api_key
|
|
9207
9067
|
WHERE api_key_hash = ${keyHash} AND is_active = true
|
|
9208
9068
|
LIMIT 1
|
|
@@ -9255,6 +9115,7 @@ async function validateApiKey(key) {
|
|
|
9255
9115
|
userId: data.created_by,
|
|
9256
9116
|
allowedServerIds,
|
|
9257
9117
|
allowedTools: normalizeAllowedTools(data.allowed_tools),
|
|
9118
|
+
deniedTools: normalizeAllowedTools(data.denied_tools),
|
|
9258
9119
|
permissions,
|
|
9259
9120
|
roleName
|
|
9260
9121
|
};
|
|
@@ -9449,7 +9310,7 @@ async function validateSshKey(pubkeyPathInput, expectedApiKeyId) {
|
|
|
9449
9310
|
return null;
|
|
9450
9311
|
}
|
|
9451
9312
|
const apiRows = await db.execute(sql`
|
|
9452
|
-
SELECT id, name, created_by, allowed_server_ids, allowed_tools, is_active, expires_at
|
|
9313
|
+
SELECT id, name, created_by, allowed_server_ids, allowed_tools, denied_tools, is_active, expires_at
|
|
9453
9314
|
FROM dashboard_mcp_api_key
|
|
9454
9315
|
WHERE id = ${keyRow.api_key_id} AND is_active = true
|
|
9455
9316
|
LIMIT 1
|
|
@@ -9504,6 +9365,7 @@ async function validateSshKey(pubkeyPathInput, expectedApiKeyId) {
|
|
|
9504
9365
|
userId: apiRow.created_by,
|
|
9505
9366
|
allowedServerIds,
|
|
9506
9367
|
allowedTools: normalizeAllowedTools(apiRow.allowed_tools),
|
|
9368
|
+
deniedTools: normalizeAllowedTools(apiRow.denied_tools),
|
|
9507
9369
|
permissions,
|
|
9508
9370
|
roleName
|
|
9509
9371
|
};
|
|
@@ -13776,13 +13638,27 @@ var TOOLS = [
|
|
|
13776
13638
|
// ----- Trigger.dev -----
|
|
13777
13639
|
...TRIGGER_TOOLS
|
|
13778
13640
|
];
|
|
13641
|
+
function readInstalledMcpVersion() {
|
|
13642
|
+
try {
|
|
13643
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
13644
|
+
const base = here.split(sep).pop();
|
|
13645
|
+
const packageRoot = base === "dist" || base === "src" ? join(here, "..") : here;
|
|
13646
|
+
const parsed = JSON.parse(
|
|
13647
|
+
readFileSync(join(packageRoot, "package.json"), "utf8")
|
|
13648
|
+
);
|
|
13649
|
+
return typeof parsed.version === "string" ? parsed.version : null;
|
|
13650
|
+
} catch {
|
|
13651
|
+
return null;
|
|
13652
|
+
}
|
|
13653
|
+
}
|
|
13779
13654
|
var MCP_VERSION = readInstalledMcpVersion() ?? "0.0.0";
|
|
13780
13655
|
async function handleListTools() {
|
|
13781
13656
|
if (!authContext) return { tools: TOOLS };
|
|
13782
13657
|
const allowedTools = authContext.allowedTools;
|
|
13783
13658
|
const isSuperadmin = authContext.roleName === "superadmin";
|
|
13784
13659
|
const accessible = TOOLS.filter((tool) => {
|
|
13785
|
-
if (
|
|
13660
|
+
if (!isMcpToolAllowed(tool.name, allowedTools, authContext.deniedTools))
|
|
13661
|
+
return false;
|
|
13786
13662
|
if (SUPERADMIN_ONLY_TOOLS.has(tool.name) && !isSuperadmin) return false;
|
|
13787
13663
|
const requiredModule = TOOL_MODULE_MAP[tool.name];
|
|
13788
13664
|
if (!requiredModule) return true;
|
|
@@ -13796,12 +13672,16 @@ async function handleCallTool(request) {
|
|
|
13796
13672
|
}
|
|
13797
13673
|
const { name, arguments: toolArgs } = request.params;
|
|
13798
13674
|
const a = toolArgs || {};
|
|
13799
|
-
if (
|
|
13675
|
+
if (!isMcpToolAllowed(
|
|
13676
|
+
name,
|
|
13677
|
+
authContext.allowedTools,
|
|
13678
|
+
authContext.deniedTools
|
|
13679
|
+
)) {
|
|
13800
13680
|
return {
|
|
13801
13681
|
content: [
|
|
13802
13682
|
{
|
|
13803
13683
|
type: "text",
|
|
13804
|
-
text: `Access denied: this API key
|
|
13684
|
+
text: `Access denied: this API key may not call "${name}".`
|
|
13805
13685
|
}
|
|
13806
13686
|
]
|
|
13807
13687
|
};
|
|
@@ -17541,11 +17421,6 @@ function createMcpServer() {
|
|
|
17541
17421
|
var server = createMcpServer();
|
|
17542
17422
|
async function main() {
|
|
17543
17423
|
console.error("Starting MG Dashboard MCP Server...");
|
|
17544
|
-
const replaced = await maybeReexecLatestMcp({
|
|
17545
|
-
argv: process.argv.slice(2),
|
|
17546
|
-
dashboardBaseUrl
|
|
17547
|
-
});
|
|
17548
|
-
if (replaced) return;
|
|
17549
17424
|
const apiAuthContext = await validateApiKey(apiKey);
|
|
17550
17425
|
if (!apiAuthContext) {
|
|
17551
17426
|
console.error("API key validation failed");
|
package/package.json
CHANGED