ship-detector-mcp 0.1.0__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.
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.4
2
+ Name: ship-detector-mcp
3
+ Version: 0.1.0
4
+ Summary: A MCP server for ship detection using YOLO
5
+ Author-email: xiacb <chenboxia111@163.com>
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: mcp[cli]>=1.9.2
8
+ Requires-Dist: numpy
9
+ Requires-Dist: opencv-python
10
+ Requires-Dist: pillow
11
+ Requires-Dist: requests
12
+ Requires-Dist: ultralytics
File without changes
@@ -0,0 +1,27 @@
1
+ [project]
2
+ name = "ship-detector-mcp"
3
+ version = "0.1.0"
4
+ description = "A MCP server for ship detection using YOLO"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ authors = [
8
+ {name = "xiacb", email = "chenboxia111@163.com"}
9
+ ]
10
+ dependencies = [
11
+ "mcp[cli]>=1.9.2",
12
+ "ultralytics",
13
+ "opencv-python",
14
+ "pillow",
15
+ "numpy",
16
+ "requests",
17
+ ]
18
+
19
+ [project.scripts]
20
+ ship-detector-mcp = "ship_detector_mcp:main"
21
+
22
+ [build-system]
23
+ requires = ["hatchling"]
24
+ build-backend = "hatchling.build"
25
+
26
+ [tool.hatch.build.targets.wheel]
27
+ packages = ["src/ship_detector_mcp"]
@@ -0,0 +1,11 @@
1
+ import argparse
2
+ from .server import mcp
3
+
4
+ def main():
5
+ """启动 MCP 服务"""
6
+ parser = argparse.ArgumentParser(description="Ship Detector MCP Server")
7
+ parser.parse_args()
8
+ mcp.run(transport="streamable-http")
9
+
10
+ if __name__ == "__main__":
11
+ main()
@@ -0,0 +1,2 @@
1
+ from ship_detector_mcp import main
2
+ main()
@@ -0,0 +1,152 @@
1
+ # server.py
2
+ from mcp.server.fastmcp import FastMCP
3
+ import os
4
+ from ultralytics import YOLO
5
+ import cv2
6
+ import numpy as np
7
+ import tempfile
8
+ import base64
9
+ from pathlib import Path
10
+ from PIL import Image
11
+ from io import BytesIO
12
+
13
+ # 初始化 MCP 服务器,配置主机和端口
14
+ mcp = FastMCP("ShipDetector", host="0.0.0.0", port=9000, stateless_http=True)
15
+
16
+ # 加载 YOLO 模型(请替换为你的模型路径)
17
+
18
+ import importlib.resources
19
+ MODEL_PATH = os.getenv("MODEL_PATH", str(Path(__file__).parent / "weights" / "best.pt"))
20
+ model = YOLO(MODEL_PATH)
21
+
22
+ def load_image_from_input(image_input: str):
23
+ """
24
+ 智能加载图片:自动判断输入是 Base64 还是文件路径
25
+
26
+ Args:
27
+ image_input: Base64 字符串 或 本地文件路径
28
+
29
+ Returns:
30
+ PIL Image 对象
31
+ """
32
+ # 判断是否是 Base64
33
+ is_base64 = False
34
+ # 检查是否是 PNG 的 Base64 头
35
+ if image_input.startswith('iVBORw0KGgo'):
36
+ is_base64 = True
37
+ # 检查是否是 JPG 的 Base64 头
38
+ elif image_input.startswith('/9j/4'):
39
+ is_base64 = True
40
+ # 检查是否是存在的文件路径
41
+ elif Path(image_input).exists():
42
+ pass # 不是 Base64,是文件路径
43
+ else:
44
+ # 尝试作为 Base64 解码前100个字符
45
+ try:
46
+ base64.b64decode(image_input[:100])
47
+ is_base64 = True
48
+ except:
49
+ raise ValueError(f"输入既不是有效的 Base64,也不是存在的文件路径: {image_input[:50]}...")
50
+
51
+ if is_base64:
52
+ print("检测到 Base64 输入,正在解码...")
53
+ image_data = base64.b64decode(image_input)
54
+ image = Image.open(BytesIO(image_data)).convert("RGB")
55
+ else:
56
+ print(f"检测到文件路径输入: {image_input}")
57
+ image = Image.open(image_input).convert("RGB")
58
+
59
+ return image
60
+
61
+ @mcp.tool()
62
+ def detect_ship(
63
+ image_data: bytes = None,
64
+ image_path: str = None,
65
+ image_url: str = None, # ← 新增
66
+ confidence: float = 0.5
67
+ ) -> str:
68
+ import requests
69
+ import tempfile
70
+ import os
71
+ import cv2
72
+ import numpy as np
73
+
74
+ temp_path = None
75
+
76
+ try:
77
+ # 情况1:有 image_data
78
+ if image_data is not None:
79
+ if isinstance(image_data, str):
80
+ import base64
81
+ image_data = base64.b64decode(image_data)
82
+
83
+ with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp:
84
+ tmp.write(image_data)
85
+ temp_path = tmp.name
86
+ image_to_use = temp_path
87
+ print(f"[INFO] 从 image_data 创建临时文件")
88
+
89
+ # 情况2:有 image_url(从 Dify 传来)
90
+ elif image_url is not None:
91
+ # 如果是相对路径,补全完整 URL
92
+ if image_url.startswith('/'):
93
+ image_url = f"http://localhost:5001{image_url}"
94
+
95
+ print(f"[INFO] 从 URL 下载图片: {image_url}")
96
+ response = requests.get(image_url, timeout=30)
97
+ response.raise_for_status()
98
+
99
+ with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp:
100
+ tmp.write(response.content)
101
+ temp_path = tmp.name
102
+ image_to_use = temp_path
103
+
104
+ # 情况3:有 image_path
105
+ elif image_path is not None:
106
+ if not os.path.exists(image_path):
107
+ return f"错误:图片文件不存在 - {image_path}"
108
+ image_to_use = image_path
109
+
110
+ else:
111
+ return "错误:请提供图片数据、URL或路径"
112
+
113
+ # 运行 YOLO 检测
114
+ results = model(image_to_use, conf=confidence)
115
+
116
+ # 处理结果
117
+ if not results or len(results) == 0:
118
+ return "未检测到船只"
119
+
120
+ detections = []
121
+ for box in results[0].boxes:
122
+ class_id = int(box.cls[0])
123
+ class_name = model.names[class_id]
124
+ conf = float(box.conf[0])
125
+ bbox = box.xyxy[0].tolist()
126
+ detections.append({
127
+ "class": class_name,
128
+ "confidence": round(conf, 4),
129
+ "bbox": [int(x) for x in bbox]
130
+ })
131
+
132
+ output = f"✅ 检测到 {len(detections)} 艘船:\n"
133
+ for i, ship in enumerate(detections, 1):
134
+ output += f"{i}. {ship['class']} - 置信度 {ship['confidence'] * 100:.2f}% - 位置 {ship['bbox']}\n"
135
+ return output
136
+
137
+ except Exception as e:
138
+ print(f"[ERROR] {e}")
139
+ return f"检测失败: {str(e)}"
140
+
141
+ finally:
142
+ if temp_path and os.path.exists(temp_path):
143
+ os.unlink(temp_path)
144
+ @mcp.tool()
145
+ def list_available_models() -> str:
146
+ """列出可用的检测模型"""
147
+ return f"当前模型: {MODEL_PATH}"
148
+
149
+
150
+ if __name__ == "__main__":
151
+ # 使用 streamable-http 传输协议运行
152
+ mcp.run(transport="streamable-http")