OpenCode 使用 自定义 MCP (ORC 示例)
前提:
- OpenCode
- 安装依赖:
pip install rapidocr-onnxruntime
在指定目录中,进行如下配置,启用orc MCP:
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"ocr": {
"type": "local",
"command": ["python", "mcp_ocr_server.py"],
"enabled": true
}
}
}
保存为文件:mcp_ocr_server.py
#!/usr/bin/env python3
"""
OCR MCP Server — 基于 MCP (Model Context Protocol) 的图片文字识别服务
=====================================================================
MCP 生命周期(stdio 传输):
1. initialize → 客户端发起握手,服务端返回能力声明与协议版本
2. notifications/initialized → 客户端发送通知,表示握手完成(无需响应)
3. tools/list / tools/call → 正常工作阶段:列出工具 & 调用工具
4. ping → 心跳检测,保持连接活跃
JSON-RPC 2.0 基础:
- 请求(Request) :带 "id" 字段 → 必须返回响应
- 通知(Notification):无 "id" 字段 → 不需要响应,返回 None 即可
- 每条消息占一行 JSON,写完必须 flush(),否则缓冲区未刷出会导致客户端挂起
依赖安装:pip install rapidocr-onnxruntime
"""
import json
import sys
from rapidocr_onnxruntime import RapidOCR
# 初始化 OCR 引擎(全局单例,避免重复加载模型)
ocr = RapidOCR()
def handle_request(req):
"""解析并处理单条 JSON-RPC 请求,返回响应(或 None 表示无需响应)"""
method = req.get("method", "")
req_id = req.get("id")
# ── 1. initialize:握手,声明协议版本与能力 ──
if method == "initialize":
return {
"jsonrpc": "2.0",
"id": req_id,
"result": {
"protocolVersion": "2024-11-05",
"capabilities": {"tools": {}},
"serverInfo": {"name": "ocr-mcp", "version": "1.0.0"},
},
}
# ── 2. notifications/initialized:JSON-RPC 通知(无 id),无需响应 ──
if method == "notifications/initialized":
return None
# ── 3. ping:心跳探活,返回空 result ──
if method == "ping":
return {"jsonrpc": "2.0", "id": req_id, "result": {}}
# ── 4. tools/list:注册工具,AI 通过此处了解工具的参数与用途 ──
if method == "tools/list":
return {
"jsonrpc": "2.0",
"id": req_id,
"result": {
"tools": [
{
"name": "ocr_image",
"description": "OCR recognize text from an image file. "
"Returns all detected text lines with confidence scores.",
"inputSchema": {
"type": "object",
"properties": {
"image_path": {
"type": "string",
"description": "Absolute path to the image file (PNG, JPG, etc.)",
},
"min_confidence": {
"type": "number",
"description": "Minimum confidence score (0-1), default 0.5",
"default": 0.5,
},
},
"required": ["image_path"],
},
}
]
},
}
# ── 5. tools/call:实际执行工具调用 ──
if method == "tools/call":
params = req.get("params", {})
tool_name = params.get("name", "")
args = params.get("arguments", {})
if tool_name == "ocr_image":
image_path = args.get("image_path", "")
# 转为 float 以防 AI 传入字符串类型
min_conf = float(args.get("min_confidence", 0.5))
try:
# elapse 为 (推理耗时, 总耗时) 元组
result, elapse = ocr(image_path)
if result is None:
return {
"jsonrpc": "2.0",
"id": req_id,
"result": {
"content": [{"type": "text", "text": "No text detected in image"}]
},
}
lines = []
for box, text, score in result:
# score 可能为字符串,统一转为 float 比较
conf = float(score)
if conf >= min_conf:
lines.append(f"[{conf:.2f}] {text}")
text = "\n".join(lines) if lines else "No text above confidence threshold"
text += (
f"\n\n---\n"
f"Total: {len(result)} lines, "
f"{len(lines)} above threshold ({elapse[0]:.2f}s)"
)
return {
"jsonrpc": "2.0",
"id": req_id,
"result": {"content": [{"type": "text", "text": text}]},
}
except Exception as e:
# 业务异常:返回 result + isError 标记,而非 JSON-RPC error
return {
"jsonrpc": "2.0",
"id": req_id,
"result": {
"content": [{"type": "text", "text": f"OCR Error: {str(e)}"}],
"isError": True,
},
}
# 协议级错误:工具名不存在
return {
"jsonrpc": "2.0",
"id": req_id,
"error": {"code": -32601, "message": f"Unknown tool: {tool_name}"},
}
# 协议级错误:方法名不存在
return {
"jsonrpc": "2.0",
"id": req_id,
"error": {"code": -32601, "message": f"Unknown method: {method}"},
}
def main():
"""stdio 主循环:逐行读取 stdin → 处理 → 写回 stdout"""
while True:
try:
line = sys.stdin.readline()
if not line: # EOF:客户端已关闭 stdin
break
req = json.loads(line.strip())
resp = handle_request(req)
# 通知类型返回 None,不输出任何内容
if resp is not None:
sys.stdout.write(json.dumps(resp) + "\n")
# 关键:立即刷新缓冲区,否则客户端会无限等待
sys.stdout.flush()
except json.JSONDecodeError:
# 忽略非法 JSON 行,避免进程崩溃
continue
except KeyboardInterrupt:
break
if __name__ == "__main__":
main()
作者:人间春风意
扫描左侧的二维码可以赞赏

本作品采用署名-非商业性使用-禁止演绎 4.0 国际 进行许可。

浙公网安备 33010602011771号