java-class-analyzer-mcp 0.1.1__tar.gz → 0.1.2__tar.gz

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 (22) hide show
  1. {java_class_analyzer_mcp-0.1.1 → java_class_analyzer_mcp-0.1.2}/PKG-INFO +1 -19
  2. {java_class_analyzer_mcp-0.1.1 → java_class_analyzer_mcp-0.1.2}/README.md +0 -18
  3. {java_class_analyzer_mcp-0.1.1 → java_class_analyzer_mcp-0.1.2}/java_class_analyzer_mcp/analyzer/java_class_analyzer.py +10 -10
  4. {java_class_analyzer_mcp-0.1.1 → java_class_analyzer_mcp-0.1.2}/java_class_analyzer_mcp/decompiler/decompiler_service.py +28 -25
  5. java_class_analyzer_mcp-0.1.2/java_class_analyzer_mcp/logger.py +99 -0
  6. {java_class_analyzer_mcp-0.1.1 → java_class_analyzer_mcp-0.1.2}/java_class_analyzer_mcp/main.py +19 -1
  7. {java_class_analyzer_mcp-0.1.1 → java_class_analyzer_mcp-0.1.2}/java_class_analyzer_mcp/scanner/dependency_scanner.py +21 -26
  8. {java_class_analyzer_mcp-0.1.1 → java_class_analyzer_mcp-0.1.2}/java_class_analyzer_mcp.egg-info/PKG-INFO +1 -19
  9. {java_class_analyzer_mcp-0.1.1 → java_class_analyzer_mcp-0.1.2}/java_class_analyzer_mcp.egg-info/SOURCES.txt +1 -0
  10. {java_class_analyzer_mcp-0.1.1 → java_class_analyzer_mcp-0.1.2}/pyproject.toml +1 -1
  11. {java_class_analyzer_mcp-0.1.1 → java_class_analyzer_mcp-0.1.2}/java_class_analyzer_mcp/__init__.py +0 -0
  12. {java_class_analyzer_mcp-0.1.1 → java_class_analyzer_mcp-0.1.2}/java_class_analyzer_mcp/analyzer/__init__.py +0 -0
  13. {java_class_analyzer_mcp-0.1.1 → java_class_analyzer_mcp-0.1.2}/java_class_analyzer_mcp/cfr-0.152.jar +0 -0
  14. {java_class_analyzer_mcp-0.1.1 → java_class_analyzer_mcp-0.1.2}/java_class_analyzer_mcp/cli/__init__.py +0 -0
  15. {java_class_analyzer_mcp-0.1.1 → java_class_analyzer_mcp-0.1.2}/java_class_analyzer_mcp/cli.py +0 -0
  16. {java_class_analyzer_mcp-0.1.1 → java_class_analyzer_mcp-0.1.2}/java_class_analyzer_mcp/decompiler/__init__.py +0 -0
  17. {java_class_analyzer_mcp-0.1.1 → java_class_analyzer_mcp-0.1.2}/java_class_analyzer_mcp/scanner/__init__.py +0 -0
  18. {java_class_analyzer_mcp-0.1.1 → java_class_analyzer_mcp-0.1.2}/java_class_analyzer_mcp.egg-info/dependency_links.txt +0 -0
  19. {java_class_analyzer_mcp-0.1.1 → java_class_analyzer_mcp-0.1.2}/java_class_analyzer_mcp.egg-info/entry_points.txt +0 -0
  20. {java_class_analyzer_mcp-0.1.1 → java_class_analyzer_mcp-0.1.2}/java_class_analyzer_mcp.egg-info/requires.txt +0 -0
  21. {java_class_analyzer_mcp-0.1.1 → java_class_analyzer_mcp-0.1.2}/java_class_analyzer_mcp.egg-info/top_level.txt +0 -0
  22. {java_class_analyzer_mcp-0.1.1 → java_class_analyzer_mcp-0.1.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: java-class-analyzer-mcp
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: MCP server for scanning Maven dependencies and decompiling Java classes
5
5
  Author: java-class-analyzer-mcp contributors
6
6
  License-Expression: MIT
@@ -150,24 +150,6 @@ java-class-analyzer-mcp/
150
150
  - `className` (string): 要分析的类全名
151
151
  - `projectPath` (string): Maven 项目根目录路径
152
152
 
153
- ## 命令行工具
154
-
155
- 安装后可直接使用命令行工具:
156
-
157
- ```bash
158
- # 启动 MCP 服务器(stdio 模式,供 MCP 客户端调用)
159
- java-class-analyzer-mcp
160
-
161
- # 生成 MCP 配置模板
162
- java-class-analyzer-mcp config -o mcp-config.json
163
-
164
- # 测试工具
165
- java-class-analyzer-mcp test -t scan -p /path/to/project
166
- java-class-analyzer-mcp test -t decompile -p /path/to/project -c com.example.MyClass
167
- java-class-analyzer-mcp test -t analyze -p /path/to/project -c com.example.MyClass
168
- java-class-analyzer-mcp test -t all -p /path/to/project -c com.example.MyClass --no-cache
169
- ```
170
-
171
153
  ## 缓存文件
172
154
 
173
155
  在项目目录下会生成以下缓存:
@@ -135,24 +135,6 @@ java-class-analyzer-mcp/
135
135
  - `className` (string): 要分析的类全名
136
136
  - `projectPath` (string): Maven 项目根目录路径
137
137
 
138
- ## 命令行工具
139
-
140
- 安装后可直接使用命令行工具:
141
-
142
- ```bash
143
- # 启动 MCP 服务器(stdio 模式,供 MCP 客户端调用)
144
- java-class-analyzer-mcp
145
-
146
- # 生成 MCP 配置模板
147
- java-class-analyzer-mcp config -o mcp-config.json
148
-
149
- # 测试工具
150
- java-class-analyzer-mcp test -t scan -p /path/to/project
151
- java-class-analyzer-mcp test -t decompile -p /path/to/project -c com.example.MyClass
152
- java-class-analyzer-mcp test -t analyze -p /path/to/project -c com.example.MyClass
153
- java-class-analyzer-mcp test -t all -p /path/to/project -c com.example.MyClass --no-cache
154
- ```
155
-
156
138
  ## 缓存文件
157
139
 
158
140
  在项目目录下会生成以下缓存:
@@ -1,16 +1,10 @@
1
1
  import os
2
- import sys
3
2
  import subprocess
4
3
  import re
5
4
  from typing import List, Optional, Dict
6
5
  from dataclasses import dataclass
7
6
  from ..scanner.dependency_scanner import DependencyScanner
8
-
9
-
10
- def _debug(msg: str) -> None:
11
- """仅在 DEBUG 模式下输出到 stderr(不污染 MCP stdout JSON-RPC)"""
12
- if os.environ.get("LOG_LEVEL", "DEBUG").upper() == "DEBUG":
13
- print(msg, file=sys.stderr)
7
+ from ..logger import get_logger
14
8
 
15
9
 
16
10
  @dataclass
@@ -51,10 +45,15 @@ class JavaClassAnalyzer:
51
45
  def __init__(self):
52
46
  self.scanner = DependencyScanner()
53
47
 
48
+ def _log(self, project_path=None):
49
+ """获取与项目路径绑定的 logger"""
50
+ return get_logger(project_path or self.scanner._project_path)
51
+
54
52
  def analyze_class(self, class_name: str, project_path: str) -> ClassAnalysis:
55
53
  """
56
54
  分析Java类的结构信息
57
55
  """
56
+ log = self._log(project_path)
58
57
  try:
59
58
  # 1. 获取类文件路径
60
59
  jar_path = self.scanner.find_jar_for_class(class_name, project_path)
@@ -66,13 +65,14 @@ class JavaClassAnalyzer:
66
65
 
67
66
  return analysis
68
67
  except Exception as e:
69
- _debug(f"分析类 {class_name} 失败: {e}")
68
+ log.debug(f"分析类 {class_name} 失败: {e}")
70
69
  raise e
71
70
 
72
71
  def analyze_class_with_javap(self, jar_path: str, class_name: str) -> ClassAnalysis:
73
72
  """
74
73
  使用 javap 工具分析JAR包中的类结构
75
74
  """
75
+ log = self._log()
76
76
  try:
77
77
  javap_cmd = self.get_javap_command()
78
78
 
@@ -91,7 +91,7 @@ class JavaClassAnalyzer:
91
91
  except subprocess.TimeoutExpired:
92
92
  raise Exception("javap分析超时")
93
93
  except Exception as e:
94
- _debug(f"javap 分析失败: {e}")
94
+ log.debug(f"javap 分析失败: {e}")
95
95
  raise Exception(f"javap 分析失败: {str(e)}")
96
96
 
97
97
  def parse_javap_output(self, output: str, class_name: str) -> ClassAnalysis:
@@ -306,7 +306,7 @@ class JavaClassAnalyzer:
306
306
  modifiers=modifiers,
307
307
  )
