generate_ui_flow
Create UI flow diagrams by scanning React/Angular repositories. Analyzes codebases to map components, relationships, and UI structure for local or GitHub repositories.
Instructions
Generate a UI flow diagram by analyzing React/Angular repositories. This tool scans the codebase to identify components, their relationships, and the overall UI structure.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| fileExtensions | No | List of file extensions to analyze (e.g., ['js', 'jsx', 'ts', 'tsx'] for React, ['ts', 'html'] for Angular) | |
| isLocal | Yes | Whether to analyze a local repository (true) or GitHub repository (false) | |
| owner | No | GitHub repository owner (required if isLocal is false) | |
| repo | No | GitHub repository name (required if isLocal is false) | |
| repoPath | Yes | Path to local repository or empty string for GitHub repos |
Implementation Reference
- src/index.ts:135-198 (handler)Main execution logic for the generate_ui_flow tool within the CallToolRequestSchema handler.if (request.params.name !== "generate_ui_flow") { throw new McpError( ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}` ); } const args = request.params.arguments as { repoPath: string; isLocal: boolean; owner?: string; repo?: string; fileExtensions?: string[]; }; const { repoPath, isLocal, owner, repo, fileExtensions } = args; try { let contents: RepoContents[]; if (isLocal) { contents = await fetchLocalRepoContents(repoPath); } else { if (!owner || !repo) { throw new McpError( ErrorCode.InvalidParams, "Owner and repo are required for GitHub repositories" ); } contents = await fetchGitHubRepoContents(owner, repo); } const components = await parseUIFlow(contents, isLocal, fileExtensions); const mermaidChart = generateMermaidFlowchart(JSON.parse(components)); // Determine output path based on repository type const outputPath = isLocal ? path.join(repoPath, "userflo.md") : path.join(process.cwd(), "userflo.md"); const flowDescription = `# UI Flow Diagram\n\nThis document describes the UI flow of the application.\n\n`; const fullContent = flowDescription + "```mermaid\n" + mermaidChart + "\n```\n\n"; await fs.writeFile(outputPath, fullContent); console.log(`[MCP] UI flow saved to ${outputPath}`); return { content: [ { type: "text", text: mermaidChart, }, ], }; } catch (error) { if (error instanceof McpError) { throw error; } throw new McpError( ErrorCode.InternalError, `Failed to generate UI flow: ${ error instanceof Error ? error.message : String(error) }` ); }
- src/index.ts:38-71 (schema)Input schema definition for the generate_ui_flow tool.inputSchema: { type: "object", properties: { repoPath: { type: "string", description: "Path to local repository or empty string for GitHub repos", }, isLocal: { type: "boolean", description: "Whether to analyze a local repository (true) or GitHub repository (false)", }, owner: { type: "string", description: "GitHub repository owner (required if isLocal is false)", }, repo: { type: "string", description: "GitHub repository name (required if isLocal is false)", }, fileExtensions: { type: "array", items: { type: "string" }, description: "List of file extensions to analyze (e.g., ['js', 'jsx', 'ts', 'tsx'] for React, ['ts', 'html'] for Angular)", default: ["js", "jsx", "ts", "tsx"], }, }, required: ["repoPath", "isLocal"], additionalProperties: false, },
- src/index.ts:34-73 (registration)Tool registration in server capabilities.generate_ui_flow: { name: "generate_ui_flow", description: "Generate a UI flow diagram by analyzing React/Angular repositories. This tool scans the codebase to identify components, their relationships, and the overall UI structure.", inputSchema: { type: "object", properties: { repoPath: { type: "string", description: "Path to local repository or empty string for GitHub repos", }, isLocal: { type: "boolean", description: "Whether to analyze a local repository (true) or GitHub repository (false)", }, owner: { type: "string", description: "GitHub repository owner (required if isLocal is false)", }, repo: { type: "string", description: "GitHub repository name (required if isLocal is false)", }, fileExtensions: { type: "array", items: { type: "string" }, description: "List of file extensions to analyze (e.g., ['js', 'jsx', 'ts', 'tsx'] for React, ['ts', 'html'] for Angular)", default: ["js", "jsx", "ts", "tsx"], }, }, required: ["repoPath", "isLocal"], additionalProperties: false, }, }, },
- src/utils/flowParser.ts:18-118 (helper)Core helper function that parses repository contents to extract UI component hierarchy and relationships.export async function parseUIFlow( contents: RepoContents[], isLocal: boolean, fileExtensions: string[] = ["js", "jsx", "ts", "tsx"] ): Promise<string> { console.log( `[MCP] Parsing UI flow with extensions: ${fileExtensions.join(", ")}` ); const components: { [key: string]: ComponentInfo } = {}; async function processContents( currentContents: RepoContents[], currentPath: string = "" ) { for (const item of currentContents) { if ( item.type === "file" && fileExtensions.some((ext) => item.name.endsWith(`.${ext}`)) ) { let content: string; if (isLocal) { content = item.content || ""; } else { try { const response = await axios.get(item.download_url || ""); content = response.data; } catch (error) { console.warn( `[MCP] Failed to fetch content for ${item.name}: ${error}` ); continue; } } const componentName = item.name.split(".")[0]; const componentPath = path.join(currentPath, componentName); const componentType = getComponentType(componentPath); components[componentPath] = { name: componentName, type: componentType, filePath: path.join(currentPath, item.name), imports: [], children: [], }; // Analyze import statements const importMatches = content.match( /import\s+(\w+|\{[^}]+\})\s+from\s+['"]([^'"]+)['"]/g ); if (importMatches) { importMatches.forEach((match) => { const [, importedComponent, importPath] = match.match( /import\s+(\w+|\{[^}]+\})\s+from\s+['"]([^'"]+)['"]/ ) || []; if (importedComponent) { const cleanedImport = importedComponent .replace(/[{}]/g, "") .trim(); const resolvedPath = path.join( currentPath, path.dirname(importPath), cleanedImport ); components[componentPath].imports.push(resolvedPath); } }); } } else if (item.type === "dir") { const subContents = isLocal ? await fetchLocalRepoContents(item.path) : await fetchGitHubRepoContents( item.owner || "", item.repo || "", item.path ); await processContents(subContents, path.join(currentPath, item.name)); } } } await processContents(contents); // Build component hierarchy const rootComponents: ComponentInfo[] = []; Object.values(components).forEach((component) => { component.imports.forEach((importPath) => { if (components[importPath]) { components[importPath].children.push(component); } }); if (component.imports.length === 0) { rootComponents.push(component); } }); return JSON.stringify(rootComponents, null, 2); }
- src/utils/flowParser.ts:149-235 (helper)Helper function that generates Mermaid flowchart syntax from parsed UI components.export function generateMermaidFlowchart(components: ComponentInfo[]): string { let chart = "flowchart TD\n"; // Create a map of all components for quick lookup const componentMap = new Map<string, ComponentInfo>(); components.forEach((component) => { componentMap.set(component.name, component); }); // Create nodes with proper styling and hierarchy const createNode = (component: ComponentInfo, depth: number = 0): string => { const nodeId = component.name.replace(/[^a-zA-Z0-9]/g, "_"); const indent = " ".repeat(depth); // Determine node style based on type let nodeStyle = ""; switch (component.type) { case "page": nodeStyle = "(( ))"; break; case "layout": nodeStyle = "{{ }}"; break; default: nodeStyle = "[/ /]"; } // Add node with proper indentation chart += `${indent}${nodeId}${nodeStyle}["${component.name} (${component.type})"]\n`; // Recursively process children component.children.forEach((child) => { const childComponent = componentMap.get(child.name); if (childComponent) { createNode(childComponent, depth + 1); } }); return nodeId; }; // Find root components (those with no parents) const rootComponents = components.filter( (component) => !components.some((c) => c.children.some((child) => child.name === component.name) ) ); // Start building the chart from root components rootComponents.forEach((component) => { createNode(component); }); // Create relationships with labels components.forEach((component) => { const parentId = component.name.replace(/[^a-zA-Z0-9]/g, "_"); component.children.forEach((child) => { const childId = child.name.replace(/[^a-zA-Z0-9]/g, "_"); const relationshipType = determineRelationshipType(component, child); chart += ` ${parentId} -->|${relationshipType}| ${childId}\n`; }); }); // Validate Mermaid.js syntax try { // Basic validation - check for required elements if (!chart.includes("flowchart TD")) { throw new Error("Missing flowchart declaration"); } if (!chart.match(/\[.*\]/)) { throw new Error("Missing node definitions"); } if (!chart.match(/-->|--/)) { throw new Error("Missing relationship definitions"); } } catch (error) { console.error("[MCP] Mermaid.js validation error:", error); throw new McpError( ErrorCode.InternalError, `Failed to generate valid Mermaid.js chart: ${error}` ); } return chart; }