@frontmcp/plugin-dashboard 1.12.0-rc.1 → 1.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/esm/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __decorateClass=(decorators,target,key,kind)=>{var result=kind>1?void 0:kind?__getOwnPropDesc(target,key):target;for(var i=decorators.length-1,decorator;i>=0;i--)if(decorator=decorators[i])result=(kind?decorator(target,key,result):decorator(result))||result;if(kind&&result)__defProp(target,key,result);return result};import{DynamicPlugin,Plugin}from"@frontmcp/sdk";import{z}from"@frontmcp/lazy-zod";var cdnConfigSchema=z.object({entrypoint:z.string().optional(),react:z.string().default("https://esm.sh/react@19"),reactDom:z.string().default("https://esm.sh/react-dom@19"),reactDomClient:z.string().default("https://esm.sh/react-dom@19/client"),reactJsxRuntime:z.string().default("https://esm.sh/react@19/jsx-runtime"),reactRouter:z.string().default("https://esm.sh/react-router-dom@7"),xyflow:z.string().default("https://esm.sh/@xyflow/react@12?external=react,react-dom"),dagre:z.string().default("https://esm.sh/dagre@0.8.5"),xyflowCss:z.string().default("https://esm.sh/@xyflow/react@12/dist/style.css")});var dashboardAuthSchema=z.object({enabled:z.boolean().default(false),token:z.string().min(1).optional()}).refine(value=>!value.enabled||Boolean(value.token),{message:"dashboard auth.enabled requires auth.token to be set",path:["token"]});var dashboardPluginOptionsSchema=z.object({enabled:z.boolean().optional(),basePath:z.string().default("/dashboard"),auth:dashboardAuthSchema.optional().transform(v=>dashboardAuthSchema.parse(v??{})),cdn:cdnConfigSchema.optional().transform(v=>cdnConfigSchema.parse(v??{}))});var defaultDashboardPluginOptions={basePath:"/dashboard",auth:{enabled:false},cdn:{}};function isDashboardEnabled(options){if(options.enabled!==void 0){return options.enabled}return process.env["NODE_ENV"]!=="production"}var publishedOptions;function publishDashboardOptions(options){if(publishedOptions&&!sameConfiguration(publishedOptions,options)){if(!sameAuth(publishedOptions,options)){throw new Error("[frontmcp:dashboard] A second, different dashboard AUTH configuration was registered in this process. Dashboard options are process-wide, so accepting it would apply one server's token to another. Run one dashboard per process, or give both servers the same auth configuration.")}console.warn("[frontmcp:dashboard] A second, different dashboard configuration was registered in this process. Dashboard options are process-wide, so the last one wins for every server here. Run one dashboard per process until per-instance configuration is supported.")}publishedOptions=options}function sameAuth(a,b){return a.auth?.enabled===b.auth?.enabled&&a.auth?.token===b.auth?.token}function sameConfiguration(a,b){return a.basePath===b.basePath&&a.enabled===b.enabled&&a.auth?.enabled===b.auth?.enabled&&a.auth?.token===b.auth?.token&&JSON.stringify(a.cdn??{})===JSON.stringify(b.cdn??{})}function resolveDashboardOptions(fallback={}){if(publishedOptions)return publishedOptions;return dashboardPluginOptionsSchema.parse({...defaultDashboardPluginOptions,...fallback})}function resetDashboardOptions(){publishedOptions=void 0}var DashboardConfigToken=Symbol("DashboardConfig");var GraphDataProviderToken=Symbol("GraphDataProvider");var ParentScopeToken=Symbol("ParentScope");var DashboardPlugin=class extends DynamicPlugin{options;constructor(options={}){super();this.options=dashboardPluginOptionsSchema.parse({...defaultDashboardPluginOptions,...options});publishDashboardOptions(this.options)}static dynamicProviders(options){const parsedOptions=dashboardPluginOptionsSchema.parse({...defaultDashboardPluginOptions,...options});publishDashboardOptions(parsedOptions);return[{name:"dashboard:config",provide:DashboardConfigToken,useValue:parsedOptions}]}};DashboardPlugin=__decorateClass([Plugin({name:"dashboard",description:"Visual dashboard for FrontMCP server monitoring and visualization",providers:[]})],DashboardPlugin);import{App,DynamicPlugin as DynamicPlugin2,FrontMcpConfig,FrontMcpContextStorage,HttpHook,Plugin as Plugin2,ScopeEntry as ScopeEntry2}from"@frontmcp/sdk";import{MCP_ERROR_CODES,PublicMcpError}from"@frontmcp/sdk";import{base64urlEncode,hmacSha256,sha256,timingSafeEqual}from"@frontmcp/utils";var DASHBOARD_TOKEN_HEADER="x-frontmcp-dashboard-token";var DASHBOARD_SESSION_COOKIE="frontmcp_dashboard";var AUTHORIZED={authorized:true};function readHeader(headers,name){if(!headers)return void 0;const key=Object.keys(headers).find(k=>k.toLowerCase()===name);const value=key?headers[key]:void 0;return Array.isArray(value)?value[0]:value}function readQuery(query,name){const value=query?.[name];return Array.isArray(value)?value[0]:value}function tokenMatches(presented,expected){if(presented.length===0||expected.length===0)return false;return timingSafeEqual(sha256(presented),sha256(expected))}var encoder=new TextEncoder;function deriveDashboardCookieValue(token){return base64urlEncode(hmacSha256(encoder.encode(token),encoder.encode("frontmcp-dashboard-session:v1")))}function readCookie(header,name){if(!header)return void 0;for(const part of header.split(";")){const separator=part.indexOf("=");if(separator===-1)continue;if(part.slice(0,separator).trim()===name)return part.slice(separator+1).trim()}return void 0}function readBearerToken(header){if(!header)return void 0;const separator=header.indexOf(" ");if(separator===-1)return void 0;if(header.slice(0,separator).toLowerCase()!=="bearer")return void 0;const token=header.slice(separator+1).trim();return token.length>0?token:void 0}function createDashboardAuthValidator(auth,surface="page"){if(!auth?.enabled)return void 0;const expected=auth.token;const expectedCookie=expected?deriveDashboardCookieValue(expected):void 0;return req=>{if(!expected||!expectedCookie){return{authorized:false,status:401,message:"Dashboard authentication is misconfigured"}}const bearer=readBearerToken(readHeader(req.headers,"authorization"));if(bearer&&tokenMatches(bearer,expected))return AUTHORIZED;if(surface==="mcp"){const headerToken=readHeader(req.headers,DASHBOARD_TOKEN_HEADER);if(headerToken&&tokenMatches(headerToken,expected))return AUTHORIZED}else{const queryToken=readQuery(req.query,"token");if(queryToken&&tokenMatches(queryToken,expected))return AUTHORIZED}const cookie=readCookie(readHeader(req.headers,"cookie"),DASHBOARD_SESSION_COOKIE);if(cookie&&tokenMatches(cookie,expectedCookie))return AUTHORIZED;return{authorized:false,status:401,message:"Unauthorized"}}}var GATE_PASSED=Symbol("frontmcp:dashboard:gate-passed");function markDashboardGatePassed(ctx){ctx?.set(GATE_PASSED,true)}var DashboardDisabledError=class extends PublicMcpError{mcpErrorCode=MCP_ERROR_CODES.FORBIDDEN;constructor(){super("The FrontMCP dashboard is disabled.","DASHBOARD_DISABLED",404)}};var DashboardUnauthorizedError=class extends PublicMcpError{mcpErrorCode=MCP_ERROR_CODES.UNAUTHORIZED;constructor(){super(`The dashboard token is required. Send it in the ${DASHBOARD_TOKEN_HEADER} header.`,"DASHBOARD_UNAUTHORIZED",401,'Bearer realm="frontmcp-dashboard"')}};function assertDashboardAccess(options,ctx){if(!isDashboardEnabled(options)){throw new DashboardDisabledError}if(!options.auth?.enabled)return;if(ctx?.get(GATE_PASSED)===true)return;const expected=options.auth.token;const presented=ctx?.metadata?.customHeaders?.[DASHBOARD_TOKEN_HEADER];if(expected&&presented&&tokenMatches(presented,expected))return;throw new DashboardUnauthorizedError}function assertDashboardToolAccess(tool){const configured=tool.tryGet(DashboardConfigToken);assertDashboardAccess(configured??resolveDashboardOptions(),tool.tryGetContext())}import{Provider}from"@frontmcp/sdk";var GraphDataProvider=class{constructor(scope,serverName,serverVersion){this.scope=scope;this.serverName=serverName;this.serverVersion=serverVersion}scope;serverName;serverVersion;cachedData=null;cacheTimestamp=0;cacheTTL=5e3;async getGraphData(){const now=Date.now();if(this.cachedData&&now-this.cacheTimestamp<this.cacheTTL){return this.cachedData}this.cachedData=this.extractGraphData();this.cacheTimestamp=now;return this.cachedData}invalidateCache(){this.cachedData=null;this.cacheTimestamp=0}getMonitoredScopes(){try{let scopeRegistries=this.scope.providers.getRegistries("ScopeRegistry");if(!scopeRegistries||scopeRegistries.length===0){let currentProviders=this.scope.providers.parentProviders;while(currentProviders&&(!scopeRegistries||scopeRegistries.length===0)){scopeRegistries=currentProviders.getRegistries?.("ScopeRegistry")||[];currentProviders=currentProviders.parentProviders}}if(!scopeRegistries||scopeRegistries.length===0){return[this.scope]}const scopeRegistry=scopeRegistries[0];const allScopes=scopeRegistry.getScopes();return allScopes.filter(s=>s.id!=="dashboard")}catch{return[this.scope]}}extractGraphData(){const nodes=[];const edges=[];const addedNodeIds=new Set;const addNode=node=>{if(!addedNodeIds.has(node.id)){nodes.push(node);addedNodeIds.add(node.id)}};const serverId=`server:${this.serverName}`;addNode({id:serverId,type:"server",label:this.serverName,data:{name:this.serverName,description:`FrontMCP Server v${this.serverVersion||"unknown"}`}});const scopes=this.getMonitoredScopes();for(const scope of scopes){const scopeId=`scope:${scope.id}`;addNode({id:scopeId,type:"scope",label:scope.id,data:{name:scope.id}});edges.push({id:`${serverId}->${scopeId}`,source:serverId,target:scopeId,type:"contains"});this.extractToolsFromScope(scope,scopeId,edges,addNode);this.extractResourcesFromScope(scope,scopeId,edges,addNode);this.extractResourceTemplatesFromScope(scope,scopeId,edges,addNode);this.extractPromptsFromScope(scope,scopeId,edges,addNode);this.extractAppsFromScope(scope,scopeId,edges,addNode)}return{nodes,edges,metadata:this.createMetadata(nodes.length,edges.length)}}extractToolsFromScope(scope,scopeId,edges,addNode){try{const tools=scope.tools?.getTools?.(true)||[];for(const tool of tools){if(tool.fullName?.startsWith("dashboard:")){continue}const toolId=`tool:${tool.fullName}`;addNode({id:toolId,type:"tool",label:tool.name,data:{name:tool.fullName,description:tool.metadata?.description,inputSchema:tool.inputSchema,outputSchema:tool.outputSchema,tags:tool.metadata?.tags,annotations:tool.metadata?.annotations}});edges.push({id:`${scopeId}->${toolId}`,source:scopeId,target:toolId,type:"provides"})}}catch{}}extractResourcesFromScope(scope,scopeId,edges,addNode){try{const resources=scope.resources?.getResources?.(true)||[];for(const resource of resources){const resourceId=`resource:${resource.uri}`;addNode({id:resourceId,type:"resource",label:resource.name,data:{name:resource.name,description:resource.metadata?.description,uri:resource.uri,mimeType:resource.metadata?.mimeType}});edges.push({id:`${scopeId}->${resourceId}`,source:scopeId,target:resourceId,type:"provides"})}}catch{}}extractResourceTemplatesFromScope(scope,scopeId,edges,addNode){try{const templates=scope.resources?.getResourceTemplates?.()||[];for(const template of templates){const templateId=`resource-template:${template.uriTemplate}`;addNode({id:templateId,type:"resource-template",label:template.name,data:{name:template.name,description:template.metadata?.description,uri:template.uriTemplate,mimeType:template.metadata?.mimeType}});edges.push({id:`${scopeId}->${templateId}`,source:scopeId,target:templateId,type:"provides"})}}catch{}}extractPromptsFromScope(scope,scopeId,edges,addNode){try{const prompts=scope.prompts?.getPrompts?.(true)||[];for(const prompt of prompts){const promptId=`prompt:${prompt.name}`;addNode({id:promptId,type:"prompt",label:prompt.name,data:{name:prompt.name,description:prompt.metadata?.description,arguments:prompt.metadata?.arguments}});edges.push({id:`${scopeId}->${promptId}`,source:scopeId,target:promptId,type:"provides"})}}catch{}}extractAppsFromScope(scope,scopeId,edges,addNode){try{const apps=scope.apps?.getApps?.()||[];for(const app of apps){const appName=app.metadata?.name||"unknown";if(appName==="dashboard"){continue}const appId=`app:${appName}`;addNode({id:appId,type:"app",label:appName,data:{name:appName,description:app.metadata?.description}});edges.push({id:`${scopeId}->${appId}`,source:scopeId,target:appId,type:"contains"})}}catch{}}createMetadata(nodeCount,edgeCount){return{serverName:this.serverName,serverVersion:this.serverVersion,generatedAt:new Date().toISOString(),entryFile:"runtime",nodeCount,edgeCount}}};GraphDataProvider=__decorateClass([Provider({name:"graph-data-provider"})],GraphDataProvider);import{z as z2}from"@frontmcp/lazy-zod";import{Tool,ToolContext}from"@frontmcp/sdk";var graphToolInputSchema={includeSchemas:z2.boolean().optional().default(false).describe("Include full input/output schemas in the response"),refresh:z2.boolean().optional().default(false).describe("Force refresh the graph data (bypass cache)")};var GraphTool=class extends ToolContext{async execute(input){assertDashboardToolAccess(this);const graphProvider=this.get(GraphDataProvider);if(input.refresh){graphProvider.invalidateCache()}const graphData=await graphProvider.getGraphData();if(!input.includeSchemas){return{...graphData,nodes:graphData.nodes.map(node=>({...node,data:{...node.data,inputSchema:void 0,outputSchema:void 0}}))}}return graphData}};GraphTool=__decorateClass([Tool({name:"dashboard:graph",description:"Get the server graph showing all registered tools, resources, prompts, apps, and their relationships. Returns nodes and edges that can be visualized.",inputSchema:graphToolInputSchema,annotations:{readOnlyHint:true}})],GraphTool);import{z as z3}from"@frontmcp/lazy-zod";import{Tool as Tool2,ToolContext as ToolContext2}from"@frontmcp/sdk";function safeRegex(pattern){if(pattern.length>100){return null}try{const regex=new RegExp(pattern,"i");regex.test("test");return regex}catch{return null}}var listResourcesInputSchema={filter:z3.string().optional().describe("Filter resources by name or URI pattern (regex supported)"),includeTemplates:z3.boolean().default(true).describe("Include resource templates in the response")};var listResourcesOutputSchema=z3.object({resources:z3.array(z3.object({name:z3.string(),uri:z3.string(),description:z3.string().optional(),mimeType:z3.string().optional(),isTemplate:z3.boolean()})),count:z3.number()});var ListResourcesTool=class extends ToolContext2{async execute(input){assertDashboardToolAccess(this);const parentScope=this.tryGet(ParentScopeToken);const targetScope=parentScope||this.scope;const results=[];try{const resources=targetScope.resources?.getResources?.(true)||[];for(const resource of resources){results.push({name:resource.name||"unnamed",uri:resource.uri||"unknown",description:resource.metadata?.description,mimeType:resource.metadata?.mimeType,isTemplate:false})}}catch{}if(input.includeTemplates){try{const templates=targetScope.resources?.getResourceTemplates?.()||[];for(const template of templates){results.push({name:template.name||"unnamed",uri:template.uriTemplate||"unknown",description:template.metadata?.description,mimeType:template.metadata?.mimeType,isTemplate:true})}}catch{}}let filtered=results;if(input.filter){const pattern=safeRegex(input.filter);if(pattern){filtered=results.filter(r=>pattern.test(r.name)||pattern.test(r.uri))}}return{resources:filtered,count:filtered.length}}};ListResourcesTool=__decorateClass([Tool2({name:"dashboard:list-resources",description:"List all resources and resource templates registered in the monitored FrontMCP server.",inputSchema:listResourcesInputSchema,outputSchema:listResourcesOutputSchema,annotations:{readOnlyHint:true}})],ListResourcesTool);import{z as z4}from"@frontmcp/lazy-zod";import{Tool as Tool3,ToolContext as ToolContext3}from"@frontmcp/sdk";var listToolsInputSchema={filter:z4.string().optional().describe("Filter tools by name pattern (regex supported)"),includePlugins:z4.boolean().default(true).describe("Include tools from plugins"),includeSchemas:z4.boolean().default(false).describe("Include input/output schemas in the response")};var listToolsOutputSchema=z4.object({tools:z4.array(z4.object({name:z4.string(),fullName:z4.string(),description:z4.string().optional(),tags:z4.array(z4.string()).optional(),inputSchema:z4.unknown().optional(),outputSchema:z4.unknown().optional()})),count:z4.number()});var ListToolsTool=class extends ToolContext3{async execute(input){assertDashboardToolAccess(this);const parentScope=this.tryGet(ParentScopeToken);const targetScope=parentScope||this.scope;let allTools=[];try{allTools=targetScope.tools?.getTools?.(input.includePlugins??true)||[]}catch{}if(input.filter){const pattern=safeRegex(input.filter);if(pattern){allTools=allTools.filter(t=>pattern.test(t.name)||pattern.test(t.fullName))}}const tools=allTools.map(tool=>({name:tool.name,fullName:tool.fullName,description:tool.metadata?.description,tags:tool.metadata?.tags,...input.includeSchemas?{inputSchema:tool.inputSchema,outputSchema:tool.outputSchema}:{}}));return{tools,count:tools.length}}};ListToolsTool=__decorateClass([Tool3({name:"dashboard:list-tools",description:"List all tools registered in the monitored FrontMCP server. Returns tool names, descriptions, and optionally schemas.",inputSchema:listToolsInputSchema,outputSchema:listToolsOutputSchema,annotations:{readOnlyHint:true}})],ListToolsTool);import{httpRespond,HttpRoute,HttpRouteContext}from"@frontmcp/sdk";function escapeForJs(str){return str.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/"/g,'\\"').replace(/</g,"\\x3c").replace(/>/g,"\\x3e").replace(/\n/g,"\\n").replace(/\r/g,"\\r")}function generateDashboardHtml(options,mcpPath=options.basePath){if(options.cdn.entrypoint){return generateExternalEntrypointHtml(options,mcpPath)}return generateInlineDashboardHtml(options,mcpPath)}function generateExternalEntrypointHtml(options,mcpPath){const{cdn}=options;const safeBasePath=escapeForJs(options.basePath);const safeMcpPath=escapeForJs(mcpPath);const safeReact=escapeForJs(cdn.react);const safeReactDom=escapeForJs(cdn.reactDom);const safeReactDomClient=escapeForJs(cdn.reactDomClient);const safeReactJsxRuntime=escapeForJs(cdn.reactJsxRuntime);const safeXyflow=escapeForJs(cdn.xyflow);const safeDagre=escapeForJs(cdn.dagre);const safeXyflowCss=escapeForJs(cdn.xyflowCss);const safeEntrypoint=escapeForJs(cdn.entrypoint||"");return`<!DOCTYPE html>
1
+ var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __decorateClass=(decorators,target,key,kind)=>{var result=kind>1?void 0:kind?__getOwnPropDesc(target,key):target;for(var i=decorators.length-1,decorator;i>=0;i--)if(decorator=decorators[i])result=(kind?decorator(target,key,result):decorator(result))||result;if(kind&&result)__defProp(target,key,result);return result};import{DynamicPlugin,Plugin}from"@frontmcp/sdk";import{z}from"@frontmcp/lazy-zod";var cdnConfigSchema=z.object({entrypoint:z.string().optional(),react:z.string().default("https://esm.sh/react@19"),reactDom:z.string().default("https://esm.sh/react-dom@19"),reactDomClient:z.string().default("https://esm.sh/react-dom@19/client"),reactJsxRuntime:z.string().default("https://esm.sh/react@19/jsx-runtime"),reactRouter:z.string().default("https://esm.sh/react-router-dom@7"),xyflow:z.string().default("https://esm.sh/@xyflow/react@12?external=react,react-dom"),dagre:z.string().default("https://esm.sh/dagre@0.8.5"),xyflowCss:z.string().default("https://esm.sh/@xyflow/react@12/dist/style.css")});var dashboardAuthSchema=z.object({enabled:z.boolean().default(false),token:z.string().min(1).optional()}).refine(value=>!value.enabled||Boolean(value.token),{message:"dashboard auth.enabled requires auth.token to be set",path:["token"]});var dashboardPluginOptionsSchema=z.object({enabled:z.boolean().optional(),basePath:z.string().default("/dashboard"),auth:dashboardAuthSchema.optional().transform(v=>dashboardAuthSchema.parse(v??{})),cdn:cdnConfigSchema.optional().transform(v=>cdnConfigSchema.parse(v??{}))});var defaultDashboardPluginOptions={basePath:"/dashboard",auth:{enabled:false},cdn:{}};function isDashboardEnabled(options){if(options.enabled!==void 0){return options.enabled}return process.env["NODE_ENV"]!=="production"}var publishedOptions;function publishDashboardOptions(options){if(publishedOptions&&!sameConfiguration(publishedOptions,options)){if(!sameAuth(publishedOptions,options)){throw new Error("[frontmcp:dashboard] A second, different dashboard AUTH configuration was registered in this process. Dashboard options are process-wide, so accepting it would apply one server's token to another. Run one dashboard per process, or give both servers the same auth configuration.")}console.warn("[frontmcp:dashboard] A second, different dashboard configuration was registered in this process. Dashboard options are process-wide, so the last one wins for every server here. Run one dashboard per process until per-instance configuration is supported.")}publishedOptions=options}function sameAuth(a,b){return a.auth?.enabled===b.auth?.enabled&&a.auth?.token===b.auth?.token}function sameConfiguration(a,b){return a.basePath===b.basePath&&a.enabled===b.enabled&&a.auth?.enabled===b.auth?.enabled&&a.auth?.token===b.auth?.token&&JSON.stringify(a.cdn??{})===JSON.stringify(b.cdn??{})}function resolveDashboardOptions(fallback={}){if(publishedOptions)return publishedOptions;return dashboardPluginOptionsSchema.parse({...defaultDashboardPluginOptions,...fallback})}function resetDashboardOptions(){publishedOptions=void 0}var DashboardConfigToken=Symbol("DashboardConfig");var GraphDataProviderToken=Symbol("GraphDataProvider");var ParentScopeToken=Symbol("ParentScope");var DashboardPlugin=class extends DynamicPlugin{options;constructor(options={}){super();this.options=dashboardPluginOptionsSchema.parse({...defaultDashboardPluginOptions,...options});publishDashboardOptions(this.options)}static dynamicProviders(options){const parsedOptions=dashboardPluginOptionsSchema.parse({...defaultDashboardPluginOptions,...options});publishDashboardOptions(parsedOptions);return[{name:"dashboard:config",provide:DashboardConfigToken,useValue:parsedOptions}]}};DashboardPlugin=__decorateClass([Plugin({name:"dashboard",description:"Visual dashboard for FrontMCP server monitoring and visualization",providers:[]})],DashboardPlugin);import{App,DynamicPlugin as DynamicPlugin2,FrontMcpConfig,FrontMcpContextStorage,HttpHook,Plugin as Plugin2,ScopeEntry as ScopeEntry2}from"@frontmcp/sdk";import{MCP_ERROR_CODES,PublicMcpError}from"@frontmcp/sdk";import{base64urlEncode,hmacSha256,sha256,timingSafeEqual}from"@frontmcp/utils";var DASHBOARD_TOKEN_HEADER="x-frontmcp-dashboard-token";var DASHBOARD_SESSION_COOKIE="frontmcp_dashboard";var AUTHORIZED={authorized:true};function readHeader(headers,name){if(!headers)return void 0;const key=Object.keys(headers).find(k=>k.toLowerCase()===name);const value=key?headers[key]:void 0;return Array.isArray(value)?value[0]:value}function readQuery(query,name){const value=query?.[name];return Array.isArray(value)?value[0]:value}function tokenMatches(presented,expected){if(presented.length===0||expected.length===0)return false;return timingSafeEqual(sha256(presented),sha256(expected))}var encoder=new TextEncoder;function deriveDashboardCookieValue(token){return base64urlEncode(hmacSha256(encoder.encode(token),encoder.encode("frontmcp-dashboard-session:v1")))}function readCookie(header,name){if(!header)return void 0;for(const part of header.split(";")){const separator=part.indexOf("=");if(separator===-1)continue;if(part.slice(0,separator).trim()===name)return part.slice(separator+1).trim()}return void 0}function readBearerToken(header){if(!header)return void 0;const separator=header.indexOf(" ");if(separator===-1)return void 0;if(header.slice(0,separator).toLowerCase()!=="bearer")return void 0;const token=header.slice(separator+1).trim();return token.length>0?token:void 0}function createDashboardAuthValidator(auth,surface="page"){if(!auth?.enabled)return void 0;const expected=auth.token;const expectedCookie=expected?deriveDashboardCookieValue(expected):void 0;return req=>{if(!expected||!expectedCookie){return{authorized:false,status:401,message:"Dashboard authentication is misconfigured"}}const bearer=readBearerToken(readHeader(req.headers,"authorization"));if(bearer&&tokenMatches(bearer,expected))return AUTHORIZED;if(surface==="mcp"){const headerToken=readHeader(req.headers,DASHBOARD_TOKEN_HEADER);if(headerToken&&tokenMatches(headerToken,expected))return AUTHORIZED}else{const queryToken=readQuery(req.query,"token");if(queryToken&&tokenMatches(queryToken,expected))return AUTHORIZED}const cookie=readCookie(readHeader(req.headers,"cookie"),DASHBOARD_SESSION_COOKIE);if(cookie&&tokenMatches(cookie,expectedCookie))return AUTHORIZED;return{authorized:false,status:401,message:"Unauthorized"}}}var GATE_PASSED=Symbol("frontmcp:dashboard:gate-passed");function markDashboardGatePassed(ctx){ctx?.set(GATE_PASSED,true)}var DashboardDisabledError=class extends PublicMcpError{mcpErrorCode=MCP_ERROR_CODES.FORBIDDEN;constructor(){super("The FrontMCP dashboard is disabled.","DASHBOARD_DISABLED",404)}};var DashboardUnauthorizedError=class extends PublicMcpError{mcpErrorCode=MCP_ERROR_CODES.UNAUTHORIZED;constructor(){super(`The dashboard token is required. Send it in the ${DASHBOARD_TOKEN_HEADER} header.`,"DASHBOARD_UNAUTHORIZED",401,'Bearer realm="frontmcp-dashboard"')}};function assertDashboardAccess(options,ctx){if(!isDashboardEnabled(options)){throw new DashboardDisabledError}if(!options.auth?.enabled)return;if(ctx?.get(GATE_PASSED)===true)return;const expected=options.auth.token;const presented=ctx?.metadata?.customHeaders?.[DASHBOARD_TOKEN_HEADER];if(expected&&presented&&tokenMatches(presented,expected))return;throw new DashboardUnauthorizedError}function assertDashboardToolAccess(tool){const configured=tool.tryGet(DashboardConfigToken);assertDashboardAccess(configured??resolveDashboardOptions(),tool.tryGetContext())}import{Provider}from"@frontmcp/sdk";var GraphDataProvider=class{constructor(scope,serverName,serverVersion){this.scope=scope;this.serverName=serverName;this.serverVersion=serverVersion}scope;serverName;serverVersion;cachedData=null;cacheTimestamp=0;cacheTTL=5e3;async getGraphData(){const now=Date.now();if(this.cachedData&&now-this.cacheTimestamp<this.cacheTTL){return this.cachedData}this.cachedData=this.extractGraphData();this.cacheTimestamp=now;return this.cachedData}invalidateCache(){this.cachedData=null;this.cacheTimestamp=0}getMonitoredScopes(){try{let scopeRegistries=this.scope.providers.getRegistries("ScopeRegistry");if(!scopeRegistries||scopeRegistries.length===0){let currentProviders=this.scope.providers.parentProviders;while(currentProviders&&(!scopeRegistries||scopeRegistries.length===0)){scopeRegistries=currentProviders.getRegistries?.("ScopeRegistry")||[];currentProviders=currentProviders.parentProviders}}if(!scopeRegistries||scopeRegistries.length===0){return[this.scope]}const scopeRegistry=scopeRegistries[0];const allScopes=scopeRegistry.getScopes();return allScopes.filter(s=>s.id!=="dashboard")}catch{return[this.scope]}}extractGraphData(){const nodes=[];const edges=[];const addedNodeIds=new Set;const addNode=node=>{if(!addedNodeIds.has(node.id)){nodes.push(node);addedNodeIds.add(node.id)}};const serverId=`server:${this.serverName}`;addNode({id:serverId,type:"server",label:this.serverName,data:{name:this.serverName,description:`FrontMCP Server v${this.serverVersion||"unknown"}`}});const scopes=this.getMonitoredScopes();for(const scope of scopes){const scopeId=`scope:${scope.id}`;addNode({id:scopeId,type:"scope",label:scope.id,data:{name:scope.id}});edges.push({id:`${serverId}->${scopeId}`,source:serverId,target:scopeId,type:"contains"});this.extractToolsFromScope(scope,scopeId,edges,addNode);this.extractResourcesFromScope(scope,scopeId,edges,addNode);this.extractResourceTemplatesFromScope(scope,scopeId,edges,addNode);this.extractPromptsFromScope(scope,scopeId,edges,addNode);this.extractAppsFromScope(scope,scopeId,edges,addNode)}return{nodes,edges,metadata:this.createMetadata(nodes.length,edges.length)}}extractToolsFromScope(scope,scopeId,edges,addNode){try{const tools=scope.tools?.getTools?.(true)||[];for(const tool of tools){if(tool.fullName?.startsWith("dashboard:")){continue}const toolId=`tool:${tool.fullName}`;addNode({id:toolId,type:"tool",label:tool.name,data:{name:tool.fullName,description:tool.metadata?.description,inputSchema:tool.inputSchema,outputSchema:tool.outputSchema,tags:tool.metadata?.tags,annotations:tool.metadata?.annotations}});edges.push({id:`${scopeId}->${toolId}`,source:scopeId,target:toolId,type:"provides"})}}catch{}}extractResourcesFromScope(scope,scopeId,edges,addNode){try{const resources=scope.resources?.getResources?.(true)||[];for(const resource of resources){const resourceId=`resource:${resource.uri}`;addNode({id:resourceId,type:"resource",label:resource.name,data:{name:resource.name,description:resource.metadata?.description,uri:resource.uri,mimeType:resource.metadata?.mimeType}});edges.push({id:`${scopeId}->${resourceId}`,source:scopeId,target:resourceId,type:"provides"})}}catch{}}extractResourceTemplatesFromScope(scope,scopeId,edges,addNode){try{const templates=scope.resources?.getResourceTemplates?.()||[];for(const template of templates){const templateId=`resource-template:${template.uriTemplate}`;addNode({id:templateId,type:"resource-template",label:template.name,data:{name:template.name,description:template.metadata?.description,uri:template.uriTemplate,mimeType:template.metadata?.mimeType}});edges.push({id:`${scopeId}->${templateId}`,source:scopeId,target:templateId,type:"provides"})}}catch{}}extractPromptsFromScope(scope,scopeId,edges,addNode){try{const prompts=scope.prompts?.getPrompts?.(true)||[];for(const prompt of prompts){const promptId=`prompt:${prompt.name}`;addNode({id:promptId,type:"prompt",label:prompt.name,data:{name:prompt.name,description:prompt.metadata?.description,arguments:prompt.metadata?.arguments}});edges.push({id:`${scopeId}->${promptId}`,source:scopeId,target:promptId,type:"provides"})}}catch{}}extractAppsFromScope(scope,scopeId,edges,addNode){try{const apps=scope.apps?.getApps?.()||[];for(const app of apps){const appName=app.metadata?.name||"unknown";if(appName==="dashboard"){continue}const appId=`app:${appName}`;addNode({id:appId,type:"app",label:appName,data:{name:appName,description:app.metadata?.description}});edges.push({id:`${scopeId}->${appId}`,source:scopeId,target:appId,type:"contains"})}}catch{}}createMetadata(nodeCount,edgeCount){return{serverName:this.serverName,serverVersion:this.serverVersion,generatedAt:new Date().toISOString(),entryFile:"runtime",nodeCount,edgeCount}}};GraphDataProvider=__decorateClass([Provider({name:"graph-data-provider"})],GraphDataProvider);import{z as z2}from"@frontmcp/lazy-zod";import{Tool,ToolContext}from"@frontmcp/sdk";var graphToolInputSchema={includeSchemas:z2.boolean().optional().default(false).describe("Include full input/output schemas in the response"),refresh:z2.boolean().optional().default(false).describe("Force refresh the graph data (bypass cache)")};var GraphTool=class extends ToolContext{async execute(input){assertDashboardToolAccess(this);const graphProvider=this.get(GraphDataProvider);if(input.refresh){graphProvider.invalidateCache()}const graphData=await graphProvider.getGraphData();if(!input.includeSchemas){return{...graphData,nodes:graphData.nodes.map(node=>({...node,data:{...node.data,inputSchema:void 0,outputSchema:void 0}}))}}return graphData}};GraphTool=__decorateClass([Tool({name:"dashboard:graph",description:"Get the server graph showing all registered tools, resources, prompts, apps, and their relationships. Returns nodes and edges that can be visualized.",inputSchema:graphToolInputSchema,annotations:{readOnlyHint:true}})],GraphTool);import{z as z3}from"@frontmcp/lazy-zod";import{Tool as Tool2,ToolContext as ToolContext2}from"@frontmcp/sdk";function containsText(text){const needle=text.toLowerCase();return value=>value.toLowerCase().includes(needle)}var listResourcesInputSchema={filter:z3.string().optional().describe("Only resources whose name or URI contains this text, ignoring case"),includeTemplates:z3.boolean().default(true).describe("Include resource templates in the response")};var listResourcesOutputSchema=z3.object({resources:z3.array(z3.object({name:z3.string(),uri:z3.string(),description:z3.string().optional(),mimeType:z3.string().optional(),isTemplate:z3.boolean()})),count:z3.number()});var ListResourcesTool=class extends ToolContext2{async execute(input){assertDashboardToolAccess(this);const parentScope=this.tryGet(ParentScopeToken);const targetScope=parentScope||this.scope;const results=[];try{const resources=targetScope.resources?.getResources?.(true)||[];for(const resource of resources){results.push({name:resource.name||"unnamed",uri:resource.uri||"unknown",description:resource.metadata?.description,mimeType:resource.metadata?.mimeType,isTemplate:false})}}catch{}if(input.includeTemplates){try{const templates=targetScope.resources?.getResourceTemplates?.()||[];for(const template of templates){results.push({name:template.name||"unnamed",uri:template.uriTemplate||"unknown",description:template.metadata?.description,mimeType:template.metadata?.mimeType,isTemplate:true})}}catch{}}let filtered=results;if(input.filter){const matches=containsText(input.filter);filtered=results.filter(r=>matches(r.name)||matches(r.uri))}return{resources:filtered,count:filtered.length}}};ListResourcesTool=__decorateClass([Tool2({name:"dashboard:list-resources",description:"List all resources and resource templates registered in the monitored FrontMCP server.",inputSchema:listResourcesInputSchema,outputSchema:listResourcesOutputSchema,annotations:{readOnlyHint:true}})],ListResourcesTool);import{z as z4}from"@frontmcp/lazy-zod";import{Tool as Tool3,ToolContext as ToolContext3}from"@frontmcp/sdk";var listToolsInputSchema={filter:z4.string().optional().describe("Only tools whose name contains this text, ignoring case"),includePlugins:z4.boolean().default(true).describe("Include tools from plugins"),includeSchemas:z4.boolean().default(false).describe("Include input/output schemas in the response")};var listToolsOutputSchema=z4.object({tools:z4.array(z4.object({name:z4.string(),fullName:z4.string(),description:z4.string().optional(),tags:z4.array(z4.string()).optional(),inputSchema:z4.unknown().optional(),outputSchema:z4.unknown().optional()})),count:z4.number()});var ListToolsTool=class extends ToolContext3{async execute(input){assertDashboardToolAccess(this);const parentScope=this.tryGet(ParentScopeToken);const targetScope=parentScope||this.scope;let allTools=[];try{allTools=targetScope.tools?.getTools?.(input.includePlugins??true)||[]}catch{}if(input.filter){const matches=containsText(input.filter);allTools=allTools.filter(t=>matches(t.name)||matches(t.fullName))}const tools=allTools.map(tool=>({name:tool.name,fullName:tool.fullName,description:tool.metadata?.description,tags:tool.metadata?.tags,...input.includeSchemas?{inputSchema:tool.inputSchema,outputSchema:tool.outputSchema}:{}}));return{tools,count:tools.length}}};ListToolsTool=__decorateClass([Tool3({name:"dashboard:list-tools",description:"List all tools registered in the monitored FrontMCP server. Returns tool names, descriptions, and optionally schemas.",inputSchema:listToolsInputSchema,outputSchema:listToolsOutputSchema,annotations:{readOnlyHint:true}})],ListToolsTool);import{httpRespond,HttpRoute,HttpRouteContext}from"@frontmcp/sdk";function escapeForJs(str){return str.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/"/g,'\\"').replace(/</g,"\\x3c").replace(/>/g,"\\x3e").replace(/\n/g,"\\n").replace(/\r/g,"\\r")}function generateDashboardHtml(options,mcpPath=options.basePath){if(options.cdn.entrypoint){return generateExternalEntrypointHtml(options,mcpPath)}return generateInlineDashboardHtml(options,mcpPath)}function generateExternalEntrypointHtml(options,mcpPath){const{cdn}=options;const safeBasePath=escapeForJs(options.basePath);const safeMcpPath=escapeForJs(mcpPath);const safeReact=escapeForJs(cdn.react);const safeReactDom=escapeForJs(cdn.reactDom);const safeReactDomClient=escapeForJs(cdn.reactDomClient);const safeReactJsxRuntime=escapeForJs(cdn.reactJsxRuntime);const safeXyflow=escapeForJs(cdn.xyflow);const safeDagre=escapeForJs(cdn.dagre);const safeXyflowCss=escapeForJs(cdn.xyflowCss);const safeEntrypoint=escapeForJs(cdn.entrypoint||"");return`<!DOCTYPE html>
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8">
package/esm/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/plugin-dashboard",
3
- "version": "1.12.0-rc.1",
3
+ "version": "1.12.1",
4
4
  "description": "Dashboard plugin for FrontMCP: a web page and MCP endpoint that show a server's apps, tools, resources and prompts",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "license": "Apache-2.0",
@@ -49,14 +49,14 @@
49
49
  }
50
50
  },