308
308
  except Exception as e:
309
- _debug(f"解析方法失败: {line}, 错误: {e}")
309
+ self._log().debug(f"解析方法失败: {line}, 错误: {e}")
310
310
  return None
311
311
 
312
312
  def split_parameters(self, params_str: str) -> List[str]:
@@ -1,16 +1,10 @@
1
1
  import os
2
- import sys
3
2
  import subprocess
4
3
  import shutil
5
4
  import zipfile
6
5
  from typing import Dict
7
6
  from ..scanner.dependency_scanner import DependencyScanner
8
-
9
-
10
- def _debug(msg: str) -> None:
11
- """仅在 DEBUG 模式下输出到 stderr(不污染 MCP stdout JSON-RPC)"""
12
- if os.environ.get("LOG_LEVEL", "DEBUG").upper() == "DEBUG":
13
- print(msg, file=sys.stderr)
7
+ from ..logger import get_logger
14
8
 
15
9
 
16
10
  class DecompilerService:
@@ -20,6 +14,10 @@ class DecompilerService:
20
14
  self.scanner = DependencyScanner()
21
15
  self.cfr_path = ""
22
16
 
17
+ def _log(self, project_path=None):
18
+ """获取与项目路径绑定的 logger"""
19
+ return get_logger(project_path or self.scanner._project_path)
20
+
23
21
  def initialize_cfr_path(self) -> None:
