agentaudit 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/cli.mjs +212 -0
  2. package/package.json +1 -1
package/cli.mjs CHANGED
@@ -602,6 +602,210 @@ async function scanRepo(url) {
602
602
  return { slug, url, info, files: files.length, findings, registryData, duration };
603
603
  }
604
604
 
605
+ // ── Discover local MCP configs ──────────────────────────
606
+
607
+ function findMcpConfigs() {
608
+ const home = process.env.HOME || process.env.USERPROFILE || '';
609
+ const platform = process.platform;
610
+
611
+ // All known MCP config locations
612
+ const candidates = [
613
+ // Claude Desktop
614
+ { name: 'Claude Desktop', path: path.join(home, '.claude', 'mcp.json') },
615
+ { name: 'Claude Desktop', path: path.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json') },
616
+ { name: 'Claude Desktop', path: path.join(home, 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json') },
617
+ { name: 'Claude Desktop', path: path.join(home, '.config', 'claude', 'claude_desktop_config.json') },
618
+ // Cursor
619
+ { name: 'Cursor', path: path.join(home, '.cursor', 'mcp.json') },
620
+ // Windsurf / Codeium
621
+ { name: 'Windsurf', path: path.join(home, '.codeium', 'windsurf', 'mcp_config.json') },
622
+ // VS Code
623
+ { name: 'VS Code', path: path.join(home, '.vscode', 'mcp.json') },
624
+ // Continue.dev
625
+ { name: 'Continue', path: path.join(home, '.continue', 'config.json') },
626
+ ];
627
+
628
+ // Also scan workspace .cursor/mcp.json, .vscode/mcp.json in cwd
629
+ const cwd = process.cwd();
630
+ candidates.push(
631
+ { name: 'Cursor (project)', path: path.join(cwd, '.cursor', 'mcp.json') },
632
+ { name: 'VS Code (project)', path: path.join(cwd, '.vscode', 'mcp.json') },
633
+ );
634
+
635
+ const found = [];
636
+ for (const c of candidates) {
637
+ if (fs.existsSync(c.path)) {
638
+ try {
639
+ const content = JSON.parse(fs.readFileSync(c.path, 'utf8'));
640
+ found.push({ ...c, content });
641
+ } catch {}
642
+ }
643
+ }
644
+ return found;
645
+ }
646
+
647
+ function extractServersFromConfig(config) {
648
+ // Handle both { mcpServers: {...} } and { servers: {...} } formats
649
+ const servers = config.mcpServers || config.servers || {};
650
+ const result = [];
651
+
652
+ for (const [name, serverConfig] of Object.entries(servers)) {
653
+ const info = {
654
+ name,
655
+ command: serverConfig.command || null,
656
+ args: serverConfig.args || [],
657
+ url: serverConfig.url || null,
658
+ sourceUrl: null,
659
+ };
660
+
661
+ // Try to extract source URL from args (common patterns)
662
+ const allArgs = [info.command, ...info.args].filter(Boolean).join(' ');
663
+
664
+ // npx package-name → npm package
665
+ const npxMatch = allArgs.match(/npx\s+(?:-y\s+)?(@?[a-z0-9][\w./-]*)/i);
666
+ if (npxMatch) info.npmPackage = npxMatch[1];
667
+
668
+ // node /path/to/something → try to find package.json
669
+ const nodePathMatch = allArgs.match(/node\s+["']?([^"'\s]+)/);
670
+ if (nodePathMatch) {
671
+ const scriptPath = nodePathMatch[1];
672
+ // Walk up to find package.json with repository
673
+ let dir = path.dirname(path.resolve(scriptPath));
674
+ for (let i = 0; i < 5; i++) {
675
+ const pkgPath = path.join(dir, 'package.json');
676
+ if (fs.existsSync(pkgPath)) {
677
+ try {
678
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
679
+ if (pkg.repository?.url) {
680
+ info.sourceUrl = pkg.repository.url.replace(/^git\+/, '').replace(/\.git$/, '');
681
+ }
682
+ if (pkg.name) info.npmPackage = pkg.name;
683
+ } catch {}
684
+ break;
685
+ }
686
+ const parent = path.dirname(dir);
687
+ if (parent === dir) break;
688
+ dir = parent;
689
+ }
690
+ }
691
+
692
+ // python/uvx with package name
693
+ const pyMatch = allArgs.match(/(?:uvx|pip run|python -m)\s+(@?[a-z0-9][\w./-]*)/i);
694
+ if (pyMatch) info.pyPackage = pyMatch[1];
695
+
696
+ result.push(info);
697
+ }
698
+ return result;
699
+ }
700
+
701
+ function serverSlug(server) {
702
+ // Try to derive a slug for registry lookup
703
+ if (server.npmPackage) return server.npmPackage.replace(/^@/, '').replace(/\//g, '-');
704
+ if (server.pyPackage) return server.pyPackage.replace(/[^a-z0-9-]/gi, '-');
705
+ return server.name.toLowerCase().replace(/[^a-z0-9-]/gi, '-');
706
+ }
707
+
708
+ async function discoverCommand() {
709
+ console.log(` ${c.bold}Discovering local MCP servers...${c.reset}`);
710
+ console.log();
711
+
712
+ const configs = findMcpConfigs();
713
+
714
+ if (configs.length === 0) {
715
+ console.log(` ${c.yellow}No MCP configurations found.${c.reset}`);
716
+ console.log(` ${c.dim}Searched: Claude Desktop, Cursor, Windsurf, VS Code${c.reset}`);
717
+ console.log();
718
+ console.log(` ${c.dim}MCP config locations:${c.reset}`);
719
+ console.log(` ${c.dim} Claude: ~/.claude/mcp.json${c.reset}`);
720
+ console.log(` ${c.dim} Cursor: ~/.cursor/mcp.json${c.reset}`);
721
+ console.log(` ${c.dim} Windsurf: ~/.codeium/windsurf/mcp_config.json${c.reset}`);
722
+ console.log(` ${c.dim} VS Code: ~/.vscode/mcp.json${c.reset}`);
723
+ console.log();
724
+ return;
725
+ }
726
+
727
+ let totalServers = 0;
728
+ let checkedServers = 0;
729
+ let auditedServers = 0;
730
+ let unauditedServers = 0;
731
+
732
+ for (const config of configs) {
733
+ const servers = extractServersFromConfig(config.content);
734
+ const serverCount = servers.length;
735
+ totalServers += serverCount;
736
+
737
+ const countLabel = serverCount === 0
738
+ ? `${c.dim}no servers${c.reset}`
739
+ : `found ${c.bold}${serverCount}${c.reset} server${serverCount > 1 ? 's' : ''}`;
740
+
741
+ console.log(`${icons.bullet} Scanning ${c.bold}${config.name}${c.reset} ${c.dim}${config.path}${c.reset} ${countLabel}`);
742
+
743
+ if (serverCount === 0) {
744
+ console.log();
745
+ continue;
746
+ }
747
+
748
+ console.log();
749
+
750
+ for (let i = 0; i < servers.length; i++) {
751
+ const server = servers[i];
752
+ const isLast = i === servers.length - 1;
753
+ const branch = isLast ? icons.treeLast : icons.tree;
754
+ const pipe = isLast ? ' ' : `${icons.pipe} `;
755
+
756
+ const slug = serverSlug(server);
757
+ checkedServers++;
758
+
759
+ // Registry lookup
760
+ const registryData = await checkRegistry(slug);
761
+
762
+ // Also try with server name directly
763
+ let regData = registryData;
764
+ if (!regData && slug !== server.name.toLowerCase()) {
765
+ regData = await checkRegistry(server.name.toLowerCase());
766
+ }
767
+
768
+ // Determine source display
769
+ let sourceLabel = '';
770
+ if (server.npmPackage) sourceLabel = `${c.dim}npm:${server.npmPackage}${c.reset}`;
771
+ else if (server.pyPackage) sourceLabel = `${c.dim}pip:${server.pyPackage}${c.reset}`;
772
+ else if (server.command) sourceLabel = `${c.dim}${[server.command, ...server.args.slice(0, 2)].join(' ')}${c.reset}`;
773
+
774
+ if (regData) {
775
+ auditedServers++;
776
+ const riskScore = regData.risk_score ?? regData.latest_risk_score ?? 0;
777
+ const hasOfficial = regData.has_official_audit;
778
+ console.log(`${branch} ${c.bold}${server.name}${c.reset} ${sourceLabel}`);
779
+ console.log(`${pipe} ${riskBadge(riskScore)} Risk ${riskScore} ${hasOfficial ? `${c.green}✔ official${c.reset} ` : ''}${c.dim}${REGISTRY_URL}/skills/${slug}${c.reset}`);
780
+ } else {
781
+ unauditedServers++;
782
+ console.log(`${branch} ${c.bold}${server.name}${c.reset} ${sourceLabel}`);
783
+ console.log(`${pipe} ${c.yellow}⚠ not audited${c.reset} ${c.dim}Run: agentaudit scan <source-url>${c.reset}`);
784
+ }
785
+
786
+ if (server.sourceUrl) {
787
+ console.log(`${pipe} ${c.dim}source: ${server.sourceUrl}${c.reset}`);
788
+ }
789
+ }
790
+
791
+ console.log();
792
+ }
793
+
794
+ // Summary
795
+ console.log(`${c.dim}${'─'.repeat(60)}${c.reset}`);
796
+ console.log(` ${c.bold}Summary${c.reset} ${totalServers} server${totalServers !== 1 ? 's' : ''} across ${configs.length} config${configs.length !== 1 ? 's' : ''}`);
797
+ console.log();
798
+ if (auditedServers > 0) console.log(` ${icons.safe} ${c.green}${auditedServers} audited${c.reset}`);
799
+ if (unauditedServers > 0) console.log(` ${icons.caution} ${c.yellow}${unauditedServers} not audited${c.reset}`);
800
+ console.log();
801
+
802
+ if (unauditedServers > 0) {
803
+ console.log(` ${c.dim}To audit unaudited servers, run:${c.reset}`);
804
+ console.log(` ${c.cyan}agentaudit scan <github-url>${c.reset}`);
805
+ console.log();
806
+ }
807
+ }
808
+
605
809
  // ── Check command ───────────────────────────────────────
606
810
 
607
811
  async function checkPackage(name) {
@@ -633,11 +837,13 @@ async function main() {
633
837
  banner();
634
838
  console.log(` ${c.bold}Usage:${c.reset}`);
635
839
  console.log(` agentaudit setup Register + configure API key`);
840
+ console.log(` agentaudit discover Find & check local MCP servers`);
636
841
  console.log(` agentaudit scan <repo-url> [repo-url...] Scan repositories`);
637
842
  console.log(` agentaudit check <package-name> Look up in registry`);
638
843
  console.log();
639
844
  console.log(` ${c.bold}Examples:${c.reset}`);
640
845
  console.log(` agentaudit setup`);
846
+ console.log(` agentaudit discover`);
641
847
  console.log(` agentaudit scan https://github.com/owner/repo`);
642
848
  console.log(` agentaudit scan repo1.git repo2.git repo3.git`);
643
849
  console.log(` agentaudit check fastmcp`);
@@ -655,6 +861,11 @@ async function main() {
655
861
  return;
656
862
  }
657
863
 
864
+ if (command === 'discover') {
865
+ await discoverCommand();
866
+ return;
867
+ }
868
+
658
869
  if (command === 'check') {
659
870
  if (targets.length === 0) {
660
871
  console.log(` ${c.red}Error: package name required${c.reset}`);
@@ -667,6 +878,7 @@ async function main() {
667
878
  if (command === 'scan') {
668
879
  if (targets.length === 0) {
669
880
  console.log(` ${c.red}Error: at least one repository URL required${c.reset}`);
881
+ console.log(` ${c.dim}Tip: use ${c.cyan}agentaudit discover${c.dim} to find & check locally installed MCP servers${c.reset}`);
670
882
  process.exit(1);
671
883
  }
672
884
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentaudit",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "Security scanner for AI packages — MCP server + CLI",
5
5
  "type": "module",
6
6
  "bin": {