claude-autopm 1.12.3 → 1.13.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.
@@ -40,6 +40,10 @@ try {
40
40
  }
41
41
 
42
42
  class MCPHandler {
43
+ // Regular expression patterns as class constants
44
+ static MCP_URI_REGEX = /mcp:\/\/([a-zA-Z0-9_-]+)/g;
45
+ static ENV_VAR_NAME_REGEX = /^[A-Z_][A-Z0-9_]*$/;
46
+
43
47
  constructor() {
44
48
  this.projectRoot = process.cwd();
45
49
  this.frameworkRoot = path.join(__dirname, '..');
@@ -47,6 +51,9 @@ class MCPHandler {
47
51
  this.configPath = path.join(this.projectRoot, '.claude', 'config.json');
48
52
  this.mcpServersPath = path.join(this.projectRoot, '.claude', 'mcp-servers.json');
49
53
  this.envPath = path.join(this.projectRoot, '.claude', '.env');
54
+
55
+ // Cache for environment status to reduce file I/O
56
+ this._envStatusCache = null;
50
57
  }
51
58
 
52
59
  /**
@@ -520,6 +527,958 @@ This server can be integrated with various agents and context pools.
520
527
  // This would update MCP-REGISTRY.md
521
528
  console.log(`📝 TODO: Remove ${name} from registry`);
522
529
  }
530
+
531
+ // ==========================================
532
+ // EXTENDED FEATURES: Agent Analysis
533
+ // ==========================================
534
+
535
+ /**
536
+ * Calculate percentage with safe division
537
+ * @param {number} numerator
538
+ * @param {number} denominator
539
+ * @returns {string} Formatted percentage or 'N/A%'
540
+ * @private
541
+ */
542
+ _calculatePercentage(numerator, denominator) {
543
+ if (denominator === 0) {
544
+ return 'N/A%';
545
+ }
546
+ return `${Math.round((numerator / denominator) * 100)}%`;
547
+ }
548
+
549
+ /**
550
+ * Analyze all agents to find MCP usage
551
+ * @returns {Object} Analysis result with agent-to-MCP mapping
552
+ */
553
+ analyzeAgents() {
554
+ const agentsDir = this.agentsDir || path.join(this.frameworkRoot, 'autopm', '.claude', 'agents');
555
+
556
+ if (!fs.existsSync(agentsDir)) {
557
+ return {
558
+ totalAgents: 0,
559
+ agentsWithMCP: 0,
560
+ agentsWithoutMCP: 0,
561
+ mcpUsage: {}
562
+ };
563
+ }
564
+
565
+ const result = {
566
+ totalAgents: 0,
567
+ agentsWithMCP: 0,
568
+ agentsWithoutMCP: 0,
569
+ mcpUsage: {}
570
+ };
571
+
572
+ // Recursively scan agent files
573
+ const scanDir = (dir) => {
574
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
575
+
576
+ entries.forEach(entry => {
577
+ const fullPath = path.join(dir, entry.name);
578
+
579
+ if (entry.isDirectory()) {
580
+ scanDir(fullPath);
581
+ } else if (entry.isFile() && entry.name.endsWith('.md')) {
582
+ result.totalAgents++;
583
+
584
+ const content = fs.readFileSync(fullPath, 'utf8');
585
+
586
+ // Extract agent name from frontmatter
587
+ const nameMatch = content.match(/^---[\s\S]*?name:\s*([^\n]+)/m);
588
+ const agentName = nameMatch ? nameMatch[1].trim() : path.basename(entry.name, '.md');
589
+
590
+ // Extract MCP URIs (mcp://server-name/path)
591
+ const matches = [...content.matchAll(MCPHandler.MCP_URI_REGEX)];
592
+
593
+ if (matches.length > 0) {
594
+ result.agentsWithMCP++;
595
+ const servers = [...new Set(matches.map(m => m[1]))];
596
+ result.mcpUsage[agentName] = servers;
597
+ }
598
+ }
599
+ });
600
+ };
601
+
602
+ scanDir(agentsDir);
603
+ result.agentsWithoutMCP = result.totalAgents - result.agentsWithMCP;
604
+
605
+ return result;
606
+ }
607
+
608
+ /**
609
+ * Get MCP usage for specific agent
610
+ * @param {string} agentName - Name of the agent
611
+ * @returns {Object} Agent MCP configuration
612
+ */
613
+ getAgentMCP(agentName) {
614
+ const agentsDir = this.agentsDir || path.join(this.frameworkRoot, 'autopm', '.claude', 'agents');
615
+
616
+ const result = {
617
+ agentName,
618
+ found: false,
619
+ mcpServers: [],
620
+ serverDetails: []
621
+ };
622
+
623
+ if (!fs.existsSync(agentsDir)) {
624
+ return result;
625
+ }
626
+
627
+ // Find agent file
628
+ const findAgentFile = (dir) => {
629
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
630
+
631
+ for (const entry of entries) {
632
+ const fullPath = path.join(dir, entry.name);
633
+
634
+ if (entry.isDirectory()) {
635
+ const found = findAgentFile(fullPath);
636
+ if (found) return found;
637
+ } else if (entry.isFile() && entry.name.endsWith('.md')) {
638
+ const content = fs.readFileSync(fullPath, 'utf8');
639
+ const nameMatch = content.match(/^---[\s\S]*?name:\s*([^\n]+)/m);
640
+ const fileAgentName = nameMatch ? nameMatch[1].trim() : path.basename(entry.name, '.md');
641
+
642
+ if (fileAgentName === agentName) {
643
+ return { path: fullPath, content };
644
+ }
645
+ }
646
+ }
647
+
648
+ return null;
649
+ };
650
+
651
+ const agentFile = findAgentFile(agentsDir);
652
+
653
+ if (!agentFile) {
654
+ return result;
655
+ }
656
+
657
+ result.found = true;
658
+
659
+ // Extract MCP URIs
660
+ const regex = MCPHandler.MCP_URI_REGEX;
661
+ const matches = [...agentFile.content.matchAll(regex)];
662
+ result.mcpServers = [...new Set(matches.map(m => m[1]))];
663
+
664
+ // Get server details
665
+ result.mcpServers.forEach(serverName => {
666
+ const server = this.getServer(serverName);
667
+ if (server) {
668
+ result.serverDetails.push({
669
+ name: serverName,
670
+ category: server.metadata.category,
671
+ description: server.metadata.description,
672
+ status: server.metadata.status
673
+ });
674
+ }
675
+ });
676
+
677
+ return result;
678
+ }
679
+
680
+ /**
681
+ * Display all agents using MCP
682
+ * @param {Object} options - Display options
683
+ */
684
+ mcpAgents(options = {}) {
685
+ console.log('🤖 Agents Using MCP\n');
686
+
687
+ const analysis = this.analyzeAgents();
688
+
689
+ if (analysis.agentsWithMCP === 0) {
690
+ console.log('ℹ️ No agents are currently using MCP servers\n');
691
+ return;
692
+ }
693
+
694
+ if (options.groupBy === 'server') {
695
+ // Group by MCP server
696
+ const serverMap = {};
697
+ Object.entries(analysis.mcpUsage).forEach(([agent, servers]) => {
698
+ servers.forEach(server => {
699
+ if (!serverMap[server]) {
700
+ serverMap[server] = [];
701
+ }
702
+ serverMap[server].push(agent);
703
+ });
704
+ });
705
+
706
+ Object.entries(serverMap).forEach(([server, agents]) => {
707
+ console.log(`📡 ${server} (${agents.length} agents)`);
708
+ agents.forEach(agent => {
709
+ console.log(` └─ ${agent}`);
710
+ });
711
+ console.log();
712
+ });
713
+ } else {
714
+ // List agents with their servers
715
+ Object.entries(analysis.mcpUsage).forEach(([agent, servers]) => {
716
+ console.log(`✅ ${agent}`);
717
+ servers.forEach(server => {
718
+ console.log(` └─ ${server}`);
719
+ });
720
+ console.log();
721
+ });
722
+ }
723
+
724
+ console.log(`📊 Summary:`);
725
+ console.log(` Total agents: ${analysis.totalAgents}`);
726
+ console.log(` Using MCP: ${analysis.agentsWithMCP}`);
727
+ console.log(` Without MCP: ${analysis.agentsWithoutMCP}`);
728
+ }
729
+
730
+ /**
731
+ * Display MCP configuration for specific agent
732
+ * @param {string} agentName - Name of the agent
733
+ */
734
+ mcpAgent(agentName) {
735
+ const config = this.loadConfig();
736
+ const activeServers = config.mcp?.activeServers || [];
737
+
738
+ const agentInfo = this.getAgentMCP(agentName);
739
+
740
+ if (!agentInfo.found) {
741
+ console.error(`❌ Agent '${agentName}' not found`);
742
+ return;
743
+ }
744
+
745
+ console.log(`\n🤖 Agent: ${agentName}`);
746
+ console.log('='.repeat(50));
747
+
748
+ if (agentInfo.mcpServers.length === 0) {
749
+ console.log('\nℹ️ This agent does not use any MCP servers');
750
+ return;
751
+ }
752
+
753
+ console.log(`\n📡 MCP Servers (${agentInfo.mcpServers.length}):\n`);
754
+
755
+ agentInfo.serverDetails.forEach(server => {
756
+ const isActive = activeServers.includes(server.name);
757
+ const status = isActive ? '✅ Active' : '⚪ Inactive';
758
+
759
+ console.log(`${status} ${server.name}`);
760
+ console.log(` Category: ${server.category || 'uncategorized'}`);
761
+ console.log(` Description: ${server.description || 'No description'}`);
762
+
763
+ // Show env vars if available
764
+ const serverDef = this.getServer(server.name);
765
+ if (serverDef && serverDef.metadata.env) {
766
+ console.log(` Environment Variables:`);
767
+ Object.keys(serverDef.metadata.env).forEach(envVar => {
768
+ console.log(` - ${envVar}`);
769
+ });
770
+ }
771
+ console.log();
772
+ });
773
+ }
774
+
775
+ /**
776
+ * Display MCP usage statistics
777
+ */
778
+ mcpUsage() {
779
+ console.log('📊 MCP Usage Statistics\n');
780
+
781
+ const analysis = this.analyzeAgents();
782
+
783
+ if (analysis.agentsWithMCP === 0) {
784
+ console.log('ℹ️ No MCP usage detected\n');
785
+ return;
786
+ }
787
+
788
+ // Group by server
789
+ const serverUsage = {};
790
+ Object.entries(analysis.mcpUsage).forEach(([agent, servers]) => {
791
+ servers.forEach(server => {
792
+ if (!serverUsage[server]) {
793
+ serverUsage[server] = [];
794
+ }
795
+ serverUsage[server].push(agent);
796
+ });
797
+ });
798
+
799
+ // Sort by usage count
800
+ const sorted = Object.entries(serverUsage)
801
+ .sort((a, b) => b[1].length - a[1].length);
802
+
803
+ console.log('📡 MCP Servers by Usage:\n');
804
+ sorted.forEach(([server, agents]) => {
805
+ console.log(`${server}: ${agents.length} agents`);
806
+ agents.forEach(agent => {
807
+ console.log(` └─ ${agent}`);
808
+ });
809
+ console.log();
810
+ });
811
+
812
+ console.log('📈 Summary:');
813
+ console.log(` Total agents: ${analysis.totalAgents}`);
814
+ const percentage = this._calculatePercentage(analysis.agentsWithMCP, analysis.totalAgents);
815
+ console.log(` Using MCP: ${analysis.agentsWithMCP} (${percentage})`);
816
+ console.log(` MCP servers in use: ${sorted.length}`);
817
+ }
818
+
819
+ // ==========================================
820
+ // EXTENDED FEATURES: Setup Wizard
821
+ // ==========================================
822
+
823
+ /**
824
+ * Detect required environment variables from active servers
825
+ * @returns {Array} List of required env vars
826
+ */
827
+ detectRequiredEnvVars() {
828
+ const config = this.loadConfig();
829
+ const activeServers = config.mcp?.activeServers || [];
830
+ const requiredVars = new Set();
831
+
832
+ activeServers.forEach(serverName => {
833
+ const server = this.getServer(serverName);
834
+ if (server && server.metadata.env) {
835
+ Object.keys(server.metadata.env).forEach(envVar => {
836
+ requiredVars.add(envVar);
837
+ });
838
+ }
839
+ });
840
+
841
+ return Array.from(requiredVars);
842
+ }
843
+
844
+ /**
845
+ * Check status of environment variables
846
+ * @param {boolean} useCache - Whether to use cached result (default: false)
847
+ * @returns {Object} Status of env vars (configured/missing)
848
+ */
849
+ checkEnvVarsStatus(useCache = false) {
850
+ // Return cached result if available and requested
851
+ if (useCache && this._envStatusCache !== null) {
852
+ return this._envStatusCache;
853
+ }
854
+
855
+ const requiredVars = this.detectRequiredEnvVars();
856
+ const configured = [];
857
+ const missing = [];
858
+
859
+ // Check .env file
860
+ let envContent = '';
861
+ if (fs.existsSync(this.envPath)) {
862
+ envContent = fs.readFileSync(this.envPath, 'utf8');
863
+ }
864
+
865
+ requiredVars.forEach(varName => {
866
+ // Check if variable is in .env file
867
+ const regex = new RegExp(`^${varName}=.+`, 'm');
868
+ if (regex.test(envContent)) {
869
+ configured.push(varName);
870
+ } else {
871
+ missing.push(varName);
872
+ }
873
+ });
874
+
875
+ const result = { configured, missing };
876
+
877
+ // Cache the result if requested
878
+ if (useCache) {
879
+ this._envStatusCache = result;
880
+ }
881
+
882
+ return result;
883
+ }
884
+
885
+ /**
886
+ * Interactive setup wizard for API keys
887
+ * @param {Object} options - Options including readline interface
888
+ */
889
+ async setupWizard(options = {}) {
890
+ console.log('🔧 MCP Configuration Setup');
891
+ console.log('='.repeat(50));
892
+ console.log();
893
+
894
+ const status = this.checkEnvVarsStatus();
895
+
896
+ if (status.missing.length === 0) {
897
+ console.log('✅ All required environment variables are configured!');
898
+ return;
899
+ }
900
+
901
+ console.log(`⚠️ Missing environment variables: ${status.missing.length}\n`);
902
+
903
+ status.missing.forEach(varName => {
904
+ console.log(`❌ ${varName}`);
905
+ });
906
+
907
+ console.log('\n💡 Configure these in .claude/.env file');
908
+ }
909
+
910
+ /**
911
+ * Save environment variables to .env file
912
+ * @param {Object} envVars - Key-value pairs of env vars
913
+ */
914
+ saveEnvVars(envVars) {
915
+ this.ensureClaudeDir();
916
+
917
+ let existingContent = '';
918
+ if (fs.existsSync(this.envPath)) {
919
+ existingContent = fs.readFileSync(this.envPath, 'utf8');
920
+ }
921
+
922
+ // Parse existing vars
923
+ const existingVars = {};
924
+ existingContent.split('\n').forEach(line => {
925
+ const trimmedLine = line.trim();
926
+ if (!trimmedLine || trimmedLine.startsWith('#')) return; // skip empty/comment lines
927
+ const parts = trimmedLine.split('=', 2);
928
+ if (parts.length === 2) {
929
+ existingVars[parts[0]] = parts[1];
930
+ }
931
+ });
932
+
933
+ // Merge with new vars
934
+ Object.assign(existingVars, envVars);
935
+
936
+ // Write back
937
+ const newContent = Object.entries(existingVars)
938
+ .map(([key, value]) => `${key}=${value}`)
939
+ .join('\n') + '\n';
940
+
941
+ fs.writeFileSync(this.envPath, newContent);
942
+
943
+ // Invalidate cache after updating env vars
944
+ this._envStatusCache = null;
945
+ }
946
+
947
+ /**
948
+ * Validate environment variable format
949
+ * @param {string} name - Variable name
950
+ * @param {string} value - Variable value
951
+ * @returns {boolean} Whether the var is valid
952
+ */
953
+ validateEnvVar(name, value) {
954
+ // Name should be uppercase with underscores
955
+ if (!MCPHandler.ENV_VAR_NAME_REGEX.test(name)) {
956
+ return false;
957
+ }
958
+
959
+ // Value should not be empty
960
+ if (!value || value.trim() === '') {
961
+ return false;
962
+ }
963
+
964
+ return true;
965
+ }
966
+
967
+ // ==========================================
968
+ // EXTENDED FEATURES: Diagnostics
969
+ // ==========================================
970
+
971
+ /**
972
+ * Extract unique environment variable names from missingEnvVars array
973
+ * @param {Array} missingEnvVars - Array of objects with server and variable properties
974
+ * @returns {Array<string>} Array of unique variable names
975
+ * @private
976
+ */
977
+ _getUniqueEnvVars(missingEnvVars) {
978
+ return [...new Set(missingEnvVars.map(v => v.variable))];
979
+ }
980
+
981
+ /**
982
+ * Check if required MCP servers are properly configured
983
+ * @returns {Object} Configuration check result
984
+ */
985
+ checkRequiredServers() {
986
+ const analysis = this.analyzeAgents();
987
+ const config = this.loadConfig();
988
+ const activeServers = config.mcp?.activeServers || [];
989
+ const envStatus = this.checkEnvVarsStatus();
990
+
991
+ const result = {
992
+ agentsUsingMCP: analysis.agentsWithMCP,
993
+ totalAgents: analysis.totalAgents,
994
+ serversInUse: new Set(),
995
+ missingServers: [],
996
+ disabledServers: [],
997
+ missingEnvVars: [],
998
+ warnings: [],
999
+ recommendations: []
1000
+ };
1001
+
1002
+ // Collect all MCP servers used by agents
1003
+ Object.values(analysis.mcpUsage).forEach(servers => {
1004
+ servers.forEach(server => result.serversInUse.add(server));
1005
+ });
1006
+
1007
+ // Check each server
1008
+ result.serversInUse.forEach(serverName => {
1009
+ const server = this.getServer(serverName);
1010
+
1011
+ if (!server) {
1012
+ result.missingServers.push({
1013
+ name: serverName,
1014
+ reason: 'Server definition not found in registry'
1015
+ });
1016
+ result.warnings.push(`⚠️ MCP server '${serverName}' is used by agents but not defined in registry`);
1017
+ } else {
1018
+ // Check if server is enabled
1019
+ if (!activeServers.includes(serverName)) {
1020
+ result.disabledServers.push({
1021
+ name: serverName,
1022
+ category: server.metadata.category,
1023
+ description: server.metadata.description
1024
+ });
1025
+ result.warnings.push(`⚠️ MCP server '${serverName}' is used by agents but NOT enabled`);
1026
+ result.recommendations.push(` Run: autopm mcp enable ${serverName}`);
1027
+ }
1028
+
1029
+ // Check environment variables for this server
1030
+ if (server.metadata.env) {
1031
+ const serverEnvVars = Object.keys(server.metadata.env);
1032
+ serverEnvVars.forEach(envVar => {
1033
+ if (envStatus.missing.includes(envVar)) {
1034
+ result.missingEnvVars.push({
1035
+ server: serverName,
1036
+ variable: envVar
1037
+ });
1038
+ }
1039
+ });
1040
+ }
1041
+ }
1042
+ });
1043
+
1044
+ // Add recommendations for missing env vars
1045
+ if (result.missingEnvVars.length > 0) {
1046
+ const uniqueVars = this._getUniqueEnvVars(result.missingEnvVars);
1047
+ result.warnings.push(`⚠️ Missing ${uniqueVars.length} environment variable(s): ${uniqueVars.join(', ')}`);
1048
+ result.recommendations.push(` Configure in .claude/.env file`);
1049
+ result.recommendations.push(` Run: autopm mcp setup`);
1050
+ }
1051
+
1052
+ return result;
1053
+ }
1054
+
1055
+ /**
1056
+ * Display quick configuration check
1057
+ */
1058
+ check() {
1059
+ console.log('🔍 MCP Configuration Check\n');
1060
+
1061
+ const checkResult = this.checkRequiredServers();
1062
+
1063
+ if (checkResult.agentsUsingMCP === 0) {
1064
+ console.log('ℹ️ No agents are using MCP servers\n');
1065
+ console.log('✅ Configuration OK - MCP not required\n');
1066
+ return;
1067
+ }
1068
+
1069
+ console.log(`📊 Overview:`);
1070
+ console.log(` Agents using MCP: ${checkResult.agentsUsingMCP}/${checkResult.totalAgents}`);
1071
+ console.log(` MCP servers in use: ${checkResult.serversInUse.size}\n`);
1072
+
1073
+ // Check for issues
1074
+ const hasIssues = checkResult.missingServers.length > 0 ||
1075
+ checkResult.disabledServers.length > 0 ||
1076
+ checkResult.missingEnvVars.length > 0;
1077
+
1078
+ if (!hasIssues) {
1079
+ console.log('✅ All required MCP servers are properly configured!\n');
1080
+ return;
1081
+ }
1082
+
1083
+ // Show warnings
1084
+ if (checkResult.warnings.length > 0) {
1085
+ console.log('⚠️ Configuration Issues:\n');
1086
+ checkResult.warnings.forEach(warning => console.log(warning));
1087
+ console.log();
1088
+ }
1089
+
1090
+ // Show disabled servers
1091
+ if (checkResult.disabledServers.length > 0) {
1092
+ console.log('🔴 Disabled Servers (used by agents):\n');
1093
+ checkResult.disabledServers.forEach(server => {
1094
+ console.log(` • ${server.name}`);
1095
+ console.log(` Category: ${server.category || 'uncategorized'}`);
1096
+ console.log(` Description: ${server.description || 'No description'}`);
1097
+ });
1098
+ console.log();
1099
+ }
1100
+
1101
+ // Show missing env vars details
1102
+ if (checkResult.missingEnvVars.length > 0) {
1103
+ console.log('🔑 Missing Environment Variables:\n');
1104
+ const byServer = Object.groupBy(checkResult.missingEnvVars, ({ server }) => server);
1105
+ Object.entries(byServer).forEach(([server, entries]) => {
1106
+ console.log(` ${server}:`);
1107
+ entries.forEach(({ variable }) => console.log(` - ${variable}`));
1108
+ });
1109
+ console.log();
1110
+ }
1111
+
1112
+ // Show recommendations
1113
+ if (checkResult.recommendations.length > 0) {
1114
+ console.log('💡 Recommendations:\n');
1115
+ checkResult.recommendations.forEach(rec => console.log(rec));
1116
+ console.log();
1117
+ }
1118
+
1119
+ console.log('🔧 Quick Fix:');
1120
+ if (Array.isArray(checkResult.disabledServers) && checkResult.disabledServers.length > 0) {
1121
+ checkResult.disabledServers.forEach(server => {
1122
+ console.log(` autopm mcp enable ${server.name}`);
1123
+ });
1124
+ }
1125
+ if (checkResult.missingEnvVars.length > 0) {
1126
+ console.log(` autopm mcp setup`);
1127
+ }
1128
+ console.log(` autopm mcp sync`);
1129
+ console.log();
1130
+ }
1131
+
1132
+ /**
1133
+ * Run comprehensive MCP diagnostics
1134
+ * @returns {Object} Diagnostic results
1135
+ */
1136
+ diagnose() {
1137
+ console.log('🔍 Running MCP Diagnostics...\n');
1138
+
1139
+ const result = {
1140
+ status: 'healthy',
1141
+ checks: [],
1142
+ errors: [],
1143
+ warnings: []
1144
+ };
1145
+
1146
+ // Check 1: .claude directory
1147
+ const claudeDirCheck = {
1148
+ name: '.claude directory exists',
1149
+ passed: fs.existsSync(path.join(this.projectRoot, '.claude'))
1150
+ };
1151
+ result.checks.push(claudeDirCheck);
1152
+
1153
+ if (!claudeDirCheck.passed) {
1154
+ result.errors.push('.claude directory not found');
1155
+ result.status = 'error';
1156
+ }
1157
+
1158
+ // Check 2: config.json
1159
+ const configCheck = {
1160
+ name: 'config.json exists and is valid',
1161
+ passed: false
1162
+ };
1163
+
1164
+ if (fs.existsSync(this.configPath)) {
1165
+ try {
1166
+ JSON.parse(fs.readFileSync(this.configPath, 'utf8'));
1167
+ configCheck.passed = true;
1168
+ } catch (e) {
1169
+ result.errors.push('config.json is invalid JSON');
1170
+ result.status = 'error';
1171
+ }
1172
+ } else {
1173
+ result.warnings.push('config.json not found');
1174
+ if (result.status === 'healthy') result.status = 'warning';
1175
+ }
1176
+ result.checks.push(configCheck);
1177
+
1178
+ // Check 3: Active servers exist
1179
+ const config = this.loadConfig();
1180
+ const activeServers = config.mcp?.activeServers || [];
1181
+
1182
+ activeServers.forEach(serverName => {
1183
+ const server = this.getServer(serverName);
1184
+ if (!server) {
1185
+ result.errors.push(`Active server '${serverName}' definition not found`);
1186
+ result.status = 'error';
1187
+ }
1188
+ });
1189
+
1190
+ // Check 4: Environment variables
1191
+ const envStatus = this.checkEnvVarsStatus();
1192
+ const envCheck = {
1193
+ name: 'environment variables configured',
1194
+ passed: envStatus.missing.length === 0
1195
+ };
1196
+ result.checks.push(envCheck);
1197
+
1198
+ if (envStatus.missing.length > 0) {
1199
+ envStatus.missing.forEach(varName => {
1200
+ result.warnings.push(`Environment variable ${varName} not configured`);
1201
+ });
1202
+ if (result.status === 'healthy') result.status = 'warning';
1203
+ }
1204
+
1205
+ // Check 5: mcp-servers.json
1206
+ if (fs.existsSync(this.mcpServersPath)) {
1207
+ try {
1208
+ JSON.parse(fs.readFileSync(this.mcpServersPath, 'utf8'));
1209
+ } catch (e) {
1210
+ result.errors.push('mcp-servers.json is invalid JSON');
1211
+ result.status = 'error';
1212
+ }
1213
+ }
1214
+
1215
+ // Check 6: Agents directory
1216
+ const agentsDir = this.agentsDir || path.join(this.frameworkRoot, 'autopm', '.claude', 'agents');
1217
+ const agentsDirCheck = {
1218
+ name: 'agents directory exists',
1219
+ passed: fs.existsSync(agentsDir)
1220
+ };
1221
+ result.checks.push(agentsDirCheck);
1222
+
1223
+ // Display results
1224
+ console.log('📋 Diagnostic Results:\n');
1225
+ result.checks.forEach(check => {
1226
+ const icon = check.passed ? '✅' : '❌';
1227
+ console.log(`${icon} ${check.name}`);
1228
+ });
1229
+
1230
+ if (result.errors.length > 0) {
1231
+ console.log('\n❌ Errors:');
1232
+ result.errors.forEach(err => console.log(` ${err}`));
1233
+ }
1234
+
1235
+ if (result.warnings.length > 0) {
1236
+ console.log('\n⚠️ Warnings:');
1237
+ result.warnings.forEach(warn => console.log(` ${warn}`));
1238
+ }
1239
+
1240
+ // Check for missing/disabled MCP servers
1241
+ console.log('\n🔌 MCP Server Requirements:');
1242
+ const serverCheck = this.checkRequiredServers();
1243
+
1244
+ if (serverCheck.agentsUsingMCP === 0) {
1245
+ console.log(' ℹ️ No agents using MCP servers');
1246
+ } else {
1247
+ console.log(` Agents using MCP: ${serverCheck.agentsUsingMCP}/${serverCheck.totalAgents}`);
1248
+ console.log(` MCP servers in use: ${serverCheck.serversInUse.size}`);
1249
+
1250
+ if (serverCheck.disabledServers.length > 0) {
1251
+ console.log(` ⚠️ ${serverCheck.disabledServers.length} required server(s) are DISABLED`);
1252
+ result.warnings.push(`${serverCheck.disabledServers.length} MCP server(s) used by agents are not enabled`);
1253
+ if (result.status === 'healthy') result.status = 'warning';
1254
+ }
1255
+
1256
+ if (serverCheck.missingServers.length > 0) {
1257
+ console.log(` ❌ ${serverCheck.missingServers.length} server(s) not found in registry`);
1258
+ result.errors.push(`${serverCheck.missingServers.length} MCP server(s) referenced but not defined`);
1259
+ result.status = 'error';
1260
+ }
1261
+
1262
+ if (serverCheck.missingEnvVars.length > 0) {
1263
+ const uniqueVars = this._getUniqueEnvVars(serverCheck.missingEnvVars);
1264
+ console.log(` ⚠️ ${uniqueVars.length} environment variable(s) not configured`);
1265
+ }
1266
+
1267
+ if (serverCheck.disabledServers.length === 0 &&
1268
+ serverCheck.missingServers.length === 0 &&
1269
+ serverCheck.missingEnvVars.length === 0) {
1270
+ console.log(' ✅ All required servers properly configured');
1271
+ } else {
1272
+ console.log('\n💡 Run "autopm mcp check" for detailed recommendations');
1273
+ }
1274
+ }
1275
+
1276
+ console.log(`\n🏥 Overall Health: ${result.status.toUpperCase()}`);
1277
+
1278
+ return result;
1279
+ }
1280
+
1281
+ /**
1282
+ * Test MCP server connection
1283
+ * @param {string} serverName - Name of server to test
1284
+ * @returns {Promise<Object>} Test results
1285
+ */
1286
+ async testServer(serverName) {
1287
+ const result = {
1288
+ serverName,
1289
+ success: false,
1290
+ message: '',
1291
+ commandCheck: false
1292
+ };
1293
+
1294
+ // Check if server exists
1295
+ const server = this.getServer(serverName);
1296
+ if (!server) {
1297
+ result.message = `Server '${serverName}' not found`;
1298
+ return result;
1299
+ }
1300
+
1301
+ // Check required env vars
1302
+ if (server.metadata.env) {
1303
+ const envVars = Object.keys(server.metadata.env);
1304
+ const envStatus = this.checkEnvVarsStatus(true); // Use cache to reduce file I/O
1305
+
1306
+ const missingVars = envVars.filter(v => envStatus.missing.includes(v));
1307
+ if (missingVars.length > 0) {
1308
+ result.message = `Missing environment variables: ${missingVars.join(', ')}`;
1309
+ return result;
1310
+ }
1311
+ }
1312
+
1313
+ // Check command accessibility (basic check)
1314
+ result.commandCheck = true;
1315
+ result.success = true;
1316
+ result.message = 'Server configuration appears valid';
1317
+
1318
+ return result;
1319
+ }
1320
+
1321
+ // ==========================================
1322
+ // EXTENDED FEATURES: Visualization
1323
+ // ==========================================
1324
+
1325
+ /**
1326
+ * Generate agent-MCP dependency tree
1327
+ * @returns {Object} Tree structure with nodes and edges
1328
+ */
1329
+ generateTree() {
1330
+ const analysis = this.analyzeAgents();
1331
+ const agentsDir = this.agentsDir || path.join(this.frameworkRoot, 'autopm', '.claude', 'agents');
1332
+
1333
+ const tree = {
1334
+ nodes: [],
1335
+ edges: []
1336
+ };
1337
+
1338
+ // Build category nodes
1339
+ const categories = {};
1340
+
1341
+ const scanDir = (dir, category = 'root') => {
1342
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
1343
+
1344
+ entries.forEach(entry => {
1345
+ const fullPath = path.join(dir, entry.name);
1346
+
1347
+ if (entry.isDirectory()) {
1348
+ if (!categories[entry.name]) {
1349
+ categories[entry.name] = true;
1350
+ tree.nodes.push({
1351
+ type: 'category',
1352
+ name: entry.name
1353
+ });
1354
+ }
1355
+ scanDir(fullPath, entry.name);
1356
+ } else if (entry.isFile() && entry.name.endsWith('.md')) {
1357
+ const content = fs.readFileSync(fullPath, 'utf8');
1358
+ const nameMatch = content.match(/^---[\s\S]*?name:\s*([^\n]+)/m);
1359
+ const agentName = nameMatch ? nameMatch[1].trim() : path.basename(entry.name, '.md');
1360
+
1361
+ tree.nodes.push({
1362
+ type: 'agent',
1363
+ name: agentName,
1364
+ category
1365
+ });
1366
+
1367
+ // Create edges to MCP servers
1368
+ if (analysis.mcpUsage[agentName]) {
1369
+ analysis.mcpUsage[agentName].forEach(server => {
1370
+ tree.edges.push({
1371
+ from: agentName,
1372
+ to: server
1373
+ });
1374
+ });
1375
+ }
1376
+ }
1377
+ });
1378
+ };
1379
+
1380
+ if (fs.existsSync(agentsDir)) {
1381
+ scanDir(agentsDir);
1382
+ }
1383
+
1384
+ return tree;
1385
+ }
1386
+
1387
+ /**
1388
+ * Display tree visualization
1389
+ */
1390
+ showTree() {
1391
+ console.log('🌳 Agent → MCP Dependency Tree\n');
1392
+
1393
+ const tree = this.generateTree();
1394
+ const analysis = this.analyzeAgents();
1395
+
1396
+ // Group agents by category
1397
+ const categories = {};
1398
+ tree.nodes.filter(n => n.type === 'agent').forEach(agent => {
1399
+ const cat = agent.category || 'root';
1400
+ if (!categories[cat]) {
1401
+ categories[cat] = [];
1402
+ }
1403
+ categories[cat].push(agent.name);
1404
+ });
1405
+
1406
+ Object.entries(categories).forEach(([category, agents]) => {
1407
+ if (category !== 'root') {
1408
+ console.log(`📁 ${category}`);
1409
+ }
1410
+
1411
+ agents.forEach((agent, index) => {
1412
+ const isLast = index === agents.length - 1;
1413
+ const prefix = isLast ? '└─' : '├─';
1414
+
1415
+ const mcpServers = analysis.mcpUsage[agent] || [];
1416
+ const status = mcpServers.length > 0 ? '✅' : '⚪';
1417
+
1418
+ console.log(`${prefix} ${agent} ${status}`);
1419
+
1420
+ if (mcpServers.length > 0) {
1421
+ mcpServers.forEach((server, sIndex) => {
1422
+ const sIsLast = sIndex === mcpServers.length - 1;
1423
+ const sPrefix = sIsLast ? ' └─' : ' ├─';
1424
+ console.log(`${sPrefix} ${server}`);
1425
+ });
1426
+ }
1427
+ });
1428
+ console.log();
1429
+ });
1430
+ }
1431
+
1432
+ /**
1433
+ * Show status of all MCP servers
1434
+ */
1435
+ showStatus() {
1436
+ console.log('📊 MCP Servers Status\n');
1437
+
1438
+ const config = this.loadConfig();
1439
+ const activeServers = config.mcp?.activeServers || [];
1440
+ const allServers = this.getAllServers();
1441
+ const analysis = this.analyzeAgents();
1442
+
1443
+ // Count agent usage per server
1444
+ const serverAgentCount = {};
1445
+ Object.values(analysis.mcpUsage).forEach(servers => {
1446
+ servers.forEach(server => {
1447
+ serverAgentCount[server] = (serverAgentCount[server] || 0) + 1;
1448
+ });
1449
+ });
1450
+
1451
+ allServers.forEach(server => {
1452
+ const isEnabled = activeServers.includes(server.name);
1453
+ const status = isEnabled ? '✅' : '⚪';
1454
+ const agentCount = serverAgentCount[server.name] || 0;
1455
+
1456
+ console.log(`${status} ${server.name}`);
1457
+ console.log(` Category: ${server.metadata.category || 'uncategorized'}`);
1458
+ console.log(` Status: ${isEnabled ? 'Enabled' : 'Disabled'}`);
1459
+ console.log(` Used by: ${agentCount} agent${agentCount !== 1 ? 's' : ''}`);
1460
+
1461
+ // Show required env vars
1462
+ if (server.metadata.env) {
1463
+ const envVars = Object.keys(server.metadata.env);
1464
+ const envStatus = this.checkEnvVarsStatus();
1465
+
1466
+ console.log(` Environment:`);
1467
+ envVars.forEach(envVar => {
1468
+ const configured = envStatus.configured.includes(envVar);
1469
+ const varStatus = configured ? '✅' : '❌';
1470
+ console.log(` ${varStatus} ${envVar}`);
1471
+ });
1472
+ }
1473
+
1474
+ console.log();
1475
+ });
1476
+
1477
+ console.log('📈 Summary:');
1478
+ console.log(` Total servers: ${allServers.length}`);
1479
+ console.log(` Enabled: ${activeServers.length}`);
1480
+ console.log(` Disabled: ${allServers.length - activeServers.length}`);
1481
+ }
523
1482
  }
524
1483
 
525
1484
  // CLI execution
@@ -553,9 +1512,55 @@ if (require.main === module) {
553
1512
  case 'info':
554
1513
  handler.info(args[0]);
555
1514
  break;
1515
+ // Extended commands
1516
+ case 'agents':
1517
+ handler.mcpAgents(args.includes('--by-server') ? { groupBy: 'server' } : {});
1518
+ break;
1519
+ case 'agent':
1520
+ if (!args[0]) {
1521
+ console.error('❌ Please specify an agent name');
1522
+ process.exit(1);
1523
+ }
1524
+ handler.mcpAgent(args[0]);
1525
+ break;
1526
+ case 'usage':
1527
+ handler.mcpUsage();
1528
+ break;
1529
+ case 'setup':
1530
+ handler.setupWizard();
1531
+ break;
1532
+ case 'check':
1533
+ handler.check();
1534
+ break;
1535
+ case 'diagnose':
1536
+ handler.diagnose();
1537
+ break;
1538
+ case 'test':
1539
+ if (!args[0]) {
1540
+ console.error('❌ Please specify a server name');
1541
+ process.exit(1);
1542
+ }
1543
+ handler.testServer(args[0]).then(result => {
1544
+ if (result.success) {
1545
+ console.log(`✅ ${result.message}`);
1546
+ } else {
1547
+ console.error(`❌ ${result.message}`);
1548
+ process.exit(1);
1549
+ }
1550
+ }).catch(error => {
1551
+ console.error(`❌ Error testing server: ${error?.message || error}`);
1552
+ console.error(`❌ Error testing server: ${error?.message || error}`);
1553
+ });
1554
+ break;
1555
+ case 'tree':
1556
+ handler.showTree();
1557
+ break;
1558
+ case 'status':
1559
+ handler.showStatus();
1560
+ break;
556
1561
  default:
557
1562
  console.log('Usage: mcp-handler <command> [options]');
558
- console.log('\nCommands:');
1563
+ console.log('\nBasic Commands:');
559
1564
  console.log(' list List all available servers');
560
1565
  console.log(' add Add a new server interactively');
561
1566
  console.log(' remove <name> Remove a server');
@@ -564,6 +1569,19 @@ if (require.main === module) {
564
1569
  console.log(' sync Sync configuration');
565
1570
  console.log(' validate Validate all servers');
566
1571
  console.log(' info <name> Show server details');
1572
+ console.log('\nAgent Analysis:');
1573
+ console.log(' agents List agents using MCP');
1574
+ console.log(' agents --by-server Group agents by MCP server');
1575
+ console.log(' agent <name> Show MCP config for specific agent');
1576
+ console.log(' usage Show MCP usage statistics');
1577
+ console.log('\nConfiguration:');
1578
+ console.log(' setup Interactive API key setup');
1579
+ console.log(' check Quick MCP configuration check');
1580
+ console.log(' diagnose Run MCP diagnostics');
1581
+ console.log(' test <server> Test MCP server connection');
1582
+ console.log('\nVisualization:');
1583
+ console.log(' tree Show agent-MCP dependency tree');
1584
+ console.log(' status Show MCP servers status');
567
1585
  process.exit(1);
568
1586
  }
569
1587
  }