24
22
  """初始化CFR工具路径"""
25
23
  if not self.cfr_path:
@@ -28,7 +26,7 @@ class DecompilerService:
28
26
  raise Exception(
29
27
  "未找到CFR反编译工具。请下载CFR jar包到lib目录或设置CFR_PATH环境变量"
30
28
  )
31
- _debug(f"CFR工具路径: {self.cfr_path}")
29
+ self._log().debug(f"CFR工具路径: {self.cfr_path}")
32
30
 
33
31
  def decompile_class(
34
32
  self,
@@ -39,25 +37,26 @@ class DecompilerService:
39
37
  """
40
38
  反编译指定的Java类文件
41
39
  """
40
+ log = self._log(project_path)
42
41
  try:
43
42
  self.initialize_cfr_path()
44
43
 
45
44
  # 1. 检查缓存
46
45
  cache_path = self.get_cache_path(class_name, project_path)
47
46
  if use_cache and os.path.exists(cache_path):
48
- _debug(f"使用缓存的反编译结果: {cache_path}")
47
+ log.debug(f"使用缓存的反编译结果: {cache_path}")
49
48
  with open(cache_path, "r", encoding="utf-8") as f:
50
49
  return f.read()
51
50
 
52
51
  # 2. 查找类对应的JAR包
53
- _debug(f"查找类 {class_name} 对应的JAR包...")
52
+ log.debug(f"查找类 {class_name} 对应的JAR包...")
54
53
  jar_path = self.scanner.find_jar_for_class(class_name, project_path)
55
54
 
56
55
  if not jar_path:
57
56
  raise Exception(
58
57
  f"未找到类 {class_name} 对应的JAR包,请先运行 scan_dependencies 建立类索引"
59
58
  )
60
- _debug(f"找到JAR包: {jar_path}")
59
+ log.debug(f"找到JAR包: {jar_path}")
61
60
 
62
61
  # 3. 从JAR包中提取.class文件
63
62
  class_file_path = self.extract_class_file(jar_path, class_name)
@@ -70,19 +69,19 @@ class DecompilerService:
70
69
  os.makedirs(os.path.dirname(cache_path), exist_ok=True)
71
70
  with open(cache_path, "w", encoding="utf-8") as f:
72
71
  f.write(source_code)
73
- _debug(f"反编译结果已缓存: {cache_path}")
72
+ log.debug(f"反编译结果已缓存: {cache_path}")
74
73
 
75
74
  # 6. 清理临时文件(只有在不使用缓存时才清理)
76
75
  if not use_cache:
77
76
  try:
78
77
  os.remove(class_file_path)
79
- _debug(f"清理临时文件: {class_file_path}")
78
+ log.debug(f"清理临时文件: {class_file_path}")
80
79
  except Exception as e:
81
- _debug(f"清理临时文件失败: {e}")
80
+ log.debug(f"清理临时文件失败: {e}")
82
81
 
83
82
  return source_code
84
83
  except Exception as e:
85
- _debug(f"反编译类 {class_name} 失败: {e}")
84
+ log.debug(f"反编译类 {class_name} 失败: {e}")
86
85
  raise e
87
86
 
88
87
  def get_cache_path(self, class_name: str, project_path: str) -> str:
@@ -99,6 +98,7 @@ class DecompilerService:
99
98
  """
100
99
  从JAR包中提取指定的.class文件
101
100
  """
101
+ log = self._log()
102
102
  class_file_name = class_name.replace(".", "/") + ".class"
103
103
  temp_dir = os.path.join(os.getcwd(), ".mcp-class-temp")
104
104
  # 按包名全路径创建目录结构
@@ -110,7 +110,7 @@ class DecompilerService:
110
110
 
111
111
  os.makedirs(package_dir, exist_ok=True)
112
112
 
113
- _debug(f"从JAR包提取类文件: {jar_path} -> {class_file_name}")
113
+ log.debug(f"从JAR包提取类文件: {jar_path} -> {class_file_name}")
114
114
 
115
115
  with zipfile.ZipFile(jar_path, "r") as zf:
116
116
  try:
@@ -119,7 +119,7 @@ class DecompilerService:
119
119
  open(class_file_path, "wb") as target,
120
120
  ):
