image-search-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,199 @@
1
+ Metadata-Version: 2.4
2
+ Name: image-search-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server for Reverse Image Search using PicImageSearch
5
+ Author-email: Your Name <your.email@example.com>
6
+ License: MIT
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: fastmcp
10
+ Requires-Dist: PicImageSearch
11
+ Requires-Dist: uvicorn
12
+
13
+ # Image Search MCP Server
14
+
15
+ 一个基于 [PicImageSearch](https://github.com/kitUIN/PicImageSearch) 的 MCP 服务器,支持多引擎以图搜图。
16
+
17
+ 支持通过 `uvx` (uv) 或 `pipx` 一键运行,无需手动克隆代码。
18
+
19
+ ## ✨ 特性
20
+
21
+ - **多引擎支持**:集成 11 种主流搜图引擎 (Yandex, SauceNAO, Google, TraceMoe, ASCII2D, EHentai, Iqdb, BaiDu, Bing, GoogleLens, Tineye)。
22
+ - **结果精简**:自动移除原始冗余数据,仅返回最关键的标题、链接、缩略图等信息。
23
+ - **智能提示**:当因机器人验证(Bot Protection)导致无结果时,自动提示配置 Cookie。
24
+ - **零配置部署**:通过 `uvx` 直接运行。
25
+ - **安全配置**:API Key 和代理设置通过环境变量管理。
26
+ - **灵活输入**:支持图片 URL 和 Base64 编码。
27
+
28
+ ## 🚀 快速开始
29
+
30
+ ### 方式 1: 直接运行 (Stdio 模式)
31
+
32
+ 适用于 Claude Desktop 或其他支持 MCP Stdio 的客户端。
33
+
34
+ **Command**:
35
+ ```bash
36
+ uvx image-search-mcp
37
+ ```
38
+
39
+ ### 方式 2: SSE 模式 (HTTP Server)
40
+
41
+ 适用于远程部署或 Web 客户端。
42
+
43
+ ```bash
44
+ uvx image-search-mcp --sse --port 8000
45
+ ```
46
+
47
+ #### 🔒 开启安全认证 (可选)
48
+
49
+ 如果在公网部署,建议设置环境变量来开启 Bearer Token 认证。
50
+
51
+ **全量环境变量配置示例:**
52
+
53
+ **Linux/macOS**:
54
+ ```bash
55
+ export MCP_AUTH_TOKEN="my-secret-token-123"
56
+ export IMAGE_SEARCH_API_KEY="your_saucenao_key"
57
+ export IMAGE_SEARCH_COOKIES="your_cookies_here"
58
+ export IMAGE_SEARCH_PROXY="http://127.0.0.1:7890"
59
+
60
+ uvx image-search-mcp --sse --host 0.0.0.0 --port 8000
61
+ ```
62
+
63
+ **Windows (PowerShell)**:
64
+ ```powershell
65
+ $env:MCP_AUTH_TOKEN="my-secret-token-123"
66
+ $env:IMAGE_SEARCH_API_KEY="your_saucenao_key"
67
+ $env:IMAGE_SEARCH_COOKIES="your_cookies_here"
68
+ $env:IMAGE_SEARCH_PROXY="http://127.0.0.1:7890"
69
+
70
+ uvx image-search-mcp --sse --host 0.0.0.0 --port 8000
71
+ ```
72
+
73
+ #### 客户端连接示例 (SSE)
74
+
75
+ **JSON 配置示例 (例如用于 Claude Desktop 或其他支持远程 MCP 的客户端):**
76
+
77
+ ```json
78
+ {
79
+ "mcpServers": {
80
+ "image-search-remote": {
81
+ "type": "sse",
82
+ "url": "http://your-server-ip:8000/sse",
83
+ "headers": {
84
+ "Authorization": "Bearer my-secret-token-123"
85
+ }
86
+ }
87
+ }
88
+ }
89
+ ```
90
+
91
+ ---
92
+
93
+ ## ⚙️ 环境配置说明
94
+
95
+ 以下是所有支持的环境变量:
96
+
97
+ | 环境变量 | 说明 | 示例 |
98
+ | :--- | :--- | :--- |
99
+ | `MCP_AUTH_TOKEN` | SSE 模式下的 Bearer Token 认证 | `my-secret-token-123` |
100
+ | `IMAGE_SEARCH_API_KEY` | SauceNAO API Key (用于 SauceNAO 引擎) | `your_api_key` |
101
+ | `IMAGE_SEARCH_COOKIES` | 通用 Cookies (用于 Google, Bing, Tineye, EHentai 等) | `igneous=...; ipb_member_id=...` |
102
+ | `IMAGE_SEARCH_PROXY` | HTTP 代理地址 (优先级最高) | `http://127.0.0.1:7890` |
103
+ | `HTTP_PROXY` / `HTTPS_PROXY` | 通用系统代理 (备选) | `http://127.0.0.1:7890` |
104
+
105
+ ### 关于 Cookies 的重要提示
106
+ Google, Bing, GoogleLens 和 Tineye 等引擎经常会有机器人验证(CAPTCHA)。如果搜索返回 "No results found" 或提示 Bot Protection,请尝试在浏览器中访问对应搜索引擎,登录并获取 Cookies,然后设置到 `IMAGE_SEARCH_COOKIES` 环境变量中。
107
+
108
+ ---
109
+
110
+ ## 💻 客户端部署指南 (本地 Stdio)
111
+
112
+ ### Claude Desktop 配置 (Stdio)
113
+
114
+ 编辑 `claude_desktop_config.json`,在 `env` 字段中配置本地运行所需的变量:
115
+
116
+ ```json
117
+ {
118
+ "mcpServers": {
119
+ "image-search-local": {
120
+ "command": "uvx",
121
+ "args": ["image-search-mcp"],
122
+ "env": {
123
+ "IMAGE_SEARCH_API_KEY": "your_saucenao_key",
124
+ "IMAGE_SEARCH_COOKIES": "your_cookies",
125
+ "IMAGE_SEARCH_PROXY": "http://127.0.0.1:7890"
126
+ }
127
+ }
128
+ }
129
+ }
130
+ ```
131
+
132
+ ---
133
+
134
+ ## 🛠 工具使用指南
135
+
136
+ ### `search_image`
137
+
138
+ **参数列表:**
139
+
140
+ | 参数 | 类型 | 必填 | 默认值 | 说明 |
141
+ | :--- | :--- | :--- | :--- | :--- |
142
+ | `source` | string | 是 | - | 图片 URL (`http://...`) 或 Base64 字符串。 |
143
+ | `engine` | string | 否 | **"Yandex"** | 搜索引擎名称。支持: Yandex, SauceNAO, Google, TraceMoe, Ascii2D, EHentai, Iqdb, BaiDu, Bing, GoogleLens, Tineye。 |
144
+ | `extra_params_json` | string | 否 | - | JSON 字符串,用于传递引擎特定的高级参数。 |
145
+ | `limit` | int | 否 | 5 | 返回结果的最大数量。 |
146
+
147
+ **调用示例:**
148
+
149
+ ```json
150
+ {
151
+ "engine": "Ascii2D",
152
+ "source": "https://example.com/image.jpg",
153
+ "extra_params_json": "{\"bovw\": true}",
154
+ "limit": 3
155
+ }
156
+ ```
157
+
158
+ ### `get_engine_info`
159
+
160
+ 获取支持的搜索引擎列表或特定引擎的详细参数信息。
161
+
162
+ **调用示例:**
163
+ ```json
164
+ {
165
+ "engine_name": "all"
166
+ }
167
+ ```
168
+
169
+ ```json
170
+ {
171
+ "engine_name": "SauceNAO"
172
+ }
173
+ ```
174
+
175
+ ---
176
+
177
+ ## 📦 开发与发布
178
+
179
+ ### 构建与上传
180
+
181
+ 1. **安装构建工具**:
182
+ ```bash
183
+ pip install build twine
184
+ ```
185
+
186
+ 2. **构建**:
187
+ ```bash
188
+ python -m build
189
+ ```
190
+
191
+ 3. **上传到 PyPI**:
192
+ ```bash
193
+ twine upload dist/*
194
+ ```
195
+
196
+ ## 环境要求
197
+
198
+ - Python >= 3.10
199
+ - *注意*:依赖库 `lxml` 建议使用 Python 3.12。
@@ -0,0 +1,187 @@
1
+ # Image Search MCP Server
2
+
3
+ 一个基于 [PicImageSearch](https://github.com/kitUIN/PicImageSearch) 的 MCP 服务器,支持多引擎以图搜图。
4
+
5
+ 支持通过 `uvx` (uv) 或 `pipx` 一键运行,无需手动克隆代码。
6
+
7
+ ## ✨ 特性
8
+
9
+ - **多引擎支持**:集成 11 种主流搜图引擎 (Yandex, SauceNAO, Google, TraceMoe, ASCII2D, EHentai, Iqdb, BaiDu, Bing, GoogleLens, Tineye)。
10
+ - **结果精简**:自动移除原始冗余数据,仅返回最关键的标题、链接、缩略图等信息。
11
+ - **智能提示**:当因机器人验证(Bot Protection)导致无结果时,自动提示配置 Cookie。
12
+ - **零配置部署**:通过 `uvx` 直接运行。
13
+ - **安全配置**:API Key 和代理设置通过环境变量管理。
14
+ - **灵活输入**:支持图片 URL 和 Base64 编码。
15
+
16
+ ## 🚀 快速开始
17
+
18
+ ### 方式 1: 直接运行 (Stdio 模式)
19
+
20
+ 适用于 Claude Desktop 或其他支持 MCP Stdio 的客户端。
21
+
22
+ **Command**:
23
+ ```bash
24
+ uvx image-search-mcp
25
+ ```
26
+
27
+ ### 方式 2: SSE 模式 (HTTP Server)
28
+
29
+ 适用于远程部署或 Web 客户端。
30
+
31
+ ```bash
32
+ uvx image-search-mcp --sse --port 8000
33
+ ```
34
+
35
+ #### 🔒 开启安全认证 (可选)
36
+
37
+ 如果在公网部署,建议设置环境变量来开启 Bearer Token 认证。
38
+
39
+ **全量环境变量配置示例:**
40
+
41
+ **Linux/macOS**:
42
+ ```bash
43
+ export MCP_AUTH_TOKEN="my-secret-token-123"
44
+ export IMAGE_SEARCH_API_KEY="your_saucenao_key"
45
+ export IMAGE_SEARCH_COOKIES="your_cookies_here"
46
+ export IMAGE_SEARCH_PROXY="http://127.0.0.1:7890"
47
+
48
+ uvx image-search-mcp --sse --host 0.0.0.0 --port 8000
49
+ ```
50
+
51
+ **Windows (PowerShell)**:
52
+ ```powershell
53
+ $env:MCP_AUTH_TOKEN="my-secret-token-123"
54
+ $env:IMAGE_SEARCH_API_KEY="your_saucenao_key"
55
+ $env:IMAGE_SEARCH_COOKIES="your_cookies_here"
56
+ $env:IMAGE_SEARCH_PROXY="http://127.0.0.1:7890"
57
+
58
+ uvx image-search-mcp --sse --host 0.0.0.0 --port 8000
59
+ ```
60
+
61
+ #### 客户端连接示例 (SSE)
62
+
63
+ **JSON 配置示例 (例如用于 Claude Desktop 或其他支持远程 MCP 的客户端):**
64
+
65
+ ```json
66
+ {
67
+ "mcpServers": {
68
+ "image-search-remote": {
69
+ "type": "sse",
70
+ "url": "http://your-server-ip:8000/sse",
71
+ "headers": {
72
+ "Authorization": "Bearer my-secret-token-123"
73
+ }
74
+ }
75
+ }
76
+ }
77
+ ```
78
+
79
+ ---
80
+
81
+ ## ⚙️ 环境配置说明
82
+
83
+ 以下是所有支持的环境变量:
84
+
85
+ | 环境变量 | 说明 | 示例 |
86
+ | :--- | :--- | :--- |
87
+ | `MCP_AUTH_TOKEN` | SSE 模式下的 Bearer Token 认证 | `my-secret-token-123` |
88
+ | `IMAGE_SEARCH_API_KEY` | SauceNAO API Key (用于 SauceNAO 引擎) | `your_api_key` |
89
+ | `IMAGE_SEARCH_COOKIES` | 通用 Cookies (用于 Google, Bing, Tineye, EHentai 等) | `igneous=...; ipb_member_id=...` |
90
+ | `IMAGE_SEARCH_PROXY` | HTTP 代理地址 (优先级最高) | `http://127.0.0.1:7890` |
91
+ | `HTTP_PROXY` / `HTTPS_PROXY` | 通用系统代理 (备选) | `http://127.0.0.1:7890` |
92
+
93
+ ### 关于 Cookies 的重要提示
94
+ Google, Bing, GoogleLens 和 Tineye 等引擎经常会有机器人验证(CAPTCHA)。如果搜索返回 "No results found" 或提示 Bot Protection,请尝试在浏览器中访问对应搜索引擎,登录并获取 Cookies,然后设置到 `IMAGE_SEARCH_COOKIES` 环境变量中。
95
+
96
+ ---
97
+
98
+ ## 💻 客户端部署指南 (本地 Stdio)
99
+
100
+ ### Claude Desktop 配置 (Stdio)
101
+
102
+ 编辑 `claude_desktop_config.json`,在 `env` 字段中配置本地运行所需的变量:
103
+
104
+ ```json
105
+ {
106
+ "mcpServers": {
107
+ "image-search-local": {
108
+ "command": "uvx",
109
+ "args": ["image-search-mcp"],
110
+ "env": {
111
+ "IMAGE_SEARCH_API_KEY": "your_saucenao_key",
112
+ "IMAGE_SEARCH_COOKIES": "your_cookies",
113
+ "IMAGE_SEARCH_PROXY": "http://127.0.0.1:7890"
114
+ }
115
+ }
116
+ }
117
+ }
118
+ ```
119
+
120
+ ---
121
+
122
+ ## 🛠 工具使用指南
123
+
124
+ ### `search_image`
125
+
126
+ **参数列表:**
127
+
128
+ | 参数 | 类型 | 必填 | 默认值 | 说明 |
129
+ | :--- | :--- | :--- | :--- | :--- |
130
+ | `source` | string | 是 | - | 图片 URL (`http://...`) 或 Base64 字符串。 |
131
+ | `engine` | string | 否 | **"Yandex"** | 搜索引擎名称。支持: Yandex, SauceNAO, Google, TraceMoe, Ascii2D, EHentai, Iqdb, BaiDu, Bing, GoogleLens, Tineye。 |
132
+ | `extra_params_json` | string | 否 | - | JSON 字符串,用于传递引擎特定的高级参数。 |
133
+ | `limit` | int | 否 | 5 | 返回结果的最大数量。 |
134
+
135
+ **调用示例:**
136
+
137
+ ```json
138
+ {
139
+ "engine": "Ascii2D",
140
+ "source": "https://example.com/image.jpg",
141
+ "extra_params_json": "{\"bovw\": true}",
142
+ "limit": 3
143
+ }
144
+ ```
145
+
146
+ ### `get_engine_info`
147
+
148
+ 获取支持的搜索引擎列表或特定引擎的详细参数信息。
149
+
150
+ **调用示例:**
151
+ ```json
152
+ {
153
+ "engine_name": "all"
154
+ }
155
+ ```
156
+
157
+ ```json
158
+ {
159
+ "engine_name": "SauceNAO"
160
+ }
161
+ ```
162
+
163
+ ---
164
+
165
+ ## 📦 开发与发布
166
+
167
+ ### 构建与上传
168
+
169
+ 1. **安装构建工具**:
170
+ ```bash
171
+ pip install build twine
172
+ ```
173
+
174
+ 2. **构建**:
175
+ ```bash
176
+ python -m build
177
+ ```
178
+
179
+ 3. **上传到 PyPI**:
180
+ ```bash
181
+ twine upload dist/*
182
+ ```
183
+
184
+ ## 环境要求
185
+
186
+ - Python >= 3.10
187
+ - *注意*:依赖库 `lxml` 建议使用 Python 3.12。
@@ -0,0 +1,25 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "image-search-mcp"
7
+ version = "0.1.0"
8
+ description = "MCP server for Reverse Image Search using PicImageSearch"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = {text = "MIT"}
12
+ authors = [
13
+ {name = "Your Name", email = "your.email@example.com"}
14
+ ]
15
+ dependencies = [
16
+ "fastmcp",
17
+ "PicImageSearch",
18
+ "uvicorn"
19
+ ]
20
+
21
+ [project.scripts]
22
+ image-search-mcp = "image_search_mcp.main:main"
23
+
24
+ [tool.setuptools.packages.find]
25
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ from .server import mcp
2
+
3
+ __all__ = ["mcp"]
@@ -0,0 +1,72 @@
1
+ import argparse
2
+ import sys
3
+ import os
4
+ from .server import mcp
5
+
6
+ class AuthMiddleware:
7
+ """Simple ASGI middleware for Bearer Token authentication."""
8
+ def __init__(self, app, token):
9
+ self.app = app
10
+ self.token = token
11
+
12
+ async def __call__(self, scope, receive, send):
13
+ if scope["type"] == "http":
14
+ # Allow health checks or OPTIONS if needed, but for strict security check everything
15
+ headers = dict(scope.get("headers", []))
16
+ auth_header = headers.get(b"authorization", b"").decode("utf-8")
17
+
18
+ # Check for "Bearer <token>"
19
+ expected = f"Bearer {self.token}"
20
+ if not auth_header or auth_header != expected:
21
+ await send({
22
+ 'type': 'http.response.start',
23
+ 'status': 401,
24
+ 'headers': [(b'content-type', b'text/plain')],
25
+ })
26
+ await send({
27
+ 'type': 'http.response.body',
28
+ 'body': b'Unauthorized: Invalid or missing Bearer token',
29
+ })
30
+ return
31
+
32
+ # Pass through to the original app
33
+ await self.app(scope, receive, send)
34
+
35
+ def main():
36
+ parser = argparse.ArgumentParser(description="Image Search MCP Server")
37
+ parser.add_argument("--sse", action="store_true", help="Run in SSE mode (HTTP)")
38
+ parser.add_argument("--port", type=int, default=8000, help="Port for SSE mode")
39
+ parser.add_argument("--host", type=str, default="127.0.0.1", help="Host for SSE mode")
40
+
41
+ args = parser.parse_args()
42
+
43
+ if args.sse:
44
+ try:
45
+ import uvicorn
46
+ # Attempt to find the ASGI app from FastMCP instance
47
+ app = mcp
48
+ if hasattr(mcp, "_sse_app"):
49
+ app = mcp._sse_app
50
+ elif hasattr(mcp, "app"):
51
+ app = mcp.app
52
+
53
+ # Check for Auth Token
54
+ auth_token = os.environ.get("MCP_AUTH_TOKEN")
55
+ if auth_token:
56
+ print(f"🔒 Authentication enabled. Require Bearer token.")
57
+ app = AuthMiddleware(app, auth_token)
58
+
59
+ print(f"Starting SSE server on {args.host}:{args.port}...")
60
+ uvicorn.run(app, host=args.host, port=args.port)
61
+ except ImportError:
62
+ print("Error: 'uvicorn' is required for SSE mode. Please install it: pip install uvicorn")
63
+ sys.exit(1)
64
+ except Exception as e:
65
+ print(f"Error starting SSE server: {e}")
66
+ sys.exit(1)
67
+ else:
68
+ # Stdio mode (Default)
69
+ mcp.run()
70
+
71
+ if __name__ == "__main__":
72
+ main()
@@ -0,0 +1,351 @@
1
+ from typing import Optional, Dict, Any, List
2
+ import json
3
+ import base64
4
+ import traceback
5
+ import sys
6
+ import os
7
+
8
+ try:
9
+ from fastmcp import FastMCP
10
+ except ImportError:
11
+ from mcp.server.fastmcp import FastMCP
12
+
13
+ from PicImageSearch import (
14
+ SauceNAO, Google, TraceMoe, Ascii2D, BaiDu, Bing,
15
+ EHentai, GoogleLens, Iqdb, Tineye, Yandex
16
+ )
17
+
18
+ # Initialize FastMCP server
19
+ mcp = FastMCP("image-search")
20
+
21
+ # Engine mapping
22
+ ENGINES = {
23
+ "SauceNAO": SauceNAO,
24
+ "Google": Google,
25
+ "TraceMoe": TraceMoe,
26
+ "Ascii2D": Ascii2D,
27
+ "BaiDu": BaiDu,
28
+ "Bing": Bing,
29
+ "EHentai": EHentai,
30
+ "GoogleLens": GoogleLens,
31
+ "Iqdb": Iqdb,
32
+ "Tineye": Tineye,
33
+ "Yandex": Yandex
34
+ }
35
+
36
+ # Engine Information (Briefs & Advanced Params)
37
+ ENGINE_INFO = {
38
+ "Yandex": {
39
+ "brief": "综合能力最强的通用搜图引擎,对裁剪、翻转和修改过的图片识别率极高。支持情况:[URL: 是, 文件: 是]。。",
40
+ "params": {
41
+ "rpt": "Search mode (default: 'imageview')",
42
+ "cbir_page": "Page type (default: 'sites')"
43
+ }
44
+ },
45
+ "SauceNAO": {
46
+ "brief": "专注二次元插画、漫画和动漫截图搜索。Pixiv 图片识别率极高。支持情况:[URL: 是, 文件: 是]。需要 API Key。",
47
+ "params": {
48
+ "numres": "Max results (1-40, default: 5)",
49
+ "hide": "Filter results (0:none, 1:explicit, 2:questionable, 3:safe, default: 0)",
50
+ "minsim": "Minimum similarity % (0-100, default: 30)",
51
+ "db": "Specific DB ID to search (default: 999 for all)",
52
+ "output_type": "Output format (default: 2)",
53
+ "testmode": "Test mode (default: 0)"
54
+ }
55
+ },
56
+ "Ascii2D": {
57
+ "brief": "专注二次元插画,特别适合查找 Twitter 和 Pixiv 上的原始画师。支持情况:[URL: 是, 文件: 是]。支持颜色搜索和特征搜索。",
58
+ "params": {
59
+ "bovw": "Boolean. If true, use 'Feature Search' (better for modified/cropped images). If false, use 'Color Search'. (Default: false)"
60
+ }
61
+ },
62
+ "TraceMoe": {
63
+ "brief": "动漫截图专用搜索引擎,可识别具体番剧名称、集数和时间点。支持情况:[URL: 是, 文件: 是]。。",
64
+ "params": {
65
+ "cutBorders": "Boolean. Cut black borders (default: true)"
66
+ }
67
+ },
68
+ "EHentai": {
69
+ "brief": "专门搜索 E-Hentai 和 ExHentai 的图库。支持情况:[URL: 是, 文件: 是]。搜索 ExHentai 需要配置 Cookies。",
70
+ "params": {
71
+ "is_ex": "Boolean. If true, search ExHentai (requires cookies). (Default: false)",
72
+ "covers": "Boolean. Search only covers. (Default: false)",
73
+ "similar": "Boolean. Enable similarity scanning. (Default: true)",
74
+ "exp": "Boolean. Include expunged galleries. (Default: false)"
75
+ }
76
+ },
77
+ "Google": {
78
+ "brief": "谷歌通用搜图。适合寻找类似图片或图片来源。支持情况:[URL: 是, 文件: 是]。。",
79
+ "params": {}
80
+ },
81
+ "GoogleLens": {
82
+ "brief": "谷歌智慧镜头。擅长识别物体、文字和商品。支持情况:[URL: 是, 文件: 是]。。",
83
+ "params": {}
84
+ },
85
+ "BaiDu": {
86
+ "brief": "百度识图,国内资源识别较好。支持情况:[URL: 是, 文件: 是]。。",
87
+ "params": {}
88
+ },
89
+ "Bing": {
90
+ "brief": "必应视觉搜索。支持情况:[URL: 是, 文件: 是]。。",
91
+ "params": {}
92
+ },
93
+ "Iqdb": {
94
+ "brief": "多站聚合搜索 (Danbooru, Konachan, etc.),适合二次元图片。支持情况:[URL: 是, 文件: 是]。。",
95
+ "params": {}
96
+ },
97
+ "Tineye": {
98
+ "brief": "老牌反向搜图引擎,擅长寻找精确匹配的图片来源。支持情况:[URL: 是, 文件: 是]。。",
99
+ "params": {}
100
+ }
101
+ }
102
+
103
+
104
+ def _parse_proxy(proxy: str) -> Dict[str, str]:
105
+ """Helper to format proxy string for httpx."""
106
+ if not proxy:
107
+ return {}
108
+ return {
109
+ "http://": proxy,
110
+ "https://": proxy
111
+ }
112
+
113
+ def _parse_cookies(cookie_str: str) -> Dict[str, str]:
114
+ """Helper to parse cookie string into dict."""
115
+ if not cookie_str:
116
+ return {}
117
+ cookies = {}
118
+ for item in cookie_str.split(";"):
119
+ if "=" in item:
120
+ k, v = item.strip().split("=", 1)
121
+ cookies[k] = v
122
+ return cookies
123
+
124
+ @mcp.tool()
125
+ def get_engine_info(engine_name: str = "all") -> str:
126
+ """
127
+ Get information about supported search engines.
128
+
129
+ Args:
130
+ engine_name: The name of the engine to get details for, or "all" for a summary list.
131
+ Default: "all".
132
+ """
133
+ if engine_name.lower() == "all":
134
+ # Return summary list
135
+ summary = ["Supported Search Engines:"]
136
+ for name, info in ENGINE_INFO.items():
137
+ summary.append(f"- {name}: {info['brief']}")
138
+ return "\n".join(summary)
139
+
140
+ # Return specific engine details
141
+ target_name = None
142
+ for name in ENGINE_INFO.keys():
143
+ if name.lower() == engine_name.lower():
144
+ target_name = name
145
+ break
146
+
147
+ if not target_name:
148
+ return f"Error: Engine '{engine_name}' not found. Supported: {', '.join(ENGINES.keys())}"
149
+
150
+ info = ENGINE_INFO[target_name]
151
+ details = [f"=== {target_name} ==="]
152
+ details.append(f"Brief: {info['brief']}")
153
+
154
+ if info['params']:
155
+ details.append("\nAdvanced Parameters (pass via 'extra_params_json'):")
156
+ for param, desc in info['params'].items():
157
+ details.append(f"- {param}: {desc}")
158
+ else:
159
+ details.append("\nNo specific advanced parameters.")
160
+
161
+ return "\n".join(details)
162
+
163
+
164
+ def _format_result_item(item: Any, engine: str) -> str:
165
+ """Helper to format a single result item based on engine type."""
166
+ lines = []
167
+
168
+ # Universal attributes (if available)
169
+ if hasattr(item, "title") and item.title:
170
+ lines.append(f"Title: {item.title}")
171
+ if hasattr(item, "url") and item.url:
172
+ lines.append(f"URL: {item.url}")
173
+ if hasattr(item, "thumbnail") and item.thumbnail:
174
+ lines.append(f"Thumbnail: {item.thumbnail}")
175
+
176
+ # Engine specific attributes
177
+ if engine == "Iqdb":
178
+ if hasattr(item, "similarity") and item.similarity is not None:
179
+ lines.append(f"Similarity: {item.similarity}%")
180
+
181
+ if engine == "SauceNAO":
182
+ if hasattr(item, "author") and item.author:
183
+ lines.append(f"Author: {item.author}")
184
+ if hasattr(item, "pixiv_id") and item.pixiv_id:
185
+ lines.append(f"Pixiv ID: {item.pixiv_id}")
186
+ if hasattr(item, "member_id") and item.member_id:
187
+ lines.append(f"Member ID: {item.member_id}")
188
+
189
+ elif engine == "TraceMoe":
190
+ if hasattr(item, "episode") and item.episode:
191
+ lines.append(f"Episode: {item.episode}")
192
+
193
+ # Time (TraceMoeItem uses .From and .To)
194
+ start_time = getattr(item, "From", None)
195
+ end_time = getattr(item, "To", None)
196
+ if start_time is not None:
197
+ lines.append(f"Time: {start_time}s - {end_time if end_time else '?'}s")
198
+
199
+ # Titles (TraceMoeItem has multiple title fields)
200
+ if hasattr(item, "title_english") and item.title_english:
201
+ lines.append(f"English Title: {item.title_english}")
202
+ if hasattr(item, "title_romaji") and item.title_romaji:
203
+ lines.append(f"Romaji Title: {item.title_romaji}")
204
+ if hasattr(item, "title_native") and item.title_native:
205
+ lines.append(f"Native Title: {item.title_native}")
206
+ if hasattr(item, "filename") and item.filename:
207
+ lines.append(f"Filename: {item.filename}")
208
+
209
+ elif engine == "Ascii2D":
210
+ if hasattr(item, "author") and item.author:
211
+ lines.append(f"Author: {item.author}")
212
+ if hasattr(item, "author_url") and item.author_url:
213
+ lines.append(f"Author URL: {item.author_url}")
214
+ if hasattr(item, "source") and item.source:
215
+ lines.append(f"Source Type: {item.source}")
216
+
217
+ elif engine == "EHentai":
218
+ if hasattr(item, "type") and item.type:
219
+ lines.append(f"Category: {item.type}")
220
+ if hasattr(item, "date") and item.date:
221
+ lines.append(f"Date: {item.date}")
222
+
223
+ # Fallback for generic lists of URLs (like ext_urls)
224
+ if hasattr(item, "ext_urls") and item.ext_urls:
225
+ lines.append(f"External URLs: {item.ext_urls}")
226
+
227
+ return "\n".join(lines)
228
+
229
+
230
+ async def _search_image_logic(
231
+ source: str,
232
+ engine: str = "Yandex",
233
+ extra_params_json: Optional[str] = None,
234
+ limit: int = 5
235
+ ) -> str:
236
+ """Core logic for image search, separated for testing."""
237
+ try:
238
+ # Load config from environment variables
239
+ api_key = os.environ.get("IMAGE_SEARCH_API_KEY")
240
+ cookies = os.environ.get("IMAGE_SEARCH_COOKIES")
241
+ proxy = os.environ.get("IMAGE_SEARCH_PROXY") or os.environ.get("HTTP_PROXY") or os.environ.get("HTTPS_PROXY")
242
+
243
+ # 1. Parse extra params
244
+ extra_params = {}
245
+ if extra_params_json:
246
+ try:
247
+ extra_params = json.loads(extra_params_json)
248
+ except json.JSONDecodeError:
249
+ return "Error: extra_params_json is not valid JSON."
250
+
251
+ engine_cls = ENGINES.get(engine)
252
+ if not engine_cls:
253
+ return f"Error: Unsupported engine '{engine}'. Use get_engine_info('all') to see available options."
254
+
255
+ # 2. Configure Engine Initialization Args
256
+ init_kwargs = {}
257
+ if proxy:
258
+ init_kwargs["proxies"] = _parse_proxy(proxy)
259
+
260
+ # Parse cookies if provided
261
+ cookie_dict = {}
262
+ if cookies:
263
+ cookie_dict = _parse_cookies(cookies)
264
+ init_kwargs["cookies"] = cookie_dict
265
+
266
+ # Engine-specific Init Params
267
+ if engine == "SauceNAO":
268
+ if api_key:
269
+ init_kwargs["api_key"] = api_key
270
+ for k in ["numres", "hide", "minsim", "db", "output_type", "testmode"]:
271
+ if k in extra_params:
272
+ init_kwargs[k] = extra_params.pop(k)
273
+
274
+ elif engine == "EHentai":
275
+ # EHentai specific params
276
+ for k in ["is_ex", "covers", "similar", "exp"]:
277
+ if k in extra_params:
278
+ init_kwargs[k] = extra_params.pop(k)
279
+
280
+ elif engine == "Ascii2D":
281
+ if "bovw" in extra_params:
282
+ init_kwargs["bovw"] = extra_params.pop("bovw")
283
+
284
+ # 3. Instantiate Engine
285
+ client = engine_cls(**init_kwargs)
286
+
287
+ # 4. Prepare Search Arguments
288
+ search_kwargs = {}
289
+
290
+ if source.strip().lower().startswith(("http://", "https://")):
291
+ search_kwargs["url"] = source.strip()
292
+ else:
293
+ b64_str = source
294
+ if "," in b64_str:
295
+ b64_str = b64_str.split(",")[1]
296
+ try:
297
+ image_bytes = base64.b64decode(b64_str)
298
+ search_kwargs["file"] = image_bytes
299
+ except Exception as e:
300
+ return f"Error decoding Base64 string: {str(e)}"
301
+
302
+ search_kwargs.update(extra_params)
303
+
304
+ # 5. Execute Search
305
+ resp = await client.search(**search_kwargs)
306
+
307
+ # 6. Format Result
308
+ result_str = f"Search Engine: {engine}\n"
309
+
310
+ if hasattr(resp, "raw") and resp.raw:
311
+ shown_count = min(len(resp.raw), limit)
312
+ result_str += f"Found {len(resp.raw)} results (showing top {shown_count}):\n"
313
+ for i, item in enumerate(resp.raw[:limit]):
314
+ result_str += f"\n--- Result {i+1} ---\n"
315
+ result_str += _format_result_item(item, engine)
316
+ result_str += "\n"
317
+ else:
318
+ result_str += f"No results found or raw data unavailable.\n"
319
+ if engine in ["Google", "Bing", "GoogleLens", "Tineye"]:
320
+ result_str += "Hint: These engines often require 'IMAGE_SEARCH_COOKIES' to be set in .env to bypass bot protection.\n"
321
+ result_str += f"Response: {resp}"
322
+
323
+ return result_str
324
+
325
+ except json.JSONDecodeError:
326
+ return f"Error: Failed to parse response from {engine}. This usually indicates bot protection (CAPTCHA) or an API change. Try setting 'IMAGE_SEARCH_COOKIES' in .env."
327
+ except Exception as e:
328
+ return f"An error occurred during search: {str(e)}\n{traceback.format_exc()}"
329
+
330
+ @mcp.tool()
331
+ async def search_image(
332
+ source: str,
333
+ engine: str = "Yandex",
334
+ extra_params_json: Optional[str] = None,
335
+ limit: int = 5
336
+ ) -> str:
337
+ """
338
+ Perform a reverse image search.
339
+
340
+ Args:
341
+ source: The image URL or Base64 encoded string.
342
+ engine: The search engine to use (default: "Yandex").
343
+ Other supported engines: SauceNAO, Google, TraceMoe, Ascii2D, EHentai, etc.
344
+ Use `get_engine_info` tool to see the full list and capabilities.
345
+ extra_params_json: (Optional) JSON string for advanced engine parameters.
346
+ Use `get_engine_info(engine_name='EngineName')` to see available parameters for a specific engine.
347
+ limit: Max number of results to return (default: 5).
348
+
349
+ Note: API Key, Cookies, and Proxy should be configured via environment variables.
350
+ """
351
+ return await _search_image_logic(source, engine, extra_params_json, limit)
@@ -0,0 +1,199 @@
1
+ Metadata-Version: 2.4
2
+ Name: image-search-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server for Reverse Image Search using PicImageSearch
5
+ Author-email: Your Name <your.email@example.com>
6
+ License: MIT
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: fastmcp
10
+ Requires-Dist: PicImageSearch
11
+ Requires-Dist: uvicorn
12
+
13
+ # Image Search MCP Server
14
+
15
+ 一个基于 [PicImageSearch](https://github.com/kitUIN/PicImageSearch) 的 MCP 服务器,支持多引擎以图搜图。
16
+
17
+ 支持通过 `uvx` (uv) 或 `pipx` 一键运行,无需手动克隆代码。
18
+
19
+ ## ✨ 特性
20
+
21
+ - **多引擎支持**:集成 11 种主流搜图引擎 (Yandex, SauceNAO, Google, TraceMoe, ASCII2D, EHentai, Iqdb, BaiDu, Bing, GoogleLens, Tineye)。
22
+ - **结果精简**:自动移除原始冗余数据,仅返回最关键的标题、链接、缩略图等信息。
23
+ - **智能提示**:当因机器人验证(Bot Protection)导致无结果时,自动提示配置 Cookie。
24
+ - **零配置部署**:通过 `uvx` 直接运行。
25
+ - **安全配置**:API Key 和代理设置通过环境变量管理。
26
+ - **灵活输入**:支持图片 URL 和 Base64 编码。
27
+
28
+ ## 🚀 快速开始
29
+
30
+ ### 方式 1: 直接运行 (Stdio 模式)
31
+
32
+ 适用于 Claude Desktop 或其他支持 MCP Stdio 的客户端。
33
+
34
+ **Command**:
35
+ ```bash
36
+ uvx image-search-mcp
37
+ ```
38
+
39
+ ### 方式 2: SSE 模式 (HTTP Server)
40
+
41
+ 适用于远程部署或 Web 客户端。
42
+
43
+ ```bash
44
+ uvx image-search-mcp --sse --port 8000
45
+ ```
46
+
47
+ #### 🔒 开启安全认证 (可选)
48
+
49
+ 如果在公网部署,建议设置环境变量来开启 Bearer Token 认证。
50
+
51
+ **全量环境变量配置示例:**
52
+
53
+ **Linux/macOS**:
54
+ ```bash
55
+ export MCP_AUTH_TOKEN="my-secret-token-123"
56
+ export IMAGE_SEARCH_API_KEY="your_saucenao_key"
57
+ export IMAGE_SEARCH_COOKIES="your_cookies_here"
58
+ export IMAGE_SEARCH_PROXY="http://127.0.0.1:7890"
59
+
60
+ uvx image-search-mcp --sse --host 0.0.0.0 --port 8000
61
+ ```
62
+
63
+ **Windows (PowerShell)**:
64
+ ```powershell
65
+ $env:MCP_AUTH_TOKEN="my-secret-token-123"
66
+ $env:IMAGE_SEARCH_API_KEY="your_saucenao_key"
67
+ $env:IMAGE_SEARCH_COOKIES="your_cookies_here"
68
+ $env:IMAGE_SEARCH_PROXY="http://127.0.0.1:7890"
69
+
70
+ uvx image-search-mcp --sse --host 0.0.0.0 --port 8000
71
+ ```
72
+
73
+ #### 客户端连接示例 (SSE)
74
+
75
+ **JSON 配置示例 (例如用于 Claude Desktop 或其他支持远程 MCP 的客户端):**
76
+
77
+ ```json
78
+ {
79
+ "mcpServers": {
80
+ "image-search-remote": {
81
+ "type": "sse",
82
+ "url": "http://your-server-ip:8000/sse",
83
+ "headers": {
84
+ "Authorization": "Bearer my-secret-token-123"
85
+ }
86
+ }
87
+ }
88
+ }
89
+ ```
90
+
91
+ ---
92
+
93
+ ## ⚙️ 环境配置说明
94
+
95
+ 以下是所有支持的环境变量:
96
+
97
+ | 环境变量 | 说明 | 示例 |
98
+ | :--- | :--- | :--- |
99
+ | `MCP_AUTH_TOKEN` | SSE 模式下的 Bearer Token 认证 | `my-secret-token-123` |
100
+ | `IMAGE_SEARCH_API_KEY` | SauceNAO API Key (用于 SauceNAO 引擎) | `your_api_key` |
101
+ | `IMAGE_SEARCH_COOKIES` | 通用 Cookies (用于 Google, Bing, Tineye, EHentai 等) | `igneous=...; ipb_member_id=...` |
102
+ | `IMAGE_SEARCH_PROXY` | HTTP 代理地址 (优先级最高) | `http://127.0.0.1:7890` |
103
+ | `HTTP_PROXY` / `HTTPS_PROXY` | 通用系统代理 (备选) | `http://127.0.0.1:7890` |
104
+
105
+ ### 关于 Cookies 的重要提示
106
+ Google, Bing, GoogleLens 和 Tineye 等引擎经常会有机器人验证(CAPTCHA)。如果搜索返回 "No results found" 或提示 Bot Protection,请尝试在浏览器中访问对应搜索引擎,登录并获取 Cookies,然后设置到 `IMAGE_SEARCH_COOKIES` 环境变量中。
107
+
108
+ ---
109
+
110
+ ## 💻 客户端部署指南 (本地 Stdio)
111
+
112
+ ### Claude Desktop 配置 (Stdio)
113
+
114
+ 编辑 `claude_desktop_config.json`,在 `env` 字段中配置本地运行所需的变量:
115
+
116
+ ```json
117
+ {
118
+ "mcpServers": {
119
+ "image-search-local": {
120
+ "command": "uvx",
121
+ "args": ["image-search-mcp"],
122
+ "env": {
123
+ "IMAGE_SEARCH_API_KEY": "your_saucenao_key",
124
+ "IMAGE_SEARCH_COOKIES": "your_cookies",
125
+ "IMAGE_SEARCH_PROXY": "http://127.0.0.1:7890"
126
+ }
127
+ }
128
+ }
129
+ }
130
+ ```
131
+
132
+ ---
133
+
134
+ ## 🛠 工具使用指南
135
+
136
+ ### `search_image`
137
+
138
+ **参数列表:**
139
+
140
+ | 参数 | 类型 | 必填 | 默认值 | 说明 |
141
+ | :--- | :--- | :--- | :--- | :--- |
142
+ | `source` | string | 是 | - | 图片 URL (`http://...`) 或 Base64 字符串。 |
143
+ | `engine` | string | 否 | **"Yandex"** | 搜索引擎名称。支持: Yandex, SauceNAO, Google, TraceMoe, Ascii2D, EHentai, Iqdb, BaiDu, Bing, GoogleLens, Tineye。 |
144
+ | `extra_params_json` | string | 否 | - | JSON 字符串,用于传递引擎特定的高级参数。 |
145
+ | `limit` | int | 否 | 5 | 返回结果的最大数量。 |
146
+
147
+ **调用示例:**
148
+
149
+ ```json
150
+ {
151
+ "engine": "Ascii2D",
152
+ "source": "https://example.com/image.jpg",
153
+ "extra_params_json": "{\"bovw\": true}",
154
+ "limit": 3
155
+ }
156
+ ```
157
+
158
+ ### `get_engine_info`
159
+
160
+ 获取支持的搜索引擎列表或特定引擎的详细参数信息。
161
+
162
+ **调用示例:**
163
+ ```json
164
+ {
165
+ "engine_name": "all"
166
+ }
167
+ ```
168
+
169
+ ```json
170
+ {
171
+ "engine_name": "SauceNAO"
172
+ }
173
+ ```
174
+
175
+ ---
176
+
177
+ ## 📦 开发与发布
178
+
179
+ ### 构建与上传
180
+
181
+ 1. **安装构建工具**:
182
+ ```bash
183
+ pip install build twine
184
+ ```
185
+
186
+ 2. **构建**:
187
+ ```bash
188
+ python -m build
189
+ ```
190
+
191
+ 3. **上传到 PyPI**:
192
+ ```bash
193
+ twine upload dist/*
194
+ ```
195
+
196
+ ## 环境要求
197
+
198
+ - Python >= 3.10
199
+ - *注意*:依赖库 `lxml` 建议使用 Python 3.12。
@@ -0,0 +1,11 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/image_search_mcp/__init__.py
4
+ src/image_search_mcp/main.py
5
+ src/image_search_mcp/server.py
6
+ src/image_search_mcp.egg-info/PKG-INFO
7
+ src/image_search_mcp.egg-info/SOURCES.txt
8
+ src/image_search_mcp.egg-info/dependency_links.txt
9
+ src/image_search_mcp.egg-info/entry_points.txt
10
+ src/image_search_mcp.egg-info/requires.txt
11
+ src/image_search_mcp.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ image-search-mcp = image_search_mcp.main:main
@@ -0,0 +1,3 @@
1
+ fastmcp
2
+ PicImageSearch
3
+ uvicorn
@@ -0,0 +1 @@
1
+ image_search_mcp