local-ocr-mcp 0.2.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.
- local_ocr_mcp-0.2.0/.gitignore +76 -0
- local_ocr_mcp-0.2.0/PKG-INFO +328 -0
- local_ocr_mcp-0.2.0/README.md +301 -0
- local_ocr_mcp-0.2.0/pyproject.toml +62 -0
- local_ocr_mcp-0.2.0/src/local_ocr_mcp/__init__.py +11 -0
- local_ocr_mcp-0.2.0/src/local_ocr_mcp/__main__.py +46 -0
- local_ocr_mcp-0.2.0/src/local_ocr_mcp/config.py +15 -0
- local_ocr_mcp-0.2.0/src/local_ocr_mcp/engines/__init__.py +7 -0
- local_ocr_mcp-0.2.0/src/local_ocr_mcp/engines/paddle.py +74 -0
- local_ocr_mcp-0.2.0/src/local_ocr_mcp/engines/paddle_parser.py +122 -0
- local_ocr_mcp-0.2.0/src/local_ocr_mcp/errors.py +26 -0
- local_ocr_mcp-0.2.0/src/local_ocr_mcp/logger.py +30 -0
- local_ocr_mcp-0.2.0/src/local_ocr_mcp/models.py +138 -0
- local_ocr_mcp-0.2.0/src/local_ocr_mcp/server.py +48 -0
- local_ocr_mcp-0.2.0/src/local_ocr_mcp/services/__init__.py +9 -0
- local_ocr_mcp-0.2.0/src/local_ocr_mcp/services/health.py +35 -0
- local_ocr_mcp-0.2.0/src/local_ocr_mcp/services/recognition.py +137 -0
- local_ocr_mcp-0.2.0/src/local_ocr_mcp/validation.py +35 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
lib/
|
|
14
|
+
lib64/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
*.egg-info/
|
|
20
|
+
.installed.cfg
|
|
21
|
+
*.egg
|
|
22
|
+
|
|
23
|
+
# Virtual environments
|
|
24
|
+
venv/
|
|
25
|
+
.venv/
|
|
26
|
+
|
|
27
|
+
env/
|
|
28
|
+
ENV/
|
|
29
|
+
.venv
|
|
30
|
+
|
|
31
|
+
# IDE
|
|
32
|
+
.vscode/
|
|
33
|
+
.idea/
|
|
34
|
+
*.swp
|
|
35
|
+
*.swo
|
|
36
|
+
*~
|
|
37
|
+
|
|
38
|
+
# Environment variables
|
|
39
|
+
.env
|
|
40
|
+
.env.local
|
|
41
|
+
|
|
42
|
+
# Testing
|
|
43
|
+
.pytest_cache/
|
|
44
|
+
.coverage
|
|
45
|
+
htmlcov/
|
|
46
|
+
.tox/
|
|
47
|
+
|
|
48
|
+
# MCP config (user-specific)
|
|
49
|
+
mcp_config.json
|
|
50
|
+
|
|
51
|
+
# Models and cache
|
|
52
|
+
*.pth
|
|
53
|
+
*.pt
|
|
54
|
+
*.tgz
|
|
55
|
+
.cache/
|
|
56
|
+
models/
|
|
57
|
+
|
|
58
|
+
# OCR results and test outputs
|
|
59
|
+
**/ocr_results/
|
|
60
|
+
**/vision_analysis/
|
|
61
|
+
**/agent_summary/
|
|
62
|
+
**/*_ocr.json
|
|
63
|
+
**/*_ocr.txt
|
|
64
|
+
**/batch_summary.json
|
|
65
|
+
**/batch_summary.md
|
|
66
|
+
**/batch_report.json
|
|
67
|
+
temp_*.json
|
|
68
|
+
temp_*.txt
|
|
69
|
+
|
|
70
|
+
# OS
|
|
71
|
+
.DS_Store
|
|
72
|
+
Thumbs.db
|
|
73
|
+
|
|
74
|
+
# Logs
|
|
75
|
+
logs/
|
|
76
|
+
*.log
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: local-ocr-mcp
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Minimal stdio-first OCR MCP service powered by PaddleOCR
|
|
5
|
+
Author: Local OCR MCP Team
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: mcp,ocr,paddleocr,stdio
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Requires-Dist: fastmcp==3.1.0
|
|
17
|
+
Requires-Dist: pillow>=10.0.0
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: black>=23.0.0; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest>=7.4.0; extra == 'dev'
|
|
22
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
23
|
+
Provides-Extra: paddleocr
|
|
24
|
+
Requires-Dist: paddleocr>=3.0.0; extra == 'paddleocr'
|
|
25
|
+
Requires-Dist: paddlepaddle>=2.6.0; extra == 'paddleocr'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# Local OCR MCP
|
|
29
|
+
|
|
30
|
+
> 基于 `FastMCP` 与 `PaddleOCR` 的最小化本地 OCR MCP 服务,只保留 `stdio -> ocr_recognize -> PaddleOCR -> 统一响应 envelope` 这一条主路径,适合本地 IDE、Agent 和 MCP Client 直接接入。
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 1. 快速开始
|
|
35
|
+
|
|
36
|
+
**环境要求**
|
|
37
|
+
|
|
38
|
+
- `Python 3.10+`
|
|
39
|
+
- `uv`
|
|
40
|
+
|
|
41
|
+
**1. 直接用 `uvx` 运行**
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
uvx --from "local-ocr-mcp[paddleocr]" local-ocr-mcp
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
适合直接接入 MCP 客户端,不需要手动创建虚拟环境。
|
|
48
|
+
|
|
49
|
+
**2. 本地开发运行**
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
git clone <repository-url>
|
|
53
|
+
cd local-ocr-mcp
|
|
54
|
+
uv sync --extra dev --extra paddleocr
|
|
55
|
+
uv run python -m local_ocr_mcp
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**3. MCP 客户端配置示例**
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"mcpServers": {
|
|
63
|
+
"local-ocr-mcp": {
|
|
64
|
+
"command": "uvx",
|
|
65
|
+
"args": ["--from", "local-ocr-mcp[paddleocr]", "local-ocr-mcp"]
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
> 📖 仓库里也保留了一份同步后的配置样例 → [mcp_config.json](/home/q/Desktop/START/repos/AI/ML/local-ocr-mcp/mcp_config.json)
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 2. 核心特性
|
|
76
|
+
|
|
77
|
+
| 特性 | 说明 |
|
|
78
|
+
|------|------|
|
|
79
|
+
| **单路径主链路** | 只保留 `stdio -> ocr_recognize -> PaddleOCR`,没有传输分支和引擎路由 |
|
|
80
|
+
| **最小工具面** | 对外只暴露 `ocr_recognize` 与 `ocr_health_check` 两个工具 |
|
|
81
|
+
| **统一响应契约** | 所有工具统一返回 `status / data / error / meta` envelope |
|
|
82
|
+
| **固定输入模型** | 当前只接受本地图片路径 `image.path`,不支持 URL、base64 或上传流 |
|
|
83
|
+
| **稳定错误语义** | 用固定错误码暴露常见失败路径,便于客户端编排与恢复 |
|
|
84
|
+
| **低认知负担** | 不保留多引擎、analysis、progress、heartbeat、registry/factory 等扩展层 |
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 3. 技术栈
|
|
89
|
+
|
|
90
|
+
- **Python 3.10+** / **uv** - 运行时与依赖管理
|
|
91
|
+
- **FastMCP** - MCP 服务框架
|
|
92
|
+
- **PaddleOCR** - 固定 OCR 引擎
|
|
93
|
+
- **Pillow** - 图片合法性校验
|
|
94
|
+
- **pytest** - 最小契约测试
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## 4. 架构概览
|
|
99
|
+
|
|
100
|
+
```text
|
|
101
|
+
┌──────────────────────────────────────────────────────┐
|
|
102
|
+
│ MCP Client / IDE / Agent │
|
|
103
|
+
├──────────────────────────────────────────────────────┤
|
|
104
|
+
│ stdio transport │
|
|
105
|
+
├──────────────────────────────────────────────────────┤
|
|
106
|
+
│ FastMCP Tools: ocr_recognize / health_check │
|
|
107
|
+
├──────────────────────────────────────────────────────┤
|
|
108
|
+
│ RecognitionService HealthService │
|
|
109
|
+
├──────────────────────────────────────────────────────┤
|
|
110
|
+
│ PaddleOCREngine │
|
|
111
|
+
├──────────────────────────────────────────────────────┤
|
|
112
|
+
│ PaddleOCR / Filesystem / Image Validation │
|
|
113
|
+
└──────────────────────────────────────────────────────┘
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
当前实现刻意保持单向依赖和短链路:
|
|
117
|
+
|
|
118
|
+
- CLI 只负责启动 `stdio`
|
|
119
|
+
- Tool 层只负责暴露 MCP 接口
|
|
120
|
+
- Service 层负责校验输入并整形成统一 envelope
|
|
121
|
+
- Engine 层只负责调用 `PaddleOCR` 并归一化结果
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 5. 工具契约
|
|
126
|
+
|
|
127
|
+
| 工具 | 说明 |
|
|
128
|
+
|------|------|
|
|
129
|
+
| `ocr_recognize` | 识别本地图片并返回统一 OCR 结果 |
|
|
130
|
+
| `ocr_health_check` | 返回轻量健康状态、运行时版本和启动时长 |
|
|
131
|
+
|
|
132
|
+
### `ocr_recognize` 请求示例
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"image": {
|
|
137
|
+
"path": "./example.png"
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### `ocr_recognize` 输入约束
|
|
143
|
+
|
|
144
|
+
- `image` 必须是对象
|
|
145
|
+
- `image.path` 必须是非空字符串
|
|
146
|
+
- 当前只允许 `image.path`
|
|
147
|
+
- 相对路径会按服务进程当前工作目录解析为绝对路径
|
|
148
|
+
|
|
149
|
+
### `ocr_recognize` 成功响应示例
|
|
150
|
+
|
|
151
|
+
```json
|
|
152
|
+
{
|
|
153
|
+
"status": "ok",
|
|
154
|
+
"data": {
|
|
155
|
+
"text": "示例文本",
|
|
156
|
+
"boxes": [
|
|
157
|
+
{"x1": 0.0, "y1": 0.0, "x2": 120.0, "y2": 32.0}
|
|
158
|
+
],
|
|
159
|
+
"confidence": 0.98,
|
|
160
|
+
"engine": "paddleocr",
|
|
161
|
+
"processing_ms": 143
|
|
162
|
+
},
|
|
163
|
+
"error": null,
|
|
164
|
+
"meta": {
|
|
165
|
+
"timestamp": "2026-03-14T00:00:00+00:00",
|
|
166
|
+
"runtime_version": "0.2.0",
|
|
167
|
+
"request_id": "8c4c9d5c-6b4d-4f1f-b6df-4ff80e3ff6b6",
|
|
168
|
+
"resolved_engine": "paddleocr",
|
|
169
|
+
"resolved_image_path": "/abs/path/example.png"
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### `ocr_health_check` 响应示例
|
|
175
|
+
|
|
176
|
+
```json
|
|
177
|
+
{
|
|
178
|
+
"status": "ok",
|
|
179
|
+
"data": {
|
|
180
|
+
"status": "healthy",
|
|
181
|
+
"uptime_ms": 1234,
|
|
182
|
+
"transport": "stdio",
|
|
183
|
+
"runtime_version": "0.2.0"
|
|
184
|
+
},
|
|
185
|
+
"error": null,
|
|
186
|
+
"meta": {
|
|
187
|
+
"timestamp": "2026-03-14T00:00:00+00:00",
|
|
188
|
+
"runtime_version": "0.2.0"
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## 6. 错误语义与运行约束
|
|
196
|
+
|
|
197
|
+
### 统一错误结构
|
|
198
|
+
|
|
199
|
+
```json
|
|
200
|
+
{
|
|
201
|
+
"status": "error",
|
|
202
|
+
"data": null,
|
|
203
|
+
"error": {
|
|
204
|
+
"code": "file_not_found|invalid_image|engine_not_available|internal_error",
|
|
205
|
+
"message": "具体错误信息",
|
|
206
|
+
"retryable": false
|
|
207
|
+
},
|
|
208
|
+
"meta": {
|
|
209
|
+
"timestamp": "...",
|
|
210
|
+
"runtime_version": "0.2.0",
|
|
211
|
+
"request_id": "...",
|
|
212
|
+
"resolved_engine": "paddleocr"
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### 错误码说明
|
|
218
|
+
|
|
219
|
+
- `file_not_found`:`image.path` 指向的文件不存在
|
|
220
|
+
- `invalid_image`:请求结构不合法,或文件存在但不是有效图片
|
|
221
|
+
- `engine_not_available`:`PaddleOCR` 未安装或初始化失败
|
|
222
|
+
- `internal_error`:识别链路内部出现未归类异常
|
|
223
|
+
|
|
224
|
+
### 当前运行约束
|
|
225
|
+
|
|
226
|
+
- 只支持 `stdio`
|
|
227
|
+
- 只支持 `PaddleOCR`
|
|
228
|
+
- 不会自动切换引擎
|
|
229
|
+
- 不支持 `streamable-http`
|
|
230
|
+
- 不支持 analysis、progress、heartbeat
|
|
231
|
+
- 不支持 URL、base64、上传流等非文件输入
|
|
232
|
+
|
|
233
|
+
### PaddleOCR 兼容行为
|
|
234
|
+
|
|
235
|
+
内部固定使用 `PaddleOCR`,但对其 Python API 保留最小兼容:
|
|
236
|
+
|
|
237
|
+
- 优先走较新的 `predict()`
|
|
238
|
+
- 如果安装版本只支持旧接口,则回退到 `ocr()`
|
|
239
|
+
- 对外继续返回统一响应结构
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## 7. 配置与脚本
|
|
244
|
+
|
|
245
|
+
### 可选环境变量
|
|
246
|
+
|
|
247
|
+
| 变量 | 说明 | 默认值 |
|
|
248
|
+
|------|------|--------|
|
|
249
|
+
| `PADDLEOCR_LANG` | PaddleOCR 初始化语言 | `ch` |
|
|
250
|
+
| `LOG_LEVEL` | 日志级别 | `INFO` |
|
|
251
|
+
|
|
252
|
+
示例:
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
PADDLEOCR_LANG=ch LOG_LEVEL=INFO uv run python -m local_ocr_mcp
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### 辅助脚本
|
|
259
|
+
|
|
260
|
+
当前 `scripts/` 目录只保留和最小实现一致的两个脚本:
|
|
261
|
+
|
|
262
|
+
- `uv run python scripts/list_tools.py`
|
|
263
|
+
- `uv run python scripts/recognize_image.py path/to/image.png --json`
|
|
264
|
+
|
|
265
|
+
> 📖 详细说明 → [scripts/README.md](/home/q/Desktop/START/repos/AI/ML/local-ocr-mcp/scripts/README.md)
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## 8. 开发与验证
|
|
270
|
+
|
|
271
|
+
常用命令:
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
uv run python -m pytest -q
|
|
275
|
+
uv run python -m local_ocr_mcp --help
|
|
276
|
+
uv run python scripts/list_tools.py
|
|
277
|
+
uv run python scripts/recognize_image.py path/to/image.png --json
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
CI 当前只覆盖最小必需验证:
|
|
281
|
+
|
|
282
|
+
- 编译检查
|
|
283
|
+
- 单元测试
|
|
284
|
+
- CLI `--help`
|
|
285
|
+
- `stdio` 启动烟测
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## 9. 常见问题
|
|
290
|
+
|
|
291
|
+
### 1. 服务启动后为什么一直不退出?
|
|
292
|
+
|
|
293
|
+
因为它在等待 MCP 客户端通过 `stdio` 调用工具。这是正常行为。
|
|
294
|
+
|
|
295
|
+
### 2. 为什么返回 `engine_not_available`?
|
|
296
|
+
|
|
297
|
+
通常说明 `PaddleOCR` 或底层依赖没有正确安装。优先重新同步依赖:
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
uv sync --extra paddleocr
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
如果你是通过 `uvx` 启动,确认命令里包含:
|
|
304
|
+
|
|
305
|
+
```text
|
|
306
|
+
uvx --from local-ocr-mcp[paddleocr] local-ocr-mcp
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### 3. 为什么返回 `invalid_image`?
|
|
310
|
+
|
|
311
|
+
常见原因:
|
|
312
|
+
|
|
313
|
+
- 请求里没有 `image.path`
|
|
314
|
+
- `image.path` 不是字符串
|
|
315
|
+
- 文件存在,但不是合法图片
|
|
316
|
+
|
|
317
|
+
### 4. 相对路径为什么找不到文件?
|
|
318
|
+
|
|
319
|
+
相对路径是相对于服务进程当前工作目录解析的。最稳妥的做法是直接传绝对路径。
|
|
320
|
+
|
|
321
|
+
### 5. 为什么不保留多引擎和 HTTP?
|
|
322
|
+
|
|
323
|
+
因为当前版本明确选择“核心最小化”路线,只保留本地 OCR 主链路,不为未来扩展提前保留复杂结构。
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
**最后更新**: 2026-03-14
|
|
328
|
+
**License**: MIT
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# Local OCR MCP
|
|
2
|
+
|
|
3
|
+
> 基于 `FastMCP` 与 `PaddleOCR` 的最小化本地 OCR MCP 服务,只保留 `stdio -> ocr_recognize -> PaddleOCR -> 统一响应 envelope` 这一条主路径,适合本地 IDE、Agent 和 MCP Client 直接接入。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. 快速开始
|
|
8
|
+
|
|
9
|
+
**环境要求**
|
|
10
|
+
|
|
11
|
+
- `Python 3.10+`
|
|
12
|
+
- `uv`
|
|
13
|
+
|
|
14
|
+
**1. 直接用 `uvx` 运行**
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
uvx --from "local-ocr-mcp[paddleocr]" local-ocr-mcp
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
适合直接接入 MCP 客户端,不需要手动创建虚拟环境。
|
|
21
|
+
|
|
22
|
+
**2. 本地开发运行**
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
git clone <repository-url>
|
|
26
|
+
cd local-ocr-mcp
|
|
27
|
+
uv sync --extra dev --extra paddleocr
|
|
28
|
+
uv run python -m local_ocr_mcp
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**3. MCP 客户端配置示例**
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"mcpServers": {
|
|
36
|
+
"local-ocr-mcp": {
|
|
37
|
+
"command": "uvx",
|
|
38
|
+
"args": ["--from", "local-ocr-mcp[paddleocr]", "local-ocr-mcp"]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
> 📖 仓库里也保留了一份同步后的配置样例 → [mcp_config.json](/home/q/Desktop/START/repos/AI/ML/local-ocr-mcp/mcp_config.json)
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 2. 核心特性
|
|
49
|
+
|
|
50
|
+
| 特性 | 说明 |
|
|
51
|
+
|------|------|
|
|
52
|
+
| **单路径主链路** | 只保留 `stdio -> ocr_recognize -> PaddleOCR`,没有传输分支和引擎路由 |
|
|
53
|
+
| **最小工具面** | 对外只暴露 `ocr_recognize` 与 `ocr_health_check` 两个工具 |
|
|
54
|
+
| **统一响应契约** | 所有工具统一返回 `status / data / error / meta` envelope |
|
|
55
|
+
| **固定输入模型** | 当前只接受本地图片路径 `image.path`,不支持 URL、base64 或上传流 |
|
|
56
|
+
| **稳定错误语义** | 用固定错误码暴露常见失败路径,便于客户端编排与恢复 |
|
|
57
|
+
| **低认知负担** | 不保留多引擎、analysis、progress、heartbeat、registry/factory 等扩展层 |
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 3. 技术栈
|
|
62
|
+
|
|
63
|
+
- **Python 3.10+** / **uv** - 运行时与依赖管理
|
|
64
|
+
- **FastMCP** - MCP 服务框架
|
|
65
|
+
- **PaddleOCR** - 固定 OCR 引擎
|
|
66
|
+
- **Pillow** - 图片合法性校验
|
|
67
|
+
- **pytest** - 最小契约测试
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 4. 架构概览
|
|
72
|
+
|
|
73
|
+
```text
|
|
74
|
+
┌──────────────────────────────────────────────────────┐
|
|
75
|
+
│ MCP Client / IDE / Agent │
|
|
76
|
+
├──────────────────────────────────────────────────────┤
|
|
77
|
+
│ stdio transport │
|
|
78
|
+
├──────────────────────────────────────────────────────┤
|
|
79
|
+
│ FastMCP Tools: ocr_recognize / health_check │
|
|
80
|
+
├──────────────────────────────────────────────────────┤
|
|
81
|
+
│ RecognitionService HealthService │
|
|
82
|
+
├──────────────────────────────────────────────────────┤
|
|
83
|
+
│ PaddleOCREngine │
|
|
84
|
+
├──────────────────────────────────────────────────────┤
|
|
85
|
+
│ PaddleOCR / Filesystem / Image Validation │
|
|
86
|
+
└──────────────────────────────────────────────────────┘
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
当前实现刻意保持单向依赖和短链路:
|
|
90
|
+
|
|
91
|
+
- CLI 只负责启动 `stdio`
|
|
92
|
+
- Tool 层只负责暴露 MCP 接口
|
|
93
|
+
- Service 层负责校验输入并整形成统一 envelope
|
|
94
|
+
- Engine 层只负责调用 `PaddleOCR` 并归一化结果
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## 5. 工具契约
|
|
99
|
+
|
|
100
|
+
| 工具 | 说明 |
|
|
101
|
+
|------|------|
|
|
102
|
+
| `ocr_recognize` | 识别本地图片并返回统一 OCR 结果 |
|
|
103
|
+
| `ocr_health_check` | 返回轻量健康状态、运行时版本和启动时长 |
|
|
104
|
+
|
|
105
|
+
### `ocr_recognize` 请求示例
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"image": {
|
|
110
|
+
"path": "./example.png"
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### `ocr_recognize` 输入约束
|
|
116
|
+
|
|
117
|
+
- `image` 必须是对象
|
|
118
|
+
- `image.path` 必须是非空字符串
|
|
119
|
+
- 当前只允许 `image.path`
|
|
120
|
+
- 相对路径会按服务进程当前工作目录解析为绝对路径
|
|
121
|
+
|
|
122
|
+
### `ocr_recognize` 成功响应示例
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"status": "ok",
|
|
127
|
+
"data": {
|
|
128
|
+
"text": "示例文本",
|
|
129
|
+
"boxes": [
|
|
130
|
+
{"x1": 0.0, "y1": 0.0, "x2": 120.0, "y2": 32.0}
|
|
131
|
+
],
|
|
132
|
+
"confidence": 0.98,
|
|
133
|
+
"engine": "paddleocr",
|
|
134
|
+
"processing_ms": 143
|
|
135
|
+
},
|
|
136
|
+
"error": null,
|
|
137
|
+
"meta": {
|
|
138
|
+
"timestamp": "2026-03-14T00:00:00+00:00",
|
|
139
|
+
"runtime_version": "0.2.0",
|
|
140
|
+
"request_id": "8c4c9d5c-6b4d-4f1f-b6df-4ff80e3ff6b6",
|
|
141
|
+
"resolved_engine": "paddleocr",
|
|
142
|
+
"resolved_image_path": "/abs/path/example.png"
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### `ocr_health_check` 响应示例
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"status": "ok",
|
|
152
|
+
"data": {
|
|
153
|
+
"status": "healthy",
|
|
154
|
+
"uptime_ms": 1234,
|
|
155
|
+
"transport": "stdio",
|
|
156
|
+
"runtime_version": "0.2.0"
|
|
157
|
+
},
|
|
158
|
+
"error": null,
|
|
159
|
+
"meta": {
|
|
160
|
+
"timestamp": "2026-03-14T00:00:00+00:00",
|
|
161
|
+
"runtime_version": "0.2.0"
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## 6. 错误语义与运行约束
|
|
169
|
+
|
|
170
|
+
### 统一错误结构
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"status": "error",
|
|
175
|
+
"data": null,
|
|
176
|
+
"error": {
|
|
177
|
+
"code": "file_not_found|invalid_image|engine_not_available|internal_error",
|
|
178
|
+
"message": "具体错误信息",
|
|
179
|
+
"retryable": false
|
|
180
|
+
},
|
|
181
|
+
"meta": {
|
|
182
|
+
"timestamp": "...",
|
|
183
|
+
"runtime_version": "0.2.0",
|
|
184
|
+
"request_id": "...",
|
|
185
|
+
"resolved_engine": "paddleocr"
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### 错误码说明
|
|
191
|
+
|
|
192
|
+
- `file_not_found`:`image.path` 指向的文件不存在
|
|
193
|
+
- `invalid_image`:请求结构不合法,或文件存在但不是有效图片
|
|
194
|
+
- `engine_not_available`:`PaddleOCR` 未安装或初始化失败
|
|
195
|
+
- `internal_error`:识别链路内部出现未归类异常
|
|
196
|
+
|
|
197
|
+
### 当前运行约束
|
|
198
|
+
|
|
199
|
+
- 只支持 `stdio`
|
|
200
|
+
- 只支持 `PaddleOCR`
|
|
201
|
+
- 不会自动切换引擎
|
|
202
|
+
- 不支持 `streamable-http`
|
|
203
|
+
- 不支持 analysis、progress、heartbeat
|
|
204
|
+
- 不支持 URL、base64、上传流等非文件输入
|
|
205
|
+
|
|
206
|
+
### PaddleOCR 兼容行为
|
|
207
|
+
|
|
208
|
+
内部固定使用 `PaddleOCR`,但对其 Python API 保留最小兼容:
|
|
209
|
+
|
|
210
|
+
- 优先走较新的 `predict()`
|
|
211
|
+
- 如果安装版本只支持旧接口,则回退到 `ocr()`
|
|
212
|
+
- 对外继续返回统一响应结构
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## 7. 配置与脚本
|
|
217
|
+
|
|
218
|
+
### 可选环境变量
|
|
219
|
+
|
|
220
|
+
| 变量 | 说明 | 默认值 |
|
|
221
|
+
|------|------|--------|
|
|
222
|
+
| `PADDLEOCR_LANG` | PaddleOCR 初始化语言 | `ch` |
|
|
223
|
+
| `LOG_LEVEL` | 日志级别 | `INFO` |
|
|
224
|
+
|
|
225
|
+
示例:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
PADDLEOCR_LANG=ch LOG_LEVEL=INFO uv run python -m local_ocr_mcp
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### 辅助脚本
|
|
232
|
+
|
|
233
|
+
当前 `scripts/` 目录只保留和最小实现一致的两个脚本:
|
|
234
|
+
|
|
235
|
+
- `uv run python scripts/list_tools.py`
|
|
236
|
+
- `uv run python scripts/recognize_image.py path/to/image.png --json`
|
|
237
|
+
|
|
238
|
+
> 📖 详细说明 → [scripts/README.md](/home/q/Desktop/START/repos/AI/ML/local-ocr-mcp/scripts/README.md)
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## 8. 开发与验证
|
|
243
|
+
|
|
244
|
+
常用命令:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
uv run python -m pytest -q
|
|
248
|
+
uv run python -m local_ocr_mcp --help
|
|
249
|
+
uv run python scripts/list_tools.py
|
|
250
|
+
uv run python scripts/recognize_image.py path/to/image.png --json
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
CI 当前只覆盖最小必需验证:
|
|
254
|
+
|
|
255
|
+
- 编译检查
|
|
256
|
+
- 单元测试
|
|
257
|
+
- CLI `--help`
|
|
258
|
+
- `stdio` 启动烟测
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## 9. 常见问题
|
|
263
|
+
|
|
264
|
+
### 1. 服务启动后为什么一直不退出?
|
|
265
|
+
|
|
266
|
+
因为它在等待 MCP 客户端通过 `stdio` 调用工具。这是正常行为。
|
|
267
|
+
|
|
268
|
+
### 2. 为什么返回 `engine_not_available`?
|
|
269
|
+
|
|
270
|
+
通常说明 `PaddleOCR` 或底层依赖没有正确安装。优先重新同步依赖:
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
uv sync --extra paddleocr
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
如果你是通过 `uvx` 启动,确认命令里包含:
|
|
277
|
+
|
|
278
|
+
```text
|
|
279
|
+
uvx --from local-ocr-mcp[paddleocr] local-ocr-mcp
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### 3. 为什么返回 `invalid_image`?
|
|
283
|
+
|
|
284
|
+
常见原因:
|
|
285
|
+
|
|
286
|
+
- 请求里没有 `image.path`
|
|
287
|
+
- `image.path` 不是字符串
|
|
288
|
+
- 文件存在,但不是合法图片
|
|
289
|
+
|
|
290
|
+
### 4. 相对路径为什么找不到文件?
|
|
291
|
+
|
|
292
|
+
相对路径是相对于服务进程当前工作目录解析的。最稳妥的做法是直接传绝对路径。
|
|
293
|
+
|
|
294
|
+
### 5. 为什么不保留多引擎和 HTTP?
|
|
295
|
+
|
|
296
|
+
因为当前版本明确选择“核心最小化”路线,只保留本地 OCR 主链路,不为未来扩展提前保留复杂结构。
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
**最后更新**: 2026-03-14
|
|
301
|
+
**License**: MIT
|