@frontmcp/plugin-dashboard 0.0.1 → 0.7.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/index.js ADDED
@@ -0,0 +1,1309 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var __decorateClass = (decorators, target, key, kind) => {
20
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
21
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
22
+ if (decorator = decorators[i])
23
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
24
+ if (kind && result) __defProp(target, key, result);
25
+ return result;
26
+ };
27
+
28
+ // plugins/plugin-dashboard/src/index.ts
29
+ var index_exports = {};
30
+ __export(index_exports, {
31
+ DashboardApp: () => DashboardApp,
32
+ DashboardConfigToken: () => DashboardConfigToken,
33
+ DashboardPlugin: () => DashboardPlugin,
34
+ GraphDataProvider: () => GraphDataProvider,
35
+ GraphDataProviderToken: () => GraphDataProviderToken,
36
+ GraphTool: () => GraphTool,
37
+ ListResourcesTool: () => ListResourcesTool,
38
+ ListToolsTool: () => ListToolsTool,
39
+ ParentScopeToken: () => ParentScopeToken,
40
+ dashboardPluginOptionsSchema: () => dashboardPluginOptionsSchema,
41
+ default: () => DashboardPlugin,
42
+ defaultDashboardPluginOptions: () => defaultDashboardPluginOptions,
43
+ isDashboardEnabled: () => isDashboardEnabled
44
+ });
45
+ module.exports = __toCommonJS(index_exports);
46
+
47
+ // plugins/plugin-dashboard/src/dashboard.plugin.ts
48
+ var import_sdk = require("@frontmcp/sdk");
49
+
50
+ // plugins/plugin-dashboard/src/dashboard.types.ts
51
+ var import_zod = require("zod");
52
+ var cdnConfigSchema = import_zod.z.object({
53
+ /** Base URL for the dashboard UI bundle (e.g., https://esm.sh/@frontmcp/dashboard-ui@1.0.0) */
54
+ entrypoint: import_zod.z.string().optional(),
55
+ /** React CDN URL */
56
+ react: import_zod.z.string().default("https://esm.sh/react@19"),
57
+ /** React DOM CDN URL */
58
+ reactDom: import_zod.z.string().default("https://esm.sh/react-dom@19"),
59
+ /** React DOM client CDN URL */
60
+ reactDomClient: import_zod.z.string().default("https://esm.sh/react-dom@19/client"),
61
+ /** React JSX runtime CDN URL */
62
+ reactJsxRuntime: import_zod.z.string().default("https://esm.sh/react@19/jsx-runtime"),
63
+ /** React Router CDN URL */
64
+ reactRouter: import_zod.z.string().default("https://esm.sh/react-router-dom@7"),
65
+ /** XYFlow (React Flow) CDN URL */
66
+ xyflow: import_zod.z.string().default("https://esm.sh/@xyflow/react@12?external=react,react-dom"),
67
+ /** Dagre layout library CDN URL */
68
+ dagre: import_zod.z.string().default("https://esm.sh/dagre@0.8.5"),
69
+ /** XYFlow CSS URL */
70
+ xyflowCss: import_zod.z.string().default("https://esm.sh/@xyflow/react@12/dist/style.css")
71
+ });
72
+ var dashboardAuthSchema = import_zod.z.object({
73
+ /** Enable authentication (default: false) */
74
+ enabled: import_zod.z.boolean().default(false),
75
+ /** Secret token for query param authentication (?token=xxx) */
76
+ token: import_zod.z.string().optional()
77
+ });
78
+ var dashboardPluginOptionsSchema = import_zod.z.object({
79
+ /** Enable/disable dashboard (undefined = auto: enabled in dev, disabled in prod) */
80
+ enabled: import_zod.z.boolean().optional(),
81
+ /** Base path for dashboard routes (default: /dashboard) */
82
+ basePath: import_zod.z.string().default("/dashboard"),
83
+ /** Authentication configuration */
84
+ auth: dashboardAuthSchema.optional().transform((v) => dashboardAuthSchema.parse(v ?? {})),
85
+ /** CDN configuration for UI loading */
86
+ cdn: cdnConfigSchema.optional().transform((v) => cdnConfigSchema.parse(v ?? {}))
87
+ });
88
+ var defaultDashboardPluginOptions = {
89
+ basePath: "/dashboard",
90
+ auth: { enabled: false },
91
+ cdn: {}
92
+ };
93
+ function isDashboardEnabled(options) {
94
+ if (options.enabled !== void 0) {
95
+ return options.enabled;
96
+ }
97
+ return process.env["NODE_ENV"] !== "production";
98
+ }
99
+
100
+ // plugins/plugin-dashboard/src/dashboard.symbol.ts
101
+ var DashboardConfigToken = /* @__PURE__ */ Symbol("DashboardConfig");
102
+ var GraphDataProviderToken = /* @__PURE__ */ Symbol("GraphDataProvider");
103
+ var ParentScopeToken = /* @__PURE__ */ Symbol("ParentScope");
104
+
105
+ // plugins/plugin-dashboard/src/dashboard.plugin.ts
106
+ var DashboardPlugin = class extends import_sdk.DynamicPlugin {
107
+ options;
108
+ constructor(options = {}) {
109
+ super();
110
+ this.options = dashboardPluginOptionsSchema.parse({
111
+ ...defaultDashboardPluginOptions,
112
+ ...options
113
+ });
114
+ }
115
+ /**
116
+ * Dynamic providers allow configuration of the dashboard with custom options.
117
+ * This injects the parsed options into the DI container so all dashboard
118
+ * components can access the configuration.
119
+ */
120
+ static dynamicProviders(options) {
121
+ const parsedOptions = dashboardPluginOptionsSchema.parse({
122
+ ...defaultDashboardPluginOptions,
123
+ ...options
124
+ });
125
+ return [
126
+ {
127
+ name: "dashboard:config",
128
+ provide: DashboardConfigToken,
129
+ useValue: parsedOptions
130
+ }
131
+ ];
132
+ }
133
+ };
134
+ DashboardPlugin = __decorateClass([
135
+ (0, import_sdk.Plugin)({
136
+ name: "dashboard",
137
+ description: "Visual dashboard for FrontMCP server monitoring and visualization",
138
+ providers: []
139
+ })
140
+ ], DashboardPlugin);
141
+
142
+ // plugins/plugin-dashboard/src/app/dashboard.app.ts
143
+ var import_sdk6 = require("@frontmcp/sdk");
144
+
145
+ // plugins/plugin-dashboard/src/providers/graph-data.provider.ts
146
+ var import_sdk2 = require("@frontmcp/sdk");
147
+ var GraphDataProvider = class {
148
+ constructor(scope, serverName, serverVersion) {
149
+ this.scope = scope;
150
+ this.serverName = serverName;
151
+ this.serverVersion = serverVersion;
152
+ }
153
+ /** Cached graph data to avoid re-extraction on every request */
154
+ cachedData = null;
155
+ /** Timestamp of when the cache was last updated */
156
+ cacheTimestamp = 0;
157
+ /** Cache time-to-live in milliseconds */
158
+ cacheTTL = 5e3;
159
+ /**
160
+ * Get graph data for the current scope.
161
+ * Uses caching to avoid expensive extraction on every request.
162
+ */
163
+ async getGraphData() {
164
+ const now = Date.now();
165
+ if (this.cachedData && now - this.cacheTimestamp < this.cacheTTL) {
166
+ return this.cachedData;
167
+ }
168
+ this.cachedData = this.extractGraphData();
169
+ this.cacheTimestamp = now;
170
+ return this.cachedData;
171
+ }
172
+ /**
173
+ * Invalidate the cache to force fresh extraction.
174
+ */
175
+ invalidateCache() {
176
+ this.cachedData = null;
177
+ this.cacheTimestamp = 0;
178
+ }
179
+ /**
180
+ * Get all scopes from the ScopeRegistry, excluding the dashboard scope.
181
+ * Traverses up the provider hierarchy to find the ScopeRegistry if not found locally.
182
+ */
183
+ getMonitoredScopes() {
184
+ try {
185
+ let scopeRegistries = this.scope.providers.getRegistries("ScopeRegistry");
186
+ if (!scopeRegistries || scopeRegistries.length === 0) {
187
+ let currentProviders = this.scope.providers.parentProviders;
188
+ while (currentProviders && (!scopeRegistries || scopeRegistries.length === 0)) {
189
+ scopeRegistries = currentProviders.getRegistries?.("ScopeRegistry") || [];
190
+ currentProviders = currentProviders.parentProviders;
191
+ }
192
+ }
193
+ if (!scopeRegistries || scopeRegistries.length === 0) {
194
+ return [this.scope];
195
+ }
196
+ const scopeRegistry = scopeRegistries[0];
197
+ const allScopes = scopeRegistry.getScopes();
198
+ return allScopes.filter((s) => s.id !== "dashboard");
199
+ } catch {
200
+ return [this.scope];
201
+ }
202
+ }
203
+ /**
204
+ * Extract graph data from all monitored scopes.
205
+ */
206
+ extractGraphData() {
207
+ const nodes = [];
208
+ const edges = [];
209
+ const addedNodeIds = /* @__PURE__ */ new Set();
210
+ const addNode = (node) => {
211
+ if (!addedNodeIds.has(node.id)) {
212
+ nodes.push(node);
213
+ addedNodeIds.add(node.id);
214
+ }
215
+ };
216
+ const serverId = `server:${this.serverName}`;
217
+ addNode({
218
+ id: serverId,
219
+ type: "server",
220
+ label: this.serverName,
221
+ data: {
222
+ name: this.serverName,
223
+ description: `FrontMCP Server v${this.serverVersion || "unknown"}`
224
+ }
225
+ });
226
+ const scopes = this.getMonitoredScopes();
227
+ for (const scope of scopes) {
228
+ const scopeId = `scope:${scope.id}`;
229
+ addNode({
230
+ id: scopeId,
231
+ type: "scope",
232
+ label: scope.id,
233
+ data: {
234
+ name: scope.id
235
+ }
236
+ });
237
+ edges.push({
238
+ id: `${serverId}->${scopeId}`,
239
+ source: serverId,
240
+ target: scopeId,
241
+ type: "contains"
242
+ });
243
+ this.extractToolsFromScope(scope, scopeId, edges, addNode);
244
+ this.extractResourcesFromScope(scope, scopeId, edges, addNode);
245
+ this.extractResourceTemplatesFromScope(scope, scopeId, edges, addNode);
246
+ this.extractPromptsFromScope(scope, scopeId, edges, addNode);
247
+ this.extractAppsFromScope(scope, scopeId, edges, addNode);
248
+ }
249
+ return {
250
+ nodes,
251
+ edges,
252
+ metadata: this.createMetadata(nodes.length, edges.length)
253
+ };
254
+ }
255
+ extractToolsFromScope(scope, scopeId, edges, addNode) {
256
+ try {
257
+ const tools = scope.tools?.getTools?.(true) || [];
258
+ for (const tool of tools) {
259
+ if (tool.fullName?.startsWith("dashboard:")) {
260
+ continue;
261
+ }
262
+ const toolId = `tool:${tool.fullName}`;
263
+ addNode({
264
+ id: toolId,
265
+ type: "tool",
266
+ label: tool.name,
267
+ data: {
268
+ name: tool.fullName,
269
+ description: tool.metadata?.description,
270
+ inputSchema: tool.inputSchema,
271
+ outputSchema: tool.outputSchema,
272
+ tags: tool.metadata?.tags,
273
+ annotations: tool.metadata?.annotations
274
+ }
275
+ });
276
+ edges.push({
277
+ id: `${scopeId}->${toolId}`,
278
+ source: scopeId,
279
+ target: toolId,
280
+ type: "provides"
281
+ });
282
+ }
283
+ } catch {
284
+ }
285
+ }
286
+ extractResourcesFromScope(scope, scopeId, edges, addNode) {
287
+ try {
288
+ const resources = scope.resources?.getResources?.(true) || [];
289
+ for (const resource of resources) {
290
+ const resourceId = `resource:${resource.uri}`;
291
+ addNode({
292
+ id: resourceId,
293
+ type: "resource",
294
+ label: resource.name,
295
+ data: {
296
+ name: resource.name,
297
+ description: resource.metadata?.description,
298
+ uri: resource.uri,
299
+ mimeType: resource.metadata?.mimeType
300
+ }
301
+ });
302
+ edges.push({
303
+ id: `${scopeId}->${resourceId}`,
304
+ source: scopeId,
305
+ target: resourceId,
306
+ type: "provides"
307
+ });
308
+ }
309
+ } catch {
310
+ }
311
+ }
312
+ extractResourceTemplatesFromScope(scope, scopeId, edges, addNode) {
313
+ try {
314
+ const templates = scope.resources?.getResourceTemplates?.() || [];
315
+ for (const template of templates) {
316
+ const templateId = `resource-template:${template.uriTemplate}`;
317
+ addNode({
318
+ id: templateId,
319
+ type: "resource-template",
320
+ label: template.name,
321
+ data: {
322
+ name: template.name,
323
+ description: template.metadata?.description,
324
+ uri: template.uriTemplate,
325
+ mimeType: template.metadata?.mimeType
326
+ }
327
+ });
328
+ edges.push({
329
+ id: `${scopeId}->${templateId}`,
330
+ source: scopeId,
331
+ target: templateId,
332
+ type: "provides"
333
+ });
334
+ }
335
+ } catch {
336
+ }
337
+ }
338
+ extractPromptsFromScope(scope, scopeId, edges, addNode) {
339
+ try {
340
+ const prompts = scope.prompts?.getPrompts?.(true) || [];
341
+ for (const prompt of prompts) {
342
+ const promptId = `prompt:${prompt.name}`;
343
+ addNode({
344
+ id: promptId,
345
+ type: "prompt",
346
+ label: prompt.name,
347
+ data: {
348
+ name: prompt.name,
349
+ description: prompt.metadata?.description,
350
+ arguments: prompt.metadata?.arguments
351
+ }
352
+ });
353
+ edges.push({
354
+ id: `${scopeId}->${promptId}`,
355
+ source: scopeId,
356
+ target: promptId,
357
+ type: "provides"
358
+ });
359
+ }
360
+ } catch {
361
+ }
362
+ }
363
+ extractAppsFromScope(scope, scopeId, edges, addNode) {
364
+ try {
365
+ const apps = scope.apps?.getApps?.() || [];
366
+ for (const app of apps) {
367
+ const appName = app.metadata?.name || "unknown";
368
+ if (appName === "dashboard") {
369
+ continue;
370
+ }
371
+ const appId = `app:${appName}`;
372
+ addNode({
373
+ id: appId,
374
+ type: "app",
375
+ label: appName,
376
+ data: {
377
+ name: appName,
378
+ description: app.metadata?.description
379
+ }
380
+ });
381
+ edges.push({
382
+ id: `${scopeId}->${appId}`,
383
+ source: scopeId,
384
+ target: appId,
385
+ type: "contains"
386
+ });
387
+ }
388
+ } catch {
389
+ }
390
+ }
391
+ createMetadata(nodeCount, edgeCount) {
392
+ return {
393
+ serverName: this.serverName,
394
+ serverVersion: this.serverVersion,
395
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
396
+ entryFile: "runtime",
397
+ nodeCount,
398
+ edgeCount
399
+ };
400
+ }
401
+ };
402
+ GraphDataProvider = __decorateClass([
403
+ (0, import_sdk2.Provider)({
404
+ name: "graph-data-provider"
405
+ })
406
+ ], GraphDataProvider);
407
+
408
+ // plugins/plugin-dashboard/src/tools/graph.tool.ts
409
+ var import_sdk3 = require("@frontmcp/sdk");
410
+ var import_zod2 = require("zod");
411
+ var graphToolInputSchema = {
412
+ includeSchemas: import_zod2.z.boolean().optional().default(false).describe("Include full input/output schemas in the response"),
413
+ refresh: import_zod2.z.boolean().optional().default(false).describe("Force refresh the graph data (bypass cache)")
414
+ };
415
+ var GraphTool = class extends import_sdk3.ToolContext {
416
+ async execute(input) {
417
+ const graphProvider = this.get(GraphDataProvider);
418
+ if (input.refresh) {
419
+ graphProvider.invalidateCache();
420
+ }
421
+ const graphData = await graphProvider.getGraphData();
422
+ if (!input.includeSchemas) {
423
+ return {
424
+ ...graphData,
425
+ nodes: graphData.nodes.map((node) => ({
426
+ ...node,
427
+ data: {
428
+ ...node.data,
429
+ inputSchema: void 0,
430
+ outputSchema: void 0
431
+ }
432
+ }))
433
+ };
434
+ }
435
+ return graphData;
436
+ }
437
+ };
438
+ GraphTool = __decorateClass([
439
+ (0, import_sdk3.Tool)({
440
+ name: "dashboard:graph",
441
+ description: "Get the server graph showing all registered tools, resources, prompts, apps, and their relationships. Returns nodes and edges that can be visualized.",
442
+ inputSchema: graphToolInputSchema,
443
+ annotations: {
444
+ readOnlyHint: true
445
+ }
446
+ })
447
+ ], GraphTool);
448
+
449
+ // plugins/plugin-dashboard/src/tools/list-tools.tool.ts
450
+ var import_sdk4 = require("@frontmcp/sdk");
451
+ var import_zod3 = require("zod");
452
+
453
+ // plugins/plugin-dashboard/src/shared/safe-regex.ts
454
+ function safeRegex(pattern) {
455
+ if (pattern.length > 100) {
456
+ return null;
457
+ }
458
+ try {
459
+ const regex = new RegExp(pattern, "i");
460
+ regex.test("test");
461
+ return regex;
462
+ } catch {
463
+ return null;
464
+ }
465
+ }
466
+
467
+ // plugins/plugin-dashboard/src/tools/list-tools.tool.ts
468
+ var listToolsInputSchema = import_zod3.z.object({
469
+ filter: import_zod3.z.string().optional().describe("Filter tools by name pattern (regex supported)"),
470
+ includePlugins: import_zod3.z.boolean().default(true).describe("Include tools from plugins"),
471
+ includeSchemas: import_zod3.z.boolean().default(false).describe("Include input/output schemas in the response")
472
+ });
473
+ var listToolsOutputSchema = import_zod3.z.object({
474
+ tools: import_zod3.z.array(
475
+ import_zod3.z.object({
476
+ name: import_zod3.z.string(),
477
+ fullName: import_zod3.z.string(),
478
+ description: import_zod3.z.string().optional(),
479
+ tags: import_zod3.z.array(import_zod3.z.string()).optional(),
480
+ inputSchema: import_zod3.z.unknown().optional(),
481
+ outputSchema: import_zod3.z.unknown().optional()
482
+ })
483
+ ),
484
+ count: import_zod3.z.number()
485
+ });
486
+ var ListToolsTool = class extends import_sdk4.ToolContext {
487
+ async execute(input) {
488
+ const parentScope = this.tryGet(ParentScopeToken);
489
+ const targetScope = parentScope || this.scope;
490
+ let allTools = [];
491
+ try {
492
+ allTools = targetScope.tools?.getTools?.(input.includePlugins ?? true) || [];
493
+ } catch {
494
+ }
495
+ if (input.filter) {
496
+ const pattern = safeRegex(input.filter);
497
+ if (pattern) {
498
+ allTools = allTools.filter((t) => pattern.test(t.name) || pattern.test(t.fullName));
499
+ }
500
+ }
501
+ const tools = allTools.map((tool) => ({
502
+ name: tool.name,
503
+ fullName: tool.fullName,
504
+ description: tool.metadata?.description,
505
+ tags: tool.metadata?.tags,
506
+ ...input.includeSchemas ? {
507
+ inputSchema: tool.inputSchema,
508
+ outputSchema: tool.outputSchema
509
+ } : {}
510
+ }));
511
+ return {
512
+ tools,
513
+ count: tools.length
514
+ };
515
+ }
516
+ };
517
+ ListToolsTool = __decorateClass([
518
+ (0, import_sdk4.Tool)({
519
+ name: "dashboard:list-tools",
520
+ description: "List all tools registered in the monitored FrontMCP server. Returns tool names, descriptions, and optionally schemas.",
521
+ inputSchema: listToolsInputSchema,
522
+ outputSchema: listToolsOutputSchema,
523
+ annotations: {
524
+ readOnlyHint: true
525
+ }
526
+ })
527
+ ], ListToolsTool);
528
+
529
+ // plugins/plugin-dashboard/src/tools/list-resources.tool.ts
530
+ var import_sdk5 = require("@frontmcp/sdk");
531
+ var import_zod4 = require("zod");
532
+ var listResourcesInputSchema = import_zod4.z.object({
533
+ filter: import_zod4.z.string().optional().describe("Filter resources by name or URI pattern (regex supported)"),
534
+ includeTemplates: import_zod4.z.boolean().default(true).describe("Include resource templates in the response")
535
+ });
536
+ var listResourcesOutputSchema = import_zod4.z.object({
537
+ resources: import_zod4.z.array(
538
+ import_zod4.z.object({
539
+ name: import_zod4.z.string(),
540
+ uri: import_zod4.z.string(),
541
+ description: import_zod4.z.string().optional(),
542
+ mimeType: import_zod4.z.string().optional(),
543
+ isTemplate: import_zod4.z.boolean()
544
+ })
545
+ ),
546
+ count: import_zod4.z.number()
547
+ });
548
+ var ListResourcesTool = class extends import_sdk5.ToolContext {
549
+ async execute(input) {
550
+ const parentScope = this.tryGet(ParentScopeToken);
551
+ const targetScope = parentScope || this.scope;
552
+ const results = [];
553
+ try {
554
+ const resources = targetScope.resources?.getResources?.(true) || [];
555
+ for (const resource of resources) {
556
+ results.push({
557
+ name: resource.name || "unnamed",
558
+ uri: resource.uri || "unknown",
559
+ description: resource.metadata?.description,
560
+ mimeType: resource.metadata?.mimeType,
561
+ isTemplate: false
562
+ });
563
+ }
564
+ } catch {
565
+ }
566
+ if (input.includeTemplates) {
567
+ try {
568
+ const templates = targetScope.resources?.getResourceTemplates?.() || [];
569
+ for (const template of templates) {
570
+ results.push({
571
+ name: template.name || "unnamed",
572
+ uri: template.uriTemplate || "unknown",
573
+ description: template.metadata?.description,
574
+ mimeType: template.metadata?.mimeType,
575
+ isTemplate: true
576
+ });
577
+ }
578
+ } catch {
579
+ }
580
+ }
581
+ let filtered = results;
582
+ if (input.filter) {
583
+ const pattern = safeRegex(input.filter);
584
+ if (pattern) {
585
+ filtered = results.filter((r) => pattern.test(r.name) || pattern.test(r.uri));
586
+ }
587
+ }
588
+ return {
589
+ resources: filtered,
590
+ count: filtered.length
591
+ };
592
+ }
593
+ };
594
+ ListResourcesTool = __decorateClass([
595
+ (0, import_sdk5.Tool)({
596
+ name: "dashboard:list-resources",
597
+ description: "List all resources and resource templates registered in the monitored FrontMCP server.",
598
+ inputSchema: listResourcesInputSchema,
599
+ outputSchema: listResourcesOutputSchema,
600
+ annotations: {
601
+ readOnlyHint: true
602
+ }
603
+ })
604
+ ], ListResourcesTool);
605
+
606
+ // plugins/plugin-dashboard/src/html/html.generator.ts
607
+ function escapeForJs(str) {
608
+ return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/"/g, '\\"').replace(/</g, "\\x3c").replace(/>/g, "\\x3e").replace(/\n/g, "\\n").replace(/\r/g, "\\r");
609
+ }
610
+ function generateDashboardHtml(options) {
611
+ const cdn = options.cdn;
612
+ const basePath = options.basePath;
613
+ if (cdn.entrypoint) {
614
+ return generateExternalEntrypointHtml(options);
615
+ }
616
+ return generateInlineDashboardHtml(options);
617
+ }
618
+ function generateExternalEntrypointHtml(options) {
619
+ const { cdn, auth } = options;
620
+ const safeBasePath = escapeForJs(options.basePath);
621
+ const token = escapeForJs(auth?.token || "");
622
+ const safeReact = escapeForJs(cdn.react);
623
+ const safeReactDom = escapeForJs(cdn.reactDom);
624
+ const safeReactDomClient = escapeForJs(cdn.reactDomClient);
625
+ const safeReactJsxRuntime = escapeForJs(cdn.reactJsxRuntime);
626
+ const safeXyflow = escapeForJs(cdn.xyflow);
627
+ const safeDagre = escapeForJs(cdn.dagre);
628
+ const safeXyflowCss = escapeForJs(cdn.xyflowCss);
629
+ const safeEntrypoint = escapeForJs(cdn.entrypoint || "");
630
+ return `<!DOCTYPE html>
631
+ <html lang="en">
632
+ <head>
633
+ <meta charset="UTF-8">
634
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
635
+ <title>FrontMCP Dashboard</title>
636
+ <script type="importmap">
637
+ {
638
+ "imports": {
639
+ "react": "${safeReact}",
640
+ "react-dom": "${safeReactDom}",
641
+ "react-dom/client": "${safeReactDomClient}",
642
+ "react/jsx-runtime": "${safeReactJsxRuntime}",
643
+ "@xyflow/react": "${safeXyflow}",
644
+ "dagre": "${safeDagre}"
645
+ }
646
+ }
647
+ </script>
648
+ <link rel="stylesheet" href="${safeXyflowCss}" />
649
+ </head>
650
+ <body>
651
+ <div id="root">Loading dashboard...</div>
652
+ <script type="module">
653
+ // Escape HTML for safe innerHTML usage
654
+ function escapeHtml(str) {
655
+ return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
656
+ }
657
+
658
+ // Dashboard configuration
659
+ window.__FRONTMCP_DASHBOARD__ = {
660
+ basePath: '${safeBasePath}',
661
+ sseUrl: '${safeBasePath}/sse${token ? `?token=${token}` : ""}',
662
+ token: '${token}',
663
+ };
664
+
665
+ // Load external dashboard UI
666
+ import('${safeEntrypoint}').then(mod => {
667
+ if (mod.mount) {
668
+ mod.mount(document.getElementById('root'), window.__FRONTMCP_DASHBOARD__);
669
+ }
670
+ }).catch(err => {
671
+ document.getElementById('root').innerHTML =
672
+ '<div style="color: red; padding: 20px;">Failed to load dashboard: ' + escapeHtml(err.message || 'Unknown error') + '</div>';
673
+ });
674
+ </script>
675
+ </body>
676
+ </html>`;
677
+ }
678
+ function generateInlineDashboardHtml(options) {
679
+ const { cdn, auth } = options;
680
+ const safeBasePath = escapeForJs(options.basePath);
681
+ const token = escapeForJs(auth?.token || "");
682
+ const sseUrl = `${safeBasePath}/sse${token ? `?token=${token}` : ""}`;
683
+ const safeReact = escapeForJs(cdn.react);
684
+ const safeReactDom = escapeForJs(cdn.reactDom);
685
+ const safeReactDomClient = escapeForJs(cdn.reactDomClient);
686
+ const safeReactJsxRuntime = escapeForJs(cdn.reactJsxRuntime);
687
+ const safeXyflow = escapeForJs(cdn.xyflow);
688
+ const safeDagre = escapeForJs(cdn.dagre);
689
+ const safeXyflowCss = escapeForJs(cdn.xyflowCss);
690
+ return `<!DOCTYPE html>
691
+ <html lang="en">
692
+ <head>
693
+ <meta charset="UTF-8">
694
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
695
+ <title>FrontMCP Dashboard</title>
696
+
697
+ <script type="importmap">
698
+ {
699
+ "imports": {
700
+ "react": "${safeReact}",
701
+ "react-dom": "${safeReactDom}",
702
+ "react-dom/client": "${safeReactDomClient}",
703
+ "react/jsx-runtime": "${safeReactJsxRuntime}",
704
+ "@xyflow/react": "${safeXyflow}",
705
+ "dagre": "${safeDagre}"
706
+ }
707
+ }
708
+ </script>
709
+ <link rel="stylesheet" href="${safeXyflowCss}" />
710
+
711
+ <style>
712
+ * { margin: 0; padding: 0; box-sizing: border-box; }
713
+ body {
714
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
715
+ background: #0f172a;
716
+ color: #e2e8f0;
717
+ }
718
+ #root { width: 100vw; height: 100vh; }
719
+
720
+ .loading {
721
+ display: flex;
722
+ flex-direction: column;
723
+ align-items: center;
724
+ justify-content: center;
725
+ height: 100vh;
726
+ gap: 16px;
727
+ }
728
+ .loading-spinner {
729
+ width: 40px;
730
+ height: 40px;
731
+ border: 3px solid #334155;
732
+ border-top-color: #3b82f6;
733
+ border-radius: 50%;
734
+ animation: spin 1s linear infinite;
735
+ }
736
+ @keyframes spin { to { transform: rotate(360deg); } }
737
+
738
+ .error {
739
+ display: flex;
740
+ flex-direction: column;
741
+ align-items: center;
742
+ justify-content: center;
743
+ height: 100vh;
744
+ gap: 12px;
745
+ color: #f87171;
746
+ }
747
+
748
+ .header {
749
+ position: fixed;
750
+ top: 20px;
751
+ left: 20px;
752
+ background: #1e293b;
753
+ padding: 16px 20px;
754
+ border-radius: 12px;
755
+ border: 1px solid #334155;
756
+ z-index: 1000;
757
+ }
758
+ .header h1 {
759
+ font-size: 18px;
760
+ font-weight: 600;
761
+ margin-bottom: 4px;
762
+ }
763
+ .header .stats {
764
+ font-size: 13px;
765
+ color: #94a3b8;
766
+ }
767
+ .header .status {
768
+ font-size: 11px;
769
+ margin-top: 8px;
770
+ display: flex;
771
+ align-items: center;
772
+ gap: 6px;
773
+ }
774
+ .status-dot {
775
+ width: 8px;
776
+ height: 8px;
777
+ border-radius: 50%;
778
+ background: #22c55e;
779
+ }
780
+ .status-dot.disconnected { background: #ef4444; }
781
+ .status-dot.connecting { background: #f59e0b; animation: pulse 1s ease-in-out infinite; }
782
+ @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
783
+
784
+ .legend {
785
+ position: fixed;
786
+ bottom: 20px;
787
+ left: 20px;
788
+ background: #1e293b;
789
+ padding: 16px;
790
+ border-radius: 12px;
791
+ border: 1px solid #334155;
792
+ z-index: 1000;
793
+ font-size: 12px;
794
+ }
795
+ .legend h3 {
796
+ margin-bottom: 12px;
797
+ font-size: 13px;
798
+ color: #f1f5f9;
799
+ }
800
+ .legend-item {
801
+ display: flex;
802
+ align-items: center;
803
+ margin-bottom: 6px;
804
+ color: #94a3b8;
805
+ }
806
+ .legend-color {
807
+ width: 16px;
808
+ height: 16px;
809
+ border-radius: 4px;
810
+ margin-right: 8px;
811
+ border: 2px solid;
812
+ }
813
+
814
+ .custom-node {
815
+ padding: 10px 14px;
816
+ border-radius: 8px;
817
+ min-width: 140px;
818
+ box-shadow: 0 4px 12px rgba(0,0,0,0.3);
819
+ }
820
+ .custom-node .node-type {
821
+ font-size: 10px;
822
+ text-transform: uppercase;
823
+ margin-bottom: 4px;
824
+ font-weight: 500;
825
+ letter-spacing: 0.5px;
826
+ }
827
+ .custom-node .node-label {
828
+ font-size: 13px;
829
+ font-weight: 600;
830
+ color: #f1f5f9;
831
+ }
832
+ .custom-node .node-desc {
833
+ font-size: 10px;
834
+ color: #94a3b8;
835
+ margin-top: 3px;
836
+ max-width: 160px;
837
+ overflow: hidden;
838
+ text-overflow: ellipsis;
839
+ white-space: nowrap;
840
+ }
841
+
842
+ .react-flow__minimap { background: #1e293b !important; }
843
+ .react-flow__controls button {
844
+ background: #1e293b !important;
845
+ border-color: #334155 !important;
846
+ color: #e2e8f0 !important;
847
+ }
848
+ .react-flow__controls button:hover {
849
+ background: #334155 !important;
850
+ }
851
+ </style>
852
+ </head>
853
+ <body>
854
+ <div id="root">
855
+ <div class="loading">
856
+ <div class="loading-spinner"></div>
857
+ <div>Connecting to server...</div>
858
+ </div>
859
+ </div>
860
+
861
+ <script type="module">
862
+ import React, { useMemo, useCallback, useState, useEffect } from 'react';
863
+ import { createRoot } from 'react-dom/client';
864
+ import {
865
+ ReactFlow,
866
+ ReactFlowProvider,
867
+ Background,
868
+ Controls,
869
+ MiniMap,
870
+ useNodesState,
871
+ useEdgesState,
872
+ Handle,
873
+ Position
874
+ } from '@xyflow/react';
875
+ import dagre from 'dagre';
876
+
877
+ // Configuration
878
+ const config = {
879
+ basePath: '${safeBasePath}',
880
+ sseUrl: '${sseUrl}',
881
+ token: '${token}',
882
+ };
883
+
884
+ // Node styling config
885
+ const nodeConfig = {
886
+ server: { fill: '#312e81', stroke: '#6366f1', icon: '\u{1F5A5}\uFE0F', textColor: '#a5b4fc' },
887
+ scope: { fill: '#1e3a5f', stroke: '#3b82f6', icon: '\u{1F4E6}', textColor: '#93c5fd' },
888
+ app: { fill: '#14532d', stroke: '#22c55e', icon: '\u{1F4F1}', textColor: '#86efac' },
889
+ plugin: { fill: '#713f12', stroke: '#f59e0b', icon: '\u{1F50C}', textColor: '#fcd34d' },
890
+ adapter: { fill: '#7f1d1d', stroke: '#ef4444', icon: '\u{1F517}', textColor: '#fca5a5' },
891
+ tool: { fill: '#164e63', stroke: '#06b6d4', icon: '\u{1F527}', textColor: '#67e8f9' },
892
+ resource: { fill: '#312e81', stroke: '#8b5cf6', icon: '\u{1F4C4}', textColor: '#c4b5fd' },
893
+ 'resource-template': { fill: '#4c1d95', stroke: '#a78bfa', icon: '\u{1F4CB}', textColor: '#ddd6fe' },
894
+ prompt: { fill: '#831843', stroke: '#ec4899', icon: '\u{1F4AC}', textColor: '#f9a8d4' },
895
+ auth: { fill: '#78350f', stroke: '#d97706', icon: '\u{1F6E1}\uFE0F', textColor: '#fcd34d' },
896
+ };
897
+
898
+ // Custom node component
899
+ function CustomNode({ data, type }) {
900
+ const cfg = nodeConfig[type] || nodeConfig.tool;
901
+
902
+ return React.createElement('div', {
903
+ className: 'custom-node',
904
+ style: { background: cfg.fill, border: '2px solid ' + cfg.stroke }
905
+ },
906
+ React.createElement(Handle, { type: 'target', position: Position.Top, style: { visibility: 'hidden' } }),
907
+ React.createElement('div', { className: 'node-type', style: { color: cfg.textColor } },
908
+ cfg.icon + ' ' + type
909
+ ),
910
+ React.createElement('div', { className: 'node-label' }, data.label),
911
+ data.description && React.createElement('div', { className: 'node-desc' }, data.description),
912
+ React.createElement(Handle, { type: 'source', position: Position.Bottom, style: { visibility: 'hidden' } })
913
+ );
914
+ }
915
+
916
+ // Create node types
917
+ const nodeTypes = {};
918
+ Object.keys(nodeConfig).forEach(type => {
919
+ nodeTypes[type] = (props) => CustomNode({ ...props, type });
920
+ });
921
+
922
+ // Layout with Dagre
923
+ function layoutGraph(graphData) {
924
+ const g = new dagre.graphlib.Graph();
925
+ g.setGraph({ rankdir: 'TB', nodesep: 50, ranksep: 80 });
926
+ g.setDefaultEdgeLabel(() => ({}));
927
+
928
+ graphData.nodes.forEach(node => {
929
+ g.setNode(node.id, { width: 160, height: 60 });
930
+ });
931
+
932
+ graphData.edges.forEach(edge => {
933
+ g.setEdge(edge.source, edge.target);
934
+ });
935
+
936
+ dagre.layout(g);
937
+
938
+ const nodes = graphData.nodes.map(node => {
939
+ const pos = g.node(node.id);
940
+ return {
941
+ id: node.id,
942
+ type: node.type,
943
+ position: { x: pos.x - 80, y: pos.y - 30 },
944
+ data: { label: node.label, ...node.data },
945
+ };
946
+ });
947
+
948
+ const edges = graphData.edges.map(edge => ({
949
+ id: edge.id,
950
+ source: edge.source,
951
+ target: edge.target,
952
+ type: 'smoothstep',
953
+ style: { stroke: '#475569', strokeWidth: 1.5 },
954
+ markerEnd: { type: 'arrowclosed', color: '#475569' },
955
+ }));
956
+
957
+ return { nodes, edges };
958
+ }
959
+
960
+ /**
961
+ * MCP Client for SSE transport communication.
962
+ * Connects to the dashboard SSE endpoint and sends JSON-RPC requests.
963
+ */
964
+ class McpClient {
965
+ constructor(sseUrl, basePath) {
966
+ this.sseUrl = sseUrl;
967
+ this.basePath = basePath;
968
+ this.messageUrl = null;
969
+ this.eventSource = null;
970
+ this.requestId = 0;
971
+ this.pendingRequests = new Map();
972
+ this.sessionId = null;
973
+ }
974
+
975
+ /**
976
+ * Connect to the SSE endpoint and wait for the message endpoint URL.
977
+ * The server sends an 'endpoint' event containing the URL for POST requests.
978
+ */
979
+ async connect() {
980
+ return new Promise((resolve, reject) => {
981
+ this.eventSource = new EventSource(this.sseUrl);
982
+
983
+ // Listen for the endpoint event to get the message URL
984
+ this.eventSource.addEventListener('endpoint', (e) => {
985
+ const endpointPath = e.data.split('?')[0].replace(this.basePath, '');
986
+ this.messageUrl = this.basePath + endpointPath;
987
+
988
+ // Extract sessionId from the endpoint URL (legacy SSE format)
989
+ const match = e.data.match(/sessionId=([^&]+)/);
990
+ this.sessionId = match ? match[1] : null;
991
+ resolve();
992
+ });
993
+
994
+ // Listen for responses to our requests
995
+ this.eventSource.addEventListener('message', (e) => {
996
+ try {
997
+ const response = JSON.parse(e.data);
998
+ if (response.id && this.pendingRequests.has(response.id)) {
999
+ const { resolve, reject } = this.pendingRequests.get(response.id);
1000
+ this.pendingRequests.delete(response.id);
1001
+ if (response.error) {
1002
+ reject(new Error(response.error.message));
1003
+ } else {
1004
+ resolve(response.result);
1005
+ }
1006
+ }
1007
+ } catch (err) {
1008
+ console.error('Failed to parse SSE message:', err);
1009
+ }
1010
+ });
1011
+
1012
+ this.eventSource.onerror = () => {
1013
+ if (!this.messageUrl) {
1014
+ reject(new Error('SSE connection failed'));
1015
+ }
1016
+ };
1017
+
1018
+ // Connection timeout
1019
+ setTimeout(() => {
1020
+ if (!this.messageUrl) {
1021
+ this.close();
1022
+ reject(new Error('SSE connection timeout'));
1023
+ }
1024
+ }, 10000);
1025
+ });
1026
+ }
1027
+
1028
+ /**
1029
+ * Call an MCP tool via JSON-RPC over HTTP POST.
1030
+ * @param {string} name - The tool name (e.g., 'dashboard:graph')
1031
+ * @param {object} args - The tool arguments
1032
+ * @returns {Promise<object>} The tool result
1033
+ */
1034
+ async callTool(name, args = {}) {
1035
+ if (!this.messageUrl) {
1036
+ throw new Error('Not connected');
1037
+ }
1038
+
1039
+ const id = ++this.requestId;
1040
+ const message = {
1041
+ jsonrpc: '2.0',
1042
+ id,
1043
+ method: 'tools/call',
1044
+ params: { name, arguments: args }
1045
+ };
1046
+
1047
+ return new Promise((resolve, reject) => {
1048
+ this.pendingRequests.set(id, { resolve, reject });
1049
+
1050
+ // Append sessionId to URL for legacy SSE transport
1051
+ const url = this.messageUrl + (this.sessionId ? '?sessionId=' + this.sessionId : '');
1052
+ fetch(url, {
1053
+ method: 'POST',
1054
+ headers: { 'Content-Type': 'application/json' },
1055
+ body: JSON.stringify(message)
1056
+ }).catch(reject);
1057
+ });
1058
+ }
1059
+
1060
+ /** Close the SSE connection */
1061
+ close() {
1062
+ if (this.eventSource) {
1063
+ this.eventSource.close();
1064
+ this.eventSource = null;
1065
+ }
1066
+ }
1067
+ }
1068
+
1069
+ // Main App
1070
+ function DashboardApp() {
1071
+ const [status, setStatus] = useState('loading');
1072
+ const [error, setError] = useState(null);
1073
+ const [graphData, setGraphData] = useState(null);
1074
+ const [nodes, setNodes, onNodesChange] = useNodesState([]);
1075
+ const [edges, setEdges, onEdgesChange] = useEdgesState([]);
1076
+
1077
+ useEffect(() => {
1078
+ const client = new McpClient(config.sseUrl, config.basePath);
1079
+
1080
+ async function init() {
1081
+ try {
1082
+ // Connect via SSE
1083
+ setStatus('connecting');
1084
+ await client.connect();
1085
+
1086
+ // Call dashboard:graph tool via MCP protocol
1087
+ const result = await client.callTool('dashboard:graph', {});
1088
+
1089
+ // Extract graph data from tool result
1090
+ let data;
1091
+ if (result && result.content && result.content[0]) {
1092
+ const content = result.content[0];
1093
+ if (content.type === 'text') {
1094
+ data = JSON.parse(content.text);
1095
+ }
1096
+ }
1097
+
1098
+ if (!data) {
1099
+ throw new Error('Invalid graph data response');
1100
+ }
1101
+
1102
+ setGraphData(data);
1103
+ setStatus('connected');
1104
+
1105
+ const { nodes: layoutNodes, edges: layoutEdges } = layoutGraph(data);
1106
+ setNodes(layoutNodes);
1107
+ setEdges(layoutEdges);
1108
+ } catch (err) {
1109
+ setStatus('error');
1110
+ setError(err.message);
1111
+ }
1112
+ }
1113
+
1114
+ init();
1115
+
1116
+ return () => client.close();
1117
+ }, []);
1118
+
1119
+ if (error) {
1120
+ return React.createElement('div', { className: 'error' },
1121
+ React.createElement('div', { style: { fontSize: 48 } }, '\u26A0\uFE0F'),
1122
+ React.createElement('div', { style: { fontSize: 18, fontWeight: 600 } }, 'Connection Failed'),
1123
+ React.createElement('div', { style: { color: '#94a3b8' } }, error)
1124
+ );
1125
+ }
1126
+
1127
+ if (!graphData) {
1128
+ return React.createElement('div', { className: 'loading' },
1129
+ React.createElement('div', { className: 'loading-spinner' }),
1130
+ React.createElement('div', null, 'Loading graph data...')
1131
+ );
1132
+ }
1133
+
1134
+ return React.createElement('div', { style: { width: '100%', height: '100%' } },
1135
+ React.createElement('div', { className: 'header' },
1136
+ React.createElement('h1', null, graphData.metadata.serverName),
1137
+ React.createElement('div', { className: 'stats' },
1138
+ graphData.metadata.nodeCount + ' nodes \xB7 ' + graphData.metadata.edgeCount + ' edges'
1139
+ ),
1140
+ React.createElement('div', { className: 'status' },
1141
+ React.createElement('span', {
1142
+ className: 'status-dot' + (status !== 'connected' ? ' ' + status : '')
1143
+ }),
1144
+ status === 'connected' ? 'Connected' : status
1145
+ )
1146
+ ),
1147
+
1148
+ React.createElement('div', { className: 'legend' },
1149
+ React.createElement('h3', null, 'Node Types'),
1150
+ ...Object.entries(nodeConfig).slice(0, 6).map(([type, cfg]) =>
1151
+ React.createElement('div', { key: type, className: 'legend-item' },
1152
+ React.createElement('div', {
1153
+ className: 'legend-color',
1154
+ style: { background: cfg.fill, borderColor: cfg.stroke }
1155
+ }),
1156
+ cfg.icon + ' ' + type
1157
+ )
1158
+ )
1159
+ ),
1160
+
1161
+ React.createElement(ReactFlow, {
1162
+ nodes,
1163
+ edges,
1164
+ onNodesChange,
1165
+ onEdgesChange,
1166
+ nodeTypes,
1167
+ fitView: true,
1168
+ fitViewOptions: { padding: 0.2 },
1169
+ minZoom: 0.1,
1170
+ maxZoom: 2,
1171
+ },
1172
+ React.createElement(Background, { color: '#334155', gap: 20 }),
1173
+ React.createElement(Controls),
1174
+ React.createElement(MiniMap, {
1175
+ nodeColor: (n) => nodeConfig[n.type]?.stroke || '#475569',
1176
+ maskColor: 'rgba(15, 23, 42, 0.8)'
1177
+ })
1178
+ )
1179
+ );
1180
+ }
1181
+
1182
+ // Mount
1183
+ const root = createRoot(document.getElementById('root'));
1184
+ root.render(
1185
+ React.createElement(ReactFlowProvider, null,
1186
+ React.createElement(DashboardApp)
1187
+ )
1188
+ );
1189
+ </script>
1190
+ </body>
1191
+ </html>`;
1192
+ }
1193
+
1194
+ // plugins/plugin-dashboard/src/app/dashboard.app.ts
1195
+ var DashboardMiddlewareToken = /* @__PURE__ */ Symbol("dashboard:middleware");
1196
+ function createDashboardMiddleware(options) {
1197
+ const html = generateDashboardHtml(options);
1198
+ return async (req, res, next) => {
1199
+ if (!isDashboardEnabled(options)) {
1200
+ return next();
1201
+ }
1202
+ const urlPath = req.path || req.url || "/";
1203
+ const method = (req.method || "GET").toUpperCase();
1204
+ if (method === "GET" && (urlPath === "/" || urlPath === "")) {
1205
+ res.setHeader?.(
1206
+ "Content-Type",
1207
+ "text/html"
1208
+ );
1209
+ res.status(200).send(html);
1210
+ return;
1211
+ }
1212
+ return next();
1213
+ };
1214
+ }
1215
+ var DashboardHttpPlugin = class extends import_sdk6.DynamicPlugin {
1216
+ options;
1217
+ constructor(options = {}) {
1218
+ super();
1219
+ this.options = dashboardPluginOptionsSchema.parse({
1220
+ ...defaultDashboardPluginOptions,
1221
+ ...options
1222
+ });
1223
+ }
1224
+ /**
1225
+ * Provide the dashboard config and middleware registration via DI.
1226
+ */
1227
+ static dynamicProviders(options) {
1228
+ const parsedOptions = dashboardPluginOptionsSchema.parse({
1229
+ ...defaultDashboardPluginOptions,
1230
+ ...options
1231
+ });
1232
+ return [
1233
+ {
1234
+ name: "dashboard:config",
1235
+ provide: DashboardConfigToken,
1236
+ useValue: parsedOptions
1237
+ },
1238
+ // Register middleware for HTML serving (must be in dynamic providers to access config)
1239
+ {
1240
+ name: "dashboard:middleware",
1241
+ provide: DashboardMiddlewareToken,
1242
+ inject: () => [import_sdk6.FrontMcpServer],
1243
+ useFactory: (server) => {
1244
+ const middleware = createDashboardMiddleware(parsedOptions);
1245
+ server.registerMiddleware(parsedOptions.basePath, middleware);
1246
+ return { registered: true };
1247
+ }
1248
+ }
1249
+ ];
1250
+ }
1251
+ };
1252
+ DashboardHttpPlugin = __decorateClass([
1253
+ (0, import_sdk6.Plugin)({
1254
+ name: "dashboard:http",
1255
+ description: "Dashboard HTTP handler for serving UI HTML"
1256
+ })
1257
+ ], DashboardHttpPlugin);
1258
+ var DashboardApp = class {
1259
+ };
1260
+ DashboardApp = __decorateClass([
1261
+ (0, import_sdk6.App)({
1262
+ name: "dashboard",
1263
+ description: "FrontMCP Dashboard for visualization and monitoring",
1264
+ providers: [
1265
+ // Provide parent scope reference (same as current scope when standalone: false)
1266
+ {
1267
+ name: "dashboard:parent-scope",
1268
+ provide: ParentScopeToken,
1269
+ inject: () => [import_sdk6.ScopeEntry],
1270
+ useFactory: (scope) => {
1271
+ return scope;
1272
+ }
1273
+ },
1274
+ // Graph data provider for extracting server structure
1275
+ {
1276
+ name: "dashboard:graph-data",
1277
+ provide: GraphDataProvider,
1278
+ inject: () => [import_sdk6.ScopeEntry, import_sdk6.FrontMcpConfig],
1279
+ useFactory: (scope, config) => {
1280
+ const serverName = config.info?.name || "FrontMCP Server";
1281
+ const serverVersion = config.info?.version;
1282
+ return new GraphDataProvider(scope, serverName, serverVersion);
1283
+ }
1284
+ }
1285
+ ],
1286
+ plugins: [DashboardHttpPlugin.init({})],
1287
+ tools: [GraphTool, ListToolsTool, ListResourcesTool],
1288
+ auth: {
1289
+ mode: "public"
1290
+ },
1291
+ standalone: true
1292
+ // Dashboard is part of root scope so GraphDataProvider can access all tools/resources
1293
+ })
1294
+ ], DashboardApp);
1295
+ // Annotate the CommonJS export names for ESM import in node:
1296
+ 0 && (module.exports = {
1297
+ DashboardApp,
1298
+ DashboardConfigToken,
1299
+ DashboardPlugin,
1300
+ GraphDataProvider,
1301
+ GraphDataProviderToken,
1302
+ GraphTool,
1303
+ ListResourcesTool,
1304
+ ListToolsTool,
1305
+ ParentScopeToken,
1306
+ dashboardPluginOptionsSchema,
1307
+ defaultDashboardPluginOptions,
1308
+ isDashboardEnabled
1309
+ });