@trops/dash-core 0.1.446 → 0.1.448

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.
@@ -424,6 +424,11 @@ const MCP_GET_CATALOG$1 = "mcp-get-catalog";
424
424
  const MCP_GET_CATALOG_COMPLETE = "mcp-get-catalog-complete";
425
425
  const MCP_GET_CATALOG_ERROR = "mcp-get-catalog-error";
426
426
 
427
+ const MCP_GET_KNOWN_EXTERNAL$1 = "mcp-get-known-external";
428
+
429
+ const MCP_INSTALL_KNOWN_EXTERNAL_CONFIRM$2 = "mcp-install-known-external-confirm";
430
+ const MCP_INSTALL_KNOWN_EXTERNAL_RESULT$2 = "mcp-install-known-external-result";
431
+
427
432
  const MCP_RUN_AUTH$1 = "mcp-run-auth";
428
433
  const MCP_RUN_AUTH_COMPLETE = "mcp-run-auth-complete";
429
434
  const MCP_RUN_AUTH_ERROR = "mcp-run-auth-error";
@@ -453,6 +458,9 @@ var mcpEvents$1 = {
453
458
  MCP_GET_CATALOG: MCP_GET_CATALOG$1,
454
459
  MCP_GET_CATALOG_COMPLETE,
455
460
  MCP_GET_CATALOG_ERROR,
461
+ MCP_GET_KNOWN_EXTERNAL: MCP_GET_KNOWN_EXTERNAL$1,
462
+ MCP_INSTALL_KNOWN_EXTERNAL_CONFIRM: MCP_INSTALL_KNOWN_EXTERNAL_CONFIRM$2,
463
+ MCP_INSTALL_KNOWN_EXTERNAL_RESULT: MCP_INSTALL_KNOWN_EXTERNAL_RESULT$2,
456
464
  MCP_RUN_AUTH: MCP_RUN_AUTH$1,
457
465
  MCP_RUN_AUTH_COMPLETE,
458
466
  MCP_RUN_AUTH_ERROR,
@@ -5296,7 +5304,7 @@ const layoutController$1 = {
5296
5304
 
5297
5305
  var layoutController_1 = layoutController$1;
5298
5306
 
5299
- var mcpController$3 = {exports: {}};
5307
+ var mcpController$4 = {exports: {}};
5300
5308
 
5301
5309
  var streamableHttp$1 = {};
5302
5310
 
@@ -26032,7 +26040,7 @@ async function refreshGoogleOAuthToken(tokenRefresh) {
26032
26040
  console.log("[mcpController] Google OAuth token refreshed successfully");
26033
26041
  }
26034
26042
 
26035
- const mcpController$2 = {
26043
+ const mcpController$3 = {
26036
26044
  /**
26037
26045
  * startServer
26038
26046
  * Start an MCP server with the given config and credentials
@@ -26070,13 +26078,13 @@ const mcpController$2 = {
26070
26078
  try {
26071
26079
  // Stop if in stale/error state
26072
26080
  if (activeServers.has(serverName)) {
26073
- await mcpController$2.stopServer(win, serverName);
26081
+ await mcpController$3.stopServer(win, serverName);
26074
26082
  }
26075
26083
 
26076
26084
  // Merge with catalog entry to pick up updated command/args
26077
26085
  // (saved provider config may reference a stale or archived package)
26078
26086
  try {
26079
- const { catalog } = mcpController$2.getCatalog(win);
26087
+ const { catalog } = mcpController$3.getCatalog(win);
26080
26088
  const catalogEntry = (catalog || []).find(
26081
26089
  (entry) => entry.name === serverName,
26082
26090
  );
@@ -26602,6 +26610,48 @@ const mcpController$2 = {
26602
26610
  }
26603
26611
  },
26604
26612
 
26613
+ /**
26614
+ * getKnownExternalCatalog
26615
+ * Load the curated allow-list of MCP servers known to exist outside the
26616
+ * built-in catalog. Used by the AI Widget Builder to suggest installable
26617
+ * servers and as the trust boundary for `install_known_mcp_server` —
26618
+ * the install tool will reject any id that doesn't appear here.
26619
+ *
26620
+ * @returns {{ success, servers } | { error, message, servers }}
26621
+ */
26622
+ getKnownExternalCatalog: () => {
26623
+ try {
26624
+ const catalogPath = path$c.join(
26625
+ __dirname,
26626
+ "..",
26627
+ "mcp",
26628
+ "knownExternalMcpServers.json",
26629
+ );
26630
+
26631
+ if (!fs$8.existsSync(catalogPath)) {
26632
+ return { success: true, servers: [] };
26633
+ }
26634
+
26635
+ const catalogData = fs$8.readFileSync(catalogPath, "utf8");
26636
+ const catalog = JSON.parse(catalogData);
26637
+
26638
+ return {
26639
+ success: true,
26640
+ servers: catalog.servers || [],
26641
+ };
26642
+ } catch (error) {
26643
+ console.error(
26644
+ "[mcpController] Error loading known-external catalog:",
26645
+ error,
26646
+ );
26647
+ return {
26648
+ error: true,
26649
+ message: error.message,
26650
+ servers: [],
26651
+ };
26652
+ }
26653
+ },
26654
+
26605
26655
  /**
26606
26656
  * listConnectedServers
26607
26657
  * Returns all connected servers with their cached tool lists.
@@ -26765,17 +26815,17 @@ const mcpController$2 = {
26765
26815
  );
26766
26816
  const promises = [];
26767
26817
  for (const [serverName] of activeServers) {
26768
- promises.push(mcpController$2.stopServer(null, serverName));
26818
+ promises.push(mcpController$3.stopServer(null, serverName));
26769
26819
  }
26770
26820
  await Promise.allSettled(promises);
26771
26821
  console.log("[mcpController] All servers stopped");
26772
26822
  },
26773
26823
  };
26774
26824
 
26775
- mcpController$3.exports = mcpController$2;
26776
- mcpController$3.exports.refreshGoogleOAuthToken = refreshGoogleOAuthToken;
26825
+ mcpController$4.exports = mcpController$3;
26826
+ mcpController$4.exports.refreshGoogleOAuthToken = refreshGoogleOAuthToken;
26777
26827
 
26778
- var mcpControllerExports = mcpController$3.exports;
26828
+ var mcpControllerExports = mcpController$4.exports;
26779
26829
 
26780
26830
  /**
26781
26831
  * Scoped package identity utilities.
@@ -51231,7 +51281,7 @@ var jsonSchemaToZod_1 = { jsonSchemaToZod: jsonSchemaToZod$1, jsonSchemaProperty
51231
51281
 
51232
51282
  const https$2 = require$$8$1;
51233
51283
  const { randomUUID } = require$$1$6;
51234
- const { BrowserWindow } = require$$0$1;
51284
+ const { BrowserWindow: BrowserWindow$1 } = require$$0$1;
51235
51285
  const { McpServer } = mcp;
51236
51286
  const {
51237
51287
  StreamableHTTPServerTransport,
@@ -51275,7 +51325,7 @@ function broadcastStateChanged(toolName, result) {
51275
51325
  /* leave null */
51276
51326
  }
51277
51327
  const payload = { toolName, result: parsed };
51278
- for (const win of BrowserWindow.getAllWindows()) {
51328
+ for (const win of BrowserWindow$1.getAllWindows()) {
51279
51329
  if (!win.isDestroyed()) {
51280
51330
  try {
51281
51331
  win.webContents.send("dash-mcp:state-changed", payload);
@@ -51341,7 +51391,7 @@ const registeredPrompts = [];
51341
51391
  * Register a tool to be exposed via the MCP server.
51342
51392
  * Call this before starting the server (or restart after registering).
51343
51393
  */
51344
- function registerTool$6(toolDef) {
51394
+ function registerTool$7(toolDef) {
51345
51395
  registeredTools.push(toolDef);
51346
51396
  }
51347
51397
 
@@ -51722,7 +51772,7 @@ const mcpDashServerController$4 = {
51722
51772
  },
51723
51773
 
51724
51774
  // Expose registration functions for other controllers
51725
- registerTool: registerTool$6,
51775
+ registerTool: registerTool$7,
51726
51776
  registerResource: registerResource$1,
51727
51777
  registerPrompt: registerPrompt$1,
51728
51778
  getServerContext,
@@ -61842,7 +61892,7 @@ async function handleListProviders$1() {
61842
61892
  * add_provider — Adds a new provider with encrypted credentials.
61843
61893
  * Credentials are accepted on input but never returned in the response.
61844
61894
  */
61845
- async function handleAddProvider$1({
61895
+ async function handleAddProvider$2({
61846
61896
  name,
61847
61897
  type,
61848
61898
  providerClass,
@@ -62667,7 +62717,7 @@ var toolHandlers$1 = {
62667
62717
  handleSearchRegistryThemes: handleSearchRegistryThemes$1,
62668
62718
  handleSearchRegistryDashboards: handleSearchRegistryDashboards$1,
62669
62719
  handleListProviders: handleListProviders$1,
62670
- handleAddProvider: handleAddProvider$1,
62720
+ handleAddProvider: handleAddProvider$2,
62671
62721
  handleRemoveProvider: handleRemoveProvider$1,
62672
62722
  handleGetSetupGuide: handleGetSetupGuide$1,
62673
62723
  handleSetLayout: handleSetLayout$1,
@@ -62692,7 +62742,7 @@ var toolHandlers$1 = {
62692
62742
  */
62693
62743
 
62694
62744
  const Anthropic = require$$0$9;
62695
- const mcpController$1 = mcpControllerExports;
62745
+ const mcpController$2 = mcpControllerExports;
62696
62746
  const cliController$1 = cliController_1;
62697
62747
  const toolDefinitions = toolDefinitions$1;
62698
62748
  const toolHandlers = toolHandlers$1;
@@ -62988,7 +63038,7 @@ const llmController$1 = {
62988
63038
  isError = true;
62989
63039
  } else {
62990
63040
  try {
62991
- const mcpResult = await mcpController$1.callTool(
63041
+ const mcpResult = await mcpController$2.callTool(
62992
63042
  win,
62993
63043
  serverName,
62994
63044
  toolBlock.name,
@@ -74145,6 +74195,99 @@ function findWidget(registry, packageId) {
74145
74195
  return null;
74146
74196
  }
74147
74197
 
74198
+ /**
74199
+ * Dedup duplicate `{type: "..."}` entries inside a `providers: [...]`
74200
+ * array literal in a .dash.js source string. Mirrors the regex used at
74201
+ * AI-build write time in dash-electron's WidgetBuilderModal so old
74202
+ * AI-generated widgets get healed before publish (the runtime parse
74203
+ * dedup keeps consumers correct, but the raw .dash.js text on disk
74204
+ * stays dirty unless we rewrite it).
74205
+ *
74206
+ * Conservative: only handles a single-level array of object literals.
74207
+ * More exotic forms fall through unchanged and the runtime dedup picks
74208
+ * up the slack.
74209
+ *
74210
+ * @param {string} source
74211
+ * @returns {{ source: string, dropped: number }}
74212
+ */
74213
+ function dedupProvidersInDashSource(source) {
74214
+ if (!source) return { source, dropped: 0 };
74215
+ let totalDropped = 0;
74216
+ const cleaned = source.replace(
74217
+ /(providers\s*:\s*\[)([^[\]]*?)(\])/,
74218
+ (match, head, body, tail) => {
74219
+ const chunks = body
74220
+ .split(/(\{[^{}]*\})/)
74221
+ .filter((s) => s && /\S/.test(s));
74222
+ const seenTypes = new Set();
74223
+ const kept = [];
74224
+ let dropped = 0;
74225
+ for (const chunk of chunks) {
74226
+ if (!chunk.startsWith("{")) continue;
74227
+ const typeMatch = chunk.match(/type\s*:\s*["']([^"']+)["']/);
74228
+ if (!typeMatch) {
74229
+ kept.push(chunk.trim());
74230
+ continue;
74231
+ }
74232
+ const t = typeMatch[1];
74233
+ if (seenTypes.has(t)) {
74234
+ dropped++;
74235
+ continue;
74236
+ }
74237
+ seenTypes.add(t);
74238
+ kept.push(chunk.trim());
74239
+ }
74240
+ if (dropped === 0) return match;
74241
+ totalDropped += dropped;
74242
+ return `${head}${kept.join(", ")}${tail}`;
74243
+ },
74244
+ );
74245
+ return { source: cleaned, dropped: totalDropped };
74246
+ }
74247
+
74248
+ /**
74249
+ * Walk a widget package's `.dash.js` files and rewrite any with
74250
+ * duplicate provider-type entries. Returns counts so the publish
74251
+ * caller can log what was healed. Errors are non-fatal — a single
74252
+ * unparseable .dash.js shouldn't block the whole publish.
74253
+ */
74254
+ function cleanupProvidersInWidgetPackage(widgetPath) {
74255
+ const summary = { filesScanned: 0, filesRewritten: 0, totalDropped: 0 };
74256
+ try {
74257
+ const widgetsDir =
74258
+ findWidgetsDir(widgetPath) || path.join(widgetPath, "widgets");
74259
+ if (!fs.existsSync(widgetsDir)) return summary;
74260
+ for (const file of fs.readdirSync(widgetsDir)) {
74261
+ if (!file.endsWith(".dash.js")) continue;
74262
+ const filePath = path.join(widgetsDir, file);
74263
+ try {
74264
+ const original = fs.readFileSync(filePath, "utf8");
74265
+ summary.filesScanned++;
74266
+ const { source: deduped, dropped } =
74267
+ dedupProvidersInDashSource(original);
74268
+ if (dropped > 0 && deduped !== original) {
74269
+ fs.writeFileSync(filePath, deduped, "utf8");
74270
+ summary.filesRewritten++;
74271
+ summary.totalDropped += dropped;
74272
+ console.log(
74273
+ `[widgetRegistry] Cleaned ${dropped} duplicate provider(s) from ${file}`,
74274
+ );
74275
+ }
74276
+ } catch (err) {
74277
+ console.warn(
74278
+ `[widgetRegistry] cleanupProviders skip ${file}: ${err.message}`,
74279
+ );
74280
+ }
74281
+ }
74282
+ } catch (err) {
74283
+ console.warn(
74284
+ "[widgetRegistry] cleanupProvidersInWidgetPackage failed:",
74285
+ err.message,
74286
+ );
74287
+ }
74288
+ return summary;
74289
+ }
74290
+
74148
74291
  /**
74149
74292
  * Scan a widget package directory for `.dash.js` component configs and
74150
74293
  * return the parsed configs. Used when the widget registry's cached
@@ -74487,6 +74630,21 @@ async function prepareWidgetForPublish$1(appId, packageId, options = {}) {
74487
74630
  }
74488
74631
  }
74489
74632
 
74633
+ // 5b. Heal `.dash.js` source files that have duplicate
74634
+ // provider-type entries before we read configs / build the
74635
+ // manifest / zip. AI-generated configs occasionally double a
74636
+ // `{type:"..."}` entry; the runtime dedup makes it invisible
74637
+ // on the publisher's machine, but we don't want the dirty raw
74638
+ // text shipping to the registry. Mirrors the write-time dedup
74639
+ // in dash-electron's WidgetBuilderModal so older widgets
74640
+ // authored before that fix landed get cleaned at publish.
74641
+ const providerCleanupSummary = cleanupProvidersInWidgetPackage(widget.path);
74642
+ if (providerCleanupSummary.filesRewritten > 0) {
74643
+ console.log(
74644
+ `[widgetRegistry] Provider cleanup: rewrote ${providerCleanupSummary.filesRewritten} file(s), removed ${providerCleanupSummary.totalDropped} duplicate(s)`,
74645
+ );
74646
+ }
74647
+
74490
74648
  // 6. Build manifest using the widget's component configs. The
74491
74649
  // registry cache may be missing widgets (orphaned / locally-
74492
74650
  // registered packages), so fall back to scanning the package's
@@ -75772,6 +75930,9 @@ const {
75772
75930
  MCP_READ_RESOURCE,
75773
75931
  MCP_SERVER_STATUS,
75774
75932
  MCP_GET_CATALOG,
75933
+ MCP_GET_KNOWN_EXTERNAL,
75934
+ MCP_INSTALL_KNOWN_EXTERNAL_CONFIRM: MCP_INSTALL_KNOWN_EXTERNAL_CONFIRM$1,
75935
+ MCP_INSTALL_KNOWN_EXTERNAL_RESULT: MCP_INSTALL_KNOWN_EXTERNAL_RESULT$1,
75775
75936
  MCP_RUN_AUTH,
75776
75937
  } = events$8;
75777
75938
 
@@ -75868,6 +76029,45 @@ const mcpApi$2 = {
75868
76029
  */
75869
76030
  getCatalog: () => ipcRenderer$i.invoke(MCP_GET_CATALOG),
75870
76031
 
76032
+ /**
76033
+ * getKnownExternalCatalog
76034
+ * Load the curated allow-list of MCP servers known to exist outside the
76035
+ * built-in catalog. The AI Widget Builder reads this to advertise
76036
+ * "you can install <X> via Add Custom MCP" and as the trust boundary
76037
+ * for the `install_known_mcp_server` dash MCP tool — only ids in this
76038
+ * list are installable via that path.
76039
+ *
76040
+ * @returns {Promise<{ success, servers } | { error, message, servers }>}
76041
+ */
76042
+ getKnownExternalCatalog: () => ipcRenderer$i.invoke(MCP_GET_KNOWN_EXTERNAL),
76043
+
76044
+ /**
76045
+ * onInstallKnownExternalConfirm
76046
+ * Subscribe to install-confirm requests emitted by the dash MCP server
76047
+ * tool `install_known_mcp_server`. The renderer renders a confirmation
76048
+ * modal and replies with { confirmed, credentials } via
76049
+ * sendInstallKnownExternalResult().
76050
+ *
76051
+ * @param {(payload: { id, requestId, server }) => void} callback
76052
+ * @returns {() => void} cleanup
76053
+ */
76054
+ onInstallKnownExternalConfirm: (callback) => {
76055
+ const handler = (_e, data) => callback(data);
76056
+ ipcRenderer$i.on(MCP_INSTALL_KNOWN_EXTERNAL_CONFIRM$1, handler);
76057
+ return () =>
76058
+ ipcRenderer$i.removeListener(MCP_INSTALL_KNOWN_EXTERNAL_CONFIRM$1, handler);
76059
+ },
76060
+
76061
+ /**
76062
+ * sendInstallKnownExternalResult
76063
+ * Reply to a confirm request with the user's decision + credentials.
76064
+ *
76065
+ * @param {string} requestId
76066
+ * @param {{ confirmed: boolean, credentials?: object, error?: string }} result
76067
+ */
76068
+ sendInstallKnownExternalResult: (requestId, result) =>
76069
+ ipcRenderer$i.send(MCP_INSTALL_KNOWN_EXTERNAL_RESULT$1, { requestId, result }),
76070
+
75871
76071
  /**
75872
76072
  * runAuth
75873
76073
  * Run a one-shot auth command for an MCP server (e.g., OAuth browser flow)
@@ -77176,7 +77376,7 @@ var mcpDashServerApi_1 = mcpDashServerApi$2;
77176
77376
  * Call registerDashboardTools() during app startup (before or after server start).
77177
77377
  */
77178
77378
 
77179
- const { registerTool: registerTool$5 } = mcpDashServerController_1;
77379
+ const { registerTool: registerTool$6 } = mcpDashServerController_1;
77180
77380
  const { dashboardTools } = toolDefinitions$1;
77181
77381
  const {
77182
77382
  handleListDashboards,
@@ -77207,7 +77407,7 @@ function registerDashboardTools$1() {
77207
77407
  console.warn(`[dashboardTools] No handler found for tool: ${tool.name}`);
77208
77408
  continue;
77209
77409
  }
77210
- registerTool$5({
77410
+ registerTool$6({
77211
77411
  name: tool.name,
77212
77412
  description: tool.description,
77213
77413
  inputSchema: tool.inputSchema,
@@ -77228,7 +77428,7 @@ var dashboardTools_1 = { registerDashboardTools: registerDashboardTools$1 };
77228
77428
  * Call registerWidgetTools() during app startup (before or after server start).
77229
77429
  */
77230
77430
 
77231
- const { registerTool: registerTool$4 } = mcpDashServerController_1;
77431
+ const { registerTool: registerTool$5 } = mcpDashServerController_1;
77232
77432
  const { widgetTools } = toolDefinitions$1;
77233
77433
  const {
77234
77434
  handleAddWidget,
@@ -77259,7 +77459,7 @@ function registerWidgetTools$1() {
77259
77459
  console.warn(`[widgetTools] No handler found for tool: ${tool.name}`);
77260
77460
  continue;
77261
77461
  }
77262
- registerTool$4({
77462
+ registerTool$5({
77263
77463
  name: tool.name,
77264
77464
  description: tool.description,
77265
77465
  inputSchema: tool.inputSchema,
@@ -77278,7 +77478,7 @@ var widgetTools_1 = { registerWidgetTools: registerWidgetTools$1 };
77278
77478
  * Call registerThemeTools() during app startup (before or after server start).
77279
77479
  */
77280
77480
 
77281
- const { registerTool: registerTool$3 } = mcpDashServerController_1;
77481
+ const { registerTool: registerTool$4 } = mcpDashServerController_1;
77282
77482
  const { themeTools } = toolDefinitions$1;
77283
77483
  const {
77284
77484
  handleListThemes,
@@ -77309,7 +77509,7 @@ function registerThemeTools$1() {
77309
77509
  console.warn(`[themeTools] No handler found for tool: ${tool.name}`);
77310
77510
  continue;
77311
77511
  }
77312
- registerTool$3({
77512
+ registerTool$4({
77313
77513
  name: tool.name,
77314
77514
  description: tool.description,
77315
77515
  inputSchema: tool.inputSchema,
@@ -77328,18 +77528,18 @@ var themeTools_1 = { registerThemeTools: registerThemeTools$1 };
77328
77528
  * Call registerProviderTools() during app startup (before or after server start).
77329
77529
  */
77330
77530
 
77331
- const { registerTool: registerTool$2 } = mcpDashServerController_1;
77531
+ const { registerTool: registerTool$3 } = mcpDashServerController_1;
77332
77532
  const { providerTools } = toolDefinitions$1;
77333
77533
  const {
77334
77534
  handleListProviders,
77335
- handleAddProvider,
77535
+ handleAddProvider: handleAddProvider$1,
77336
77536
  handleRemoveProvider,
77337
77537
  } = toolHandlers$1;
77338
77538
 
77339
77539
  // Map tool names to handler functions
77340
77540
  const handlerMap$4 = {
77341
77541
  list_providers: handleListProviders,
77342
- add_provider: handleAddProvider,
77542
+ add_provider: handleAddProvider$1,
77343
77543
  remove_provider: handleRemoveProvider,
77344
77544
  };
77345
77545
 
@@ -77353,7 +77553,7 @@ function registerProviderTools$1() {
77353
77553
  console.warn(`[providerTools] No handler found for tool: ${tool.name}`);
77354
77554
  continue;
77355
77555
  }
77356
- registerTool$2({
77556
+ registerTool$3({
77357
77557
  name: tool.name,
77358
77558
  description: tool.description,
77359
77559
  inputSchema: tool.inputSchema,
@@ -77374,7 +77574,7 @@ var providerTools_1 = { registerProviderTools: registerProviderTools$1 };
77374
77574
  * Call registerGuideTools() during app startup.
77375
77575
  */
77376
77576
 
77377
- const { registerTool: registerTool$1 } = mcpDashServerController_1;
77577
+ const { registerTool: registerTool$2 } = mcpDashServerController_1;
77378
77578
  const { guideTools } = toolDefinitions$1;
77379
77579
  const { handleGetSetupGuide } = toolHandlers$1;
77380
77580
 
@@ -77389,7 +77589,7 @@ function registerGuideTools$1() {
77389
77589
  console.warn(`[guideTools] No handler found for tool: ${tool.name}`);
77390
77590
  continue;
77391
77591
  }
77392
- registerTool$1({
77592
+ registerTool$2({
77393
77593
  name: tool.name,
77394
77594
  description: tool.description,
77395
77595
  inputSchema: tool.inputSchema,
@@ -77408,7 +77608,7 @@ var guideTools_1 = { registerGuideTools: registerGuideTools$1 };
77408
77608
  * Call registerLayoutTools() during app startup (before or after server start).
77409
77609
  */
77410
77610
 
77411
- const { registerTool } = mcpDashServerController_1;
77611
+ const { registerTool: registerTool$1 } = mcpDashServerController_1;
77412
77612
  const { layoutTools } = toolDefinitions$1;
77413
77613
  const {
77414
77614
  handleSetLayout,
@@ -77432,7 +77632,7 @@ function registerLayoutTools$1() {
77432
77632
  console.warn(`[layoutTools] No handler found for tool: ${tool.name}`);
77433
77633
  continue;
77434
77634
  }
77435
- registerTool({
77635
+ registerTool$1({
77436
77636
  name: tool.name,
77437
77637
  description: tool.description,
77438
77638
  inputSchema: tool.inputSchema,
@@ -77444,6 +77644,194 @@ function registerLayoutTools$1() {
77444
77644
 
77445
77645
  var layoutTools_1 = { registerLayoutTools: registerLayoutTools$1 };
77446
77646
 
77647
+ /**
77648
+ * installExternalMcpTool.js
77649
+ *
77650
+ * Registers the `install_known_mcp_server` tool on Dash's own MCP server.
77651
+ *
77652
+ * Why this tool exists:
77653
+ * The AI Widget Builder asks Claude to use MCP-first when a user requests
77654
+ * a widget for an external service. The built-in catalog covers 13 servers;
77655
+ * for anything else the AI consults the curated allow-list at
77656
+ * `electron/mcp/knownExternalMcpServers.json`. To keep the user in control
77657
+ * (npm install + spawning a child process is a non-trivial trust grant),
77658
+ * the actual install is gated by a renderer-side confirmation modal: this
77659
+ * tool emits an IPC event with the curated entry, waits for the user's
77660
+ * response, and only then routes through the existing add-provider flow.
77661
+ *
77662
+ * Trust boundary: the `id` argument MUST match an entry in
77663
+ * knownExternalMcpServers.json. Any other id is rejected before the
77664
+ * confirmation modal is even shown.
77665
+ *
77666
+ * Returns one of:
77667
+ * - { success: true, name } — installed
77668
+ * - { success: false, declined } — user clicked Cancel
77669
+ * - { success: false, error } — id not in allow-list, IPC timeout, or
77670
+ * install error from add-provider flow
77671
+ */
77672
+
77673
+ const { BrowserWindow } = require$$0$1;
77674
+ const { registerTool } = mcpDashServerController_1;
77675
+ const mcpController$1 = mcpControllerExports;
77676
+ const {
77677
+ MCP_INSTALL_KNOWN_EXTERNAL_CONFIRM,
77678
+ MCP_INSTALL_KNOWN_EXTERNAL_RESULT,
77679
+ } = mcpEvents$1;
77680
+ const { handleAddProvider } = toolHandlers$1;
77681
+ const { ipcMain } = require$$0$1;
77682
+
77683
+ const CONFIRM_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes — user may stop to read
77684
+
77685
+ // Pending requests keyed by requestId so we can resolve when the result IPC arrives.
77686
+ const pendingRequests = new Map();
77687
+
77688
+ let resultListenerInstalled = false;
77689
+ function ensureResultListener() {
77690
+ if (resultListenerInstalled) return;
77691
+ resultListenerInstalled = true;
77692
+ ipcMain.on(MCP_INSTALL_KNOWN_EXTERNAL_RESULT, (_event, payload) => {
77693
+ const { requestId, result } = payload || {};
77694
+ const pending = pendingRequests.get(requestId);
77695
+ if (!pending) return;
77696
+ pendingRequests.delete(requestId);
77697
+ clearTimeout(pending.timer);
77698
+ pending.resolve(result || { confirmed: false });
77699
+ });
77700
+ }
77701
+
77702
+ function findEntryInAllowList(id) {
77703
+ const { servers } = mcpController$1.getKnownExternalCatalog();
77704
+ if (!Array.isArray(servers)) return null;
77705
+ return servers.find((s) => s && s.id === id) || null;
77706
+ }
77707
+
77708
+ function newRequestId() {
77709
+ return `mcp-install-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
77710
+ }
77711
+
77712
+ async function awaitUserConfirmation(server) {
77713
+ ensureResultListener();
77714
+ const requestId = newRequestId();
77715
+ return new Promise((resolve) => {
77716
+ const timer = setTimeout(() => {
77717
+ if (pendingRequests.has(requestId)) {
77718
+ pendingRequests.delete(requestId);
77719
+ resolve({ confirmed: false, error: "Install confirmation timed out" });
77720
+ }
77721
+ }, CONFIRM_TIMEOUT_MS);
77722
+ pendingRequests.set(requestId, { resolve, timer });
77723
+
77724
+ // Broadcast to all open windows; whichever has the modal mounted picks it up.
77725
+ const wins = BrowserWindow.getAllWindows();
77726
+ if (wins.length === 0) {
77727
+ pendingRequests.delete(requestId);
77728
+ clearTimeout(timer);
77729
+ resolve({ confirmed: false, error: "No window available" });
77730
+ return;
77731
+ }
77732
+ for (const win of wins) {
77733
+ if (!win.isDestroyed()) {
77734
+ win.webContents.send(MCP_INSTALL_KNOWN_EXTERNAL_CONFIRM, {
77735
+ requestId,
77736
+ id: server.id,
77737
+ server,
77738
+ });
77739
+ }
77740
+ }
77741
+ });
77742
+ }
77743
+
77744
+ function ok(payload) {
77745
+ return {
77746
+ content: [{ type: "text", text: JSON.stringify(payload) }],
77747
+ };
77748
+ }
77749
+
77750
+ function fail(payload) {
77751
+ return {
77752
+ content: [{ type: "text", text: JSON.stringify(payload) }],
77753
+ isError: true,
77754
+ };
77755
+ }
77756
+
77757
+ async function handleInstallKnownMcpServer({ id, name }) {
77758
+ if (!id || typeof id !== "string") {
77759
+ return fail({ success: false, error: "id is required" });
77760
+ }
77761
+ const server = findEntryInAllowList(id);
77762
+ if (!server) {
77763
+ return fail({
77764
+ success: false,
77765
+ error: `id "${id}" is not in the known-external MCP allow-list. The user can only install servers explicitly listed in knownExternalMcpServers.json.`,
77766
+ });
77767
+ }
77768
+
77769
+ const decision = await awaitUserConfirmation(server);
77770
+ if (!decision || !decision.confirmed) {
77771
+ if (decision?.error) {
77772
+ return fail({ success: false, error: decision.error });
77773
+ }
77774
+ return ok({ success: false, declined: true });
77775
+ }
77776
+
77777
+ // Route through the existing add-provider tool handler. The renderer
77778
+ // collected credentials in its modal — we trust those to match the
77779
+ // curated `credentialSchema`.
77780
+ const providerName = (name && name.trim()) || server.name || server.id;
77781
+ try {
77782
+ const addResult = await handleAddProvider({
77783
+ name: providerName,
77784
+ type: server.id,
77785
+ providerClass: "mcp",
77786
+ credentials: decision.credentials || {},
77787
+ mcpConfig: server.mcpConfig,
77788
+ });
77789
+ if (addResult?.isError) {
77790
+ // Surface add-provider's error text verbatim.
77791
+ return addResult;
77792
+ }
77793
+ return ok({ success: true, name: providerName, type: server.id });
77794
+ } catch (err) {
77795
+ return fail({ success: false, error: err.message });
77796
+ }
77797
+ }
77798
+
77799
+ const installExternalMcpToolDef = {
77800
+ name: "install_known_mcp_server",
77801
+ description:
77802
+ "Install an MCP server from Dash's curated known-external allow-list. The user is shown a confirmation modal before any install runs. Use this when the user asks for a widget that needs a service NOT in the built-in MCP catalog but IS in the known-external list. Returns { success, name } on install, { success: false, declined: true } if the user cancels, or an error if the id isn't allow-listed.",
77803
+ inputSchema: {
77804
+ type: "object",
77805
+ properties: {
77806
+ id: {
77807
+ type: "string",
77808
+ description:
77809
+ "The id of the server in the known-external catalog (e.g. 'trello', 'asana', 'stripe').",
77810
+ },
77811
+ name: {
77812
+ type: "string",
77813
+ description:
77814
+ "Optional display name for the resulting provider entry. Defaults to the catalog entry's name.",
77815
+ },
77816
+ },
77817
+ required: ["id"],
77818
+ },
77819
+ };
77820
+
77821
+ function registerInstallKnownMcpServerTool$1() {
77822
+ registerTool({
77823
+ ...installExternalMcpToolDef,
77824
+ handler: handleInstallKnownMcpServer,
77825
+ });
77826
+ console.log("[installExternalMcpTool] Registered install_known_mcp_server");
77827
+ }
77828
+
77829
+ var installExternalMcpTool = {
77830
+ registerInstallKnownMcpServerTool: registerInstallKnownMcpServerTool$1,
77831
+ // Exported for tests
77832
+ handleInstallKnownMcpServer,
77833
+ };
77834
+
77447
77835
  /**
77448
77836
  * resourceDefinitions.js
77449
77837
  *
@@ -78414,6 +78802,9 @@ const { registerThemeTools } = themeTools_1;
78414
78802
  const { registerProviderTools } = providerTools_1;
78415
78803
  const { registerGuideTools } = guideTools_1;
78416
78804
  const { registerLayoutTools } = layoutTools_1;
78805
+ const {
78806
+ registerInstallKnownMcpServerTool,
78807
+ } = installExternalMcpTool;
78417
78808
  const { registerResources } = resources;
78418
78809
  const { registerPrompts } = promptRegistration;
78419
78810
  registerDashboardTools();
@@ -78422,6 +78813,7 @@ registerThemeTools();
78422
78813
  registerProviderTools();
78423
78814
  registerGuideTools();
78424
78815
  registerLayoutTools();
78816
+ registerInstallKnownMcpServerTool();
78425
78817
  registerResources();
78426
78818
  registerPrompts();
78427
78819