在 Claude Code 里做 OCR:接入 space ocr MCP 服务器
把托管的 space ocr MCP 服务器接进 Claude Code:文档图片变成带页面坐标的结构化字段,附带一份待复核清单,并可直接归档成随时查询的行。
Claude Code 可以把一张文档图片交给 OCR 服务,拿回带名字的字段,而不是一段还得自己解析的文字——发票、收据、名片、证件、各类表单都一样。入口是 space ocr 的 MCP 服务器:托管的一个端点,注册一次之后,它的工具就和 Claude Code 自带的工具并列,遇到文档时助手会自己去用。
这改变了什么,值得说清楚。把图片贴进对话是可行的,直到你需要每次都是同一组字段、需要每个值在页面上的位置记录、需要一个存放结果的地方。而自己搭一套——OCR 引擎、解析层、数据库——能解决这些,但也把三套东西的维护留给了你。这里抽取跑在服务端并返回固定形状,每个值都带着它被读出来的坐标,同一个服务器还会把文档归档进一个工作区,第二次不用重读,直接查询。
接入
服务器在 https://mcp.space-ocr.com/mcp,走 Streamable HTTP 上的 MCP。没有东西要装,也没有进程要常驻:你注册一个 URL,API key 以 bearer 请求头随每次请求发出。在 Claude Code 里就是一条命令。
claude mcp add --transport http space-ocr https://mcp.space-ocr.com/mcp \
--header "Authorization: Bearer YOUR_API_KEY"Cursor、VS Code、Windsurf 把同样的 URL 和请求头写进 mcp.json。设不了请求头的客户端——claude.ai、Claude 桌面版、Claude 手机版——把同一个 URL 加成自定义连接器,OAuth 授权页会让你选用哪把 API key。两条路都一样:key 只用于那次请求,服务端不保存。
{
"mcpServers": {
"space-ocr": {
"url": "https://mcp.space-ocr.com/mcp",
"headers": { "Authorization": "Bearer YOUR_API_KEY" }
}
}
}密钥与计费
到 space-ocr.com → Developer → API Keys 生成一把 key(以 spocr_ 开头)。每个账户每月有 100 个免费点数,无需绑卡,每月重置;超出部分每点数 $0.05。
计费单位是页。三个读取工具中的任意一个,或一张上传的图片,各计恰好 1 点数,读取失败的会自动退还。其余都是免费的——浏览目录树、查询已存的行、更正单元格、发放上传链接。余量由 space_balance 返回:免费额度、套餐额度、预付余额,按这个顺序消耗。大批量之前值得先调一次。
十三个工具
先让它读 space_guide——这是服务器对自己的说明,六个短主题(start、upload、schemas、verification、queries、workflows)直接读进对话,不发网络请求,也不花点数。
三个工具负责读取,且什么都不存:
ocr_extract—— 抽取带名字的字段,可以自己声明fieldsschema,也可以用autoFields: true让模型提出一份。ocr_markdown—— 保留版面的 Markdown:标题、列表、表格(每个单元格带row/col),并按元素给出坐标。ocr_text—— 还原阅读顺序的纯文本,多栏版面不会交错回来,并按块给出坐标。
另外十个负责工作区:space_list 浏览目录树,space_view 读取并查询条目,space_create 建文件夹/表格/文档束/备忘,space_inbox 发放上传链接,space_upload 收已经是 URL 的图片,space_job 查上传作业,space_edit 更正单元格或改写备忘,space_balance 报余量,space_delete 分两步删除。完整工具表以及每个工具背后的 REST 路由见 API 文档。
怎么把图片送进去
工具调用装不下图片的字节,第一次尝试大多卡在这里。space_upload 只收已经是公开 https:// URL 的图片(每次最多 20 张,每张 20MB)。其余一律走 space_inbox:本地磁盘上的文件、对话里附带的照片、正要扫的一份纸质件。它针对某一张表格或文档束发放一个有时效的上传链接,字节从那台机器直接送到 space ocr,不经过对话。响应里两种结局都给了:能执行 shell 命令就用其中的 curl 行,不能就把链接展示给用户。
上传是异步的。space_upload 立刻返回作业,一页大约二十秒;你可以用 space_job 轮询,也可以稍等片刻直接用 space_view 读目标,行都会在。只处理图片:PDF 会毫无怨言地上传,然后永远不会被读,所以要先把它的页面转成图片再发。
一次性读取,还是留下来的行
ocr_* 什么都不存,适合没人会重复的一次查询,或者在设计表格列之前用 autoFields 先抽样看一份陌生文档。用户可能再回来看的东西,应该放进工作区。
建结构靠 space_create。文件夹用来分组,也是根目录唯一接受的类型。表格会从投进去的每张图片里抽取一组固定的 columns,一张图一行——要比较、要筛选的值放这里。文档束把每一页转成 markdown(用来读的正文)或 text(用来搜的词),并让这些页保持在一起。备忘就是纯文本。
有一条地址规则能省你半天:文件夹用名字寻址,其余一律用列表或创建调用返回的 path,它的最后一段是条目的 uniqueKey 而不是显示名——名叫 March 的表格并不住在 /invoices/March。把拿到的 path 留着,别用显示名重新拼一个。
行到位之后,查询也是 space_view 的活:where 过滤(重复即 AND,运算符有 = != > >= < <= 和表示包含的 ~),sort 排序,select 投影列,limit / offset 分页。坐标默认不返回以保持响应精简——需要指着页面说话、或读每个单元格的判定时,加上 boxes: true。读取不花点数,所以把筛选推到服务端,正是让答案和上下文都保持精简的办法。
返回的东西
三个读取工具,以及上传生成的行,共用同一种形状。data.values 就是你声明的 schema 里的数据。data.cells 是以 path 为键的扁平映射(total、items[0].price),每一条带着该值被读出位置的轴对齐 box 与四点 quad、判定 verified、review,以及背后的 evidence。data.review 是整页的汇总,flagged 里列出值得再看一眼的 path 及其 reasons。data.normalized 只在声明了标量类型时出现,把解析后的值放在印出来的值旁边。data.image 是把 0–1000 归一化坐标换算回像素的基准。
{
"status": "success",
"data": {
"values": {
"store_name": "超市 ABC",
"date": "2025-04-10",
"invoice_no": "",
"total": "$4.94"
},
"cells": {
"total": {
"box": { "xmin": 380, "ymin": 720, "xmax": 530, "ymax": 742 },
"quad": [{"x":380,"y":720},{"x":530,"y":720},{"x":530,"y":742},{"x":380,"y":742}],
"verified": true,
"review": null,
"evidence": { "text_match": true, "source": "vision_symbol_match", "match_ratio": 1.0 },
"normalized": { "value": 4.94, "type": "number", "method": "deterministic" }
}
},
"review": {
"unit": "field",
"declared": 4,
"returned": 3,
"boxed": 3,
"verified": 3,
"flagged": [ { "path": "invoice_no", "reasons": ["missing"] } ],
"by_reason": { "missing": 1 }
},
"normalized": { "total": 4.94 },
"image": { "width": 1654, "height": 2339 }
}
}为什么这些值可以核对。 坐标不是模型猜出来的位置:它锚定到页面上实际识别出的 OCR 符号,落在 0–1000 的归一化网格上,换算成像素的依据是 data.image。因为位置是真的,值可以画在文档上,用肉眼和它被读出的地方逐一对照。旁边的 verified 是判定——立起了复核理由是 false,核对跑过且什么都没立是 true,没有可核对的对象是 null;逐字比对本身由 evidence.text_match 报告,所以“被你声明的规则拦下、字符却仍然一致”是正常组合,不是矛盾。两者都是证据而非证明——两套引擎可能在同一处误读上达成一致——所以业务侧的校验要留着。
删除要两次调用
space_delete 第一次调用永远不会删东西。不带 confirm 时,它回报这个路径下有什么——目标,以及其下文件夹/表格/文档束/备忘/图片的数量和一份样例——并返回一个约十分钟有效的签名 confirm 令牌。助手把这份摘要给用户看,等到明确同意,再带上令牌调用一次。令牌绑定调用方的 key 和那个确切路径,既编不出来,为表格签发的令牌也删不掉它的某一行。
之所以要这套流程,是因为删除会级联且无法撤销:删掉文件夹,里面的图片也一起没了。删掉一行不会退还点数——那一页在上传时就已经读过并计费。
四条习惯
服务器把自己的使用准则写明了。一个又省又能给出出处的助手,和一个烧点数靠猜的助手,差别就在这四条。
- 存下来,别倾倒。 超过一份文档就别直接调
ocr_*,改走space_create→space_inbox,让重数据留在 API 后面,而不是贴回对话里。 - 扫描前先核。 批处理前
space_balance;再建一张表格之前先space_list,看看是不是已经有了。已经成为一行的文档没必要再付一次钱。 - 从存好的行回答。 用
where/sort/select/limit查表格,而不是把每一行都拉进上下文。读取免费,读图片不免费。 - 标注位置,标出不确定。 每个值都带着它被读出的框和一个判定。
data.review.flagged列出来的,要当作待人工确认的东西呈上,而不是直接断言;并且要求逐字照抄——页面上没有的值,无法锚定到页面。