51
51
  "devDependencies": {
52
- "@frontmcp/sdk": "1.12.0-rc.1",
52
+ "@frontmcp/sdk": "1.12.1",
53
53
  "reflect-metadata": "^0.2.2"
54
54
  },
55
55
  "dependencies": {
56
- "@frontmcp/utils": "1.12.0-rc.1"
56
+ "@frontmcp/utils": "1.12.1"
57
57
  },
58
58
  "peerDependencies": {
59
- "@frontmcp/lazy-zod": "1.12.0-rc.1",
60
- "@frontmcp/sdk": "1.12.0-rc.1"
59
+ "@frontmcp/lazy-zod": "1.12.1",
60
+ "@frontmcp/sdk": "1.12.1"
61
61
  }
62
62
  }
package/index.js CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __hasOwnProp=Object.prototype.hasOwnProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:true})};var __copyProps=(to,from,except,desc)=>{if(from&&typeof from==="object"||typeof from==="function"){for(let key of __getOwnPropNames(from))if(!__hasOwnProp.call(to,key)&&key!==except)__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable})}return to};var __toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:true}),mod);var __decorateClass=(decorators,target,key,kind)=>{var result=kind>1?void 0:kind?__getOwnPropDesc(target,key):target;for(var i=decorators.length-1,decorator;i>=0;i--)if(decorator=decorators[i])result=(kind?decorator(target,key,result):decorator(result))||result;if(kind&&result)__defProp(target,key,result);return result};var index_exports={};__export(index_exports,{DashboardApp:()=>DashboardApp,DashboardConfigToken:()=>DashboardConfigToken,DashboardPlugin:()=>DashboardPlugin,GraphDataProvider:()=>GraphDataProvider,GraphDataProviderToken:()=>GraphDataProviderToken,GraphTool:()=>GraphTool,ListResourcesTool:()=>ListResourcesTool,ListToolsTool:()=>ListToolsTool,ParentScopeToken:()=>ParentScopeToken,dashboardPluginOptionsSchema:()=>dashboardPluginOptionsSchema,default:()=>DashboardPlugin,defaultDashboardPluginOptions:()=>defaultDashboardPluginOptions,isDashboardEnabled:()=>isDashboardEnabled,resetDashboardOptions:()=>resetDashboardOptions});module.exports=__toCommonJS(index_exports);var import_sdk=require("@frontmcp/sdk");var import_lazy_zod=require("@frontmcp/lazy-zod");var cdnConfigSchema=import_lazy_zod.z.object({entrypoint:import_lazy_zod.z.string().optional(),react:import_lazy_zod.z.string().default("https://esm.sh/react@19"),reactDom:import_lazy_zod.z.string().default("https://esm.sh/react-dom@19"),reactDomClient:import_lazy_zod.z.string().default("https://esm.sh/react-dom@19/client"),reactJsxRuntime:import_lazy_zod.z.string().default("https://esm.sh/react@19/jsx-runtime"),reactRouter:import_lazy_zod.z.string().default("https://esm.sh/react-router-dom@7"),xyflow:import_lazy_zod.z.string().default("https://esm.sh/@xyflow/react@12?external=react,react-dom"),dagre:import_lazy_zod.z.string().default("https://esm.sh/dagre@0.8.5"),xyflowCss:import_lazy_zod.z.string().default("https://esm.sh/@xyflow/react@12/dist/style.css")});var dashboardAuthSchema=import_lazy_zod.z.object({enabled:import_lazy_zod.z.boolean().default(false),token:import_lazy_zod.z.string().min(1).optional()}).refine(value=>!value.enabled||Boolean(value.token),{message:"dashboard auth.enabled requires auth.token to be set",path:["token"]});var dashboardPluginOptionsSchema=import_lazy_zod.z.object({enabled:import_lazy_zod.z.boolean().optional(),basePath:import_lazy_zod.z.string().default("/dashboard"),auth:dashboardAuthSchema.optional().transform(v=>dashboardAuthSchema.parse(v??{})),cdn:cdnConfigSchema.optional().transform(v=>cdnConfigSchema.parse(v??{}))});var defaultDashboardPluginOptions={basePath:"/dashboard",auth:{enabled:false},cdn:{}};function isDashboardEnabled(options){if(options.enabled!==void 0){return options.enabled}return process.env["NODE_ENV"]!=="production"}var publishedOptions;function publishDashboardOptions(options){if(publishedOptions&&!sameConfiguration(publishedOptions,options)){if(!sameAuth(publishedOptions,options)){throw new Error("[frontmcp:dashboard] A second, different dashboard AUTH configuration was registered in this process. Dashboard options are process-wide, so accepting it would apply one server's token to another. Run one dashboard per process, or give both servers the same auth configuration.")}console.warn("[frontmcp:dashboard] A second, different dashboard configuration was registered in this process. Dashboard options are process-wide, so the last one wins for every server here. Run one dashboard per process until per-instance configuration is supported.")}publishedOptions=options}function sameAuth(a,b){return a.auth?.enabled===b.auth?.enabled&&a.auth?.token===b.auth?.token}function sameConfiguration(a,b){return a.basePath===b.basePath&&a.enabled===b.enabled&&a.auth?.enabled===b.auth?.enabled&&a.auth?.token===b.auth?.token&&JSON.stringify(a.cdn??{})===JSON.stringify(b.cdn??{})}function resolveDashboardOptions(fallback={}){if(publishedOptions)return publishedOptions;return dashboardPluginOptionsSchema.parse({...defaultDashboardPluginOptions,...fallback})}function resetDashboardOptions(){publishedOptions=void 0}var DashboardConfigToken=Symbol("DashboardConfig");var GraphDataProviderToken=Symbol("GraphDataProvider");var ParentScopeToken=Symbol("ParentScope");var DashboardPlugin=class extends import_sdk.DynamicPlugin{options;constructor(options={}){super();this.options=dashboardPluginOptionsSchema.parse({...defaultDashboardPluginOptions,...options});publishDashboardOptions(this.options)}static dynamicProviders(options){const parsedOptions=dashboardPluginOptionsSchema.parse({...defaultDashboardPluginOptions,...options});publishDashboardOptions(parsedOptions);return[{name:"dashboard:config",provide:DashboardConfigToken,useValue:parsedOptions}]}};DashboardPlugin=__decorateClass([(0,import_sdk.Plugin)({name:"dashboard",description:"Visual dashboard for FrontMCP server monitoring and visualization",providers:[]})],DashboardPlugin);var import_sdk8=require("@frontmcp/sdk");var import_sdk2=require("@frontmcp/sdk");var import_utils=require("@frontmcp/utils");var DASHBOARD_TOKEN_HEADER="x-frontmcp-dashboard-token";var DASHBOARD_SESSION_COOKIE="frontmcp_dashboard";var AUTHORIZED={authorized:true};function readHeader(headers,name){if(!headers)return void 0;const key=Object.keys(headers).find(k=>k.toLowerCase()===name);const value=key?headers[key]:void 0;return Array.isArray(value)?value[0]:value}function readQuery(query,name){const value=query?.[name];return Array.isArray(value)?value[0]:value}function tokenMatches(presented,expected){if(presented.length===0||expected.length===0)return false;return(0,import_utils.timingSafeEqual)((0,import_utils.sha256)(presented),(0,import_utils.sha256)(expected))}var encoder=new TextEncoder;function deriveDashboardCookieValue(token){return(0,import_utils.base64urlEncode)((0,import_utils.hmacSha256)(encoder.encode(token),encoder.encode("frontmcp-dashboard-session:v1")))}function readCookie(header,name){if(!header)return void 0;for(const part of header.split(";")){const separator=part.indexOf("=");if(separator===-1)continue;if(part.slice(0,separator).trim()===name)return part.slice(separator+1).trim()}return void 0}function readBearerToken(header){if(!header)return void 0;const separator=header.indexOf(" ");if(separator===-1)return void 0;if(header.slice(0,separator).toLowerCase()!=="bearer")return void 0;const token=header.slice(separator+1).trim();return token.length>0?token:void 0}function createDashboardAuthValidator(auth,surface="page"){if(!auth?.enabled)return void 0;const expected=auth.token;const expectedCookie=expected?deriveDashboardCookieValue(expected):void 0;return req=>{if(!expected||!expectedCookie){return{authorized:false,status:401,message:"Dashboard authentication is misconfigured"}}const bearer=readBearerToken(readHeader(req.headers,"authorization"));if(bearer&&tokenMatches(bearer,expected))return AUTHORIZED;if(surface==="mcp"){const headerToken=readHeader(req.headers,DASHBOARD_TOKEN_HEADER);if(headerToken&&tokenMatches(headerToken,expected))return AUTHORIZED}else{const queryToken=readQuery(req.query,"token");if(queryToken&&tokenMatches(queryToken,expected))return AUTHORIZED}const cookie=readCookie(readHeader(req.headers,"cookie"),DASHBOARD_SESSION_COOKIE);if(cookie&&tokenMatches(cookie,expectedCookie))return AUTHORIZED;return{authorized:false,status:401,message:"Unauthorized"}}}var GATE_PASSED=Symbol("frontmcp:dashboard:gate-passed");function markDashboardGatePassed(ctx){ctx?.set(GATE_PASSED,true)}var DashboardDisabledError=class extends import_sdk2.PublicMcpError{mcpErrorCode=import_sdk2.MCP_ERROR_CODES.FORBIDDEN;constructor(){super("The FrontMCP dashboard is disabled.","DASHBOARD_DISABLED",404)}};var DashboardUnauthorizedError=class extends import_sdk2.PublicMcpError{mcpErrorCode=import_sdk2.MCP_ERROR_CODES.UNAUTHORIZED;constructor(){super(`The dashboard token is required. Send it in the ${DASHBOARD_TOKEN_HEADER} header.`,"DASHBOARD_UNAUTHORIZED",401,'Bearer realm="frontmcp-dashboard"')}};function assertDashboardAccess(options,ctx){if(!isDashboardEnabled(options)){throw new DashboardDisabledError}if(!options.auth?.enabled)return;if(ctx?.get(GATE_PASSED)===true)return;const expected=options.auth.token;const presented=ctx?.metadata?.customHeaders?.[DASHBOARD_TOKEN_HEADER];if(expected&&presented&&tokenMatches(presented,expected))return;throw new DashboardUnauthorizedError}function assertDashboardToolAccess(tool){const configured=tool.tryGet(DashboardConfigToken);assertDashboardAccess(configured??resolveDashboardOptions(),tool.tryGetContext())}var import_sdk3=require("@frontmcp/sdk");var GraphDataProvider=class{constructor(scope,serverName,serverVersion){this.scope=scope;this.serverName=serverName;this.serverVersion=serverVersion}scope;serverName;serverVersion;cachedData=null;cacheTimestamp=0;cacheTTL=5e3;async getGraphData(){const now=Date.now();if(this.cachedData&&now-this.cacheTimestamp<this.cacheTTL){return this.cachedData}this.cachedData=this.extractGraphData();this.cacheTimestamp=now;return this.cachedData}invalidateCache(){this.cachedData=null;this.cacheTimestamp=0}getMonitoredScopes(){try{let scopeRegistries=this.scope.providers.getRegistries("ScopeRegistry");if(!scopeRegistries||scopeRegistries.length===0){let currentProviders=this.scope.providers.parentProviders;while(currentProviders&&(!scopeRegistries||scopeRegistries.length===0)){scopeRegistries=currentProviders.getRegistries?.("ScopeRegistry")||[];currentProviders=currentProviders.parentProviders}}if(!scopeRegistries||scopeRegistries.length===0){return[this.scope]}const scopeRegistry=scopeRegistries[0];const allScopes=scopeRegistry.getScopes();return allScopes.filter(s=>s.id!=="dashboard")}catch{return[this.scope]}}extractGraphData(){const nodes=[];const edges=[];const addedNodeIds=new Set;const addNode=node=>{if(!addedNodeIds.has(node.id)){nodes.push(node);addedNodeIds.add(node.id)}};const serverId=`server:${this.serverName}`;addNode({id:serverId,type:"server",label:this.serverName,data:{name:this.serverName,description:`FrontMCP Server v${this.serverVersion||"unknown"}`}});const scopes=this.getMonitoredScopes();for(const scope of scopes){const scopeId=`scope:${scope.id}`;addNode({id:scopeId,type:"scope",label:scope.id,data:{name:scope.id}});edges.push({id:`${serverId}->${scopeId}`,source:serverId,target:scopeId,type:"contains"});this.extractToolsFromScope(scope,scopeId,edges,addNode);this.extractResourcesFromScope(scope,scopeId,edges,addNode);this.extractResourceTemplatesFromScope(scope,scopeId,edges,addNode);this.extractPromptsFromScope(scope,scopeId,edges,addNode);this.extractAppsFromScope(scope,scopeId,edges,addNode)}return{nodes,edges,metadata:this.createMetadata(nodes.length,edges.length)}}extractToolsFromScope(scope,scopeId,edges,addNode){try{const tools=scope.tools?.getTools?.(true)||[];for(const tool of tools){if(tool.fullName?.startsWith("dashboard:")){continue}const toolId=`tool:${tool.fullName}`;addNode({id:toolId,type:"tool",label:tool.name,data:{name:tool.fullName,description:tool.metadata?.description,inputSchema:tool.inputSchema,outputSchema:tool.outputSchema,tags:tool.metadata?.tags,annotations:tool.metadata?.annotations}});edges.push({id:`${scopeId}->${toolId}`,source:scopeId,target:toolId,type:"provides"})}}catch{}}extractResourcesFromScope(scope,scopeId,edges,addNode){try{const resources=scope.resources?.getResources?.(true)||[];for(const resource of resources){const resourceId=`resource:${resource.uri}`;addNode({id:resourceId,type:"resource",label:resource.name,data:{name:resource.name,description:resource.metadata?.description,uri:resource.uri,mimeType:resource.metadata?.mimeType}});edges.push({id:`${scopeId}->${resourceId}`,source:scopeId,target:resourceId,type:"provides"})}}catch{}}extractResourceTemplatesFromScope(scope,scopeId,edges,addNode){try{const templates=scope.resources?.getResourceTemplates?.()||[];for(const template of templates){const templateId=`resource-template:${template.uriTemplate}`;addNode({id:templateId,type:"resource-template",label:template.name,data:{name:template.name,description:template.metadata?.description,uri:template.uriTemplate,mimeType:template.metadata?.mimeType}});edges.push({id:`${scopeId}->${templateId}`,source:scopeId,target:templateId,type:"provides"})}}catch{}}extractPromptsFromScope(scope,scopeId,edges,addNode){try{const prompts=scope.prompts?.getPrompts?.(true)||[];for(const prompt of prompts){const promptId=`prompt:${prompt.name}`;addNode({id:promptId,type:"prompt",label:prompt.name,data:{name:prompt.name,description:prompt.metadata?.description,arguments:prompt.metadata?.arguments}});edges.push({id:`${scopeId}->${promptId}`,source:scopeId,target:promptId,type:"provides"})}}catch{}}extractAppsFromScope(scope,scopeId,edges,addNode){try{const apps=scope.apps?.getApps?.()||[];for(const app of apps){const appName=app.metadata?.name||"unknown";if(appName==="dashboard"){continue}const appId=`app:${appName}`;addNode({id:appId,type:"app",label:appName,data:{name:appName,description:app.metadata?.description}});edges.push({id:`${scopeId}->${appId}`,source:scopeId,target:appId,type:"contains"})}}catch{}}createMetadata(nodeCount,edgeCount){return{serverName:this.serverName,serverVersion:this.serverVersion,generatedAt:new Date().toISOString(),entryFile:"runtime",nodeCount,edgeCount}}};GraphDataProvider=__decorateClass([(0,import_sdk3.Provider)({name:"graph-data-provider"})],GraphDataProvider);var import_lazy_zod2=require("@frontmcp/lazy-zod");var import_sdk4=require("@frontmcp/sdk");var graphToolInputSchema={includeSchemas:import_lazy_zod2.z.boolean().optional().default(false).describe("Include full input/output schemas in the response"),refresh:import_lazy_zod2.z.boolean().optional().default(false).describe("Force refresh the graph data (bypass cache)")};var GraphTool=class extends import_sdk4.ToolContext{async execute(input){assertDashboardToolAccess(this);const graphProvider=this.get(GraphDataProvider);if(input.refresh){graphProvider.invalidateCache()}const graphData=await graphProvider.getGraphData();if(!input.includeSchemas){return{...graphData,nodes:graphData.nodes.map(node=>({...node,data:{...node.data,inputSchema:void 0,outputSchema:void 0}}))}}return graphData}};GraphTool=__decorateClass([(0,import_sdk4.Tool)({name:"dashboard:graph",description:"Get the server graph showing all registered tools, resources, prompts, apps, and their relationships. Returns nodes and edges that can be visualized.",inputSchema:graphToolInputSchema,annotations:{readOnlyHint:true}})],GraphTool);var import_lazy_zod3=require("@frontmcp/lazy-zod");var import_sdk5=require("@frontmcp/sdk");function safeRegex(pattern){if(pattern.length>100){return null}try{const regex=new RegExp(pattern,"i");regex.test("test");return regex}catch{return null}}var listResourcesInputSchema={filter:import_lazy_zod3.z.string().optional().describe("Filter resources by name or URI pattern (regex supported)"),includeTemplates:import_lazy_zod3.z.boolean().default(true).describe("Include resource templates in the response")};var listResourcesOutputSchema=import_lazy_zod3.z.object({resources:import_lazy_zod3.z.array(import_lazy_zod3.z.object({name:import_lazy_zod3.z.string(),uri:import_lazy_zod3.z.string(),description:import_lazy_zod3.z.string().optional(),mimeType:import_lazy_zod3.z.string().optional(),isTemplate:import_lazy_zod3.z.boolean()})),count:import_lazy_zod3.z.number()});var ListResourcesTool=class extends import_sdk5.ToolContext{async execute(input){assertDashboardToolAccess(this);const parentScope=this.tryGet(ParentScopeToken);const targetScope=parentScope||this.scope;const results=[];try{const resources=targetScope.resources?.getResources?.(true)||[];for(const resource of resources){results.push({name:resource.name||"unnamed",uri:resource.uri||"unknown",description:resource.metadata?.description,mimeType:resource.metadata?.mimeType,isTemplate:false})}}catch{}if(input.includeTemplates){try{const templates=targetScope.resources?.getResourceTemplates?.()||[];for(const template of templates){results.push({name:template.name||"unnamed",uri:template.uriTemplate||"unknown",description:template.metadata?.description,mimeType:template.metadata?.mimeType,isTemplate:true})}}catch{}}let filtered=results;if(input.filter){const pattern=safeRegex(input.filter);if(pattern){filtered=results.filter(r=>pattern.test(r.name)||pattern.test(r.uri))}}return{resources:filtered,count:filtered.length}}};ListResourcesTool=__decorateClass([(0,import_sdk5.Tool)({name:"dashboard:list-resources",description:"List all resources and resource templates registered in the monitored FrontMCP server.",inputSchema:listResourcesInputSchema,outputSchema:listResourcesOutputSchema,annotations:{readOnlyHint:true}})],ListResourcesTool);var import_lazy_zod4=require("@frontmcp/lazy-zod");var import_sdk6=require("@frontmcp/sdk");var listToolsInputSchema={filter:import_lazy_zod4.z.string().optional().describe("Filter tools by name pattern (regex supported)"),includePlugins:import_lazy_zod4.z.boolean().default(true).describe("Include tools from plugins"),includeSchemas:import_lazy_zod4.z.boolean().default(false).describe("Include input/output schemas in the response")};var listToolsOutputSchema=import_lazy_zod4.z.object({tools:import_lazy_zod4.z.array(import_lazy_zod4.z.object({name:import_lazy_zod4.z.string(),fullName:import_lazy_zod4.z.string(),description:import_lazy_zod4.z.string().optional(),tags:import_lazy_zod4.z.array(import_lazy_zod4.z.string()).optional(),inputSchema:import_lazy_zod4.z.unknown().optional(),outputSchema:import_lazy_zod4.z.unknown().optional()})),count:import_lazy_zod4.z.number()});var ListToolsTool=class extends import_sdk6.ToolContext{async execute(input){assertDashboardToolAccess(this);const parentScope=this.tryGet(ParentScopeToken);const targetScope=parentScope||this.scope;let allTools=[];try{allTools=targetScope.tools?.getTools?.(input.includePlugins??true)||[]}catch{}if(input.filter){const pattern=safeRegex(input.filter);if(pattern){allTools=allTools.filter(t=>pattern.test(t.name)||pattern.test(t.fullName))}}const tools=allTools.map(tool=>({name:tool.name,fullName:tool.fullName,description:tool.metadata?.description,tags:tool.metadata?.tags,...input.includeSchemas?{inputSchema:tool.inputSchema,outputSchema:tool.outputSchema}:{}}));return{tools,count:tools.length}}};ListToolsTool=__decorateClass([(0,import_sdk6.Tool)({name:"dashboard:list-tools",description:"List all tools registered in the monitored FrontMCP server. Returns tool names, descriptions, and optionally schemas.",inputSchema:listToolsInputSchema,outputSchema:listToolsOutputSchema,annotations:{readOnlyHint:true}})],ListToolsTool);var import_sdk7=require("@frontmcp/sdk");function escapeForJs(str){return str.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/"/g,'\\"').replace(/</g,"\\x3c").replace(/>/g,"\\x3e").replace(/\n/g,"\\n").replace(/\r/g,"\\r")}function generateDashboardHtml(options,mcpPath=options.basePath){if(options.cdn.entrypoint){return generateExternalEntrypointHtml(options,mcpPath)}return generateInlineDashboardHtml(options,mcpPath)}function generateExternalEntrypointHtml(options,mcpPath){const{cdn}=options;const safeBasePath=escapeForJs(options.basePath);const safeMcpPath=escapeForJs(mcpPath);const safeReact=escapeForJs(cdn.react);const safeReactDom=escapeForJs(cdn.reactDom);const safeReactDomClient=escapeForJs(cdn.reactDomClient);const safeReactJsxRuntime=escapeForJs(cdn.reactJsxRuntime);const safeXyflow=escapeForJs(cdn.xyflow);const safeDagre=escapeForJs(cdn.dagre);const safeXyflowCss=escapeForJs(cdn.xyflowCss);const safeEntrypoint=escapeForJs(cdn.entrypoint||"");return`<!DOCTYPE html>
1
+ "use strict";var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __hasOwnProp=Object.prototype.hasOwnProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:true})};var __copyProps=(to,from,except,desc)=>{if(from&&typeof from==="object"||typeof from==="function"){for(let key of __getOwnPropNames(from))if(!__hasOwnProp.call(to,key)&&key!==except)__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable})}return to};var __toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:true}),mod);var __decorateClass=(decorators,target,key,kind)=>{var result=kind>1?void 0:kind?__getOwnPropDesc(target,key):target;for(var i=decorators.length-1,decorator;i>=0;i--)if(decorator=decorators[i])result=(kind?decorator(target,key,result):decorator(result))||result;if(kind&&result)__defProp(target,key,result);return result};var index_exports={};__export(index_exports,{DashboardApp:()=>DashboardApp,DashboardConfigToken:()=>DashboardConfigToken,DashboardPlugin:()=>DashboardPlugin,GraphDataProvider:()=>GraphDataProvider,GraphDataProviderToken:()=>GraphDataProviderToken,GraphTool:()=>GraphTool,ListResourcesTool:()=>ListResourcesTool,ListToolsTool:()=>ListToolsTool,ParentScopeToken:()=>ParentScopeToken,dashboardPluginOptionsSchema:()=>dashboardPluginOptionsSchema,default:()=>DashboardPlugin,defaultDashboardPluginOptions:()=>defaultDashboardPluginOptions,isDashboardEnabled:()=>isDashboardEnabled,resetDashboardOptions:()=>resetDashboardOptions});module.exports=__toCommonJS(index_exports);var import_sdk=require("@frontmcp/sdk");var import_lazy_zod=require("@frontmcp/lazy-zod");var cdnConfigSchema=import_lazy_zod.z.object({entrypoint:import_lazy_zod.z.string().optional(),react:import_lazy_zod.z.string().default("https://esm.sh/react@19"),reactDom:import_lazy_zod.z.string().default("https://esm.sh/react-dom@19"),reactDomClient:import_lazy_zod.z.string().default("https://esm.sh/react-dom@19/client"),reactJsxRuntime:import_lazy_zod.z.string().default("https://esm.sh/react@19/jsx-runtime"),reactRouter:import_lazy_zod.z.string().default("https://esm.sh/react-router-dom@7"),xyflow:import_lazy_zod.z.string().default("https://esm.sh/@xyflow/react@12?external=react,react-dom"),dagre:import_lazy_zod.z.string().default("https://esm.sh/dagre@0.8.5"),xyflowCss:import_lazy_zod.z.string().default("https://esm.sh/@xyflow/react@12/dist/style.css")});var dashboardAuthSchema=import_lazy_zod.z.object({enabled:import_lazy_zod.z.boolean().default(false),token:import_lazy_zod.z.string().min(1).optional()}).refine(value=>!value.enabled||Boolean(value.token),{message:"dashboard auth.enabled requires auth.token to be set",path:["token"]});var dashboardPluginOptionsSchema=import_lazy_zod.z.object({enabled:import_lazy_zod.z.boolean().optional(),basePath:import_lazy_zod.z.string().default("/dashboard"),auth:dashboardAuthSchema.optional().transform(v=>dashboardAuthSchema.parse(v??{})),cdn:cdnConfigSchema.optional().transform(v=>cdnConfigSchema.parse(v??{}))});var defaultDashboardPluginOptions={basePath:"/dashboard",auth:{enabled:false},cdn:{}};function isDashboardEnabled(options){if(options.enabled!==void 0){return options.enabled}return process.env["NODE_ENV"]!=="production"}var publishedOptions;function publishDashboardOptions(options){if(publishedOptions&&!sameConfiguration(publishedOptions,options)){if(!sameAuth(publishedOptions,options)){throw new Error("[frontmcp:dashboard] A second, different dashboard AUTH configuration was registered in this process. Dashboard options are process-wide, so accepting it would apply one server's token to another. Run one dashboard per process, or give both servers the same auth configuration.")}console.warn("[frontmcp:dashboard] A second, different dashboard configuration was registered in this process. Dashboard options are process-wide, so the last one wins for every server here. Run one dashboard per process until per-instance configuration is supported.")}publishedOptions=options}function sameAuth(a,b){return a.auth?.enabled===b.auth?.enabled&&a.auth?.token===b.auth?.token}function sameConfiguration(a,b){return a.basePath===b.basePath&&a.enabled===b.enabled&&a.auth?.enabled===b.auth?.enabled&&a.auth?.token===b.auth?.token&&JSON.stringify(a.cdn??{})===JSON.stringify(b.cdn??{})}function resolveDashboardOptions(fallback={}){if(publishedOptions)return publishedOptions;return dashboardPluginOptionsSchema.parse({...defaultDashboardPluginOptions,...fallback})}function resetDashboardOptions(){publishedOptions=void 0}var DashboardConfigToken=Symbol("DashboardConfig");var GraphDataProviderToken=Symbol("GraphDataProvider");var ParentScopeToken=Symbol("ParentScope");var DashboardPlugin=class extends import_sdk.DynamicPlugin{options;constructor(options={}){super();this.options=dashboardPluginOptionsSchema.parse({...defaultDashboardPluginOptions,...options});publishDashboardOptions(this.options)}static dynamicProviders(options){const parsedOptions=dashboardPluginOptionsSchema.parse({...defaultDashboardPluginOptions,...options});publishDashboardOptions(parsedOptions);return[{name:"dashboard:config",provide:DashboardConfigToken,useValue:parsedOptions}]}};DashboardPlugin=__decorateClass([(0,import_sdk.Plugin)({name:"dashboard",description:"Visual dashboard for FrontMCP server monitoring and visualization",providers:[]})],DashboardPlugin);var import_sdk8=require("@frontmcp/sdk");var import_sdk2=require("@frontmcp/sdk");var import_utils=require("@frontmcp/utils");var DASHBOARD_TOKEN_HEADER="x-frontmcp-dashboard-token";var DASHBOARD_SESSION_COOKIE="frontmcp_dashboard";var AUTHORIZED={authorized:true};function readHeader(headers,name){if(!headers)return void 0;const key=Object.keys(headers).find(k=>k.toLowerCase()===name);const value=key?headers[key]:void 0;return Array.isArray(value)?value[0]:value}function readQuery(query,name){const value=query?.[name];return Array.isArray(value)?value[0]:value}function tokenMatches(presented,expected){if(presented.length===0||expected.length===0)return false;return(0,import_utils.timingSafeEqual)((0,import_utils.sha256)(presented),(0,import_utils.sha256)(expected))}var encoder=new TextEncoder;function deriveDashboardCookieValue(token){return(0,import_utils.base64urlEncode)((0,import_utils.hmacSha256)(encoder.encode(token),encoder.encode("frontmcp-dashboard-session:v1")))}function readCookie(header,name){if(!header)return void 0;for(const part of header.split(";")){const separator=part.indexOf("=");if(separator===-1)continue;if(part.slice(0,separator).trim()===name)return part.slice(separator+1).trim()}return void 0}function readBearerToken(header){if(!header)return void 0;const separator=header.indexOf(" ");if(separator===-1)return void 0;if(header.slice(0,separator).toLowerCase()!=="bearer")return void 0;const token=header.slice(separator+1).trim();return token.length>0?token:void 0}function createDashboardAuthValidator(auth,surface="page"){if(!auth?.enabled)return void 0;const expected=auth.token;const expectedCookie=expected?deriveDashboardCookieValue(expected):void 0;return req=>{if(!expected||!expectedCookie){return{authorized:false,status:401,message:"Dashboard authentication is misconfigured"}}const bearer=readBearerToken(readHeader(req.headers,"authorization"));if(bearer&&tokenMatches(bearer,expected))return AUTHORIZED;if(surface==="mcp"){const headerToken=readHeader(req.headers,DASHBOARD_TOKEN_HEADER);if(headerToken&&tokenMatches(headerToken,expected))return AUTHORIZED}else{const queryToken=readQuery(req.query,"token");if(queryToken&&tokenMatches(queryToken,expected))return AUTHORIZED}const cookie=readCookie(readHeader(req.headers,"cookie"),DASHBOARD_SESSION_COOKIE);if(cookie&&tokenMatches(cookie,expectedCookie))return AUTHORIZED;return{authorized:false,status:401,message:"Unauthorized"}}}var GATE_PASSED=Symbol("frontmcp:dashboard:gate-passed");function markDashboardGatePassed(ctx){ctx?.set(GATE_PASSED,true)}var DashboardDisabledError=class extends import_sdk2.PublicMcpError{mcpErrorCode=import_sdk2.MCP_ERROR_CODES.FORBIDDEN;constructor(){super("The FrontMCP dashboard is disabled.","DASHBOARD_DISABLED",404)}};var DashboardUnauthorizedError=class extends import_sdk2.PublicMcpError{mcpErrorCode=import_sdk2.MCP_ERROR_CODES.UNAUTHORIZED;constructor(){super(`The dashboard token is required. Send it in the ${DASHBOARD_TOKEN_HEADER} header.`,"DASHBOARD_UNAUTHORIZED",401,'Bearer realm="frontmcp-dashboard"')}};function assertDashboardAccess(options,ctx){if(!isDashboardEnabled(options)){throw new DashboardDisabledError}if(!options.auth?.enabled)return;if(ctx?.get(GATE_PASSED)===true)return;const expected=options.auth.token;const presented=ctx?.metadata?.customHeaders?.[DASHBOARD_TOKEN_HEADER];if(expected&&presented&&tokenMatches(presented,expected))return;throw new DashboardUnauthorizedError}function assertDashboardToolAccess(tool){const configured=tool.tryGet(DashboardConfigToken);assertDashboardAccess(configured??resolveDashboardOptions(),tool.tryGetContext())}var import_sdk3=require("@frontmcp/sdk");var GraphDataProvider=class{constructor(scope,serverName,serverVersion){this.scope=scope;this.serverName=serverName;this.serverVersion=serverVersion}scope;serverName;serverVersion;cachedData=null;cacheTimestamp=0;cacheTTL=5e3;async getGraphData(){const now=Date.now();if(this.cachedData&&now-this.cacheTimestamp<this.cacheTTL){return this.cachedData}this.cachedData=this.extractGraphData();this.cacheTimestamp=now;return this.cachedData}invalidateCache(){this.cachedData=null;this.cacheTimestamp=0}getMonitoredScopes(){try{let scopeRegistries=this.scope.providers.getRegistries("ScopeRegistry");if(!scopeRegistries||scopeRegistries.length===0){let currentProviders=this.scope.providers.parentProviders;while(currentProviders&&(!scopeRegistries||scopeRegistries.length===0)){scopeRegistries=currentProviders.getRegistries?.("ScopeRegistry")||[];currentProviders=currentProviders.parentProviders}}if(!scopeRegistries||scopeRegistries.length===0){return[this.scope]}const scopeRegistry=scopeRegistries[0];const allScopes=scopeRegistry.getScopes();return allScopes.filter(s=>s.id!=="dashboard")}catch{return[this.scope]}}extractGraphData(){const nodes=[];const edges=[];const addedNodeIds=new Set;const addNode=node=>{if(!addedNodeIds.has(node.id)){nodes.push(node);addedNodeIds.add(node.id)}};const serverId=`server:${this.serverName}`;addNode({id:serverId,type:"server",label:this.serverName,data:{name:this.serverName,description:`FrontMCP Server v${this.serverVersion||"unknown"}`}});const scopes=this.getMonitoredScopes();for(const scope of scopes){const scopeId=`scope:${scope.id}`;addNode({id:scopeId,type:"scope",label:scope.id,data:{name:scope.id}});edges.push({id:`${serverId}->${scopeId}`,source:serverId,target:scopeId,type:"contains"});this.extractToolsFromScope(scope,scopeId,edges,addNode);this.extractResourcesFromScope(scope,scopeId,edges,addNode);this.extractResourceTemplatesFromScope(scope,scopeId,edges,addNode);this.extractPromptsFromScope(scope,scopeId,edges,addNode);this.extractAppsFromScope(scope,scopeId,edges,addNode)}return{nodes,edges,metadata:this.createMetadata(nodes.length,edges.length)}}extractToolsFromScope(scope,scopeId,edges,addNode){try{const tools=scope.tools?.getTools?.(true)||[];for(const tool of tools){if(tool.fullName?.startsWith("dashboard:")){continue}const toolId=`tool:${tool.fullName}`;addNode({id:toolId,type:"tool",label:tool.name,data:{name:tool.fullName,description:tool.metadata?.description,inputSchema:tool.inputSchema,outputSchema:tool.outputSchema,tags:tool.metadata?.tags,annotations:tool.metadata?.annotations}});edges.push({id:`${scopeId}->${toolId}`,source:scopeId,target:toolId,type:"provides"})}}catch{}}extractResourcesFromScope(scope,scopeId,edges,addNode){try{const resources=scope.resources?.getResources?.(true)||[];for(const resource of resources){const resourceId=`resource:${resource.uri}`;addNode({id:resourceId,type:"resource",label:resource.name,data:{name:resource.name,description:resource.metadata?.description,uri:resource.uri,mimeType:resource.metadata?.mimeType}});edges.push({id:`${scopeId}->${resourceId}`,source:scopeId,target:resourceId,type:"provides"})}}catch{}}extractResourceTemplatesFromScope(scope,scopeId,edges,addNode){try{const templates=scope.resources?.getResourceTemplates?.()||[];for(const template of templates){const templateId=`resource-template:${template.uriTemplate}`;addNode({id:templateId,type:"resource-template",label:template.name,data:{name:template.name,description:template.metadata?.description,uri:template.uriTemplate,mimeType:template.metadata?.mimeType}});edges.push({id:`${scopeId}->${templateId}`,source:scopeId,target:templateId,type:"provides"})}}catch{}}extractPromptsFromScope(scope,scopeId,edges,addNode){try{const prompts=scope.prompts?.getPrompts?.(true)||[];for(const prompt of prompts){const promptId=`prompt:${prompt.name}`;addNode({id:promptId,type:"prompt",label:prompt.name,data:{name:prompt.name,description:prompt.metadata?.description,arguments:prompt.metadata?.arguments}});edges.push({id:`${scopeId}->${promptId}`,source:scopeId,target:promptId,type:"provides"})}}catch{}}extractAppsFromScope(scope,scopeId,edges,addNode){try{const apps=scope.apps?.getApps?.()||[];for(const app of apps){const appName=app.metadata?.name||"unknown";if(appName==="dashboard"){continue}const appId=`app:${appName}`;addNode({id:appId,type:"app",label:appName,data:{name:appName,description:app.metadata?.description}});edges.push({id:`${scopeId}->${appId}`,source:scopeId,target:appId,type:"contains"})}}catch{}}createMetadata(nodeCount,edgeCount){return{serverName:this.serverName,serverVersion:this.serverVersion,generatedAt:new Date().toISOString(),entryFile:"runtime",nodeCount,edgeCount}}};GraphDataProvider=__decorateClass([(0,import_sdk3.Provider)({name:"graph-data-provider"})],GraphDataProvider);var import_lazy_zod2=require("@frontmcp/lazy-zod");var import_sdk4=require("@frontmcp/sdk");var graphToolInputSchema={includeSchemas:import_lazy_zod2.z.boolean().optional().default(false).describe("Include full input/output schemas in the response"),refresh:import_lazy_zod2.z.boolean().optional().default(false).describe("Force refresh the graph data (bypass cache)")};var GraphTool=class extends import_sdk4.ToolContext{async execute(input){assertDashboardToolAccess(this);const graphProvider=this.get(GraphDataProvider);if(input.refresh){graphProvider.invalidateCache()}const graphData=await graphProvider.getGraphData();if(!input.includeSchemas){return{...graphData,nodes:graphData.nodes.map(node=>({...node,data:{...node.data,inputSchema:void 0,outputSchema:void 0}}))}}return graphData}};GraphTool=__decorateClass([(0,import_sdk4.Tool)({name:"dashboard:graph",description:"Get the server graph showing all registered tools, resources, prompts, apps, and their relationships. Returns nodes and edges that can be visualized.",inputSchema:graphToolInputSchema,annotations:{readOnlyHint:true}})],GraphTool);var import_lazy_zod3=require("@frontmcp/lazy-zod");var import_sdk5=require("@frontmcp/sdk");function containsText(text){const needle=text.toLowerCase();return value=>value.toLowerCase().includes(needle)}var listResourcesInputSchema={filter:import_lazy_zod3.z.string().optional().describe("Only resources whose name or URI contains this text, ignoring case"),includeTemplates:import_lazy_zod3.z.boolean().default(true).describe("Include resource templates in the response")};var listResourcesOutputSchema=import_lazy_zod3.z.object({resources:import_lazy_zod3.z.array(import_lazy_zod3.z.object({name:import_lazy_zod3.z.string(),uri:import_lazy_zod3.z.string(),description:import_lazy_zod3.z.string().optional(),mimeType:import_lazy_zod3.z.string().optional(),isTemplate:import_lazy_zod3.z.boolean()})),count:import_lazy_zod3.z.number()});var ListResourcesTool=class extends import_sdk5.ToolContext{async execute(input){assertDashboardToolAccess(this);const parentScope=this.tryGet(ParentScopeToken);const targetScope=parentScope||this.scope;const results=[];try{const resources=targetScope.resources?.getResources?.(true)||[];for(const resource of resources){results.push({name:resource.name||"unnamed",uri:resource.uri||"unknown",description:resource.metadata?.description,mimeType:resource.metadata?.mimeType,isTemplate:false})}}catch{}if(input.includeTemplates){try{const templates=targetScope.resources?.getResourceTemplates?.()||[];for(const template of templates){results.push({name:template.name||"unnamed",uri:template.uriTemplate||"unknown",description:template.metadata?.description,mimeType:template.metadata?.mimeType,isTemplate:true})}}catch{}}let filtered=results;if(input.filter){const matches=containsText(input.filter);filtered=results.filter(r=>matches(r.name)||matches(r.uri))}return{resources:filtered,count:filtered.length}}};ListResourcesTool=__decorateClass([(0,import_sdk5.Tool)({name:"dashboard:list-resources",description:"List all resources and resource templates registered in the monitored FrontMCP server.",inputSchema:listResourcesInputSchema,outputSchema:listResourcesOutputSchema,annotations:{readOnlyHint:true}})],ListResourcesTool);var import_lazy_zod4=require("@frontmcp/lazy-zod");var import_sdk6=require("@frontmcp/sdk");var listToolsInputSchema={filter:import_lazy_zod4.z.string().optional().describe("Only tools whose name contains this text, ignoring case"),includePlugins:import_lazy_zod4.z.boolean().default(true).describe("Include tools from plugins"),includeSchemas:import_lazy_zod4.z.boolean().default(false).describe("Include input/output schemas in the response")};var listToolsOutputSchema=import_lazy_zod4.z.object({tools:import_lazy_zod4.z.array(import_lazy_zod4.z.object({name:import_lazy_zod4.z.string(),fullName:import_lazy_zod4.z.string(),description:import_lazy_zod4.z.string().optional(),tags:import_lazy_zod4.z.array(import_lazy_zod4.z.string()).optional(),inputSchema:import_lazy_zod4.z.unknown().optional(),outputSchema:import_lazy_zod4.z.unknown().optional()})),count:import_lazy_zod4.z.number()});var ListToolsTool=class extends import_sdk6.ToolContext{async execute(input){assertDashboardToolAccess(this);const parentScope=this.tryGet(ParentScopeToken);const targetScope=parentScope||this.scope;let allTools=[];try{allTools=targetScope.tools?.getTools?.(input.includePlugins??true)||[]}catch{}if(input.filter){const matches=containsText(input.filter);allTools=allTools.filter(t=>matches(t.name)||matches(t.fullName))}const tools=allTools.map(tool=>({name:tool.name,fullName:tool.fullName,description:tool.metadata?.description,tags:tool.metadata?.tags,...input.includeSchemas?{inputSchema:tool.inputSchema,outputSchema:tool.outputSchema}:{}}));return{tools,count:tools.length}}};ListToolsTool=__decorateClass([(0,import_sdk6.Tool)({name:"dashboard:list-tools",description:"List all tools registered in the monitored FrontMCP server. Returns tool names, descriptions, and optionally schemas.",inputSchema:listToolsInputSchema,outputSchema:listToolsOutputSchema,annotations:{readOnlyHint:true}})],ListToolsTool);var import_sdk7=require("@frontmcp/sdk");function escapeForJs(str){return str.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/"/g,'\\"').replace(/</g,"\\x3c").replace(/>/g,"\\x3e").replace(/\n/g,"\\n").replace(/\r/g,"\\r")}function generateDashboardHtml(options,mcpPath=options.basePath){if(options.cdn.entrypoint){return generateExternalEntrypointHtml(options,mcpPath)}return generateInlineDashboardHtml(options,mcpPath)}function generateExternalEntrypointHtml(options,mcpPath){const{cdn}=options;const safeBasePath=escapeForJs(options.basePath);const safeMcpPath=escapeForJs(mcpPath);const safeReact=escapeForJs(cdn.react);const safeReactDom=escapeForJs(cdn.reactDom);const safeReactDomClient=escapeForJs(cdn.reactDomClient);const safeReactJsxRuntime=escapeForJs(cdn.reactJsxRuntime);const safeXyflow=escapeForJs(cdn.xyflow);const safeDagre=escapeForJs(cdn.dagre);const safeXyflowCss=escapeForJs(cdn.xyflowCss);const safeEntrypoint=escapeForJs(cdn.entrypoint||"");return`<!DOCTYPE html>
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/plugin-dashboard",
3
- "version": "1.12.0-rc.1",
3
+ "version": "1.12.1",
4
4
  "description": "Dashboard plugin for FrontMCP: a web page and MCP endpoint that show a server's apps, tools, resources and prompts",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "license": "Apache-2.0",
@@ -49,14 +49,14 @@
49
49
  }
