| 596 | |
| 597 | // Callback to execute custom Python / Pyodide MCP tools inside the browser! |
| 598 | const executeToolCallback = async (toolName: string, args: any) => { |
| 599 | console.log(`[Explore Tunnel] Running Python MCP Tool: name=${toolName}`, args); |
| 600 | |
| 601 | const targetRepo = args?.repo || args?.repository || ""; |
| 602 | |
| 603 | const isGlobalTool = ["list_indexed_repositories", "search_registry_bundles"].includes(toolName); |
| 604 | let currentGraph = null; |
| 605 | |
| 606 | if (!isGlobalTool) { |
| 607 | currentGraph = await resolveGraph(targetRepo); |
| 608 | if (!currentGraph) { |
| 609 | throw new Error(`SILENT_IGNORE: The repository '${targetRepo}' is not actively loaded or cached in this browser tab.`); |
| 610 | } |
| 611 | } |
| 612 | |
| 613 | const nodes = currentGraph?.nodes || []; |
| 614 | const links = currentGraph?.links || []; |
| 615 | |
| 616 | switch (toolName) { |
| 617 | case "get_repository_stats": { |
| 618 | const filesCount = new Set(nodes.map((n: any) => n.file).filter(Boolean)).size; |
| 619 | const classesCount = nodes.filter((n: any) => n.type === "Class").length; |
| 620 | const functionsCount = nodes.filter((n: any) => n.type === "Function").length; |
| 621 | |
| 622 | return { |
| 623 | repository: targetRepo || activeRepoPath, |
| 624 | total_nodes: nodes.length, |
| 625 | total_links: links.length, |
| 626 | files_count: filesCount, |
| 627 | classes_count: classesCount, |
| 628 | functions_count: functionsCount |
| 629 | }; |
| 630 | } |
| 631 | |
| 632 | case "find_dead_code": { |
| 633 | // Identify orphan nodes with 0 incoming or outgoing dependencies |
| 634 | const referencedIds = new Set(links.flatMap((l: any) => [l.source, l.target])); |
| 635 | const deadNodes = nodes.filter((n: any) => !referencedIds.has(n.id) && (n.type === "Function" || n.type === "Class")); |
| 636 | |
| 637 | return { |
| 638 | repository: targetRepo || activeRepoPath, |
| 639 | dead_symbols: deadNodes.map((n: any) => ({ name: n.name, type: n.type, file: n.file })), |
| 640 | total_dead_symbols: deadNodes.length |
| 641 | }; |
| 642 | } |
| 643 | |
| 644 | case "calculate_cyclomatic_complexity": |
| 645 | case "find_most_complex_functions": { |
| 646 | const limit = args?.limit || 10; |
| 647 | const complexNodes = nodes |
| 648 | .filter((n: any) => typeof n.complexity === "number") |
| 649 | .sort((a: any, b: any) => b.complexity - a.complexity) |
| 650 | .slice(0, limit); |
| 651 | |
| 652 | return { |
| 653 | repository: targetRepo || activeRepoPath, |
| 654 | most_complex_functions: complexNodes.map((n: any) => ({ name: n.name, file: n.file, complexity: n.complexity })) |
| 655 | }; |