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()

  

 

posted @ 2026-07-27 15:39  人间春风意  阅读(17)  评论(0)    收藏  举报