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 +38 -0
- package/bin/dicom-mcp.js +8 -2
- package/dicom_mcp/server.py +52 -19
- package/package.json +1 -1
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
|
-
|
|
72
|
-
|
|
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) {
|
package/dicom_mcp/server.py
CHANGED
|
@@ -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
|
|
16
|
+
# Resolve path to dicom_download - supports multiple deployment methods:
|
|
17
17
|
# 1. Local development: git clone后,dicom_download 在 dicom_mcp 的上级目录
|
|
18
|
-
# 2.
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
|
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,
|
|
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 分钟)
|
|
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("✅
|
|
296
|
-
print("=" * 70
|
|
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}
|
|
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,
|