121
121
  shutil.copyfileobj(source, target)
122
- _debug(f"类文件提取成功: {class_file_path}")
122
+ log.debug(f"类文件提取成功: {class_file_path}")
123
123
  return class_file_path
124
124
  except KeyError:
125
125
  raise Exception(f"在JAR包 {jar_path} 中未找到类文件: {class_file_name}")
@@ -128,12 +128,13 @@ class DecompilerService:
128
128
  """
129
129
  使用CFR反编译.class文件
130
130
  """
131
+ log = self._log()
131
132
  if not self.cfr_path:
132
133
  raise Exception("未找到CFR反编译工具,请确保CFR jar包在classpath中")
133
134
 
134
135
  try:
135
136
  java_cmd = self.get_java_command()
136
- _debug(
137
+ log.debug(
137
138
  f'执行CFR反编译: {java_cmd} -jar "{self.cfr_path}" "{class_file_path}"'
138
139
  )
139
140
 
@@ -145,7 +146,7 @@ class DecompilerService:
145
146
  )
146
147
 
147
148
  if result.stderr and result.stderr.strip():
148
- _debug(f"CFR警告: {result.stderr}")
149
+ log.debug(f"CFR警告: {result.stderr}")
149
150
 
150
151
  if not result.stdout or result.stdout.strip() == "":
151
152
  raise Exception("CFR反编译返回空结果,可能是类文件损坏或CFR版本不兼容")
@@ -154,7 +155,7 @@ class DecompilerService:
154
155
  except subprocess.TimeoutExpired:
155
156
  raise Exception("CFR反编译超时,请检查Java环境和CFR工具")
156
157
  except Exception as e:
157
- _debug(f"CFR反编译执行失败: {e}")
158
+ log.debug(f"CFR反编译执行失败: {e}")
158
159
  raise Exception(f"CFR反编译失败: {str(e)}")
159
160
 
160
161
  def find_cfr_jar(self) -> str:
@@ -162,14 +163,15 @@ class DecompilerService:
162
163
  查找CFR jar包路径。
163
164
  优先级:① CFR_PATH 环境变量(用户配置)→ ② 包内置 jar(随 pip 安装分发)
