App Intents 與 MCP:路由的抉擇
類型: frontier-essay。本文為前沿 Apple 開發中的代理式應用提出一條路由規則。兩種協定(App Intents 與 MCP)都讓外部代理能夠操作應用程式的領域。它們並不會合而為一。問題在於哪一個歸哪邊,以及為什麼每個協定都是其呼叫者的正確答案。
Apple 推出 App Intents,旨在為 Apple Intelligence 提供一個型別化、宣告式的介面,讓它無需碰觸 UI 即可操作第三方應用程式。1 Anthropic 推出 Model Context Protocol,旨在為任何 LLM 提供一個型別化、伺服器中介的介面,讓它無需碰觸 UI 即可操作任何工具。2 兩者形態相似,但呼叫者不同。把它們視為同一個介面,會造就一個兩邊都無法滿足的架構。
本系列前兩篇文章分別介紹了這兩種協定:App Intents 在 App Intents Are Apple’s New API to Your App,MCP 則在 Two Agent Ecosystems, One Shopping List。本文要回答的是路由的抉擇。何時該為某項能力配上 AppIntent、何時該配上 MCP 工具、何時兩者皆需,又有哪些只應留在應用程式內部?
TL;DR
- App Intents 是通往 Apple Intelligence、Siri、捷徑以及系統建議堆疊的唯一路徑。系統會在應用程式安裝時與更新時讓它們可用,並透過 App Shortcuts 的捐贈與索引機制,將其浮現至 Spotlight 與 Siri 建議中。
- MCP 工具則是通往所有非 Apple LLM(Claude、ChatGPT、Gemini、本地模型)的路徑。傳輸協定為 stdio 或 Streamable HTTP,
.mcpb是一種常見的封裝格式,通常用於發布本地 stdio 伺服器;主機在連線時載入工具。 - 兩種協定都採用型別化結構描述、
entity → action → result的形態,以及參數解析。它們在身分、持久性、延遲與呈現介面上有所分歧。 - 路由規則:若某項能力是使用者可能會問 Siri 或從 Spotlight 召喚的,就走 App Intents。若某項能力是開發者可能會接入 Claude Code 工作階段或外部代理執行的,就走 MCP。大多數應用程式對同一個領域都會兩者皆需。
兩種協定,同一形態
兩種協定都在外部呼叫者與應用程式領域之間定義了一份操作合約。合約由三部分組成:結構描述(呼叫者可以要求什麼)、解析器(應用程式如何找到結構描述指名的實體),以及動作(執行什麼、回傳什麼)。
App Intents 以 Swift 表達合約。協定介面為 AppIntent、AppEntity、AppEnum,由 @Parameter 巨集驅動結構描述,並以 func perform() 回傳結果。3 結構描述在編譯時產生,並隨應用程式一同安裝。Apple Intelligence、Siri、捷徑與 Spotlight 都讀取相同的結構描述,並透過同一個 perform() 進入點路由型別化的請求。
MCP 則以 JSON-RPC over stdio 或 Streamable HTTP 表達合約。協定介面為 tools/list 與 tools/call 方法,每個工具會宣告一個名稱、一段描述,以及 inputSchema(2025-06-18 規範新增了選用的 outputSchema 以支援結構化回傳)。4 MCP 主機(Claude Desktop、Claude Code、Cursor、ChatGPT 桌面應用程式)在工作階段開始時探索工具,並以 JSON 酬載依名稱呼叫它們。主機執行模型;伺服器執行工具。
形態是相同的:結構描述、解析器、動作。差別在於誰執行哪一部分、信任邊界落在何處。App Intents 在應用程式行程內、使用者裝置上、應用程式的權限下執行,由系統中介呼叫路由。MCP 伺服器則執行於開發者選定之處(本地 stdio、託管 HTTP、嵌入式套件),由主機 LLM 對一組無界限的工具進行呼叫路由。
兩種協定的分歧之處
除了表面形態之外,在路由方面有四個關鍵的營運差異:
身分與持久性。 App Intents 以 AppEntity 型別表達,系統可以儲存、呈現,並在日後重新解析。我今天透過 Hey Siri, log 250ml in Water 儲存的水分紀錄,可在重開機後保留下來,透過 NSUbiquitousKeyValueStore 同步,並可在日後被其他 intent 引用(Show me yesterday’s water entries)。系統會在所有這些呼叫之間追蹤實體的 ID。3 MCP 本身是具有生命週期管理的有狀態協定,而 Streamable HTTP 也支援用於連線連續性的工作階段 ID,但持久的領域身分是伺服器自己負責的事;在協定層級上並沒有 AppEntity 識別碼那種主機模型可以跨工作階段依賴的對應物。MCP 透過 resources 支援持久性參考資料,但身分仍是伺服器端的責任,而非協定的一級合約。4
延遲與電池。 App Intent 的 perform() 主體在裝置上、應用程式或應用程式擴充功能的執行環境中執行。任何網路使用都來自應用程式自身的程式碼,或是來自包覆它的 Apple Intelligence/Siri 層,而非來自 intent 合約本身。一個型別化、在裝置上執行、回傳型別化結果的動作,在常見情況下速度很快。MCP 工具(即使是本地的)會經過 stdio JSON-RPC 的封包框架,跨越獨立的行程邊界,而遠端 MCP 工具還會產生 HTTP 來回延遲。延遲預算是不同的。一個 log 250ml 的 App Intent 可以在 Siri 一輪對話的時間內完成。一個遠端 MCP 工具則可能成為 Claude Code 工作階段中的瓶頸。
呈現介面。 App Intents 回傳的結果由 Apple Intelligence 渲染至系統 UI:鎖定畫面橫幅、Siri 回應、捷徑輸出、Spotlight 結果。應用程式無法控制結果如何呈現。MCP 工具則回傳內容區塊(文字、圖像、音訊、嵌入資源或結構化內容),由主機模型讀取並決定如何呈現。Claude Code 工作階段可能將結果回引給開發者、加以摘要,或將其饋入後續呼叫。呈現決策落在模型層。
可發現性。 Apple Intelligence 在應用程式安裝後就讓 App Intents 可用,並透過 App Shortcuts 的捐贈與索引機制,根據使用者行為將 intent 浮現至 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 處理或從捷徑叫用的? 如果是,這項能力就需要 App Intent。Log 250ml of water、Start a meditation、Add bananas to my list、What did I weigh yesterday 都是 intent,因為使用者可能會把它們說出口、輸入到 Spotlight,或在捷徑中串接它們。這類能力的 App Intent 不可省略;沒有別的東西能讓您接觸到 Apple Intelligence 的第一方代理介面。
這項能力是否應該讓外部代理能夠驅動? 如果是,這項能力就需要 MCP 工具。Add an item to the shopping list from a Claude Code session、Read Get Bananas state into a Cursor agent context、Trigger a workflow from a remote tool-using LLM 都是 MCP 工具,因為呼叫者並非 Apple Intelligence;呼叫者是開發者所接入的任何 LLM。MCP 工具可以包覆 App Intent 所呼叫的同一個領域層 Swift 函式,但協定介面是 JSON-RPC over 開發者所選定的傳輸層。
這項能力是否需要在系統可知、具穩定身分的單一工作階段之外延續存在? 如果是,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)")
兩個適配器看起來不同,因為它們的呼叫者不同。它們呼叫的函式則相同。
哪些保留在應用程式內
有一小組重要的能力應該僅留給應用程式自身。把它們路由到任一協定都是錯誤。
UI 狀態能力。 「打開第三個分頁」、「捲動到底部」、「標亮這一列」並不是領域操作,而是互動原語。App Intents 透過 OpensIntent 與捷徑能支援其中一些,但類型上並不契合;使用者通常想要的是結果,而非導航。MCP 對 UI 導航的支援更糟糕:模型並不是在驅動畫面,而是在驅動工具。
需要使用者本人在迴路中的能力。 拍照、生物特徵驗證、敏感 PII 輸入,任何需要使用者看著螢幕並點擊的流程。Apple 為相機流程提供了 CameraCaptureIntent,但設計意圖是啟動前景拍攝活動,而非授予代理背景相機存取權。對兩種協定來說誠實的規則是:相機、生物特徵與敏感輸入流程應作為前景 UI 執行,並要求使用者明確確認,而非以靜默的 intent 或工具呼叫執行。把這些能力留在應用程式 UI 後方,讓代理把使用者帶到畫面前,而不是帶過畫面。
長時間執行的背景工作。 App Intents 提供 ProgressReportingIntent 來呈現進度,MCP 也包含進度通知與任務原語,但兩種協定的呈現層都不是為消費型流程上的持續進度而設計的。MCP 工作階段中的主機模型,通常會在多分鐘的工具完成之前就先把推理鏈逾時。對於消費者導向的長時間工作,應透過應用程式自身的 UI 揭露該操作;讓協定回傳請求已加入佇列,並連結到狀態畫面。
任何觸及他人資料的操作。 兩種協定中的信任邊界都是呼叫端代理。Apple Intelligence 在使用者的 iCloud 帳號下執行。MCP 則在開發者所接入的任何憑證下執行。跨使用者的操作(分享、多帳號存取、管理員動作)透過任一協定都不安全,因為呼叫端的身分是錯誤的身分。
我會以不同方式建構什麼
了解上述路由規則後,我現在設計 Swift 應用程式的領域層,就像我設計服務邊界上的 API 一樣。領域方法接受型別化輸入並回傳型別化輸出,不內建任何協定假設。App Intents 以 @Parameter 結構描述與 perform() 黏合層薄薄地包覆領域方法。MCP 工具以 JSON 結構描述與 stdio 封包框架薄薄地包覆同樣的領域方法。兩種協定都是薄適配器;真正的工作在領域中。
由此衍生兩個結果。
呼叫者身分是領域層的事,不是協定層的事。 App Intent 主體接收系統解析的參數,並在使用者已走過系統 intent 叫用流程的情境下執行。MCP 工具主體則接收主機所安排的任何憑證。兩者都將其作為明確的 caller 引數傳遞至領域方法。領域方法負責執行授權、確認提示,以及任何其他領域不變式。任一協定都不能假裝呼叫者就是使用者。
兩個適配器揭露相同的能力集合。 哪些能力要開放給哪些呼叫者,這個決策應記錄在兩份清單裡,而不是散落在協定程式碼中。新增一項能力等於一個領域方法、兩個適配器包裝、兩筆清單條目。移除一項能力對稱進行。上述矩陣會成為一份真實的檔案。
未來幾年 Apple 平台的前沿並不是挑選其中一種協定,而是把兩者視為在同一個領域層上組合的正交合約。Apple Intelligence 代理對使用者有一套責任(在裝置上執行、會說 Siri、透過系統呈現)。外部 LLM 代理對開發者則有不同的責任(在何處執行皆可、會說 JSON-RPC、透過開發者選定的模型呈現)。兩者都應在您的應用程式中擁有型別化的介面,但兩者都不應是唯一的介面。
何時不該兩者都做
論點是雙向的。有些應用程式只需要其中一種協定,而非兩者。
沒有開發者介面的純消費型工具程式。 一支手電筒。一個鳥鳴辨識器。一個擴增實境捲尺。使用者可能想透過 Siri 叫用它(App Intents 有用),但沒有開發者會把它接入 LLM 工作流(MCP 只會是裝飾)。
沒有終端使用者介面的純開發者工具。 一個程式碼格式化的 MCP 伺服器。一個倉庫搜尋工具。一個套件版本檢查器。使用者就是 Claude Code 工作階段中的開發者;Siri 與 Apple Intelligence 並無角色。
對兩種代理類別都服務不佳的應用程式。 高度互動的遊戲、即時多人應用程式、價值在於進入應用程式並停留在螢幕前的應用程式。任一協定都不適合;正確答案是一個出色的應用程式,而沒有代理合約。
決策不是預設兩者皆做或擇其一。決策是這個應用程式是為什麼而生,還有誰可能想要操作它的領域。答案可能是兩者皆否、其中之一,或兩者皆是。如果領域層形態良好,建構其中一個的成本很小。在那個領域層之上建構兩個的成本同樣不大。當需求成立卻不建構其中之一時,代價是該能力在那個代理介面上完全缺席。
這個模式對 iOS 26+ Apple 堆疊意味著什麼
兩個重點。
-
把 App Intents 與 MCP 視為同一個領域上的正交合約,而非互相競爭的協定。 Apple Intelligence、Siri、捷徑與 Spotlight 是一類具系統層級責任的呼叫者。Claude、Cursor、ChatGPT 等則是另一類具工作階段層級責任的呼叫者。兩者都應有型別化存取權。底下的領域層並不會改變。
-
路由規則看的是誰在呼叫,不是要執行什麼。 App Intent 與 MCP 工具可以呼叫同一個 Swift 函式。它們在呼叫者所承擔的責任、所拿回的呈現,以及所期望的持久性上有所不同。把函式做對;讓協定層保持輕薄。
完整的 Apple Ecosystem 系列:App Intents(供 Apple Intelligence)、MCP 伺服器(供跨 LLM 代理)、Live Activities(用於鎖定畫面狀態機)、Liquid Glass 模式(用於視覺層),以及多平台出貨(用於跨裝置觸及)。系列總覽見 Apple Ecosystem 系列。如需更廣的 iOS 與 AI 代理脈絡,請參閱 iOS Agent Development guide。
FAQ
同一項能力,我什麼時候該建構 App Intent,什麼時候該建構 MCP 工具?
當這項能力應該觸及 Apple Intelligence、Siri、捷徑或 Spotlight 時,建構 App Intent。當這項能力應該觸及外部 LLM(Claude、ChatGPT、Claude Code 或 Cursor 中的代理)時,建構 MCP 工具。對於應該服務這兩類呼叫者的領域能力,在共用的 Swift 領域方法之上,建構兩者作為薄適配器。
App Intents 與 MCP 伺服器會互相競爭嗎?
不會。App Intents 是通往 Apple 第一方代理堆疊的路徑;MCP 是通往其他所有 LLM 的路徑。Apple Intelligence 不會呼叫 MCP 工具,外部 LLM 代理也無法直接叫用 App Intents(它們得透過系統)。兩種協定服務於不同的呼叫者類別,具有不同的信任模型、不同的延遲預算與不同的呈現介面。
一個應用程式可以同時透過兩種協定揭露其領域嗎?
可以,而且大多數想要完整代理觸及的非瑣碎應用程式都應該這麼做。Get Bananas(在 MCP 伺服器一文中介紹)與 Water(在 App Intents 一文中介紹)是早期的範例。其模式為下方一個領域層,上方則有 App Intent 適配器與 MCP 工具適配器。兩個適配器都呼叫同樣的 Swift 函式。
Apple Intelligence 追蹤了哪些 MCP 不會追蹤的狀態?
Apple Intelligence 會跨呼叫、工作階段與重開機追蹤 AppEntity 身分。實體模型賦予系統持久的引用,使用者可以跨 intent 串接這些引用。MCP 本身是具有生命週期管理的有狀態協定,Streamable HTTP 中也有工作階段 ID,但持久的領域身分是伺服器端的責任,而非協定的一級合約;主機模型無法從協定介面拿到 AppEntity 等價的識別碼。MCP 的 resources 概念支援持久參考資料,但運作於同樣由伺服器擁有的層級。
是否有些能力我不應該透過任一協定揭露?
是。UI 狀態能力(打開這個分頁、捲動到此處)、需要使用者本人在迴路中的能力(相機拍攝、生物特徵驗證、敏感輸入)、長時間執行的背景工作,以及跨多位使用者資料的操作,都應該留在應用程式 UI 後方。兩種協定對這些都只有薄弱的原語,且兩者都不具備跨使用者安全運作所需的信任訊號。
參考資料
-
Apple Developer, “App Intents framework”. 用於宣告 intent、實體、參數與查詢的介面,Apple Intelligence、Siri、捷徑與 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 Are Apple’s New API to Your App 與 Two Agent Ecosystems, One Shopping List 中的分析。雙適配器模式(一個 Swift 領域方法、兩個協定包裝)在兩篇文章中分別針對 Water 與 Get Bananas 的實作層級有所描述。 ↩