App Intents 与 MCP:路由该怎么分
App Intents 和 MCP 这两种协议,都能让外部代理操作一个应用的领域能力。但它们并不会合并成一个。真正的问题是:哪项能力走哪条路,以及为什么每种协议对自己的调用方而言都是正确答案。
Apple 推出 App Intents,是为了给 Apple Intelligence 一个类型化的声明式接口,让它无需触碰界面就能操作第三方应用。1 Anthropic 推出 Model Context Protocol,是为了给任何 LLM 一个类型化、由服务器中介的接口,让它无需触碰界面就能操作任何工具。2 形状相似,调用方却不同。把两者当成同一个接口来对待,只会得到一套两头都不讨好的架构。
本系列前两篇文章分别单独讲了这两种协议:App Intents 见《App Intents 是 Apple 通往你的应用的新 API》,MCP 见《两套代理生态,一份购物清单》。这一篇讲的是路由问题。一项能力什么时候该做成 AppIntent,什么时候该做成 MCP 工具,什么时候两者都要,又有哪些只应留在应用内部?
TL;DR
- App Intents 是通往 Apple Intelligence、Siri、Shortcuts 以及系统建议栈的唯一路径。系统从安装那一刻起、并通过应用更新持续让它们可用,App Shortcuts 的 donation 与索引则把它们呈现到 Spotlight 与 Siri 建议中。
- MCP 工具是通往所有非 Apple 大模型(Claude、ChatGPT、Gemini、本地模型)的路径。传输方式是 stdio 或 Streamable HTTP,
.mcpb是一种打包格式,通常内含一个本地 stdio 服务器;宿主在会话开始时加载工具。当前规范修订版是 2025-11-25,2026-07-28 的候选发布版则围绕无状态内核重构了该协议67。 - 两种协议在类型化 schema、
entity → action → result的形状以及参数解析上是一致的。分歧出现在身份、持久化、延迟和渲染界面上。 - 路由规则是这样的:如果用户可能会问 Siri 或从 Spotlight 唤起这项能力,就用 App Intents。如果开发者可能把它接进 Claude Code 会话或外部代理的运行流程,就用 MCP。大多数应用对同一套领域能力两者都需要。
两种协议,同一种形状
两种协议定义的都是外部调用方与应用领域之间的操作契约。契约由三部分组成:schema(调用方能请求什么)、解析器(应用如何找到 schema 所指的实体)、以及动作(执行什么、返回什么)。
App Intents 用 Swift 表达这份契约。协议接口是 AppIntent、AppEntity、AppEnum,由 @Parameter 宏驱动 schema,func perform() 返回结果。3 schema 在编译期生成,随安装打包进应用。Apple Intelligence、Siri、Shortcuts 与 Spotlight 读取的是同一份 schema,并把类型化的请求送进同一个 perform() 入口。
MCP 用 stdio 或 Streamable HTTP 之上的 JSON-RPC 表达这份契约。协议接口是 tools/list 与 tools/call 两个方法,每个工具声明名称、描述和 inputSchema(2025-06-18 规范为结构化返回值增加了可选的 outputSchema,2025-11-25 修订版把 JSON Schema 2020-12 确立为默认方言,加入了作为元数据的工具图标,并把治理结构正式化到 Anthropic 之外)。46 MCP 宿主(Claude Desktop、Claude Code、Cursor、ChatGPT 桌面应用)在会话开始时发现工具,并用 JSON 载荷按名称调用。跑模型的是宿主,跑工具的是服务器。这套协议本身也正处在过渡期:2026-07-28 的候选发布版把 MCP 重建在一个可运行于普通 HTTP 基础设施之上的无状态内核上,把长时间运行的工作移入 Tasks 扩展,并通过 MCP Apps 加入了服务器端渲染的界面7。下文关于路由的论证在这次修订之后依然成立,因为其中没有任何一点改变了调用方是谁。
形状是一样的:schema、解析器、动作。区别在于每一部分由谁来运行,以及信任边界落在哪里。App Intents 运行在应用进程内、用户的设备上、应用自身的授权之下,由系统中介调用路由。MCP 服务器运行在开发者选定的地方(本地 stdio、托管 HTTP、内嵌包),由宿主大模型在一个没有边界的工具集合上中介调用路由。
还有第三个形状相同的调用方值得一提,它让整幅图景完整起来:Apple Foundation Models 框架背后的端侧模型通过 Tool 协议调用应用代码,同样是一项有名称、有描述、有类型的能力。一个形状良好的领域层,能在不知道是谁在调用的情况下同时供养这三个接口。
两种协议分歧在哪里
除了表层形状之外,还有四项运行层面的差异会影响路由。
身份与持久化。 App Intents 说的是 AppEntity 类型,系统可以存储、呈现它,并在之后重新解析。我今天通过嘿 Siri,在 Water 里记录 250ml 保存的一条饮水记录,能跨重启留存,在用户的 iCloud 设备之间同步,日后还可以被其他意图引用(给我看昨天的饮水记录)。系统会在所有这些调用之间追踪实体 ID,3 而 iOS 27 把这个故事延伸到了硬件之间:遵循 SyncableEntity 的实体会带上一个在用户各设备间保持一致的标识符,于是 Siri 可以把关于同一个对象的对话从 iPhone 交给 Mac,再交给 Watch。8 MCP 本身也是一种带生命周期管理的有状态协议,Streamable HTTP 支持用会话 ID 维持连接的连续性;但持久的领域身份属于服务器自己的事,协议层面没有一个可与 AppEntity 标识符相提并论、能让宿主模型跨会话依赖的东西。MCP 提供 resources 来承载持久的参考数据,2025-11-25 规范又加入了用于追踪持久请求的实验性 tasks,但领域对象的身份仍是服务器端的责任,而不是一等的协议契约。46 底层的纪律与应用自身数据层适用的那一条相同:使用能活过进程生命周期的稳定标识符,SwiftData 的部分见《schema 纪律》。
延迟与电量。 App Intent 的 perform() 方法体在设备上的应用或应用扩展上下文中执行。若有网络访问,也来自应用自身的代码或外围的 Apple Intelligence / Siri 层,而不是意图契约本身。一个类型化的端侧动作返回一个类型化的结果,在常见情形下很快。MCP 工具即使是本地的,也要经过 stdio 的 JSON-RPC 分帧并跨越一道独立的进程边界,远程 MCP 工具还要付出 HTTP 往返的代价。延迟预算是不一样的。一个记录 250ml 的 App Intent 可以在 Siri 一轮对话的窗口内完成;一个远程 MCP 工具则可能成为 Claude Code 会话的瓶颈。
渲染界面。 App Intents 返回的结果由 Apple Intelligence 渲染进系统界面:锁屏横幅、Siri 回应、Shortcuts 输出、Spotlight 结果。应用无法控制结果如何呈现。MCP 工具返回的是内容块(文本、图像、音频、内嵌资源或结构化内容),由宿主模型读取后决定怎么展示。一个 Claude Code 会话可能把结果原样引给开发者,也可能做摘要,或者把它喂进下一次调用。渲染的决定权在模型层。
可发现性。 Apple Intelligence 从安装那一刻起就让 App Intents 可用,App Shortcuts 的 donation 与索引会依据用户行为把意图呈现到 Spotlight 搜索与 Siri 建议中;应用更新和动态实体则让这个接口随时间调整。用户从不需要键入工具名。MCP 宿主在会话开始时读取工具,由用户(或系统提示词)决定模型能看到哪些工具。发现在 MCP 这一侧是显式配置,在 App Intents 那一侧是隐式的系统推断。
两种协议在身份、延迟、渲染和发现这四项属性上分歧,而这四项都源自同一个根本区别:App Intents 服务的是一个用户并未配置过的系统级代理,MCP 服务的是一个开发者配置出来的会话级代理。调用方不同,义务也不同。
路由规则
一个同时具备两种协议的应用,其能力地图看起来是这样的:
┌──────────────────────────────────────────┐
│ App's domain capabilities │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ CRUD │ │ Queries │ │ Actions │ │
│ └─────────┘ └─────────┘ └─────────┘ │
└────┬────────────┬────────────┬────────────┘
│ │ │
┌──────────┴──────┐ │ ┌────────┴──────────┐
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌────────────┐ ┌─────────────────────┐ ┌──────────────┐
│ App Intents │ │ Both (AppIntent + │ │ MCP tools │
│ only │ │ MCP tool wrapper) │ │ only │
└────────────┘ └─────────────────────┘ └──────────────┘
│ │ │
Siri / Spotlight Cross-protocol Claude Code,
Shortcuts capabilities external agents,
Apple Intelligence where both callers LLM tooling,
proactive surfaces should reach the dev workflows
same domain
路由规则是依次要问的三个问题。
这项能力是用户会去问 Siri、或从 Shortcuts 调用的吗? 如果是,这项能力就需要一个 App Intent。记录 250 毫升饮水、开始一次冥想、把香蕉加进我的清单、我昨天体重多少都属于意图,因为用户可能会把它们说出口、输入 Spotlight,或者在 Shortcuts 里串起来。对这类能力而言,App Intent 不是可选项——没有别的办法能让你抵达 Apple Intelligence 的第一方代理接口。
这项能力应该让外部代理能够驱动吗? 如果是,这项能力就需要一个 MCP 工具。从 Claude Code 会话往购物清单里加一项、把 Get Bananas 的状态读进 Cursor 代理的上下文、从一个远程的会用工具的 LLM 触发工作流都属于 MCP 工具,因为调用方不是 Apple Intelligence,而是开发者接上的那个大模型。MCP 工具可以包装 App Intent 所调用的同一个领域层 Swift 函数,但协议接口是开发者选定传输方式之上的 JSON-RPC。
这项能力是否需要带着系统已知的稳定身份活过单次会话? 如果是,App Intent 这条路天然合适,因为系统免费给了你 AppEntity 身份、查询支持和持久化语义。如果不是,MCP 工具可以返回一个内容块,把持久身份交给服务器自行决定,并省下建模实体的成本。
大多数不那么简单的应用能力都落在两者皆需那一列。Water 里的饮水记录能力既有 AppIntent(好让 Siri 接收口述),也有 MCP 工具(好让 Claude Code 会话从导出的日志里回填数据)。两条路径共用一个 Swift 函数,而这个函数并不知道是哪个调用方触发了它。5
落到代码上,形状就是一个领域方法,加上两个都调用它的适配器包装:
// Domain layer (Swift, no protocol assumptions)
func logWater(amount: Measurement<UnitVolume>, at: Date, caller: Caller) throws -> WaterEntry {
try guards.requireWritePermission(caller)
let entry = WaterEntry(amount: amount, timestamp: at)
try store.insert(entry)
return entry
}
// Adapter 1: App Intent (Apple Intelligence / Siri / Shortcuts)
struct LogWaterIntent: AppIntent {
static var title: LocalizedStringResource = "Log Water"
@Parameter(title: "Amount") var amount: Measurement<UnitVolume>
func perform() async throws -> some IntentResult & ReturnsValue<WaterEntry> {
let entry = try domain.logWater(amount: amount, at: .now, caller: .siri)
return .result(value: entry)
}
}
// Adapter 2: MCP tool (Claude Desktop / Code / external agent)
// Tool name "log_water" with inputSchema {amount_ml: number}
// Handler:
let entry = try domain.logWater(
amount: .init(value: ml, unit: .milliliters),
at: .now,
caller: .mcp(host: hostName)
)
return .text("Logged \(entry.amount) at \(entry.timestamp)")
两个适配器看起来不一样,是因为它们的调用方不一样。它们调用的函数是同一个。
哪些能力应该留在应用内
有一小部分但很重要的能力应当只对应用自身开放。把它们交给任何一种协议都是错误。
界面状态类能力。 “打开第三个标签页”“滚动到底部”“高亮这一行”不是领域操作,而是交互原语。App Intents 通过 OpensIntent 和 Shortcuts 在一定程度上支持这类事情,但类型上并不契合;用户通常想要的是一个结果,而不是一次导航。MCP 对界面导航的支持更糟:模型驱动的不是屏幕,而是工具。
必须有人的身体在回路里的能力。 拍照、生物识别认证、敏感个人信息录入,以及任何需要用户看着屏幕并点按的流程。Apple 的 CameraCaptureIntent 是为相机流程而存在的,但其设计意图是启动一个前台拍摄活动,而不是把后台相机权限交给代理。对两种协议都成立的诚实规则是:相机、生物识别与敏感录入流程应当作为带明确用户确认的前台界面来运行,而不是作为静默的意图调用或工具调用。请把这些能力留在应用界面之后,让代理把用户引到屏幕前,而不是替他穿过屏幕。
长时间运行的后台工作。 两种协议在这里都长出了真正的原语,于是判断也从“绝不”变成了“借助协议自身的机制,有意识地做”。在 Apple 这一侧,iOS 27 的 LongRunningIntent 允许意图突破系统 30 秒的后台限制,条件是它必须上报进度(该协议细化自 ProgressReportingIntent),进度则由 Live Activities 渲染8。在 MCP 这一侧,2025-11-25 规范加入了面向持久请求的实验性 tasks,支持轮询与延迟取回结果,并在 2026-07-28 候选发布版中升格为 Tasks 扩展67。如今诚实的边界是:当调用方主动发起了这项工作并期待看着它完成时,就使用这些原语;当消费方需要的“进度”比一个百分比更丰富,或者宿主模型会在等待中让推理链超时时,就把工作留在应用自己的界面之后。对任何开放式的任务来说,请求已入队,这里是状态页面仍然是正确的返回值。
任何触及他人数据的操作。 两种协议的信任边界都是发起调用的代理。Apple Intelligence 运行在用户的 iCloud 账户之下,MCP 运行在开发者接入的那套凭据之下。跨用户的操作(共享、多账户访问、管理员动作)通过任何一种协议都不安全,因为调用方的身份并不是正确的身份。
如果重做,我会怎么做
知道了上面的路由规则,我会用现在设计服务边界 API 的方式来设计 Swift 应用的领域层:领域方法接受类型化输入、返回类型化输出,不把任何协议假设写死进去。App Intents 用 @Parameter schema 和 perform() 胶水代码薄薄地包一层领域方法。MCP 工具用 JSON schema 和 stdio 分帧薄薄地包同一批领域方法。两种协议都是薄适配器,功夫都在领域层。
由此有两个推论。
调用方身份是领域层的事,不是协议层的事。 App Intent 的方法体收到的是系统解析好的参数,并运行在用户已经走完系统意图唤起流程的上下文里。MCP 工具的方法体收到的是宿主准备好的任何凭据。两者都作为一个显式的 caller 参数透传给领域方法,由领域方法来强制执行授权、确认提示以及其他领域不变量。任何一种协议都无权假装调用方就是用户。
两个适配器暴露的是同一组可供性。 哪些能力开放给哪类调用方,这个决定记录在两份清单里,而不是散落在协议代码中。新增一项能力,就是一个领域方法、两个适配器包装、两条清单条目;移除一项能力同样对称。上面那张矩阵图会变成一个真实存在的文件。
未来几年 Apple 平台的前沿不在于二选一,而在于把两者视为在同一个领域层上组合的正交契约。Apple Intelligence 代理对用户负有一组义务(端侧运行、用 Siri 说话、通过系统渲染)。外部大模型代理对开发者负有另一组义务(在任何地方运行、用 JSON-RPC 说话、通过开发者选定的模型渲染)。两者都值得拥有一个通往你应用的类型化接口,而两者都不该成为唯一的接口。
什么时候不该两个都做
这个论证是双向的。有些应用只需要其中一种协议,不需要另一种。
没有开发者面的纯消费级工具。 手电筒。鸟鸣识别器。增强现实卷尺。用户或许想通过 Siri 唤起它(App Intents 有用),但没有开发者会把它接进 LLM 工作流(MCP 只是装点门面)。
没有终端用户面的纯开发者工具。 一个代码格式化 MCP 服务器。一个仓库搜索工具。一个包版本检查器。这里的用户就是 Claude Code 会话里的开发者,Siri 与 Apple Intelligence 无用武之地。
两类代理都服务不好的应用。 高度交互的游戏、实时多人应用,以及价值就在于让人留在应用内、盯着屏幕的应用。两种协议都不合适,正确答案是做一个出色的应用,不签任何代理契约。
要决定的不是默认做一个还是两个,而是这个应用是为什么而做,还有谁可能想操作它的领域能力。答案可能是都不做、只做一个,或者两个都做。只要领域层形状良好,做一个的成本很小;在同一个领域层之上再做另一个,成本同样很小。真正昂贵的,是在用例明确需要时却不做——那意味着这项能力在那个代理接口上彻底缺席。
这套模式对 iOS 26 及以后的 Apple 技术栈意味着什么
两点收获。
-
把 App Intents 与 MCP 当作同一个领域之上的正交契约,而不是相互竞争的协议。 Apple Intelligence、Siri、Shortcuts 与 Spotlight 是一类带系统级义务的调用方;Claude、Cursor、ChatGPT 及其余则是第二类带会话级义务的调用方。两者都值得拥有类型化的访问方式,而它们之下的领域层并不因此改变。
-
路由规则看的是谁在调用,而不是运行的是什么。 App Intent 和 MCP 工具可以调用同一个 Swift 函数。它们的区别在于调用方所承担的义务、拿回去的渲染方式,以及对持久化的预期。把函数写对,让协议层保持轻薄。
完整的 Apple Ecosystem 系列是这样的:面向 Apple Intelligence 的类型化 App Intents、面向跨大模型代理的 MCP 服务器、面向锁屏状态机的 Live Activities、面向视觉层的 Liquid Glass 模式,以及面向跨设备触达的多平台发布。系列主页在 Apple Ecosystem 系列。若想了解 iOS 与 AI 代理更广阔的背景,请参阅 iOS 代理开发指南。
常见问题
同一项能力,我该做 App Intent 还是 MCP 工具?
如果这项能力应当抵达 Apple Intelligence、Siri、Shortcuts 或 Spotlight,就做 App Intent。如果它应当抵达外部大模型(Claude、ChatGPT,以及 Claude Code 或 Cursor 中的代理),就做 MCP 工具。对于两类调用方都该服务的领域能力,就在一个共享的 Swift 领域方法之上,把两者都做成薄适配器。
App Intents 和 MCP 服务器是竞争关系吗?
不是。App Intents 是通往 Apple 第一方代理栈的路径,MCP 是通往其他所有大模型的路径。Apple Intelligence 不会调用 MCP 工具,外部大模型代理也无法直接唤起 App Intents(它们要经过系统)。两种协议服务的是不同类别的调用方,它们有不同的信任模型、不同的延迟预算和不同的渲染界面。
一个应用可以通过两种协议同时开放它的领域能力吗?
可以,而且大多数希望获得完整代理触达的复杂应用都应该这么做。Get Bananas(见 MCP 服务器那篇)和 Water(见 App Intents 那篇)就是早期的例子。模式是:下面一个领域层,上面并排放 App Intent 适配器与 MCP 工具适配器。两个适配器调用的是同一批 Swift 函数。
Apple Intelligence 会追踪、而 MCP 不会追踪的状态是什么?
Apple Intelligence 会跨调用、跨会话、跨重启追踪 AppEntity 身份,借助 iOS 27 的 SyncableEntity 还能跨用户的各台设备追踪8。实体模型给了系统持久的引用,用户可以借此把多个意图串起来。MCP 本身是一种带生命周期管理的有状态协议,Streamable HTTP 中也有会话 ID,但持久的领域身份是服务器端的责任,而不是一等的协议契约;宿主模型无法从协议接口拿到与 AppEntity 等价的标识符。MCP 的 resources 概念和 2025-11-25 规范的实验性 tasks 分别支撑持久的参考数据与持久的请求,但两者都工作在服务器自己拥有的那一层6。
有没有哪些能力两种协议都不该开放?
有。界面状态类能力(打开这个标签页、滚动到这里)、必须有人的身体在回路里的能力(拍照、生物识别认证、敏感录入),以及跨多个用户数据的操作,都应留在应用界面之后——两种协议都不携带跨用户安全操作所需的信任信号。长时间运行的后台工作是那个已经挪动了的例外:iOS 27 的 LongRunningIntent 与 MCP 的 tasks 扩展如今给了它真正的原语,所以请通过这套机制有意识地开放,只把开放式的、比进度条更丰富的工作留在你自己的界面之后。
参考资料
-
Apple Developer, “App Intents framework”. 用于声明意图、实体、参数与查询的接口,Apple Intelligence、Siri、Shortcuts 和 Spotlight 都能对其进行路由。 ↩
-
Anthropic, “Model Context Protocol”. 跨 LLM 宿主进行类型化工具开放的开放协议。传输方式为 stdio 或 Streamable HTTP;
.mcpb是一种打包格式,通常内含一个本地 stdio 服务器。规范涵盖tools/list、tools/call、resources与提示词。 ↩ -
Apple Developer, “Creating your first app intent” 与 “AppEntity”. 涉及
AppIntent协议、@Parameter宏、func perform()入口点,以及承载持久身份的AppEntity。 ↩↩ -
Anthropic, “MCP Specification: Tools (2025-06-18)”、“MCP Architecture” 与 “Transports (2025-06-18)”. 涉及
tools/list与tools/call的 JSON-RPC 方法定义、inputSchema与可选的outputSchema、宿主职责、生命周期管理,以及 stdio / Streamable HTTP 传输。 ↩↩ -
作者在《App Intents 是 Apple 通往你的应用的新 API》与《两套代理生态,一份购物清单》中的分析。双适配器模式(一个 Swift 领域方法,两个协议包装)在两篇文章中分别针对 Water 与 Get Bananas 做了实现层面的说明。 ↩
-
Model Context Protocol, “Key Changes” changelog for the 2025-11-25 revision. 自 2025-06-18 以来的主要变化包括:支持 OpenID Connect Discovery,工具/资源/提示词图标,工具命名指南,通过
tools与toolChoice在采样中调用工具,OAuth Client ID Metadata Documents,”experimental support for tasks to enable tracking durable requests with polling and deferred result retrieval”,以及把 JSON Schema 2020-12 作为默认 schema 方言。该修订版还把 MCP 的治理结构正式化。 ↩↩↩↩↩ -
Model Context Protocol blog, “The 2026-07-28 MCP Specification Release Candidate”. 该候选发布版带来了 “a stateless core that scales on ordinary HTTP infrastructure”,把 tasks 从实验性的核心特性移入面向长时间运行工作的 Tasks 扩展,通过 MCP Apps 加入服务器端渲染的界面,并围绕 OAuth 2.0 与 OpenID Connect 强化了授权。定稿预计在 2026 年 7 月 28 日;撰写本文时它仍是候选发布版。 ↩↩↩
-
Apple Developer, “LongRunningIntent”(iOS 27 beta),声明为
protocol LongRunningIntent : ProgressReportingIntent,它以上报进度为条件,通过performBackgroundTask(options:operation:)把意图的后台运行时间延长到标准的 30 秒限制之外;以及 “SyncableEntity”(iOS 27 beta),它声明AppEntity带有一个在用户各设备间保持一致的标识符。两者在 App Intents in iOS 27: Background, Sync, Spotlight 中有深入讨论。 ↩↩↩