标题:Uno Platform 如何用 .NET、MCP 和 AI 构建高质量应用
原文:
Uno Platform 如何用 .NET、MCP 和 AI 构建高质量应用
本文由 Sam Basu 撰写的客座文章。Sam 是技术专家、作家、演讲者、Microsoft MVP,同时也是 Uno Platform 的开发者布道师。如果你在过去几年里做过任何软件开发,你一定能感受到这种变化。AI 不再是场边的新奇玩意——它已经进入了编辑器、终端和构建流程。对于 .NET 开发者来说,这一刻尤其令人兴奋。生态深厚、工具链出色,而 AI 在驾驭这些方面的能力也在不断提升。
但“强大的 AI”和“有上下文、接地气的 AI”是两回事。一个 AI 智能体可以很乐意为你写一个跨平台 .NET 应用的设置页面,代码能编译,如果你只看一眼,它甚至能通过代码评审。但等到应用真正跑在你面前时,你才会发现它错在哪些看不见的地方。弥合这个差距,正是我们在 Uno Platform 要解决的问题——现在你可以在浏览器里用 AI 构建跨平台 .NET 应用,可以在这里试试:https://platform.uno/ 。
在这个时代,能收获最多的开发者,不是那些最会写提示词的人,而是能给 AI 提供正确上下文、让它真正把活干好的人。我们关注的重点是质量——我们如何给 AI 提供足够的护栏,让它能够成功,并能自己验证自己的成果,同时提供让 .NET 开发者从一开始就高效起来的工具。下面展开说说。
为什么选 MCP,以及为什么我们最终搞出了两个服务端
最显而易见的做法是先做“上下文填充”:把文档塞进提示词,加一份很长的指令文件,然后祈祷结果能行。这种方法失败的原因,事后看非常清楚:文档体量太大,其中有用的部分又少又随问题变化,而且无论提示词里塞多少内容,都不如让智能体在产生疑问的那一刻自己去查资料来得有效。
Model Context Protocol(MCP)解决了“查资料”的问题,但它解决不了“验证”的问题。知道 API 应该是什么样,并不等于告诉智能体它刚写出来的布局真的能正确渲染。这是两个不同的任务,生命周期也不一样,而这正是…… —— 后续段落继续。
正是这个区别,让我们最终选择了两个服务器而不是一个。我们最终确定的分工方式,正好对应那两类生命周期。
文档服务器:提供依据
文档服务器公开托管在 https://mcp.platform.uno/v1,通过 HTTP 对外提供访问,本身无状态。它回答的是“这个框架目前真实的情况是什么”——这类问题的答案只在发布新版本时变化,而不会因为你运行某个应用就改变。
它提供以下工具:
uno_platform_docs_search—— 搜索官方文档,返回最相关的结果uno_platform_docs_fetch—— 拉取完整文档页面并以 Markdown 格式返回uno_platform_agent_rules_init—— 初始化代理会话,设置针对正在运行应用的工作规则uno_platform_usage_rules_init—— 加载常见 API 使用规则
它还内置了两个提示词:
/new:按当前最佳实践搭建新应用/init:在给现有代码库添加功能之前,先对当前对话做初始化
这个服务器最关键的设计属性是:它随我们的文档做版本管理,而不是跟随开发者的 SDK。只要修正了一个文档页面,所有代理在下一次调用时都会拿到修正后的内容。这和把使用指南打包进 NuGet 包分发是两种完全不同的维护体验,也是我们选择自行托管而不是随包分发的根本原因。
应用服务器:眼睛和手
应用服务器则在各个维度上正好相反。它以 .NET 工具的形式发布,通过 stdio(标准输入输出)启动,运行在开发者自己的机器上,作为通往 Uno DevServer 的桥梁;它是有状态的,并且只归属于某一个会话。它回答的是“当前实际发生了什么”。它给代理提供四种能力:
- 运行应用 ——
uno_app_start以调试模式启动并启用热重载(Hot Reload),代理可以控制整个应用生命周期,而不必等人按下 F5。 - 查看界面 ——
uno_app_get_screenshot获取像素截图,uno_app_visualtree_snapshot获取视觉树的 XML 快照。 - 操作界面 ——
uno_app_pointer_click、uno_app_key_press、uno_app_type_text以及uno_app_element_peer_action,可以直接调用自动化对等体(automation peers)。 - 检查自身 ——
uno_health报告桥接状态及其连接情况,因为一个连“应用坏了”和……都分不清的代理,是无法正常调试的。
“my connection dropped” 会自信地排查错误的方向。这是两个服务器并排展示,也就是 agent 实际看到的样子。其中可视化树工具才是真正发挥价值的那一个。截图告诉模型“看起来哪里不对”,XML 树则告诉它“具体是哪个元素出了问题、属性是什么”。像素负责发现问题,结构负责定位病根,两者缺一不可。
这个工具列表里还有一个细节值得单独提一下:看看 uno_app_pointer_click 的描述,里面写着“请优先使用 uno_app_element_peer_action”。这个偏好直接写进了工具描述里,而不是放在没人翻的文档里,因为按坐标点击在不同窗口尺寸和 DPI 下很不稳定,而自动化对等节点(automation peer)则稳定得多。下面还会讲到这一点为什么重要。
构建过程:MCP C# SDK 在生产环境中的实战
两个服务器都用 C# 编写,基于官方 MCP C# SDK,这个 SDK 由微软和社区共同维护。有两点经验想告诉所有准备做同样工作的 .NET 团队。
先根据拓扑结构选传输方式。 文档服务器走 HTTP,因为它是托管的多人服务,需要 OAuth。应用服务器走 stdio,因为它是跑在开发者本机的一个子进程,只需要和当前运行的应用通信。拓扑决定了传输方式;只要你把约束条件写清楚,其实没什么好纠结的。
工具定义是对上下文窗口的一笔永久开销。 模型开始干活之前,每个工具的名称、描述和输入 schema 都要先加载进来。我们的文档服务器大约消耗 6.4k tokens,应用服务器大约 1.5k tokens——作为对比,同一会话里内置的 GitHub MCP 服务器大约消耗 5.2k。这是还没回答任何问题之前就花掉的真金白银,所以简洁、高信息量的工具描述绝不是风格偏好问题。
同一组服务器在 GitHub Copilot CLI 中使用,注意每个服务器对应的 token 成本。
第二个观点还有一个推论:工具描述是提示词,不是文档。 模型从来不调用的工具,等于不存在;而你唯一能影响模型选择的就是描述措辞。这正是为什么 uno_app_pointer_click 会明确告诉模型优先使用
自动化协作工具(automation-peer tool)——这不是在记录偏好,而是在决策发生的当下引导决策。
生成代码和功能验证是两个不同的问题。这里有一个大胆的判断:AI 写 UI 代码的速度比任何人类团队都快,但它无法判断自己写出来的东西是否正确。随着智能体(agent)工作流成为常态,这种不对称就成了瓶颈。生成变得很便宜,验证却没有。
Web 开发者已经解决了他们那半边的问题。Playwright 可以驱动真实浏览器,所以一个在 Web 应用上工作的智能体能够自己检查工作成果。但在 Windows、macOS、Linux、iOS、Android 或 WebAssembly 上运行的原生跨平台 .NET 应用,一直缺少类似的工具——应用一启动就成了黑盒。我们的答案是应用服务器(App Server):为 .NET 应用提供 Playwright 风格的 UI 自动化。智能体写出一处改动,应用热重载,智能体截屏、读取可视化树(visual tree)、点击走一遍流程,然后自己判断改动是否达到了要求。如果没有达到,智能体会在把结果交回之前先修复问题。代码很廉价,软件不是。这样就能同时守住这两个事实。
Skills:告诉智能体“怎么做”
MCP 工具告诉智能体“用什么”,但没说什么时候该用哪个、按什么顺序用,以及“完成”长什么样。这正是 Skills(技能)要解决的问题。用做饭来打比方很贴切:MCP 工具是食材——都是原子化的,每样只干一件事;Skills 是菜谱卡片——把食材组合成一道菜的、可复用的操作说明;智能体则是厨师——选一个菜谱,再根据厨房里实际有什么来灵活调整。
我们的 Skills 库按你实际要做的事来组织:MVUX 状态与数据源(feeds)、导航、主题、Uno Toolkit 控件,以及测试。其中让整个闭环转起来的是 uno-testing-ui:它通过应用服务器实现 UI 测试自动化。这个 Skill 知道按什么顺序调用工具,智能体就不必每次会话都从头推演一遍。基于真实文档、可实时检查的应用,以及精心编排的流程……
真正起作用的工作流,是把智能体和这些工作流绑在一起——我们所说的情境式 AI(contextual AI)就是这个意思。技能(Skill)以插件形式安装,任何兼容 MCP 的智能体都能调用。
这些加起来意味着什么
我们用这堆技术做出来的最有趣的东西,不是一份功能清单,而是一个跑在「本不该出现」之地的编译器。Uno Platform Studio 3.0 能在浏览器里完整生成一个跨平台 .NET 应用。提示框背后,由 Microsoft Agent Framework 编排的专用智能体,在并行步骤和多轮对话中规划并执行工作。随后,一个完整的 Roslyn(.NET 编译器平台)工作区接管:编译智能体写出的代码、加载生成的程序集、解析 NuGet 变更,再把结果热重载(Hot Reload)进正在运行的应用——全部在浏览器里完成,你可以全程盯着看。
文档服务器让智能体的知识始终不过时;应用服务器让它能自己核对工作成果;技能则确保它不跑偏。这些事交给 Roslyn、Microsoft Agent Framework 和 MCP C# SDK 来做,放在几年前还属于研究项目,而整套技术栈都是 .NET。
右侧输入提示词,左侧就是编译完、正在运行的 .NET 应用——不是效果图。
对开发团队来说,实际意义在于:智能体不再是打字飞快的手。它懂你的设计体系,会对照运行中的应用验证自己的输出,并且遵守你选定的工作流。这和「写代码更快」完全是两码事。
生成的 .NET 应用在浏览器里完全可交互,支持页面导航,还能用 Preview 功能单独打磨应用界面。开发者可以让 Agent 来迭代 UI,也可以在浏览器里手动用 Hot Design 调整,改动通过 Hot Reload 立刻可见,没有任何上手门槛:先浏览器起步,用 Agent 或 Hot Design 迭代界面,等准备好了,再带着同样的工具落到本地 IDE 或 CLI 环境。
为什么我们选择在上游投入
如果地基不由我们掌控,这一切都不可能建成——这是我们选择在特定方向投入的真实原因。我们和微软 .NET 团队共同维护 SkiaSharp。SkiaSharp 是大量 .NET 应用底层的 2D 图形 API。
图表、自定义控件和数据可视化,全都构建在 Google 的 Skia 之上——Chrome 和 Android 用的就是同一套渲染引擎——Uno Platform 也靠它来做渲染。在 SkiaSharp 4.0 发布之前,成为共同维护者让多年的投入正式落了地,而这次版本也是该项目多年来最大的一次更新。我们还与 Microsoft .NET 团队正式合作,直接参与 .NET 运行时的工作,给 .NET for Android、.NET for iOS 的绑定,以及 .NET 10 的 AOT 编译贡献代码。这和整篇文章讲的是同一个道理:你在越上游修复问题,从此不用再操心它的人就越多。
总结
如果你正在为自己的 .NET 技术栈搭建 MCP 服务器,我们想分享两点经验。第一,按生命周期拆分服务器,而不是按功能拆分——随发布而更新的知识,和随应用运行而变化的状态,不应该放在同一个进程里。第二,认真花时间写工具描述,因为它们本质上就是提示词。模型永远都不会选用的工具,跟不存在没什么区别。