一篇关于「把 MCP 用好」而不是「把 MCP 装好」的实践记录。
我在一个车企超级 APP 项目的 iOS 迭代里,用蓝湖(Lanhu)MCP + Lookin MCP 搭了一套项目无关的工具层(harness),把 AI 写代码从「看着截图猜」变成了「读着需求写、对着真机验」。这篇文章讲清楚两件事:MCP 和 harness 到底差在哪一层,以及这套东西是怎么一步步落地的。
一、先分清两件事:MCP 是协议,Harness 是交付形态
这两年 MCP(Model Context Protocol)很火,但很多人把它当成万能药:装个 MCP server,AI 就能用了。实际用下来你会发现,MCP 只解决「连接」,不解决「复用」。
我的理解是两层:
- MCP 层:一种开放协议,让 AI 应用(Claude Code、Cursor、ZCode……)以统一方式调用外部工具。它回答的问题是「AI 怎么连上某个能力」。
- Harness 层:在能力之上做一层项目无关的封装——命令行入口、接入模板、提示词模板、产出目录约定。它回答的问题是「这个能力怎么被任何项目、任何 AI 工具顺手用起来」。
用一个比喻:MCP 是「通了电的插座」,harness 是「把插座装成你能顺手插拔的接线板,还贴好了每个插口的标签」。插座本身很有价值,但真正让工作流跑起来的是那套组织方式。
我们实际搭了两套 harness,分别包着两个 MCP:
| 蓝湖 MCP(lanhu-mcp) | Lookin MCP | |
|---|---|---|
| 管什么 | 设计源头:需求文档(Axure)、设计稿识别 | 运行端:真机/模拟器上 App 的实时视图层级 |
| 解决什么 | AI 读得懂需求、看得见设计 | AI 验得了实现、对得上真机 |
| Harness | lanhu-harness(CLI + 模板 + 产出分目录) | lookin-harness(stdio 桥 + 自检 + 多 IDE 安装) |
二、痛点:AI 写 iOS 代码的两个断层
AI 写 iOS 代码这件事,真正卡人的不是「写不出来」,而是两个断层:
断层一:设计源头读不到。 需求在蓝湖的 Axure 原型里,设计稿在蓝湖的切图里。让 AI「打开网页看看」不现实——登录墙、动态渲染、截图信息密度太低。没有结构化输入,AI 只能靠人肉翻译需求,等于没省事。
断层二:运行端验不了。 代码写完了,对不对?光编译通过不够——UI 是不是按设计稿还原的?文案、字号、颜色、约束对不对?传统做法是人眼对着模拟器截图比对,一轮一轮改。AI 自己看不见运行中的 App,就没法自证。
一个管「源头」,一个管「运行端」,两个 MCP 正好补上这两个断层,形成闭环:
1 | 蓝湖(需求/设计稿)→ AI 理解 → 写代码 → 真机跑起来 → Lookin(实时视图树)→ AI 比对 → 通过/差异 |
三、落地一:lanhu-harness(设计源头)
蓝湖社区有一个很火的 MCP:dsphper/lanhu-mcp(2k+ star),把蓝湖的文档分析、设计稿识别暴露成 13 个 lanhu_* 工具。它解决了「连上」的问题,但直接用有几个不爽:
- 依赖 IDE 的 MCP 集成,换工具就要重新配;
- 没有统一的提示词模板,每次识别结果结构都不一样;
- 产出散落各处,没法按迭代回溯。
于是我们做了 lanhu-harness,四个东西:
1 | lanhu-harness/ |
关键设计是 bin/lanhu-mcp:一个不依赖任何 IDE 的 stdio MCP 客户端——按需拉起 server、调用工具、把结构化结果和页面截图落盘。终端能跑、脚本能调、任何 AI Agent 都能用。doc 子命令一条命令拉完一个 Axure 文档的指定页面(文本 + 截图 + 样式参考),之后把「截图 + 原文」交给 AI 按 prompts/ 模板做内容识别。
实战效果:在车企 APP 的 0931 迭代里,用它从蓝湖 Axure 里识别了 APP 端 6 页需求——需求跟踪表 7 条、变更类型逐项定性、UI 要点带截图锚点,直接作为迭代计划的需求输入。整个过程从「人肉扒原型」变成「一条命令 + AI 整理」。
四、落地二:lookin-harness(运行端)
Lookin(hughkli/Lookin + QMUI/LookinServer)是 iOS 开发常用的 UI 调试工具(类似 Reveal,免费开源):macOS 端连上跑着 LookinServer 的 Debug 包,就能看到实时视图树、改属性、看约束。它也有 MCP 服务(10 个工具:get_hierarchy / get_view / get_view_attributes / get_screenshot / search_views ……),让 AI 能直接读运行中的 App 视图层级。
但原生的 Lookin MCP 是 HTTP/SSE 常驻服务,接入不同 AI 工具时各有各的别扭。我们做了 lookin-harness,把它的能力桥接成标准 stdio MCP:
1 | lookin-harness/ |
调通路上踩过的四个坑
本地回环被代理劫持。 环境里有
HTTP_PROXY时,urllib 会把127.0.0.1的请求也送进代理,拿回502 Bad Gateway而不是Connection refused——端口探测和报错全部失真。解法:客户端显式用ProxyHandler({})绕开系统代理。这是排查时最容易误导人的一个。MCP 会话是「有状态」的。 Lookin 的 MCP 服务用 per-session transport:
initialize握手后要从响应头拿MCP-Session-Id,后续所有请求必须带上;会话断了必须重建。最初没按这个来,表现为「偶尔能调通、过一会全失败」。「先开 IDE 后开 Lookin」就废了。 很多 MCP 客户端只在启动时拉一次工具列表,拉失败就把整个 server 判死、之后不再重试。Lookin.app 没起时,工具列表直接拉不到。解法是 FALLBACK_TOOLS 静态兜底:桥接层内置一份从实时
tools/list原样抄来的 10 工具清单,App 没起时也返回工具(工具永远可见),把真实的连接错误推迟到真正调用工具时才报——那时信息更准确,也不至于让 IDE 误杀 server。后台假死最难查。 App 切到后台时链路握手会失败,但 TCP socket 已经建立(
ESTABLISHED)且永不重建——看起来「连上了」,实际拿不到任何数据。修法只有一条:重启 Lookin.app 并把目标 App 拉回前台(LookinServer 在didBecomeActive之后才开始监听)。为此我们把「五步链路自检」做成了doctor命令:MCP 端口 → 协议握手 → App 连接 → LookinServer 传输层 → 本机 App,一次定位卡在哪一步。
两个「纸上没写」的真机细节
- iOS App on Mac 上截图是空白的。 macOS 合成器的渲染路径下,
get_screenshot拿不到内容。所以视觉比对不能依赖截图,要用get_view/get_view_attributes的数值(frame、font、color、constraints)比对——这也更精确。 search_views的 text 语义。 它是按文字内容搜的;UILabel 文案为空时,Lookin 会把属性名(比如titleLabel)填进 text 字段——搜「空文案」会搜出一堆属性名,别被误导。
五、沉淀下来的五条设计原则
两套 harness 做完,我们总结出五条原则,也成了后续所有工具封装的模板:
- 项目无关是底线。 README 第一句就写明「不依附任何业务项目,终端 / 脚本 / 任意 AI Agent 可用」。绑定某个项目或 IDE 的封装,换个项目就废。
- CLI 优先。 先给一个终端能跑的命令行,再谈 IDE 集成。CLI 是万能接入点:脚本、CI、任何 AI 工具都能调。
- 提示词模板化。 识别任务和验证任务各有一份模板,强制「任务 / 范围判定 / 输出结构 / 质量红线」四要素——保证不同项目、不同迭代的产出结构一致,AI 的发挥空间留给内容而不是格式。
- 产出落盘、按目录分。 每次识别的结构化结果、截图、原始数据都落盘到
output/,按迭代分目录。AI 会话会丢,落盘的东西不会。 - 给失败设计兜底。 FALLBACK 工具清单、doctor 自检、连接错误延迟报告——这套「失败了也要让上层拿到可诊断的信息」的思路,比把错误闷在日志里有用得多。
六、写在最后
这套东西目前还在我们自己的迭代里打磨——还有真机环境的完整复测、Cookie/凭据的自动化刷新、一键安装脚本这些收尾没做完,所以暂时不打算封装发布。
不过方向是明确的:MCP 生态会越来越丰富,但「怎么把 MCP 用好」这件事,值得每个团队自己沉淀一套 harness。等我们把坑填得差不多了,大概率会把这两套 harness 整理共享出来——如果你也在做类似的事情,欢迎到时候来拍砖。
(文中项目信息已脱敏。lanhu-mcp 为开源项目 dsphper/lanhu-mcp;Lookin 为开源项目 hughkli/Lookin + QMUI/LookinServer。)