dicom-mcp 1.1.7 → 1.2.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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,44 @@
5
5
  格式遵循 [Keep a Changelog](https://keepachangelog.com/),
6
6
  版本号遵循 [Semantic Versioning](https://semver.org/)。
7
7
 
8
+ ## [1.2.1] - 2025-01-13
9
+
10
+ ### 改进
11
+
12
+ - **增强的路径解析**: 改进 dicom_download 查找逻辑
13
+ - 支持本地开发、NPM 包和 PyPI 安装等多种部署方式
14
+ - 添加诊断日志,显示查找的路径和找到的位置
15
+ - 优化查找顺序,确保 multi_download.py 存在
16
+
17
+ ## [1.2.0] - 2025-01-13
18
+
19
+ ### 新功能
20
+
21
+ - **实时下载进度反馈**: 在下载过程中向用户显示详细的进度信息
22
+ - 下载开始:显示下载数量、输出目录、扫描参数
23
+ - 下载进行中:显示逐个 URL 的处理进度 [1/N], [2/N]...
24
+ - 下载完成:显示每个 URL 保存的文件数
25
+ - 最终汇总:显示总共下载的文件数量
26
+ - 失败处理:显示详细的错误信息
27
+
28
+ ### 改进
29
+
30
+ - **可选的 pip 进度显示**: 添加环保变量控制 pip 安装输出
31
+ - 默认:抑制 pip 输出(用于 Claude Desktop,避免 JSON 污染)
32
+ - 调试模式:设置 `DICOM_MCP_VERBOSE=1` 显示 pip 进度
33
+ - 用法:`DICOM_MCP_VERBOSE=1 npx dicom-mcp@latest`
34
+
35
+ ## [1.1.8] - 2025-01-13
36
+
37
+ ### 修复
38
+
39
+ - **pip 输出完全抑制**: 彻底解决 pip 安装大量日志污染 JSON 消息流
40
+ - 原因:pip 的 stdout 包含 Looking in、Installing、Requirements 等大量进度信息
41
+ - 解决:
42
+ - 添加 `-q`(quiet)参数到 pip 命令
43
+ - 改用 `stdio: 'pipe'` 完全抑制 stdout 输出
44
+ - 影响:MCP Inspector 现在可以正常运行,无 JSON 解析错误
45
+
8
46
  ## [1.1.7] - 2025-01-13
9
47
 
10
48
  ### 修复
package/bin/dicom-mcp.js CHANGED
@@ -68,8 +68,14 @@ function installLocalPackage(pythonCmd) {
68
68
  process.exit(1);
69
69
  }
70
70
 
71
- execSync(`${pythonCmd} -m pip install -e "${rootDir}"`, {
72
- stdio: ['inherit', 'pipe', 'inherit'], // stdin: inherit, stdout: pipe (suppress), stderr: inherit
71
+ // Check if verbose mode is enabled (for debugging)
72
+ const verbose = process.env.DICOM_MCP_VERBOSE === '1' || process.env.DICOM_MCP_VERBOSE === 'true';
73
+ const quietFlag = verbose ? '' : '-q';
74
+ const stdioOption = verbose ? 'inherit' : 'pipe';
75
+
76
+ // Install with optional verbosity
77
+ execSync(`${pythonCmd} -m pip install ${quietFlag} -e "${rootDir}"`, {
78
+ stdio: stdioOption,
73
79
  });
74
80
  console.error('✓ Local dicom_mcp package installed');
75
81
  } catch (error) {
@@ -13,31 +13,49 @@ from dataclasses import dataclass
13
13
  from mcp.server.fastmcp import FastMCP
14
14
  from pydantic import BaseModel, Field
15
15
 
16
- # Resolve path to dicom_download - supports two deployment methods:
16
+ # Resolve path to dicom_download - supports multiple deployment methods:
17
17
  # 1. Local development: git clone后,dicom_download 在 dicom_mcp 的上级目录
18
- # 2. Installed package: 从 PyPI/npm 安装时,dicom_download 作为依赖已安装
18
+ # 2. NPM package: npx安装时,dicom_download 在node_modules同级
19
+ # 3. Installed package: 从 PyPI 安装时,dicom_download 作为依赖已安装
19
20
  def _resolve_dicom_download_path() -> Path:
20
21
  """Resolve path to dicom_download module."""
21
- # Method 1: Local development (git clone)
22
- local_dev_path = Path(__file__).parent.parent.parent / "dicom_download"
23
- if local_dev_path.exists():
22
+ current_dir = Path(__file__).parent
23
+
24
+ # Method 1: Local development - check parent directory
25
+ local_dev_path = current_dir.parent.parent / "dicom_download"
26
+ if local_dev_path.exists() and (local_dev_path / "multi_download.py").exists():
27
+ print(f"[dicom-mcp] Found dicom_download at: {local_dev_path}", file=sys.stderr)
24
28
  return local_dev_path
25
29
 
26
- # Method 2: Check site-packages or installation location
30
+ # Method 2: NPM package - check in the same package directory
31
+ npm_pkg_path = current_dir.parent / "dicom_download"
32
+ if npm_pkg_path.exists() and (npm_pkg_path / "multi_download.py").exists():
33
+ print(f"[dicom-mcp] Found dicom_download at: {npm_pkg_path}", file=sys.stderr)
34
+ return npm_pkg_path
35
+
36
+ # Method 3: Check site-packages or installation location
27
37
  try:
28
38
  import dicom_download as dd_module
29
39
  if dd_module.__file__:
30
- return Path(dd_module.__file__).parent
40
+ dd_path = Path(dd_module.__file__).parent
41
+ if (dd_path / "multi_download.py").exists():
42
+ print(f"[dicom-mcp] Found dicom_download at: {dd_path}", file=sys.stderr)
43
+ return dd_path
31
44
  except ImportError:
32
45
  pass
33
46
 
34
- # Method 3: Try to find in Python path
47
+ # Method 4: Try to find in Python path
35
48
  for path_item in sys.path:
36
49
  candidate = Path(path_item) / "dicom_download"
37
- if candidate.exists():
50
+ if candidate.exists() and (candidate / "multi_download.py").exists():
51
+ print(f"[dicom-mcp] Found dicom_download at: {candidate}", file=sys.stderr)
38
52
  return candidate
39
53
 
40
- # Fallback to local dev path even if not exists
54
+ # Fallback - return the most likely path with diagnostic message
55
+ print(f"[dicom-mcp] WARNING: Could not find dicom_download. Tried paths:", file=sys.stderr)
56
+ print(f" 1. {local_dev_path}", file=sys.stderr)
57
+ print(f" 2. {npm_pkg_path}", file=sys.stderr)
58
+ print(f" 3. Python path entries", file=sys.stderr)
41
59
  return local_dev_path
42
60
 
43
61
  DICOM_DOWNLOAD_PATH = _resolve_dicom_download_path()
@@ -266,14 +284,15 @@ async def run_multi_download(
266
284
  cmd.extend(["--max-rounds", str(max_rounds)])
267
285
  cmd.extend(["--step-wait-ms", str(step_wait_ms)])
268
286
 
269
- # Show progress banner (to stderr, not stdout)
287
+ # Show progress banner (to stderr, visible to Claude)
270
288
  print("\n" + "=" * 70, file=sys.stderr)
271
289
  print("🚀 DICOM 下载开始", file=sys.stderr)
272
290
  print("=" * 70, file=sys.stderr)
273
291
  print(f"📍 下载数量: {len(urls)} 个URL", file=sys.stderr)
274
292
  print(f"📁 输出目录: {output_parent}", file=sys.stderr)
275
293
  print(f"⚙️ 扫描次数: {max_rounds}, 帧间延迟: {step_wait_ms}ms", file=sys.stderr)
276
- print("⏳ 请稍候,下载中... (可能需要 2-10 分钟)\n", file=sys.stderr)
294
+ print("⏳ 请稍候,下载中... (可能需要 2-10 分钟)", file=sys.stderr)
295
+ print("", file=sys.stderr)
277
296
 
278
297
  # Run subprocess with real-time output streaming
279
298
  process = await asyncio.create_subprocess_exec(
@@ -292,15 +311,19 @@ async def run_multi_download(
292
311
 
293
312
  if returncode == 0:
294
313
  print("\n" + "=" * 70, file=sys.stderr)
295
- print("✅ 下载完成!处理结果中...", file=sys.stderr)
296
- print("=" * 70 + "\n", file=sys.stderr)
314
+ print("✅ 下载完成!", file=sys.stderr)
315
+ print("=" * 70, file=sys.stderr)
316
+ print("📊 处理结果中...", file=sys.stderr)
317
+ print("", file=sys.stderr)
297
318
 
298
319
  # Parse output directories from stdout
299
320
  results = []
300
- for url in urls:
321
+ for idx, url in enumerate(urls, 1):
301
322
  # Extract share_id and construct output dir
302
323
  from common_utils import extract_share_id
303
324
 
325
+ print(f"[{idx}/{len(urls)}] 处理: {url}", file=sys.stderr)
326
+
304
327
  share_id = extract_share_id(url)
305
328
  out_dir = os.path.join(output_parent, share_id)
306
329
  file_count = count_files_recursive(out_dir)
@@ -310,6 +333,8 @@ async def run_multi_download(
310
333
  else None
311
334
  )
312
335
 
336
+ print(f" ✓ 已保存 {file_count} 个文件到: {out_dir}", file=sys.stderr)
337
+
313
338
  results.append(
314
339
  DownloadResult(
315
340
  success=True,
@@ -320,13 +345,21 @@ async def run_multi_download(
320
345
  file_count=file_count,
321
346
  )
322
347
  )
348
+
349
+ # Final summary
350
+ total_files = sum(r.file_count or 0 for r in results)
351
+ print("=" * 70, file=sys.stderr)
352
+ print(f"📈 汇总: 共下载 {total_files} 个文件", file=sys.stderr)
353
+ print("=" * 70, file=sys.stderr)
354
+ print("", file=sys.stderr)
323
355
  return results
324
356
  else:
325
357
  error_msg = stderr if stderr else "Unknown error"
326
- print("\n" + "=" * 70)
327
- print("❌ 下载失败")
328
- print("=" * 70)
329
- print(f"错误信息: {error_msg}\n")
358
+ print("\n" + "=" * 70, file=sys.stderr)
359
+ print("❌ 下载失败", file=sys.stderr)
360
+ print("=" * 70, file=sys.stderr)
361
+ print(f"错误信息: {error_msg}", file=sys.stderr)
362
+ print("", file=sys.stderr)
330
363
  return [
331
364
  DownloadResult(
332
365
  success=False,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dicom-mcp",
3
- "version": "1.1.7",
3
+ "version": "1.2.1",
4
4
  "description": "MCP server for DICOM image downloading from multiple medical imaging sources",
5
5
  "main": "lib/index.js",
6
6
  "type": "module",