@mgsoftwarebv/mg-dashboard-mcp 3.3.0 → 3.4.1
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 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
9
9
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
10
10
|
import { ListToolsRequestSchema, CallToolRequestSchema, isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
|
|
11
11
|
import { createServer } from 'http';
|
|
12
|
-
import { randomUUID,
|
|
12
|
+
import { randomUUID, createHash, randomBytes, createCipheriv, createDecipheriv } from 'crypto';
|
|
13
13
|
import { createClient } from '@supabase/supabase-js';
|
|
14
14
|
import { readFile, mkdtemp, writeFile, rm } from 'fs/promises';
|
|
15
15
|
import { tmpdir } from 'os';
|
|
@@ -83,7 +83,7 @@ function deriveAuthBaseUrl(proxyUrl2) {
|
|
|
83
83
|
const u = new URL(proxyUrl2);
|
|
84
84
|
return `${u.protocol}//${u.host}`;
|
|
85
85
|
}
|
|
86
|
-
async function performHandshake(proxyUrl2, pubPath, pubText) {
|
|
86
|
+
async function performHandshake(proxyUrl2, apiKey2, pubPath, pubText) {
|
|
87
87
|
const base = deriveAuthBaseUrl(proxyUrl2);
|
|
88
88
|
const challengeRes = await fetch(`${base}/v1/auth/ssh/challenge`, {
|
|
89
89
|
method: "POST",
|
|
@@ -110,6 +110,7 @@ async function performHandshake(proxyUrl2, pubPath, pubText) {
|
|
|
110
110
|
headers: { "Content-Type": "application/json" },
|
|
111
111
|
body: JSON.stringify({
|
|
112
112
|
challenge_id: challenge.challenge_id,
|
|
113
|
+
api_key: apiKey2,
|
|
113
114
|
pubkey: pubText,
|
|
114
115
|
signature: sign.stdout
|
|
115
116
|
})
|
|
@@ -127,10 +128,11 @@ async function performHandshake(proxyUrl2, pubPath, pubText) {
|
|
|
127
128
|
}
|
|
128
129
|
async function runBridge(handshake, proxyUrl2, refresh) {
|
|
129
130
|
let currentToken = handshake.token;
|
|
131
|
+
const requestHeaders = {
|
|
132
|
+
Authorization: `Bearer ${currentToken}`
|
|
133
|
+
};
|
|
130
134
|
const upstreamTransport = new StreamableHTTPClientTransport(new URL(proxyUrl2), {
|
|
131
|
-
requestInit:
|
|
132
|
-
headers: { Authorization: `Bearer ${currentToken}` }
|
|
133
|
-
})
|
|
135
|
+
requestInit: { headers: requestHeaders }
|
|
134
136
|
});
|
|
135
137
|
const upstream = new Client$1({ name: "mg-dashboard-mcp-proxy-bridge", version: "1.0.0" });
|
|
136
138
|
const scheduleRefresh = (result) => {
|
|
@@ -139,6 +141,7 @@ async function runBridge(handshake, proxyUrl2, refresh) {
|
|
|
139
141
|
try {
|
|
140
142
|
const fresh = await refresh();
|
|
141
143
|
currentToken = fresh.token;
|
|
144
|
+
requestHeaders.Authorization = `Bearer ${currentToken}`;
|
|
142
145
|
console.error(`[Proxy] Token refreshed (key: ${fresh.proxyKeyName})`);
|
|
143
146
|
scheduleRefresh(fresh);
|
|
144
147
|
} catch (err) {
|
|
@@ -172,11 +175,16 @@ async function runBridge(handshake, proxyUrl2, refresh) {
|
|
|
172
175
|
}
|
|
173
176
|
async function runProxyMode(args2) {
|
|
174
177
|
const proxyUrl2 = getArg(args2, "proxy-url") || process.env.MG_DASHBOARD_PROXY_URL;
|
|
178
|
+
const apiKey2 = getArg(args2, "api-key") || process.env.MG_DASHBOARD_API_KEY;
|
|
175
179
|
const sshKey = getArg(args2, "ssh-key") || process.env.MG_DASHBOARD_SSH_KEY;
|
|
176
180
|
if (!proxyUrl2) {
|
|
177
181
|
console.error("--proxy-url is required (or set MG_DASHBOARD_PROXY_URL)");
|
|
178
182
|
process.exit(1);
|
|
179
183
|
}
|
|
184
|
+
if (!apiKey2) {
|
|
185
|
+
console.error("--api-key is required in proxy mode (or set MG_DASHBOARD_API_KEY)");
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
180
188
|
if (!sshKey) {
|
|
181
189
|
console.error("--ssh-key is required in proxy mode (or set MG_DASHBOARD_SSH_KEY)");
|
|
182
190
|
process.exit(1);
|
|
@@ -189,7 +197,7 @@ async function runProxyMode(args2) {
|
|
|
189
197
|
console.error(`[Proxy] ${err instanceof Error ? err.message : String(err)}`);
|
|
190
198
|
process.exit(1);
|
|
191
199
|
}
|
|
192
|
-
const refresh = () => performHandshake(proxyUrl2, resolved.pubPath, resolved.pubText);
|
|
200
|
+
const refresh = () => performHandshake(proxyUrl2, apiKey2, resolved.pubPath, resolved.pubText);
|
|
193
201
|
let handshake;
|
|
194
202
|
try {
|
|
195
203
|
handshake = await refresh();
|
|
@@ -1173,8 +1181,8 @@ var encryptionKey = getArg2("encryption-key") || process.env.ENCRYPTION_KEY;
|
|
|
1173
1181
|
var mijnhostApiKey = getArg2("mijnhost-api-key") || process.env.MIJNHOST_API_KEY;
|
|
1174
1182
|
var httpMode = args.includes("--http");
|
|
1175
1183
|
var httpPort = Number(getArg2("port")) || 3100;
|
|
1176
|
-
if (!apiKey
|
|
1177
|
-
console.error("Authentication required. Use --api-key=dk_xxx
|
|
1184
|
+
if (!apiKey || !sshKeyPath) {
|
|
1185
|
+
console.error("Authentication required. Use both --api-key=dk_xxx and --ssh-key=PATH (path to your SSH private or public key), or set MG_DASHBOARD_API_KEY and MG_DASHBOARD_SSH_KEY.");
|
|
1178
1186
|
process.exit(1);
|
|
1179
1187
|
}
|
|
1180
1188
|
if (!supabaseUrl || !supabaseKey) {
|
|
@@ -1466,7 +1474,7 @@ async function sshKeygenVerify(pubkeyLine, signature, challenge, namespace) {
|
|
|
1466
1474
|
await rm(dir, { recursive: true, force: true }).catch(() => void 0);
|
|
1467
1475
|
}
|
|
1468
1476
|
}
|
|
1469
|
-
async function validateSshKey(pubkeyPathInput) {
|
|
1477
|
+
async function validateSshKey(pubkeyPathInput, expectedApiKeyId) {
|
|
1470
1478
|
let pubPath;
|
|
1471
1479
|
let pubText;
|
|
1472
1480
|
try {
|
|
@@ -1513,6 +1521,12 @@ async function validateSshKey(pubkeyPathInput) {
|
|
|
1513
1521
|
);
|
|
1514
1522
|
return null;
|
|
1515
1523
|
}
|
|
1524
|
+
if (keyRow.api_key_id !== expectedApiKeyId) {
|
|
1525
|
+
console.error(
|
|
1526
|
+
`SSH key "${keyRow.name}" is registered, but it is not linked to the provided MCP API key. Add this SSH key under the same MCP API Key entry used by --api-key.`
|
|
1527
|
+
);
|
|
1528
|
+
return null;
|
|
1529
|
+
}
|
|
1516
1530
|
const { data: apiRow, error: apiErr } = await supabase.from("dashboard_mcp_api_key").select("id, name, created_by, allowed_server_ids, is_active, expires_at").eq("id", keyRow.api_key_id).eq("is_active", true).maybeSingle();
|
|
1517
1531
|
if (apiErr || !apiRow) {
|
|
1518
1532
|
console.error("SSH key is linked to an inactive or missing MCP API key entry.");
|
|
@@ -3177,18 +3191,15 @@ function createMcpServer() {
|
|
|
3177
3191
|
var server = createMcpServer();
|
|
3178
3192
|
async function main() {
|
|
3179
3193
|
console.error("Starting MG Dashboard MCP Server...");
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
console.error("API key validation failed");
|
|
3190
|
-
process.exit(1);
|
|
3191
|
-
}
|
|
3194
|
+
const apiAuthContext = await validateApiKey(apiKey);
|
|
3195
|
+
if (!apiAuthContext) {
|
|
3196
|
+
console.error("API key validation failed");
|
|
3197
|
+
process.exit(1);
|
|
3198
|
+
}
|
|
3199
|
+
authContext = await validateSshKey(sshKeyPath, apiAuthContext.apiKeyId);
|
|
3200
|
+
if (!authContext) {
|
|
3201
|
+
console.error("SSH-key authentication failed");
|
|
3202
|
+
process.exit(1);
|
|
3192
3203
|
}
|
|
3193
3204
|
console.error(`[Security] MCP v${MCP_VERSION} | Key: ${authContext.apiKeyName}`);
|
|
3194
3205
|
const toolNames = TOOLS.map((t) => t.name).join(", ");
|