50
50
  },
51
51
  "devDependencies": {
52
- "@frontmcp/sdk": "1.12.0-rc.1",
52
+ "@frontmcp/sdk": "1.12.1",
53
53
  "reflect-metadata": "^0.2.2"
54
54
  },
55
55
  "dependencies": {
56
- "@frontmcp/utils": "1.12.0-rc.1"
56
+ "@frontmcp/utils": "1.12.1"
57
57
  },
58
58
  "peerDependencies": {
59
- "@frontmcp/lazy-zod": "1.12.0-rc.1",
60
- "@frontmcp/sdk": "1.12.0-rc.1"
59
+ "@frontmcp/lazy-zod": "1.12.1",
60
+ "@frontmcp/sdk": "1.12.1"
61
61
  }
62
62
  }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * A filter for names that contain `text`, ignoring case, in time linear in the name's length. Plain text matches
3
+ * what the case-insensitive regular expression of the same text matched; regular expression syntax is not
4
+ * interpreted.
5
+ */
6
+ export declare function containsText(text: string): (value: string) => boolean;
7
+ //# sourceMappingURL=name-filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"name-filter.d.ts","sourceRoot":"","sources":["../../src/shared/name-filter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAGrE"}
@@ -1 +1 @@
1
- {"version":3,"file":"list-resources.tool.d.ts","sourceRoot":"","sources":["../../src/tools/list-resources.tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,oBAAoB,CAAC;AACvC,OAAO,EAAQ,WAAW,EAAmB,MAAM,eAAe,CAAC;AAMnE;;GAEG;AACH,eAAO,MAAM,wBAAwB;;;CAGpC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,wBAAwB,CAAC,CAAC,CAAC;AAExF;;GAEG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;gCAWpC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE7E;;GAEG;AAUH,MAAM,CAAC,OAAO,OAAO,iBAAkB,SAAQ,WAAW;IAClD,OAAO,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;CA0DvE"}
1
+ {"version":3,"file":"list-resources.tool.d.ts","sourceRoot":"","sources":["../../src/tools/list-resources.tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,oBAAoB,CAAC;AACvC,OAAO,EAAQ,WAAW,EAAmB,MAAM,eAAe,CAAC;AAMnE;;GAEG;AACH,eAAO,MAAM,wBAAwB;;;CAGpC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,wBAAwB,CAAC,CAAC,CAAC;AAExF;;GAEG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;gCAWpC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE7E;;GAEG;AAUH,MAAM,CAAC,OAAO,OAAO,iBAAkB,SAAQ,WAAW;IAClD,OAAO,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;CAsDvE"}
@@ -1 +1 @@
1
- {"version":3,"file":"list-tools.tool.d.ts","sourceRoot":"","sources":["../../src/tools/list-tools.tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,oBAAoB,CAAC;AACvC,OAAO,EAAQ,WAAW,EAAmB,MAAM,eAAe,CAAC;AAMnE;;GAEG;AACH,eAAO,MAAM,oBAAoB;;;;CAIhC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,oBAAoB,CAAC,CAAC,CAAC;AAEhF;;GAEG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;gCAYhC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAErE;;GAEG;AAWH,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,WAAW;IAC9C,OAAO,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;CAkD/D"}
1
+ {"version":3,"file":"list-tools.tool.d.ts","sourceRoot":"","sources":["../../src/tools/list-tools.tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,oBAAoB,CAAC;AACvC,OAAO,EAAQ,WAAW,EAAmB,MAAM,eAAe,CAAC;AAMnE;;GAEG;AACH,eAAO,MAAM,oBAAoB;;;;CAIhC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,oBAAoB,CAAC,CAAC,CAAC;AAEhF;;GAEG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;gCAYhC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAErE;;GAEG;AAWH,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,WAAW;IAC9C,OAAO,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;CA8C/D"}
@@ -1,9 +0,0 @@
1
- /**
2
- * Safely create a RegExp from user input to prevent ReDoS attacks.
3
- * Returns null if the pattern is invalid or potentially dangerous.
4
- *
5
- * @param pattern - The regex pattern string from user input
6
- * @returns A compiled RegExp or null if the pattern is unsafe/invalid
7
- */
8
- export declare function safeRegex(pattern: string): RegExp | null;
9
- //# sourceMappingURL=safe-regex.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"safe-regex.d.ts","sourceRoot":"","sources":["../../src/shared/safe-regex.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAcxD"}