164
165
  """
166
+ log = self._log()
165
167
  # ① 优先使用用户通过环境变量指定的路径
166
168
  cfr_path = os.environ.get("CFR_PATH", "").strip()
167
169
  if cfr_path:
168
170
  if os.path.isfile(cfr_path):
169
- _debug(f"使用 CFR_PATH 环境变量指定的路径: {cfr_path}")
171
+ log.debug(f"使用 CFR_PATH 环境变量指定的路径: {cfr_path}")
170
172
  return cfr_path
171
173
  else:
172
- _debug(f"CFR_PATH 指定的文件不存在: {cfr_path},继续查找内置 jar")
174
+ log.debug(f"CFR_PATH 指定的文件不存在: {cfr_path},继续查找内置 jar")
173
175
 
174
176
  # ② 使用包内置的 cfr jar(通过 importlib.resources 获取,兼容 pip 安装后的路径)
175
177
  try:
@@ -179,10 +181,10 @@ class DecompilerService:
179
181
  for fname in os.listdir(pkg_dir):
180
182
  if fname.lower().startswith("cfr") and fname.endswith(".jar"):
181
183
  found = os.path.join(pkg_dir, fname)
182
- _debug(f"找到内置 CFR jar: {found}")
184
+ log.debug(f"找到内置 CFR jar: {found}")
183
185
  return found
184
186
  except Exception as e:
185
- _debug(f"查找内置 CFR jar 失败: {e}")
187
+ log.debug(f"查找内置 CFR jar 失败: {e}")
186
188
 
187
189
  return ""
188
190
 
@@ -195,6 +197,7 @@ class DecompilerService:
195
197
  """
196
198
  批量反编译多个类
197
199
  """
200
+ log = self._log(project_path)
198
201
  results = {}
199
202
 
200
203
  for class_name in class_names:
@@ -202,7 +205,7 @@ class DecompilerService:
202
205
  source_code = self.decompile_class(class_name, project_path, use_cache)
203
206
  results[class_name] = source_code
204
207
  except Exception as e:
205
- _debug(f"反编译类 {class_name} 失败: {e}")
208
+ log.debug(f"反编译类 {class_name} 失败: {e}")
206
209
  results[class_name] = f"// 反编译失败: {e}"
207
210
 
208
211
  return results
@@ -0,0 +1,99 @@
1
+ """
2
+ 统一日志模块
3
+
4
+ 日志同时输出到:
5
+ 1. stderr(不污染 MCP stdout JSON-RPC)
6
+ 2. <projectPath>/.java-class-analyzer/logs/YYYY-MM-DD.log(按天滚动)
7
+
8
+ 用法:
9
+ from .logger import get_logger
10
+ logger = get_logger(project_path) # project_path 可为 None
11
+ logger.debug("...")
12
+ logger.info("...")
13
+ logger.warning("...")
14
+ logger.error("...")
15
+ """
16
+
17
+ import logging
18
+ import os
19
+ import sys
20
+ from logging.handlers import TimedRotatingFileHandler
21
+ from typing import Optional
22
+
23
+ _LOG_DIR_NAME = ".java-class-analyzer"
24
+ _LOG_SUBDIR = "logs"
25
+
26
+ # 全局 logger 缓存,key = project_path(或 "" 表示无项目路径)
27
+ _loggers: dict[str, logging.Logger] = {}
28
+
29
+
30
+ def _is_debug() -> bool:
31
+ return os.environ.get("LOG_LEVEL", "DEBUG").upper() == "DEBUG"
32
+
33
+
34
+ def _build_logger(name: str, log_file: Optional[str]) -> logging.Logger:
35
+ """构造一个 Logger 实例(stderr + 可选的文件处理器)"""
36
+ logger = logging.getLogger(name)
37
+ # 避免重复添加 handler
38
+ if logger.handlers:
39
+ return logger
40
+
41
+ logger.setLevel(logging.DEBUG if _is_debug() else logging.INFO)
42
+ logger.propagate = False
43
+
44
+ fmt = logging.Formatter(
45
+ fmt="%(asctime)s [%(levelname)s] %(message)s",
46
+ datefmt="%Y-%m-%d %H:%M:%S",
47
+ )
48
+
49
+ # Handler 1: stderr
50
+ stderr_handler = logging.StreamHandler(sys.stderr)
51
+ stderr_handler.setLevel(logging.DEBUG)
52
+ stderr_handler.setFormatter(fmt)
53
+ logger.addHandler(stderr_handler)
54
+
55
+ # Handler 2: 按天滚动的文件日志
56
+ if log_file:
57
+ try:
58
+ os.makedirs(os.path.dirname(log_file), exist_ok=True)
59
+ file_handler = TimedRotatingFileHandler(
60
+ filename=log_file,
61
+ when="midnight", # 每天零点切割
62
+ interval=1,
63
+ backupCount=30, # 保留最近 30 天
64
+ encoding="utf-8",
65
+ utc=False,
66
+ )
67
+ # 切割后的文件名后缀格式:YYYY-MM-DD
68
+ file_handler.suffix = "%Y-%m-%d"
69
+ file_handler.setLevel(logging.DEBUG)
70
+ file_handler.setFormatter(fmt)
71
+ logger.addHandler(file_handler)
72
+ except Exception as e:
73
+ # 文件日志初始化失败不影响程序运行
74
+ print(f"[logger] 初始化文件日志失败: {e}", file=sys.stderr)
75
+
76
+ return logger
77
+
78
+
79
+ def get_logger(project_path: Optional[str] = None) -> logging.Logger:
80
+ """
81
+ 获取(或创建)与 project_path 绑定的 Logger。
82
+
83
+ Args:
84
+ project_path: Maven 项目根目录路径。为 None 时只写 stderr,不写文件。
85
+ """
86
+ key = project_path or ""
87
+ if key in _loggers:
88
+ return _loggers[key]
89
+
90
+ log_file: Optional[str] = None
91
+ if project_path:
92
+ log_dir = os.path.join(project_path, _LOG_DIR_NAME, _LOG_SUBDIR)
93
+ # 文件名使用当天日期(TimedRotatingFileHandler 会自动按天切割)
94
+ log_file = os.path.join(log_dir, "java-class-analyzer.log")
95
+
96
+ logger_name = f"java_class_analyzer.{key}" if key else "java_class_analyzer"
97
+ logger = _build_logger(logger_name, log_file)
98
+ _loggers[key] = logger
99
+ return logger
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
3
  import os
4
- import sys
5
4
 
6
5
  from .analyzer.java_class_analyzer import JavaClassAnalyzer
7
6
  from .scanner.dependency_scanner import DependencyScanner
8
7
  from .decompiler.decompiler_service import DecompilerService
8
+ from .logger import get_logger
9
9
 
10
10
  from mcp.server.fastmcp import FastMCP
11
11
 
@@ -15,6 +15,9 @@ _scanner = DependencyScanner()
15
15
  _decompiler = DecompilerService()
16
16
  _analyzer = JavaClassAnalyzer()
17
17
 
18
+ # 全局 logger(不绑定项目路径,仅写 stderr;工具调用时会按项目路径再获取对应 logger)
19
+ _log = get_logger()
20
+
18
21
 
19
22
  def _ensure_index(project_path: str) -> None:
20
23
  """确保索引文件存在,不存在则自动创建"""
@@ -31,8 +34,15 @@ def scan_dependencies(projectPath: str, forceRefresh: bool = False) -> str:
31
34
  projectPath: Maven项目根目录路径
32
35
  forceRefresh: 是否强制刷新索引,默认false
33
36
  """
