dicom-mcp 1.2.0 → 1.2.5
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 +48 -0
- package/dicom_mcp/server.py +106 -12
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,54 @@
|
|
|
5
5
|
格式遵循 [Keep a Changelog](https://keepachangelog.com/),
|
|
6
6
|
版本号遵循 [Semantic Versioning](https://semver.org/)。
|
|
7
7
|
|
|
8
|
+
## [1.2.5] - 2025-01-13
|
|
9
|
+
|
|
10
|
+
### 改进
|
|
11
|
+
|
|
12
|
+
- **支持多格式密码提取**: 增强密码识别的灵活性
|
|
13
|
+
- 支持:`安全码:8492`、`密码:8492`、`password:8492`、`code:8492`、`验证码:8492`
|
|
14
|
+
- 同时支持半角冒号 `:` 和全角冒号 `:`
|
|
15
|
+
- 自动清理 URL 中的密码部分
|
|
16
|
+
- 添加诊断日志显示提取的密码
|
|
17
|
+
|
|
18
|
+
## [1.2.4] - 2025-01-13
|
|
19
|
+
|
|
20
|
+
### 改进
|
|
21
|
+
|
|
22
|
+
- **标准化安全码提取**: 简化并规范化密码提取逻辑
|
|
23
|
+
- 只支持标准格式:`URL 安全码:8492` 或 `URL 安全码:8492`
|
|
24
|
+
- 自动清理 URL 中的安全码部分
|
|
25
|
+
- 添加诊断日志显示提取的密码
|
|
26
|
+
- 代码更清晰,性能更好
|
|
27
|
+
|
|
28
|
+
## [1.2.3] - 2025-01-13
|
|
29
|
+
|
|
30
|
+
### 新功能
|
|
31
|
+
|
|
32
|
+
- **自动密码提取**: 从 URL 中自动识别和提取安全码/密码
|
|
33
|
+
- 支持多种格式:`安全码:8492`、`密码:8492`、`password:8492`、`code:8492`
|
|
34
|
+
- 用户只需粘贴原始链接,无需手动提取密码
|
|
35
|
+
- 自动清理 URL,将密码传递给下载脚本
|
|
36
|
+
- 例如:`https://ylyyx.shdc.org.cn/code.html?... 安全码:8492`
|
|
37
|
+
|
|
38
|
+
## [1.2.2] - 2025-01-13
|
|
39
|
+
|
|
40
|
+
### 新功能
|
|
41
|
+
|
|
42
|
+
- **安全码/密码支持**: 支持需要安全码(password)的医院链接
|
|
43
|
+
- 添加 `password` 参数到 `download_dicom` 和 `batch_download_dicom` 工具
|
|
44
|
+
- 自动将密码传递给底层下载脚本
|
|
45
|
+
- 用法:提供 URL 和密码参数即可自动填入
|
|
46
|
+
|
|
47
|
+
## [1.2.1] - 2025-01-13
|
|
48
|
+
|
|
49
|
+
### 改进
|
|
50
|
+
|
|
51
|
+
- **增强的路径解析**: 改进 dicom_download 查找逻辑
|
|
52
|
+
- 支持本地开发、NPM 包和 PyPI 安装等多种部署方式
|
|
53
|
+
- 添加诊断日志,显示查找的路径和找到的位置
|
|
54
|
+
- 优化查找顺序,确保 multi_download.py 存在
|
|
55
|
+
|
|
8
56
|
## [1.2.0] - 2025-01-13
|
|
9
57
|
|
|
10
58
|
### 新功能
|
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()
|
|
@@ -110,6 +128,9 @@ class BatchDownloadRequest(BaseModel):
|
|
|
110
128
|
)
|
|
111
129
|
mode: str = Field(default="all", description="Download mode")
|
|
112
130
|
headless: bool = Field(default=True, description="Run in headless mode")
|
|
131
|
+
password: Optional[str] = Field(
|
|
132
|
+
default=None, description="Password/share code if required by the site"
|
|
133
|
+
)
|
|
113
134
|
create_zip: bool = Field(default=True, description="Create ZIP archives")
|
|
114
135
|
max_rounds: int = Field(
|
|
115
136
|
default=_DEFAULT_MAX_ROUNDS,
|
|
@@ -214,6 +235,7 @@ async def run_multi_download(
|
|
|
214
235
|
provider: str = "auto",
|
|
215
236
|
mode: str = "all",
|
|
216
237
|
headless: bool = True,
|
|
238
|
+
password: Optional[str] = None,
|
|
217
239
|
create_zip: bool = True,
|
|
218
240
|
max_rounds: int = 3,
|
|
219
241
|
step_wait_ms: int = 40,
|
|
@@ -259,6 +281,9 @@ async def run_multi_download(
|
|
|
259
281
|
else:
|
|
260
282
|
cmd.append("--no-headless")
|
|
261
283
|
|
|
284
|
+
if password:
|
|
285
|
+
cmd.extend(["--password", password])
|
|
286
|
+
|
|
262
287
|
if not create_zip:
|
|
263
288
|
cmd.append("--no-zip")
|
|
264
289
|
|
|
@@ -358,6 +383,50 @@ async def run_multi_download(
|
|
|
358
383
|
pass
|
|
359
384
|
|
|
360
385
|
|
|
386
|
+
# ============================================================================
|
|
387
|
+
# Helper Functions for Password Extraction
|
|
388
|
+
# ============================================================================
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def _extract_password_from_url(url: str) -> tuple[str, Optional[str]]:
|
|
392
|
+
"""
|
|
393
|
+
Extract password/security code from URL string.
|
|
394
|
+
|
|
395
|
+
Supports multiple formats:
|
|
396
|
+
- URL 安全码:8492 or URL 安全码:8492
|
|
397
|
+
- URL 密码:8492 or URL 密码:8492
|
|
398
|
+
- URL password:8492 or URL password:8492
|
|
399
|
+
- URL code:8492 or URL code:8492
|
|
400
|
+
- URL 验证码:8492 or URL 验证码:8492
|
|
401
|
+
|
|
402
|
+
Returns: (clean_url, password)
|
|
403
|
+
"""
|
|
404
|
+
import re
|
|
405
|
+
|
|
406
|
+
# Pattern: look for various password indicators with both half-width and full-width colons
|
|
407
|
+
patterns = [
|
|
408
|
+
r'\s*安全码[::]\s*(\d+)', # 安全码:8492 or 安全码:8492
|
|
409
|
+
r'\s*密码[::]\s*(\d+)', # 密码:8492 or 密码:8492
|
|
410
|
+
r'\s*验证码[::]\s*(\d+)', # 验证码:8492 or 验证码:8492
|
|
411
|
+
r'\s*password[::]\s*(\S+)', # password:8492 or password:8492
|
|
412
|
+
r'\s*code[::]\s*(\d+)', # code:8492 or code:8492
|
|
413
|
+
]
|
|
414
|
+
|
|
415
|
+
password = None
|
|
416
|
+
clean_url = url
|
|
417
|
+
|
|
418
|
+
for pattern in patterns:
|
|
419
|
+
match = re.search(pattern, url)
|
|
420
|
+
if match:
|
|
421
|
+
password = match.group(1)
|
|
422
|
+
# Remove password from URL
|
|
423
|
+
clean_url = re.sub(pattern, '', url).strip()
|
|
424
|
+
print(f"[dicom-mcp] 提取密码: {password}", file=sys.stderr)
|
|
425
|
+
break
|
|
426
|
+
|
|
427
|
+
return clean_url, password
|
|
428
|
+
|
|
429
|
+
|
|
361
430
|
# ============================================================================
|
|
362
431
|
# MCP Tools
|
|
363
432
|
# ============================================================================
|
|
@@ -373,14 +442,22 @@ async def download_dicom(request: DownloadRequest) -> DownloadResult:
|
|
|
373
442
|
- fz: 复肿 (ylyyx.shdc.org.cn)
|
|
374
443
|
- nyfy: 宁夏总医院 (zhyl.nyfy.com.cn)
|
|
375
444
|
- cloud: *.medicalimagecloud.com and other cloud-based systems
|
|
445
|
+
|
|
446
|
+
Automatically extracts password/security code from URL if present.
|
|
447
|
+
Formats: "URL 安全码:8492", "URL password:8492", etc.
|
|
376
448
|
"""
|
|
449
|
+
# Auto-extract password from URL if not explicitly provided
|
|
450
|
+
clean_url, extracted_password = _extract_password_from_url(request.url)
|
|
451
|
+
password = request.password or extracted_password
|
|
452
|
+
|
|
377
453
|
os.makedirs(request.output_dir, exist_ok=True)
|
|
378
454
|
results = await run_multi_download(
|
|
379
|
-
[
|
|
455
|
+
[clean_url],
|
|
380
456
|
request.output_dir,
|
|
381
457
|
provider=request.provider or "auto",
|
|
382
458
|
mode=request.mode,
|
|
383
459
|
headless=request.headless,
|
|
460
|
+
password=password,
|
|
384
461
|
create_zip=request.create_zip,
|
|
385
462
|
max_rounds=request.max_rounds,
|
|
386
463
|
step_wait_ms=request.step_wait_ms,
|
|
@@ -400,14 +477,31 @@ async def batch_download_dicom(request: BatchDownloadRequest) -> list[DownloadRe
|
|
|
400
477
|
|
|
401
478
|
Each URL gets its own subdirectory. Supports auto-detection of provider
|
|
402
479
|
based on domain, or manual provider specification.
|
|
480
|
+
|
|
481
|
+
Automatically extracts password/security code from URL if present.
|
|
482
|
+
Formats: "URL 安全码:8492", "URL password:8492", etc.
|
|
403
483
|
"""
|
|
484
|
+
# Auto-extract passwords from URLs
|
|
485
|
+
clean_urls = []
|
|
486
|
+
extracted_password = None
|
|
487
|
+
|
|
488
|
+
for url in request.urls:
|
|
489
|
+
clean_url, pwd = _extract_password_from_url(url)
|
|
490
|
+
clean_urls.append(clean_url)
|
|
491
|
+
# Use first extracted password if no explicit password provided
|
|
492
|
+
if not extracted_password and pwd:
|
|
493
|
+
extracted_password = pwd
|
|
494
|
+
|
|
495
|
+
password = request.password or extracted_password
|
|
496
|
+
|
|
404
497
|
os.makedirs(request.output_parent, exist_ok=True)
|
|
405
498
|
return await run_multi_download(
|
|
406
|
-
|
|
499
|
+
clean_urls,
|
|
407
500
|
request.output_parent,
|
|
408
501
|
provider=request.provider,
|
|
409
502
|
mode=request.mode,
|
|
410
503
|
headless=request.headless,
|
|
504
|
+
password=password,
|
|
411
505
|
create_zip=request.create_zip,
|
|
412
506
|
max_rounds=request.max_rounds,
|
|
413
507
|
step_wait_ms=request.step_wait_ms,
|