mcp 可以做成 免费 不付费
#!/usr/bin/env python3
"""
OCR MCP Server — 基于 macOS Vision 框架的图片文字识别
纯 Python stdlib 实现,零外部依赖。注册到 ~/.claude/settings.json 后永久生效。
注册方式(在 settings.json 的 mcpServers 中添加):
"ocr": {
"command": "python3",
"args": ["buildchain/scripts/ocr_mcp_server.py"]
}
"""
import json
import sys
import subprocess
import os
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
SWIFT_SCRIPT = os.path.join(SCRIPT_DIR, "ocr_image.swift")
TOOLS = [
{
"name": "ocr_image",
"description": "对图片文件进行 OCR 文字识别,使用 macOS Vision 框架。支持中文、英文等多语言。"
"返回图片中的文字内容(纯文本),可过滤低置信度结果。"
"适用于:App Store 截图文字提取、钉钉文档截图信息提取、UI 截图文字识别等。",
"inputSchema": {
"type": "object",
"properties": {
"image_path": {
"type": "string",
"description": "图片文件的绝对路径,支持 PNG/JPG/HEIC 等格式"
},
"min_confidence": {
"type": "integer",
"description": "最低置信度阈值 (0-100),默认 30。建议中文截图设为 30,精确提取设为 70",
"default": 30
},
"lang": {
"type": "string",
"description": "识别语言代码,如 zh-Hans、en-US。默认 zh-Hans+en-US 自动检测",
"default": "zh-Hans"
}
},
"required": ["image_path"]
}
},
{
"name": "ocr_image_json",
"description": "对图片文件进行 OCR 文字识别,返回结构化 JSON(含每个文字块的置信度、坐标框)。"
"适用于:需要定位文字在图中位置的场景(如截图中的表格数据提取)。",
"inputSchema": {
"type": "object",
"properties": {
"image_path": {
"type": "string",
"description": "图片文件的绝对路径"
},
"min_confidence": {
"type": "integer",
"description": "最低置信度阈值 (0-100),默认 30",
"default": 30
}
},
"required": ["image_path"]
}
}
]
def handle_initialize(req_id):
return {
"protocolVersion": "2024-11-05",
"capabilities": {"tools": {}},
"serverInfo": {
"name": "ocr-mcp-server",
"version": "1.0.0"
}
}
def handle_tools_list(req_id):
return {"tools": TOOLS}
def handle_tools_call(req_id, params):
name = params.get("name", "")
args = params.get("arguments", {})
if name == "ocr_image":
image_path = args.get("image_path", "")
min_conf = args.get("min_confidence", 30)
lang = args.get("lang", "zh-Hans")
result = subprocess.run(
["swift", SWIFT_SCRIPT, image_path, "--min", str(min_conf), "--lang", lang],
capture_output=True, text=True, timeout=60
)
text = result.stdout.strip() or result.stderr.strip() or "(未识别到文字)"
return {"content": [{"type": "text", "text": text}]}
elif name == "ocr_image_json":
image_path = args.get("image_path", "")
min_conf = args.get("min_confidence", 30)
result = subprocess.run(
["swift", SWIFT_SCRIPT, image_path, "--json", "--min", str(min_conf)],
capture_output=True, text=True, timeout=60
)
text = result.stdout.strip() or result.stderr.strip() or "[]"
return {"content": [{"type": "text", "text": text}]}
return {"content": [{"type": "text", "text": f"未知工具: {name}"}], "isError": True}
def main():
# 确保 Swift 脚本存在
if not os.path.exists(SWIFT_SCRIPT):
print(f"ERROR: Swift OCR 脚本不存在: {SWIFT_SCRIPT}", file=sys.stderr)
sys.exit(1)
initialized = False
for line in sys.stdin:
line = line.strip()
if not line:
continue
try:
req = json.loads(line)
except json.JSONDecodeError:
continue
req_id = req.get("id")
method = req.get("method", "")
if method == "initialize":
resp = {"jsonrpc": "2.0", "id": req_id, "result": handle_initialize(req_id)}
sys.stdout.write(json.dumps(resp) + "\n")
sys.stdout.flush()
initialized = True
continue
if method == "notifications/initialized":
continue # 无需响应
if not initialized:
continue
if method == "tools/list":
resp = {"jsonrpc": "2.0", "id": req_id, "result": handle_tools_list(req_id)}
elif method == "tools/call":
resp = {"jsonrpc": "2.0", "id": req_id, "result": handle_tools_call(req_id, req.get("params", {}))}
else:
resp = {"jsonrpc": "2.0", "id": req_id, "error": {"code": -32601, "message": f"未知方法: {method}"}}
sys.stdout.write(json.dumps(resp) + "\n")
sys.stdout.flush()
if __name__ == "__main__":
main()
#!/usr/bin/env swift
// ==========================================================================
// ocr_image.swift — macOS Vision 框架 OCR 工具
// 使用 Apple 原生 Vision 框架提取图片中的文字,100% 离线,支持中英文
// ==========================================================================
// 用法:
// swift buildchain/scripts/ocr_image.swift <图片路径> # 纯文本输出
// swift buildchain/scripts/ocr_image.swift <图片路径> --json # JSON 输出(含置信度+坐标)
// swift buildchain/scripts/ocr_image.swift <图片路径> --min 70 # 只保留置信度≥70%的结果
// swift buildchain/scripts/ocr_image.swift <图片路径> --lang en-US # 指定识别语言
// ==========================================================================
import Vision
import AppKit
import Foundation
// ── 解析参数 ──
let args = CommandLine.arguments
guard args.count >= 2 else {
print("用法: swift ocr_image.swift <图片路径> [--json] [--min 置信度%] [--lang 语言代码]")
print("示例: swift ocr_image.swift screenshot.png --json --min 70")
exit(1)
}
let imagePath = args[1]
var outputJSON = false
var minConfidence: Float = 0.3
var languages: [String] = ["zh-Hans", "en-US"]
var i = 2
while i < args.count {
switch args[i] {
case "--json": outputJSON = true
case "--min": i += 1; minConfidence = (Float(args[i]) ?? 50) / 100.0
case "--lang": i += 1; languages = [args[i]]
default: break
}
i += 1
}
// ── 加载图片 ──
guard let image = NSImage(contentsOfFile: imagePath),
let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) else {
let msg = "ERROR: 无法加载图片 \(imagePath)"
if outputJSON { print(#"{"error":"\#(msg)"}"#) } else { print(msg) }
exit(1)
}
// ── 执行 OCR ──
let semaphore = DispatchSemaphore(value: 0)
var results: [[String: Any]] = []
let request = VNRecognizeTextRequest { request, error in
defer { semaphore.signal() }
if let error = error {
if outputJSON { print(#"{"error":"\#(error.localizedDescription)"}"#) }
else { print("ERROR: \(error.localizedDescription)") }
return
}
guard let observations = request.results as? [VNRecognizedTextObservation] else { return }
for obs in observations {
guard let top = obs.topCandidates(1).first,
top.confidence >= minConfidence else { continue }
let text = top.string.trimmingCharacters(in: .whitespacesAndNewlines)
guard !text.isEmpty else { continue }
results.append([
"text": text,
"confidence": Int(top.confidence * 100),
"x": Int(obs.boundingBox.origin.x * 100),
"y": Int(obs.boundingBox.origin.y * 100),
"w": Int(obs.boundingBox.size.width * 100),
"h": Int(obs.boundingBox.size.height * 100)
])
}
}
request.recognitionLevel = .accurate
request.recognitionLanguages = languages
request.usesLanguageCorrection = true
let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
try? handler.perform([request])
semaphore.wait()
// ── 输出 ──
if outputJSON {
if let jsonData = try? JSONSerialization.data(withJSONObject: results, options: .prettyPrinted),
let jsonStr = String(data: jsonData, encoding: .utf8) {
print(jsonStr)
}
} else {
for r in results {
if let text = r["text"] as? String,
let conf = r["confidence"] as? Int {
let prefix = conf >= 70 ? "" : "(\(conf)%) "
print("\(prefix)\(text)")
}
}
}