@trops/dash-core 0.1.490 → 0.1.492
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/electron/index.js +347 -62
- package/dist/electron/index.js.map +1 -1
- package/dist/index.esm.js +345 -182
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +345 -182
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/electron/index.js
CHANGED
|
@@ -17,7 +17,7 @@ var require$$0$5 = require('@modelcontextprotocol/sdk/client/index.js');
|
|
|
17
17
|
var require$$1$4 = require('@modelcontextprotocol/sdk/client/stdio.js');
|
|
18
18
|
var require$$0$4 = require('pkce-challenge');
|
|
19
19
|
var require$$2$1 = require('os');
|
|
20
|
-
var require$$
|
|
20
|
+
var require$$11 = require('child_process');
|
|
21
21
|
var require$$3$2 = require('adm-zip');
|
|
22
22
|
var require$$4$1 = require('url');
|
|
23
23
|
var require$$2$2 = require('vm');
|
|
@@ -21522,6 +21522,183 @@ var permissionGate = {
|
|
|
21522
21522
|
PATH_ARG_KEYS,
|
|
21523
21523
|
};
|
|
21524
21524
|
|
|
21525
|
+
/**
|
|
21526
|
+
* mcpServerKey.js
|
|
21527
|
+
*
|
|
21528
|
+
* Slice 3a: per-workspace MCP server process isolation.
|
|
21529
|
+
*
|
|
21530
|
+
* `mcpController.activeServers` was historically keyed by `serverName`
|
|
21531
|
+
* alone, so two workspaces using the same MCP server type (e.g. both
|
|
21532
|
+
* using `filesystem`) shared one process. Slice 3a keys by the
|
|
21533
|
+
* compound `(workspaceId, serverName)` so each workspace gets its
|
|
21534
|
+
* own process. Slice 3b will configure each process with the union
|
|
21535
|
+
* of grants from widgets on that workspace; for now, processes are
|
|
21536
|
+
* simply isolated.
|
|
21537
|
+
*
|
|
21538
|
+
* Format: `<workspaceId>::<serverName>`. The `::` separator is
|
|
21539
|
+
* unlikely to appear in a UUID-shaped workspace id; serverName is
|
|
21540
|
+
* everything after the first `::` so server names containing `::`
|
|
21541
|
+
* round-trip cleanly.
|
|
21542
|
+
*
|
|
21543
|
+
* Callers without a workspace context (legacy IPC, dash MCP server
|
|
21544
|
+
* tools, AI Builder previews) supply `null`/`undefined` and land on
|
|
21545
|
+
* the `NO_WORKSPACE` sentinel bucket — that's the pre-Slice-3 bucket.
|
|
21546
|
+
*
|
|
21547
|
+
* NOTE: workspaceId is renderer-supplied. Slice 3a uses it only as a
|
|
21548
|
+
* process-isolation key, NOT as a trust boundary. Slice 3b will tie
|
|
21549
|
+
* server scope (e.g. filesystem `--allowed` paths) to it; that's when
|
|
21550
|
+
* the trust boundary appears.
|
|
21551
|
+
*/
|
|
21552
|
+
|
|
21553
|
+
const NO_WORKSPACE = "__no_workspace__";
|
|
21554
|
+
const SEP = "::";
|
|
21555
|
+
|
|
21556
|
+
function serverKey$1(workspaceId, serverName) {
|
|
21557
|
+
if (typeof serverName !== "string" || !serverName) {
|
|
21558
|
+
throw new Error("serverKey: serverName is required");
|
|
21559
|
+
}
|
|
21560
|
+
const wid =
|
|
21561
|
+
typeof workspaceId === "string" && workspaceId ? workspaceId : NO_WORKSPACE;
|
|
21562
|
+
return wid + SEP + serverName;
|
|
21563
|
+
}
|
|
21564
|
+
|
|
21565
|
+
function parseServerKey$1(key) {
|
|
21566
|
+
if (typeof key !== "string") {
|
|
21567
|
+
throw new Error("parseServerKey: key must be a string");
|
|
21568
|
+
}
|
|
21569
|
+
const idx = key.indexOf(SEP);
|
|
21570
|
+
if (idx < 0) {
|
|
21571
|
+
throw new Error(
|
|
21572
|
+
"parseServerKey: malformed key (no '::' separator): " + key,
|
|
21573
|
+
);
|
|
21574
|
+
}
|
|
21575
|
+
return {
|
|
21576
|
+
workspaceId: key.slice(0, idx),
|
|
21577
|
+
serverName: key.slice(idx + SEP.length),
|
|
21578
|
+
};
|
|
21579
|
+
}
|
|
21580
|
+
|
|
21581
|
+
var mcpServerKey = {
|
|
21582
|
+
serverKey: serverKey$1,
|
|
21583
|
+
parseServerKey: parseServerKey$1,
|
|
21584
|
+
NO_WORKSPACE,
|
|
21585
|
+
SEP,
|
|
21586
|
+
};
|
|
21587
|
+
|
|
21588
|
+
/**
|
|
21589
|
+
* mcpScopeResolver.js
|
|
21590
|
+
*
|
|
21591
|
+
* Slice 3b: per-workspace path scope reconfiguration.
|
|
21592
|
+
*
|
|
21593
|
+
* The Slice-3a process isolation key (`workspaceId::serverName`) is the
|
|
21594
|
+
* lifecycle handle. This module computes WHAT the spawned process is
|
|
21595
|
+
* configured to see — the union of granted paths from widgets on the
|
|
21596
|
+
* active workspace, applied as credential overrides at spawn time.
|
|
21597
|
+
*
|
|
21598
|
+
* Design:
|
|
21599
|
+
* - The renderer enumerates widgets on the active workspace, looks up
|
|
21600
|
+
* each widget's grant via window.mainApi.widgetMcp.getGrant, and
|
|
21601
|
+
* hands the array to unionPathScope() to compute the workspace
|
|
21602
|
+
* scope for a given server (e.g. "filesystem").
|
|
21603
|
+
* - The renderer passes the resulting scope as `pathScope` to
|
|
21604
|
+
* mcpStartServer.
|
|
21605
|
+
* - mcpController applies the scope to credentials (replacing
|
|
21606
|
+
* allowedPaths etc.) before its existing argsMapping spreads them
|
|
21607
|
+
* into the spawn args. Servers that don't declare argsMapping for
|
|
21608
|
+
* the path keys are unaffected.
|
|
21609
|
+
*
|
|
21610
|
+
* Feature flag: the controller only applies the override when
|
|
21611
|
+
* `security.enforceWidgetMcpPermissions` is on. When off, server starts
|
|
21612
|
+
* with credentials as-configured (pre-3b behavior).
|
|
21613
|
+
*
|
|
21614
|
+
* Out of scope here:
|
|
21615
|
+
* - Hot-respawn on widget add/remove (see Slice 3b plan, deferred).
|
|
21616
|
+
* - Catalog schema for new path-scoped servers (filesystem already
|
|
21617
|
+
* has argsMapping.allowedPaths; others added as discovered).
|
|
21618
|
+
*/
|
|
21619
|
+
|
|
21620
|
+
/**
|
|
21621
|
+
* Compute the workspace-scoped path union for a given server.
|
|
21622
|
+
*
|
|
21623
|
+
* @param {Array<{widgetId, granted}>} grants - widgets-on-workspace + their grants
|
|
21624
|
+
* @param {string} serverName - the MCP server name (e.g. "filesystem")
|
|
21625
|
+
* @returns {{ readPaths: string[], writePaths: string[], allowedPaths: string[] }}
|
|
21626
|
+
*
|
|
21627
|
+
* `allowedPaths` is the dedup union of read+write — used by
|
|
21628
|
+
* filesystem-style servers that take a single allowed-list. Servers
|
|
21629
|
+
* that distinguish read-only vs read-write can use the readPaths /
|
|
21630
|
+
* writePaths arrays directly.
|
|
21631
|
+
*/
|
|
21632
|
+
function unionPathScope(grants, serverName) {
|
|
21633
|
+
const reads = new Set();
|
|
21634
|
+
const writes = new Set();
|
|
21635
|
+
|
|
21636
|
+
if (!Array.isArray(grants)) {
|
|
21637
|
+
return { readPaths: [], writePaths: [], allowedPaths: [] };
|
|
21638
|
+
}
|
|
21639
|
+
|
|
21640
|
+
for (const entry of grants) {
|
|
21641
|
+
if (!entry || typeof entry !== "object") continue;
|
|
21642
|
+
const granted = entry.granted;
|
|
21643
|
+
if (!granted || typeof granted !== "object") continue;
|
|
21644
|
+
const servers = granted.servers;
|
|
21645
|
+
if (!servers || typeof servers !== "object") continue;
|
|
21646
|
+
const serverPerms = servers[serverName];
|
|
21647
|
+
if (!serverPerms || typeof serverPerms !== "object") continue;
|
|
21648
|
+
|
|
21649
|
+
if (Array.isArray(serverPerms.readPaths)) {
|
|
21650
|
+
for (const p of serverPerms.readPaths) {
|
|
21651
|
+
if (typeof p === "string" && p) reads.add(p);
|
|
21652
|
+
}
|
|
21653
|
+
}
|
|
21654
|
+
if (Array.isArray(serverPerms.writePaths)) {
|
|
21655
|
+
for (const p of serverPerms.writePaths) {
|
|
21656
|
+
if (typeof p === "string" && p) writes.add(p);
|
|
21657
|
+
}
|
|
21658
|
+
}
|
|
21659
|
+
}
|
|
21660
|
+
|
|
21661
|
+
const readPaths = [...reads];
|
|
21662
|
+
const writePaths = [...writes];
|
|
21663
|
+
const allowedPaths = [...new Set([...reads, ...writes])];
|
|
21664
|
+
|
|
21665
|
+
return { readPaths, writePaths, allowedPaths };
|
|
21666
|
+
}
|
|
21667
|
+
|
|
21668
|
+
/**
|
|
21669
|
+
* Override credential keys with values derived from a path scope.
|
|
21670
|
+
*
|
|
21671
|
+
* Filesystem-style servers expect `allowedPaths` as a comma-separated
|
|
21672
|
+
* string (the catalog's `argsMapping.allowedPaths.split` then expands
|
|
21673
|
+
* it back into positional args at spawn time). This helper joins the
|
|
21674
|
+
* scope's allowedPaths to match that convention.
|
|
21675
|
+
*
|
|
21676
|
+
* Returns a NEW credentials object — does not mutate the input.
|
|
21677
|
+
*
|
|
21678
|
+
* If pathScope is empty (no granted paths at all), the existing
|
|
21679
|
+
* credentials are returned unchanged so the user's globally-configured
|
|
21680
|
+
* allowedPaths still works for the LLM tool path / NO_WORKSPACE bucket.
|
|
21681
|
+
*/
|
|
21682
|
+
function applyPathScopeToCredentials$1(credentials, pathScope) {
|
|
21683
|
+
const base =
|
|
21684
|
+
credentials && typeof credentials === "object" ? { ...credentials } : {};
|
|
21685
|
+
|
|
21686
|
+
if (!pathScope || typeof pathScope !== "object") return base;
|
|
21687
|
+
|
|
21688
|
+
const allowed = Array.isArray(pathScope.allowedPaths)
|
|
21689
|
+
? pathScope.allowedPaths
|
|
21690
|
+
: [];
|
|
21691
|
+
if (allowed.length === 0) return base;
|
|
21692
|
+
|
|
21693
|
+
base.allowedPaths = allowed.join(",");
|
|
21694
|
+
return base;
|
|
21695
|
+
}
|
|
21696
|
+
|
|
21697
|
+
var mcpScopeResolver = {
|
|
21698
|
+
unionPathScope,
|
|
21699
|
+
applyPathScopeToCredentials: applyPathScopeToCredentials$1,
|
|
21700
|
+
};
|
|
21701
|
+
|
|
21525
21702
|
/**
|
|
21526
21703
|
* mcpController.js
|
|
21527
21704
|
*
|
|
@@ -21547,6 +21724,8 @@ const fs$9 = require$$0$2;
|
|
|
21547
21724
|
const os$2 = require$$2$1;
|
|
21548
21725
|
const responseCache$2 = responseCache_1;
|
|
21549
21726
|
const { gateToolCall } = permissionGate;
|
|
21727
|
+
const { serverKey, parseServerKey } = mcpServerKey;
|
|
21728
|
+
const { applyPathScopeToCredentials } = mcpScopeResolver;
|
|
21550
21729
|
const { app: app$7 } = require$$0$1;
|
|
21551
21730
|
|
|
21552
21731
|
// Read the widget-MCP-enforcement feature flag from settings.json.
|
|
@@ -21703,7 +21882,7 @@ function getShellPath$1() {
|
|
|
21703
21882
|
return _shellPath$1;
|
|
21704
21883
|
}
|
|
21705
21884
|
|
|
21706
|
-
const { execSync } = require$$
|
|
21885
|
+
const { execSync } = require$$11;
|
|
21707
21886
|
const fallbackDirs = ["/usr/local/bin", "/opt/homebrew/bin"];
|
|
21708
21887
|
|
|
21709
21888
|
// Scan nvm versions, tracking both latest and best compatible version
|
|
@@ -21936,17 +22115,50 @@ const mcpController$3 = {
|
|
|
21936
22115
|
* startServer
|
|
21937
22116
|
* Start an MCP server with the given config and credentials
|
|
21938
22117
|
*
|
|
22118
|
+
* Slice 3a: server instances are keyed by `(workspaceId, serverName)`
|
|
22119
|
+
* so two workspaces using the same server type get separate processes.
|
|
22120
|
+
* Pass `null`/`undefined` workspaceId to land on the legacy
|
|
22121
|
+
* NO_WORKSPACE bucket (e.g. dash MCP server tools, AI Builder previews).
|
|
22122
|
+
*
|
|
22123
|
+
* Slice 3b: when `security.enforceWidgetMcpPermissions` is on AND a
|
|
22124
|
+
* non-empty `pathScope` is supplied, the scope's allowed paths
|
|
22125
|
+
* override the server's existing path-style credentials before the
|
|
22126
|
+
* catalog's argsMapping spreads them into spawn args. This scopes
|
|
22127
|
+
* the server's OS-level capability to the union of widget grants on
|
|
22128
|
+
* the active workspace.
|
|
22129
|
+
*
|
|
21939
22130
|
* @param {BrowserWindow} win the main window
|
|
21940
22131
|
* @param {string} serverName unique name for this server instance
|
|
21941
22132
|
* @param {object} mcpConfig { transport, command, args, envMapping }
|
|
21942
22133
|
* @param {object} credentials decrypted credentials object
|
|
22134
|
+
* @param {string|null} workspaceId active workspace id (Slice 3a)
|
|
22135
|
+
* @param {object|null} pathScope { readPaths, writePaths, allowedPaths } (Slice 3b)
|
|
21943
22136
|
* @returns {{ success, serverName, tools, status } | { error, message }}
|
|
21944
22137
|
*/
|
|
21945
|
-
startServer: async (
|
|
22138
|
+
startServer: async (
|
|
22139
|
+
win,
|
|
22140
|
+
serverName,
|
|
22141
|
+
mcpConfig,
|
|
22142
|
+
credentials,
|
|
22143
|
+
workspaceId,
|
|
22144
|
+
pathScope = null,
|
|
22145
|
+
) => {
|
|
22146
|
+
const key = serverKey(workspaceId, serverName);
|
|
22147
|
+
// Slice 3b: when the gate is enforced, override credentials with
|
|
22148
|
+
// the workspace's union of granted paths so the spawned process
|
|
22149
|
+
// can't see anything broader than the user has consented to.
|
|
22150
|
+
if (
|
|
22151
|
+
isWidgetPermissionEnforcementEnabled() &&
|
|
22152
|
+
pathScope &&
|
|
22153
|
+
Array.isArray(pathScope.allowedPaths) &&
|
|
22154
|
+
pathScope.allowedPaths.length > 0
|
|
22155
|
+
) {
|
|
22156
|
+
credentials = applyPathScopeToCredentials(credentials, pathScope);
|
|
22157
|
+
}
|
|
21946
22158
|
// 1. Already connected? Return existing connection
|
|
21947
|
-
const existing = activeServers.get(
|
|
22159
|
+
const existing = activeServers.get(key);
|
|
21948
22160
|
if (existing && existing.status === STATUS$1.CONNECTED && existing.client) {
|
|
21949
|
-
console.log(`[mcpController] Server already connected: ${
|
|
22161
|
+
console.log(`[mcpController] Server already connected: ${key}`);
|
|
21950
22162
|
return {
|
|
21951
22163
|
success: true,
|
|
21952
22164
|
serverName,
|
|
@@ -21957,19 +22169,19 @@ const mcpController$3 = {
|
|
|
21957
22169
|
}
|
|
21958
22170
|
|
|
21959
22171
|
// 2. Already starting? Piggyback on the pending promise
|
|
21960
|
-
if (pendingStarts.has(
|
|
22172
|
+
if (pendingStarts.has(key)) {
|
|
21961
22173
|
console.log(
|
|
21962
|
-
`[mcpController] Server already starting, deduplicating: ${
|
|
22174
|
+
`[mcpController] Server already starting, deduplicating: ${key}`,
|
|
21963
22175
|
);
|
|
21964
|
-
return pendingStarts.get(
|
|
22176
|
+
return pendingStarts.get(key);
|
|
21965
22177
|
}
|
|
21966
22178
|
|
|
21967
22179
|
// 3. Fresh start — wrap in a promise and track it
|
|
21968
22180
|
const startPromise = (async () => {
|
|
21969
22181
|
try {
|
|
21970
22182
|
// Stop if in stale/error state
|
|
21971
|
-
if (activeServers.has(
|
|
21972
|
-
await mcpController$3.stopServer(win, serverName);
|
|
22183
|
+
if (activeServers.has(key)) {
|
|
22184
|
+
await mcpController$3.stopServer(win, serverName, workspaceId);
|
|
21973
22185
|
}
|
|
21974
22186
|
|
|
21975
22187
|
// Merge with catalog entry to pick up updated command/args
|
|
@@ -22100,12 +22312,14 @@ const mcpController$3 = {
|
|
|
22100
22312
|
}
|
|
22101
22313
|
|
|
22102
22314
|
// Update status to connecting
|
|
22103
|
-
activeServers.set(
|
|
22315
|
+
activeServers.set(key, {
|
|
22104
22316
|
client: null,
|
|
22105
22317
|
transport,
|
|
22106
22318
|
tools: [],
|
|
22107
22319
|
resources: [],
|
|
22108
22320
|
status: STATUS$1.CONNECTING,
|
|
22321
|
+
workspaceId: workspaceId || null,
|
|
22322
|
+
serverName,
|
|
22109
22323
|
});
|
|
22110
22324
|
|
|
22111
22325
|
// Create MCP client
|
|
@@ -22139,16 +22353,18 @@ const mcpController$3 = {
|
|
|
22139
22353
|
}
|
|
22140
22354
|
|
|
22141
22355
|
// Store the active connection
|
|
22142
|
-
activeServers.set(
|
|
22356
|
+
activeServers.set(key, {
|
|
22143
22357
|
client,
|
|
22144
22358
|
transport,
|
|
22145
22359
|
tools,
|
|
22146
22360
|
resources,
|
|
22147
22361
|
status: STATUS$1.CONNECTED,
|
|
22362
|
+
workspaceId: workspaceId || null,
|
|
22363
|
+
serverName,
|
|
22148
22364
|
});
|
|
22149
22365
|
|
|
22150
22366
|
console.log(
|
|
22151
|
-
`[mcpController] Server connected: ${
|
|
22367
|
+
`[mcpController] Server connected: ${key} (${tools.length} tools, ${resources.length} resources)`,
|
|
22152
22368
|
);
|
|
22153
22369
|
|
|
22154
22370
|
return {
|
|
@@ -22173,13 +22389,15 @@ const mcpController$3 = {
|
|
|
22173
22389
|
}
|
|
22174
22390
|
|
|
22175
22391
|
// Mark as error state
|
|
22176
|
-
activeServers.set(
|
|
22392
|
+
activeServers.set(key, {
|
|
22177
22393
|
client: null,
|
|
22178
22394
|
transport: null,
|
|
22179
22395
|
tools: [],
|
|
22180
22396
|
resources: [],
|
|
22181
22397
|
status: STATUS$1.ERROR,
|
|
22182
22398
|
error: errorMessage,
|
|
22399
|
+
workspaceId: workspaceId || null,
|
|
22400
|
+
serverName,
|
|
22183
22401
|
});
|
|
22184
22402
|
|
|
22185
22403
|
return {
|
|
@@ -22189,11 +22407,11 @@ const mcpController$3 = {
|
|
|
22189
22407
|
status: STATUS$1.ERROR,
|
|
22190
22408
|
};
|
|
22191
22409
|
} finally {
|
|
22192
|
-
pendingStarts.delete(
|
|
22410
|
+
pendingStarts.delete(key);
|
|
22193
22411
|
}
|
|
22194
22412
|
})();
|
|
22195
22413
|
|
|
22196
|
-
pendingStarts.set(
|
|
22414
|
+
pendingStarts.set(key, startPromise);
|
|
22197
22415
|
return startPromise;
|
|
22198
22416
|
},
|
|
22199
22417
|
|
|
@@ -22203,20 +22421,22 @@ const mcpController$3 = {
|
|
|
22203
22421
|
*
|
|
22204
22422
|
* @param {BrowserWindow} win the main window
|
|
22205
22423
|
* @param {string} serverName the server to stop
|
|
22424
|
+
* @param {string|null} workspaceId active workspace id (Slice 3a)
|
|
22206
22425
|
* @returns {{ success, serverName } | { error, message }}
|
|
22207
22426
|
*/
|
|
22208
|
-
stopServer: async (win, serverName) => {
|
|
22427
|
+
stopServer: async (win, serverName, workspaceId) => {
|
|
22428
|
+
const key = serverKey(workspaceId, serverName);
|
|
22209
22429
|
try {
|
|
22210
22430
|
// Wait for any in-flight start to finish before stopping
|
|
22211
|
-
if (pendingStarts.has(
|
|
22431
|
+
if (pendingStarts.has(key)) {
|
|
22212
22432
|
try {
|
|
22213
|
-
await pendingStarts.get(
|
|
22433
|
+
await pendingStarts.get(key);
|
|
22214
22434
|
} catch (e) {
|
|
22215
22435
|
/* stopping anyway */
|
|
22216
22436
|
}
|
|
22217
22437
|
}
|
|
22218
22438
|
|
|
22219
|
-
const server = activeServers.get(
|
|
22439
|
+
const server = activeServers.get(key);
|
|
22220
22440
|
if (!server) {
|
|
22221
22441
|
return {
|
|
22222
22442
|
success: true,
|
|
@@ -22225,7 +22445,7 @@ const mcpController$3 = {
|
|
|
22225
22445
|
};
|
|
22226
22446
|
}
|
|
22227
22447
|
|
|
22228
|
-
console.log(`[mcpController] Stopping server: ${
|
|
22448
|
+
console.log(`[mcpController] Stopping server: ${key}`);
|
|
22229
22449
|
|
|
22230
22450
|
// Close the client connection
|
|
22231
22451
|
if (server.client) {
|
|
@@ -22233,27 +22453,24 @@ const mcpController$3 = {
|
|
|
22233
22453
|
await server.client.close();
|
|
22234
22454
|
} catch (closeError) {
|
|
22235
22455
|
console.warn(
|
|
22236
|
-
`[mcpController] Error closing client for ${
|
|
22456
|
+
`[mcpController] Error closing client for ${key}:`,
|
|
22237
22457
|
closeError.message,
|
|
22238
22458
|
);
|
|
22239
22459
|
}
|
|
22240
22460
|
}
|
|
22241
22461
|
|
|
22242
|
-
activeServers.delete(
|
|
22462
|
+
activeServers.delete(key);
|
|
22243
22463
|
|
|
22244
|
-
console.log(`[mcpController] Server stopped: ${
|
|
22464
|
+
console.log(`[mcpController] Server stopped: ${key}`);
|
|
22245
22465
|
|
|
22246
22466
|
return {
|
|
22247
22467
|
success: true,
|
|
22248
22468
|
serverName,
|
|
22249
22469
|
};
|
|
22250
22470
|
} catch (error) {
|
|
22251
|
-
console.error(
|
|
22252
|
-
`[mcpController] Error stopping server ${serverName}:`,
|
|
22253
|
-
error,
|
|
22254
|
-
);
|
|
22471
|
+
console.error(`[mcpController] Error stopping server ${key}:`, error);
|
|
22255
22472
|
// Clean up anyway
|
|
22256
|
-
activeServers.delete(
|
|
22473
|
+
activeServers.delete(key);
|
|
22257
22474
|
return {
|
|
22258
22475
|
error: true,
|
|
22259
22476
|
message: error.message,
|
|
@@ -22270,6 +22487,10 @@ const mcpController$3 = {
|
|
|
22270
22487
|
* @param {string} toolName the tool to call
|
|
22271
22488
|
* @param {object} args arguments for the tool
|
|
22272
22489
|
* @param {Array<string>} allowedTools optional whitelist of allowed tool names
|
|
22490
|
+
* @param {string|null} widgetId the widget originating the call (Slice 1+2)
|
|
22491
|
+
* @param {string|null} workspaceId the active workspace (Slice 3a) — used
|
|
22492
|
+
* to scope the server process per workspace.
|
|
22493
|
+
* Slice 3b will tie path scope to this id.
|
|
22273
22494
|
* @returns {{ result } | { error, message }}
|
|
22274
22495
|
*/
|
|
22275
22496
|
callTool: async (
|
|
@@ -22279,11 +22500,13 @@ const mcpController$3 = {
|
|
|
22279
22500
|
args,
|
|
22280
22501
|
allowedTools = null,
|
|
22281
22502
|
widgetId = null,
|
|
22503
|
+
workspaceId = null,
|
|
22282
22504
|
) => {
|
|
22505
|
+
const key = serverKey(workspaceId, serverName);
|
|
22283
22506
|
try {
|
|
22284
|
-
const server = activeServers.get(
|
|
22507
|
+
const server = activeServers.get(key);
|
|
22285
22508
|
if (!server || !server.client) {
|
|
22286
|
-
throw new Error(`Server not connected: ${
|
|
22509
|
+
throw new Error(`Server not connected: ${key}`);
|
|
22287
22510
|
}
|
|
22288
22511
|
|
|
22289
22512
|
// Per-widget manifest gate. Activated by the
|
|
@@ -22316,7 +22539,7 @@ const mcpController$3 = {
|
|
|
22316
22539
|
}
|
|
22317
22540
|
|
|
22318
22541
|
const doCall = async () => {
|
|
22319
|
-
console.log(`[mcpController] Calling tool: ${
|
|
22542
|
+
console.log(`[mcpController] Calling tool: ${key}/${toolName}`);
|
|
22320
22543
|
const result = await server.client.callTool({
|
|
22321
22544
|
name: toolName,
|
|
22322
22545
|
arguments: args || {},
|
|
@@ -22327,18 +22550,21 @@ const mcpController$3 = {
|
|
|
22327
22550
|
};
|
|
22328
22551
|
};
|
|
22329
22552
|
|
|
22330
|
-
// Cache read-only tool calls with in-flight dedup.
|
|
22331
|
-
//
|
|
22553
|
+
// Cache read-only tool calls with in-flight dedup. Cache key is
|
|
22554
|
+
// scoped per (workspace, server, tool, args) so two workspaces
|
|
22555
|
+
// calling the same read on the same server type don't share a
|
|
22556
|
+
// cached response — they're separate processes with potentially
|
|
22557
|
+
// different scopes (Slice 3b will make that explicit).
|
|
22332
22558
|
if (isReadOnlyTool(toolName)) {
|
|
22333
|
-
const
|
|
22334
|
-
return responseCache$2.get(
|
|
22559
|
+
const cacheKey = `mcp:${key}:${toolName}:${JSON.stringify(args || {})}`;
|
|
22560
|
+
return responseCache$2.get(cacheKey, doCall, {
|
|
22335
22561
|
ttl: DEFAULT_TOOL_CACHE_TTL,
|
|
22336
22562
|
});
|
|
22337
22563
|
}
|
|
22338
22564
|
|
|
22339
22565
|
// Write/mutation: invalidate any cached reads for this server
|
|
22340
22566
|
// (safest default — broad invalidation when state changes)
|
|
22341
|
-
responseCache$2.invalidatePrefix(`mcp:${
|
|
22567
|
+
responseCache$2.invalidatePrefix(`mcp:${key}:`);
|
|
22342
22568
|
return doCall();
|
|
22343
22569
|
} catch (error) {
|
|
22344
22570
|
console.error(
|
|
@@ -22358,13 +22584,15 @@ const mcpController$3 = {
|
|
|
22358
22584
|
*
|
|
22359
22585
|
* @param {BrowserWindow} win the main window
|
|
22360
22586
|
* @param {string} serverName the server name
|
|
22587
|
+
* @param {string|null} workspaceId active workspace id (Slice 3a)
|
|
22361
22588
|
* @returns {{ tools } | { error, message }}
|
|
22362
22589
|
*/
|
|
22363
|
-
listTools: async (win, serverName) => {
|
|
22590
|
+
listTools: async (win, serverName, workspaceId) => {
|
|
22591
|
+
const key = serverKey(workspaceId, serverName);
|
|
22364
22592
|
try {
|
|
22365
|
-
const server = activeServers.get(
|
|
22593
|
+
const server = activeServers.get(key);
|
|
22366
22594
|
if (!server || !server.client) {
|
|
22367
|
-
throw new Error(`Server not connected: ${
|
|
22595
|
+
throw new Error(`Server not connected: ${key}`);
|
|
22368
22596
|
}
|
|
22369
22597
|
|
|
22370
22598
|
// Refresh tool list from server
|
|
@@ -22396,13 +22624,15 @@ const mcpController$3 = {
|
|
|
22396
22624
|
*
|
|
22397
22625
|
* @param {BrowserWindow} win the main window
|
|
22398
22626
|
* @param {string} serverName the server name
|
|
22627
|
+
* @param {string|null} workspaceId active workspace id (Slice 3a)
|
|
22399
22628
|
* @returns {{ resources } | { error, message }}
|
|
22400
22629
|
*/
|
|
22401
|
-
listResources: async (win, serverName) => {
|
|
22630
|
+
listResources: async (win, serverName, workspaceId) => {
|
|
22631
|
+
const key = serverKey(workspaceId, serverName);
|
|
22402
22632
|
try {
|
|
22403
|
-
const server = activeServers.get(
|
|
22633
|
+
const server = activeServers.get(key);
|
|
22404
22634
|
if (!server || !server.client) {
|
|
22405
|
-
throw new Error(`Server not connected: ${
|
|
22635
|
+
throw new Error(`Server not connected: ${key}`);
|
|
22406
22636
|
}
|
|
22407
22637
|
|
|
22408
22638
|
const resourcesResult = await server.client.listResources();
|
|
@@ -22434,13 +22664,15 @@ const mcpController$3 = {
|
|
|
22434
22664
|
* @param {BrowserWindow} win the main window
|
|
22435
22665
|
* @param {string} serverName the server name
|
|
22436
22666
|
* @param {string} uri the resource URI
|
|
22667
|
+
* @param {string|null} workspaceId active workspace id (Slice 3a)
|
|
22437
22668
|
* @returns {{ resource } | { error, message }}
|
|
22438
22669
|
*/
|
|
22439
|
-
readResource: async (win, serverName, uri) => {
|
|
22670
|
+
readResource: async (win, serverName, uri, workspaceId) => {
|
|
22671
|
+
const key = serverKey(workspaceId, serverName);
|
|
22440
22672
|
try {
|
|
22441
|
-
const server = activeServers.get(
|
|
22673
|
+
const server = activeServers.get(key);
|
|
22442
22674
|
if (!server || !server.client) {
|
|
22443
|
-
throw new Error(`Server not connected: ${
|
|
22675
|
+
throw new Error(`Server not connected: ${key}`);
|
|
22444
22676
|
}
|
|
22445
22677
|
|
|
22446
22678
|
const result = await server.client.readResource({ uri });
|
|
@@ -22469,8 +22701,9 @@ const mcpController$3 = {
|
|
|
22469
22701
|
* @param {string} serverName the server name
|
|
22470
22702
|
* @returns {{ status, tools, error }}
|
|
22471
22703
|
*/
|
|
22472
|
-
getServerStatus: (win, serverName) => {
|
|
22473
|
-
const
|
|
22704
|
+
getServerStatus: (win, serverName, workspaceId) => {
|
|
22705
|
+
const key = serverKey(workspaceId, serverName);
|
|
22706
|
+
const server = activeServers.get(key);
|
|
22474
22707
|
if (!server) {
|
|
22475
22708
|
return {
|
|
22476
22709
|
serverName,
|
|
@@ -22603,7 +22836,7 @@ const mcpController$3 = {
|
|
|
22603
22836
|
* @returns {{ success } | { error, message }}
|
|
22604
22837
|
*/
|
|
22605
22838
|
runAuth: async (win, mcpConfig, credentials, authCommand) => {
|
|
22606
|
-
const { spawn } = require$$
|
|
22839
|
+
const { spawn } = require$$11;
|
|
22607
22840
|
|
|
22608
22841
|
const env = cleanEnvForChildProcess();
|
|
22609
22842
|
|
|
@@ -22732,12 +22965,39 @@ const mcpController$3 = {
|
|
|
22732
22965
|
`[mcpController] Stopping all servers (${activeServers.size} active)`,
|
|
22733
22966
|
);
|
|
22734
22967
|
const promises = [];
|
|
22735
|
-
|
|
22736
|
-
|
|
22968
|
+
// Slice 3a: keys are compound `(workspaceId, serverName)`. Parse
|
|
22969
|
+
// to call stopServer with the original args.
|
|
22970
|
+
for (const [key] of activeServers) {
|
|
22971
|
+
const { workspaceId, serverName } = parseServerKey(key);
|
|
22972
|
+
promises.push(mcpController$3.stopServer(null, serverName, workspaceId));
|
|
22737
22973
|
}
|
|
22738
22974
|
await Promise.allSettled(promises);
|
|
22739
22975
|
console.log("[mcpController] All servers stopped");
|
|
22740
22976
|
},
|
|
22977
|
+
|
|
22978
|
+
/**
|
|
22979
|
+
* stopServersForWorkspace
|
|
22980
|
+
* Stop every server keyed under the given workspaceId. Called when
|
|
22981
|
+
* a workspace unmounts so its scoped MCP processes don't leak.
|
|
22982
|
+
*
|
|
22983
|
+
* @param {string} workspaceId the workspace whose servers to stop
|
|
22984
|
+
*/
|
|
22985
|
+
stopServersForWorkspace: async (workspaceId) => {
|
|
22986
|
+
if (!workspaceId) return;
|
|
22987
|
+
const promises = [];
|
|
22988
|
+
for (const [key] of activeServers) {
|
|
22989
|
+
const parsed = parseServerKey(key);
|
|
22990
|
+
if (parsed.workspaceId !== workspaceId) continue;
|
|
22991
|
+
promises.push(
|
|
22992
|
+
mcpController$3.stopServer(null, parsed.serverName, workspaceId),
|
|
22993
|
+
);
|
|
22994
|
+
}
|
|
22995
|
+
if (promises.length === 0) return;
|
|
22996
|
+
console.log(
|
|
22997
|
+
`[mcpController] Stopping ${promises.length} server(s) for workspace ${workspaceId}`,
|
|
22998
|
+
);
|
|
22999
|
+
await Promise.allSettled(promises);
|
|
23000
|
+
},
|
|
22741
23001
|
};
|
|
22742
23002
|
|
|
22743
23003
|
mcpController$4.exports = mcpController$3;
|
|
@@ -47743,7 +48003,7 @@ var mcpDashServerController_1 = mcpDashServerController$4;
|
|
|
47743
48003
|
* can use the Chat widget without a separate API key.
|
|
47744
48004
|
*/
|
|
47745
48005
|
|
|
47746
|
-
const { spawn, execSync } = require$$
|
|
48006
|
+
const { spawn, execSync } = require$$11;
|
|
47747
48007
|
const {
|
|
47748
48008
|
LLM_STREAM_DELTA: LLM_STREAM_DELTA$2,
|
|
47749
48009
|
LLM_STREAM_TOOL_CALL: LLM_STREAM_TOOL_CALL$2,
|
|
@@ -62132,13 +62392,27 @@ const mcpApi$2 = {
|
|
|
62132
62392
|
* @param {string} serverName unique name for this server instance
|
|
62133
62393
|
* @param {object} mcpConfig { transport, command, args, envMapping }
|
|
62134
62394
|
* @param {object} credentials decrypted credentials object
|
|
62395
|
+
* @param {string|null} workspaceId active workspace id (Slice 3a) —
|
|
62396
|
+
* server processes are keyed per workspace.
|
|
62397
|
+
* @param {object|null} pathScope (Slice 3b) — when provided, the
|
|
62398
|
+
* workspace's union of granted paths overrides the server's
|
|
62399
|
+
* path-style credentials at spawn time. Shape:
|
|
62400
|
+
* `{ readPaths, writePaths, allowedPaths }`.
|
|
62135
62401
|
* @returns {Promise<{ success, serverName, tools, status } | { error, message }>}
|
|
62136
62402
|
*/
|
|
62137
|
-
startServer: (
|
|
62403
|
+
startServer: (
|
|
62404
|
+
serverName,
|
|
62405
|
+
mcpConfig,
|
|
62406
|
+
credentials,
|
|
62407
|
+
workspaceId = null,
|
|
62408
|
+
pathScope = null,
|
|
62409
|
+
) =>
|
|
62138
62410
|
ipcRenderer$i.invoke(MCP_START_SERVER, {
|
|
62139
62411
|
serverName,
|
|
62140
62412
|
mcpConfig,
|
|
62141
62413
|
credentials,
|
|
62414
|
+
workspaceId,
|
|
62415
|
+
pathScope,
|
|
62142
62416
|
}),
|
|
62143
62417
|
|
|
62144
62418
|
/**
|
|
@@ -62146,19 +62420,22 @@ const mcpApi$2 = {
|
|
|
62146
62420
|
* Stop a running MCP server
|
|
62147
62421
|
*
|
|
62148
62422
|
* @param {string} serverName the server to stop
|
|
62423
|
+
* @param {string|null} workspaceId active workspace id (Slice 3a)
|
|
62149
62424
|
* @returns {Promise<{ success, serverName } | { error, message }>}
|
|
62150
62425
|
*/
|
|
62151
|
-
stopServer: (serverName) =>
|
|
62152
|
-
ipcRenderer$i.invoke(MCP_STOP_SERVER, { serverName }),
|
|
62426
|
+
stopServer: (serverName, workspaceId = null) =>
|
|
62427
|
+
ipcRenderer$i.invoke(MCP_STOP_SERVER, { serverName, workspaceId }),
|
|
62153
62428
|
|
|
62154
62429
|
/**
|
|
62155
62430
|
* listTools
|
|
62156
62431
|
* List available tools for a running MCP server
|
|
62157
62432
|
*
|
|
62158
62433
|
* @param {string} serverName the server name
|
|
62434
|
+
* @param {string|null} workspaceId active workspace id (Slice 3a)
|
|
62159
62435
|
* @returns {Promise<{ tools } | { error, message }>}
|
|
62160
62436
|
*/
|
|
62161
|
-
listTools: (serverName
|
|
62437
|
+
listTools: (serverName, workspaceId = null) =>
|
|
62438
|
+
ipcRenderer$i.invoke(MCP_LIST_TOOLS, { serverName, workspaceId }),
|
|
62162
62439
|
|
|
62163
62440
|
/**
|
|
62164
62441
|
* callTool
|
|
@@ -62173,6 +62450,9 @@ const mcpApi$2 = {
|
|
|
62173
62450
|
* used to look up the widget's MCP permission manifest and gate
|
|
62174
62451
|
* the call accordingly. Should be the npm package name of the
|
|
62175
62452
|
* calling widget (e.g. "@trops/notes-summarizer").
|
|
62453
|
+
* @param {string|null} workspaceId active workspace id (Slice 3a) —
|
|
62454
|
+
* the server process is scoped per (workspace, server). Slice 3b
|
|
62455
|
+
* will tie path scope to this id.
|
|
62176
62456
|
* @returns {Promise<{ result } | { error, message }>}
|
|
62177
62457
|
*/
|
|
62178
62458
|
callTool: (
|
|
@@ -62181,6 +62461,7 @@ const mcpApi$2 = {
|
|
|
62181
62461
|
args,
|
|
62182
62462
|
allowedTools = null,
|
|
62183
62463
|
widgetId = null,
|
|
62464
|
+
workspaceId = null,
|
|
62184
62465
|
) =>
|
|
62185
62466
|
ipcRenderer$i.invoke(MCP_CALL_TOOL, {
|
|
62186
62467
|
serverName,
|
|
@@ -62188,6 +62469,7 @@ const mcpApi$2 = {
|
|
|
62188
62469
|
args,
|
|
62189
62470
|
allowedTools,
|
|
62190
62471
|
widgetId,
|
|
62472
|
+
workspaceId,
|
|
62191
62473
|
}),
|
|
62192
62474
|
|
|
62193
62475
|
/**
|
|
@@ -62195,10 +62477,11 @@ const mcpApi$2 = {
|
|
|
62195
62477
|
* List available resources for a running MCP server
|
|
62196
62478
|
*
|
|
62197
62479
|
* @param {string} serverName the server name
|
|
62480
|
+
* @param {string|null} workspaceId active workspace id (Slice 3a)
|
|
62198
62481
|
* @returns {Promise<{ resources } | { error, message }>}
|
|
62199
62482
|
*/
|
|
62200
|
-
listResources: (serverName) =>
|
|
62201
|
-
ipcRenderer$i.invoke(MCP_LIST_RESOURCES, { serverName }),
|
|
62483
|
+
listResources: (serverName, workspaceId = null) =>
|
|
62484
|
+
ipcRenderer$i.invoke(MCP_LIST_RESOURCES, { serverName, workspaceId }),
|
|
62202
62485
|
|
|
62203
62486
|
/**
|
|
62204
62487
|
* readResource
|
|
@@ -62206,20 +62489,22 @@ const mcpApi$2 = {
|
|
|
62206
62489
|
*
|
|
62207
62490
|
* @param {string} serverName the server name
|
|
62208
62491
|
* @param {string} uri the resource URI
|
|
62492
|
+
* @param {string|null} workspaceId active workspace id (Slice 3a)
|
|
62209
62493
|
* @returns {Promise<{ resource } | { error, message }>}
|
|
62210
62494
|
*/
|
|
62211
|
-
readResource: (serverName, uri) =>
|
|
62212
|
-
ipcRenderer$i.invoke(MCP_READ_RESOURCE, { serverName, uri }),
|
|
62495
|
+
readResource: (serverName, uri, workspaceId = null) =>
|
|
62496
|
+
ipcRenderer$i.invoke(MCP_READ_RESOURCE, { serverName, uri, workspaceId }),
|
|
62213
62497
|
|
|
62214
62498
|
/**
|
|
62215
62499
|
* getServerStatus
|
|
62216
62500
|
* Get the connection status of a server
|
|
62217
62501
|
*
|
|
62218
62502
|
* @param {string} serverName the server name
|
|
62503
|
+
* @param {string|null} workspaceId active workspace id (Slice 3a)
|
|
62219
62504
|
* @returns {Promise<{ status, tools, error }>}
|
|
62220
62505
|
*/
|
|
62221
|
-
getServerStatus: (serverName) =>
|
|
62222
|
-
ipcRenderer$i.invoke(MCP_SERVER_STATUS, { serverName }),
|
|
62506
|
+
getServerStatus: (serverName, workspaceId = null) =>
|
|
62507
|
+
ipcRenderer$i.invoke(MCP_SERVER_STATUS, { serverName, workspaceId }),
|
|
62223
62508
|
|
|
62224
62509
|
/**
|
|
62225
62510
|
* getCatalog
|