37
+ log = get_logger(projectPath)
38
+ log.info(
39
+ f"scan_dependencies 开始: projectPath={projectPath}, forceRefresh={forceRefresh}"
40
+ )
34
41
  result = _scanner.scan_project(projectPath, forceRefresh)
35
42
  sample = "\n".join(result.sample_entries[:5])
43
+ log.info(
44
+ f"scan_dependencies 完成: jarCount={result.jar_count}, classCount={result.class_count}"
45
+ )
36
46
  return (
37
47
  f"依赖扫描完成!\n\n"
38
48
  f"扫描的JAR包数量: {result.jar_count}\n"
@@ -55,10 +65,14 @@ def decompile_class(
55
65
  projectPath: Maven项目根目录路径
56
66
  useCache: 是否使用缓存,默认true
57
67
  """
68
+ log = get_logger(projectPath)
69
+ log.info(f"decompile_class 开始: className={className}, projectPath={projectPath}")
58
70
  _ensure_index(projectPath)
59
71
  source_code = _decompiler.decompile_class(className, projectPath, useCache)
60
72
  if not source_code or not source_code.strip():
73
+ log.warning(f"decompile_class 结果为空: className={className}")
61
74
  return f"警告: 类 {className} 的反编译结果为空,可能是CFR工具问题或类文件损坏"
75
+ log.info(f"decompile_class 完成: className={className}")
62
76
  return f"类 {className} 的反编译源码:\n\n```java\n{source_code}\n```"
63
77
 
64
78
 
@@ -70,6 +84,8 @@ def analyze_class(className: str, projectPath: str) -> str:
70
84
  className: 要分析的Java类全名
71
85
  projectPath: Maven项目根目录路径
72
86
  """
87
+ log = get_logger(projectPath)
88
+ log.info(f"analyze_class 开始: className={className}, projectPath={projectPath}")
73
89
  _ensure_index(projectPath)
74
90
  analysis = _analyzer.analyze_class(className, projectPath)
75
91
 
@@ -91,10 +107,12 @@ def analyze_class(className: str, projectPath: str) -> str:
91
107
  for method in analysis.methods:
92
108
  result += f" - {' '.join(method.modifiers)} {method.return_type} {method.name}({', '.join(method.parameters)})\n"
93
109
 
110
+ log.info(f"analyze_class 完成: className={className}")
94
111
  return result
95
112
 
96
113
 
97
114
  def main():
115
+ _log.info("java-class-analyzer MCP server 启动")
98
116
  mcp.run(transport="stdio")
99
117
 
100
118
 
@@ -1,25 +1,10 @@
1
1
  import os
2
- import sys
3
2
  import subprocess
4
3
  import json
5
4
  import zipfile
6
5
  from typing import List, Dict, Optional, Set
7
6
  from pathlib import Path
8
-
9
-
10
- def _is_debug() -> bool:
11
- return os.environ.get("LOG_LEVEL", "DEBUG").upper() == "DEBUG"
12
-
13
-
14
- def _log(msg: str) -> None:
15
- """仅在 DEBUG 模式下输出到 stderr(不污染 MCP stdout)"""
16
- if _is_debug():
17
- print(msg, file=sys.stderr)
18
-
19
-
20
- def _log_always(msg: str) -> None:
21
- """始终输出到 stderr(不污染 MCP stdout)"""
22
- print(msg, file=sys.stderr)
7
+ from ..logger import get_logger
23
8
 
24
9
 
25
10
  class ClassIndexEntry:
@@ -55,6 +40,13 @@ class DependencyScanner:
55
40
 
56
41
  def __init__(self):
57
42
  self.index_cache: Dict[str, List[ClassIndexEntry]] = {}
43
+ # logger 在首次使用项目路径时通过 _logger(project_path) 懒加载
44
+ self._project_path: Optional[str] = None
45
+
46
+ def _logger(self, project_path: Optional[str] = None):
47
+ """获取与当前项目路径绑定的 logger"""
48
+ path = project_path or self._project_path
49
+ return get_logger(path)
58
50
 
59
51
  def get_package_prefixes(self) -> List[str]:
60
52
  """
@@ -73,16 +65,18 @@ class DependencyScanner:
73
65
  """
74
66
  扫描Maven项目的所有依赖,建立类名到JAR包的映射索引
75
67
  """
68
+ self._project_path = project_path
69
+ log = self._logger(project_path)
76
70
  index_path = os.path.join(project_path, ".mcp-class-index.json")
77
71
 
78
72
  # 如果强制刷新,先删除旧的索引文件
79
73
  if force_refresh and os.path.exists(index_path):
80
- _log("强制刷新:删除旧的索引文件")
74
+ log.debug("强制刷新:删除旧的索引文件")
81
75
  os.remove(index_path)
82
76
 
83
77
  # 检查缓存
84
78
  if not force_refresh and os.path.exists(index_path):
85
- _log("使用缓存的类索引")
79
+ log.debug("使用缓存的类索引")
86
80
  with open(index_path, "r", encoding="utf-8") as f:
87
81
  cached_index = json.load(f)
88
82
  return ScanResult(
@@ -92,22 +86,22 @@ class DependencyScanner:
92
86
  cached_index["sampleEntries"],
93
87
  )
94
88
 
95
- _log("开始扫描Maven依赖...")
89
+ log.debug("开始扫描Maven依赖...")
96
90
 
97
91
  # 读取包名前缀过滤配置
98
92
  package_prefixes = self.get_package_prefixes()
99
93
  if package_prefixes:
100
- _log_always(
94
+ log.info(
101
95
  f"包名前缀过滤已启用,只索引以下前缀的类: {', '.join(package_prefixes)}"
102
96
  )
103
97
  else:
104
- _log(
98
+ log.debug(
105
99
  "包名前缀过滤未配置,将索引所有类(可通过 CLASS_PACKAGE_PREFIXES 环境变量限制)"
106
100
  )
107
101
 
108
102
  # 1. 获取Maven依赖树
109
103
  dependencies = self.get_maven_dependencies(project_path)
110
- _log(f"找到 {len(dependencies)} 个依赖JAR包")
104
+ log.debug(f"找到 {len(dependencies)} 个依赖JAR包")
111
105
 
112
106
  # 2. 解析每个JAR包,建立类索引
113
107
  class_index: List[ClassIndexEntry] = []
@@ -120,9 +114,9 @@ class DependencyScanner:
120
114
  processed_jars += 1
121
115
 
122
116
  if processed_jars % 10 == 0:
123
- _log(f"已处理 {processed_jars}/{len(dependencies)} 个JAR包")
117
+ log.debug(f"已处理 {processed_jars}/{len(dependencies)} 个JAR包")
124
118
  except Exception as e:
125
- _log(f"处理JAR包失败: {jar_path}, 错误: {e}")
119
+ log.debug(f"处理JAR包失败: {jar_path}, 错误: {e}")
126
120
 
127
121
  # 3. 保存索引到文件
128
122
  sample_entries = [
@@ -155,7 +149,7 @@ class DependencyScanner:
155
149
  with open(index_path, "w", encoding="utf-8") as f:
156
150
  json.dump(index_data, f, indent=2, ensure_ascii=False)
157
151
 
158
- _log(
152
+ log.debug(
159
153
  f"扫描完成!处理了 {processed_jars} 个JAR包,索引了 {len(class_index)} 个类"
160
154
  )
161
155
 
@@ -165,6 +159,7 @@ class DependencyScanner:
165
159
  """
166
160
  获取Maven依赖树中的所有JAR包路径
167
161
  """
162
+ log = self._logger(project_path)
168
163
  try:
169
164
  # 构建Maven命令路径
170
165
  maven_cmd = self.get_maven_command()
@@ -201,7 +196,7 @@ class DependencyScanner:
201
196
 
202
197
  return list(jar_paths)
203
198
  except Exception as e:
204
- _log(f"获取Maven依赖失败: {e}")
199
+ log.debug(f"获取Maven依赖失败: {e}")
205
200
  # 如果Maven命令失败,尝试从本地仓库扫描
206
201
  return self.scan_local_maven_repo(project_path)
207
202
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: java-class-analyzer-mcp
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: MCP server for scanning Maven dependencies and decompiling Java classes
5
5
  Author: java-class-analyzer-mcp contributors
6
6
  License-Expression: MIT
@@ -150,24 +150,6 @@ java-class-analyzer-mcp/
150
150
  - `className` (string): 要分析的类全名
151
151
  - `projectPath` (string): Maven 项目根目录路径
152
152
 
153
- ## 命令行工具
154
-
155
- 安装后可直接使用命令行工具:
156
-
157
- ```bash
158
- # 启动 MCP 服务器(stdio 模式,供 MCP 客户端调用)
159
- java-class-analyzer-mcp
160
-
161
- # 生成 MCP 配置模板
162
- java-class-analyzer-mcp config -o mcp-config.json
163
-
164
- # 测试工具
165
- java-class-analyzer-mcp test -t scan -p /path/to/project
166
- java-class-analyzer-mcp test -t decompile -p /path/to/project -c com.example.MyClass
167
- java-class-analyzer-mcp test -t analyze -p /path/to/project -c com.example.MyClass
168
- java-class-analyzer-mcp test -t all -p /path/to/project -c com.example.MyClass --no-cache
169
- ```
170
-
171
153
  ## 缓存文件
172
154
 
173
155
  在项目目录下会生成以下缓存:
@@ -3,6 +3,7 @@ pyproject.toml
3
3
  java_class_analyzer_mcp/__init__.py
4
4
  java_class_analyzer_mcp/cfr-0.152.jar
5
5
  java_class_analyzer_mcp/cli.py
6
+ java_class_analyzer_mcp/logger.py
6
7
  java_class_analyzer_mcp/main.py
7
8
  java_class_analyzer_mcp.egg-info/PKG-INFO
8
9
  java_class_analyzer_mcp.egg-info/SOURCES.txt
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "java-class-analyzer-mcp"
7
- version = "0.1.1"
7
+ version = "0.1.2"
8
8
  description = "MCP server for scanning Maven dependencies and decompiling Java classes"
9
9
  readme = "README.md"
10
10
  license = "MIT"