<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>tRPC 與 JSON-RPC：兩種輕量 RPC 的適用條件 on Tarragon</title><link>https://tarrragon.github.io/blog/backend/11-api-design/styles/rpc-revival/</link><description>Recent content in tRPC 與 JSON-RPC：兩種輕量 RPC 的適用條件 on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Fri, 03 Jul 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/backend/11-api-design/styles/rpc-revival/index.xml" rel="self" type="application/rss+xml"/><item><title>tRPC 型別共享：型別即契約的前提與代價</title><link>https://tarrragon.github.io/blog/backend/11-api-design/styles/rpc-revival/rpc-revival-trpc-type-sharing/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/backend/11-api-design/styles/rpc-revival/rpc-revival-trpc-type-sharing/</guid><description>&lt;p>tRPC 的選型位置是「前後端同倉、且都是 TypeScript」這個消費者形狀 —— 在這個形狀裡它把契約同步的成本壓到最低、離開這個形狀它的前提就不成立。做法是把 API 契約放進 TypeScript 型別系統本身：server 定義 router、client 直接推導出型別、中間沒有 IDL 檔、沒有 codegen。官方對這個定位的措辭是「build &amp;amp; consume fully typesafe APIs without schemas or code generation」（見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/rpc-trpc-design-philosophy/" data-link-title="11.C33 tRPC 設計哲學：無 schema 無 codegen 的型別共享" data-link-desc="把 API 契約從 IDL 檔搬進型別系統的極端點、官方自述的前提與代價（TS-only、同倉共置）">11.C33&lt;/a>、tRPC 官方文件）。以下拆這個換法的前提、代價、與什麼時候別用。&lt;/p>
&lt;h2 id="前提同倉-ts-only">前提：同倉 TS-only&lt;/h2>
&lt;p>型別推導能當契約、靠的是 client 與 server 在同一個 TypeScript 專案裡一起編譯 —— 這是 tRPC 官方 FAQ 自己標明的前提。脫離 monorepo、client 就失去「跟 server 型別一起運作」的保證；唯一的替代是把 backend 型別發成 private npm package 讓 client 依賴。這條前提直接鎖死兩個維度：語言鎖定 TypeScript（型別推導不跨語言）、部署形態鎖定同倉或私有套件。官方也自列一個能力邊界：動態型別輸出做不到、因為它需要 higher-kinded types（型別的型別、TypeScript 型別系統目前還沒到的能力上限）。&lt;/p>
&lt;p>落到操作、判斷很乾脆：消費者是不是你自己團隊、跟 server 共用同一個 TS 倉。是 —— tRPC 的零 codegen 是真優勢；不是 —— 前提不成立、優勢歸零。作者社群把這條邊界說得很白：tRPC 無法有效服務公開的第三方 API（見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-echobind-trpc-retreat/" data-link-title="11.C23 Echobind：從 GraphQL 撤到 tRPC 的量化帳（反例）" data-link-desc="反例：五層重複宣告與三層 codegen 拖垮 DX 的量化紀錄、同時自列 tRPC 的適用前提">11.C23&lt;/a> 作者自列）。公開 API 的消費者是你控制不了的匿名開發者、他們沒有你的型別、也不會為了呼叫你裝一個 TS 專案 —— 這個形狀要把答案推回 HTTP+JSON。&lt;/p>
&lt;h2 id="代價的另一面契約中介層何時變純開銷">代價的另一面：契約中介層何時變純開銷&lt;/h2>
&lt;p>tRPC 的優勢反過來看、是「契約中介層」在單一團隊場景下的成本。一個真實團隊的量化帳可以把這個成本說清楚：Echobind 從 GraphQL 遷到 tRPC 前、同一份資料形狀要在 Prisma、Nexus、GraphQL operations、codegen types、client queries 五層各宣告一次；三層 codegen 產出 8,200 行型別檔、常需重啟編輯器的 language server；依賴體積 GraphQL 側 81.2kb、tRPC 側 23.7kb；遷移後淨減 1,608 行程式碼（見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-echobind-trpc-retreat/" data-link-title="11.C23 Echobind：從 GraphQL 撤到 tRPC 的量化帳（反例）" data-link-desc="反例：五層重複宣告與三層 codegen 拖垮 DX 的量化紀錄、同時自列 tRPC 的適用前提">11.C23&lt;/a>）。&lt;/p>
&lt;p>這些數字劃出一條判準：schema 這個中介層、在「跨團隊、跨 client 的契約」場景是價值、在「單一團隊同時擁有前後端」場景變純開銷。判準的對象是 schema 的適用場景，跟 tRPC 對 GraphQL 的優劣排名無關。同構 TypeScript 的單團隊、多維護一份 schema 換不到跨方協調的好處、只多出五層宣告要同步。判讀訊號因此是「這份 schema 在協調誰」：協調不同團隊或不同語言的 client、留著；只在協調你自己前後端、它是可以拿掉的中間層。但「單團隊」不直接等於「拿掉」—— 需要對外 API 文件、要跑 contract test、或預期未來出現非 TypeScript 消費者（mobile、第三方）時、schema 仍賺得回維護成本；拿掉的前提是這三者都不成立。同一份帳也出現在 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/graphql/graphql-public-api-tradeoffs/" data-link-title="公開 API 的 GraphQL 進退" data-link-desc="GitHub 雙軌、Shopify all-in、與撤退案例 — 同一技術不同結局的情境變數、GraphQL 的適用邊界">公開 API 的 GraphQL 進退&lt;/a> 的適用邊界段、兩章從 GraphQL 與 tRPC 兩側看同一條界線。&lt;/p>
&lt;h2 id="schema-first-vs-inference-first契約放在相反的地方">schema-first vs inference-first：契約放在相反的地方&lt;/h2>
&lt;p>tRPC 與 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/grpc/grpc-proto-evolution-discipline/" data-link-title="gRPC proto 演進紀律：編碼層相容與 CI gate" data-link-desc="要改 proto 又得保證 wire 相容、並想把相容檢查落成 merge 前 CI gate、選檢查等級時的判準">gRPC 的 proto&lt;/a> 都在解同一個問題 —— 契約怎麼跨 client 與 server 同步 —— 但把契約放在相反的地方。protobuf 走 schema-first：契約是一份外置的 IDL 檔、跨語言、可用 CI 做 breaking 檢查、代價是要維護檔案與 codegen。tRPC 走 inference-first：契約是型別推導的結果、零 codegen、代價是鎖定單一語言與同倉。這組對照是演進成本這條選型軸的具體兩極：團隊承擔得起「外置 schema 的維護」還是需要「零 codegen 的即時同步」、判準見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/api-style-selection/" data-link-title="11.2 風格選型總覽" data-link-desc="REST 式 HTTP&amp;#43;JSON、GraphQL、gRPC、tRPC、JSON-RPC、event 之間選哪個 — 用消費者形狀、演進成本、操作可及性三軸判讀">11.2&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>tRPC 的選型位置是「前後端同倉、且都是 TypeScript」這個消費者形狀 —— 在這個形狀裡它把契約同步的成本壓到最低、離開這個形狀它的前提就不成立。做法是把 API 契約放進 TypeScript 型別系統本身：server 定義 router、client 直接推導出型別、中間沒有 IDL 檔、沒有 codegen。官方對這個定位的措辭是「build &amp; consume fully typesafe APIs without schemas or code generation」（見 <a href="/blog/backend/11-api-design/cases/rpc-trpc-design-philosophy/" data-link-title="11.C33 tRPC 設計哲學：無 schema 無 codegen 的型別共享" data-link-desc="把 API 契約從 IDL 檔搬進型別系統的極端點、官方自述的前提與代價（TS-only、同倉共置）">11.C33</a>、tRPC 官方文件）。以下拆這個換法的前提、代價、與什麼時候別用。</p>
<h2 id="前提同倉-ts-only">前提：同倉 TS-only</h2>
<p>型別推導能當契約、靠的是 client 與 server 在同一個 TypeScript 專案裡一起編譯 —— 這是 tRPC 官方 FAQ 自己標明的前提。脫離 monorepo、client 就失去「跟 server 型別一起運作」的保證；唯一的替代是把 backend 型別發成 private npm package 讓 client 依賴。這條前提直接鎖死兩個維度：語言鎖定 TypeScript（型別推導不跨語言）、部署形態鎖定同倉或私有套件。官方也自列一個能力邊界：動態型別輸出做不到、因為它需要 higher-kinded types（型別的型別、TypeScript 型別系統目前還沒到的能力上限）。</p>
<p>落到操作、判斷很乾脆：消費者是不是你自己團隊、跟 server 共用同一個 TS 倉。是 —— tRPC 的零 codegen 是真優勢；不是 —— 前提不成立、優勢歸零。作者社群把這條邊界說得很白：tRPC 無法有效服務公開的第三方 API（見 <a href="/blog/backend/11-api-design/cases/graphql-echobind-trpc-retreat/" data-link-title="11.C23 Echobind：從 GraphQL 撤到 tRPC 的量化帳（反例）" data-link-desc="反例：五層重複宣告與三層 codegen 拖垮 DX 的量化紀錄、同時自列 tRPC 的適用前提">11.C23</a> 作者自列）。公開 API 的消費者是你控制不了的匿名開發者、他們沒有你的型別、也不會為了呼叫你裝一個 TS 專案 —— 這個形狀要把答案推回 HTTP+JSON。</p>
<h2 id="代價的另一面契約中介層何時變純開銷">代價的另一面：契約中介層何時變純開銷</h2>
<p>tRPC 的優勢反過來看、是「契約中介層」在單一團隊場景下的成本。一個真實團隊的量化帳可以把這個成本說清楚：Echobind 從 GraphQL 遷到 tRPC 前、同一份資料形狀要在 Prisma、Nexus、GraphQL operations、codegen types、client queries 五層各宣告一次；三層 codegen 產出 8,200 行型別檔、常需重啟編輯器的 language server；依賴體積 GraphQL 側 81.2kb、tRPC 側 23.7kb；遷移後淨減 1,608 行程式碼（見 <a href="/blog/backend/11-api-design/cases/graphql-echobind-trpc-retreat/" data-link-title="11.C23 Echobind：從 GraphQL 撤到 tRPC 的量化帳（反例）" data-link-desc="反例：五層重複宣告與三層 codegen 拖垮 DX 的量化紀錄、同時自列 tRPC 的適用前提">11.C23</a>）。</p>
<p>這些數字劃出一條判準：schema 這個中介層、在「跨團隊、跨 client 的契約」場景是價值、在「單一團隊同時擁有前後端」場景變純開銷。判準的對象是 schema 的適用場景，跟 tRPC 對 GraphQL 的優劣排名無關。同構 TypeScript 的單團隊、多維護一份 schema 換不到跨方協調的好處、只多出五層宣告要同步。判讀訊號因此是「這份 schema 在協調誰」：協調不同團隊或不同語言的 client、留著；只在協調你自己前後端、它是可以拿掉的中間層。但「單團隊」不直接等於「拿掉」—— 需要對外 API 文件、要跑 contract test、或預期未來出現非 TypeScript 消費者（mobile、第三方）時、schema 仍賺得回維護成本；拿掉的前提是這三者都不成立。同一份帳也出現在 <a href="/blog/backend/11-api-design/styles/graphql/graphql-public-api-tradeoffs/" data-link-title="公開 API 的 GraphQL 進退" data-link-desc="GitHub 雙軌、Shopify all-in、與撤退案例 — 同一技術不同結局的情境變數、GraphQL 的適用邊界">公開 API 的 GraphQL 進退</a> 的適用邊界段、兩章從 GraphQL 與 tRPC 兩側看同一條界線。</p>
<h2 id="schema-first-vs-inference-first契約放在相反的地方">schema-first vs inference-first：契約放在相反的地方</h2>
<p>tRPC 與 <a href="/blog/backend/11-api-design/styles/grpc/grpc-proto-evolution-discipline/" data-link-title="gRPC proto 演進紀律：編碼層相容與 CI gate" data-link-desc="要改 proto 又得保證 wire 相容、並想把相容檢查落成 merge 前 CI gate、選檢查等級時的判準">gRPC 的 proto</a> 都在解同一個問題 —— 契約怎麼跨 client 與 server 同步 —— 但把契約放在相反的地方。protobuf 走 schema-first：契約是一份外置的 IDL 檔、跨語言、可用 CI 做 breaking 檢查、代價是要維護檔案與 codegen。tRPC 走 inference-first：契約是型別推導的結果、零 codegen、代價是鎖定單一語言與同倉。這組對照是演進成本這條選型軸的具體兩極：團隊承擔得起「外置 schema 的維護」還是需要「零 codegen 的即時同步」、判準見 <a href="/blog/backend/11-api-design/api-style-selection/" data-link-title="11.2 風格選型總覽" data-link-desc="REST 式 HTTP&#43;JSON、GraphQL、gRPC、tRPC、JSON-RPC、event 之間選哪個 — 用消費者形狀、演進成本、操作可及性三軸判讀">11.2</a>。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>契約外置的對照路線：<a href="/blog/backend/11-api-design/styles/grpc/grpc-proto-evolution-discipline/" data-link-title="gRPC proto 演進紀律：編碼層相容與 CI gate" data-link-desc="要改 proto 又得保證 wire 相容、並想把相容檢查落成 merge 前 CI gate、選檢查等級時的判準">gRPC proto 演進紀律</a></li>
<li>另一種輕量 RPC 的適用條件：<a href="/blog/backend/11-api-design/styles/rpc-revival/rpc-revival-jsonrpc-conditions/" data-link-title="JSON-RPC 的適用條件：最小夠用的訊息層" data-link-desc="本地雙向低頻、需 notification 語意、生態要求零 codegen 可自省 —— 這組條件下 JSON-RPC 比重型 RPC 更貼">JSON-RPC 的適用條件</a></li>
<li>同一條邊界的 GraphQL 側：<a href="/blog/backend/11-api-design/styles/graphql/graphql-public-api-tradeoffs/" data-link-title="公開 API 的 GraphQL 進退" data-link-desc="GitHub 雙軌、Shopify all-in、與撤退案例 — 同一技術不同結局的情境變數、GraphQL 的適用邊界">公開 API 的 GraphQL 進退</a></li>
<li>三軸選型判準：<a href="/blog/backend/11-api-design/api-style-selection/" data-link-title="11.2 風格選型總覽" data-link-desc="REST 式 HTTP&#43;JSON、GraphQL、gRPC、tRPC、JSON-RPC、event 之間選哪個 — 用消費者形狀、演進成本、操作可及性三軸判讀">11.2 風格選型總覽</a></li>
<li>案例原文：<a href="/blog/backend/11-api-design/cases/" data-link-title="模組十一案例庫：API 設計與對外契約" data-link-desc="API 風格流派、版本與相容、介面語意、規範治理的已驗證公開案例集；含反例與覆蓋缺口標明">模組十一案例庫</a></li>
</ul>
]]></content:encoded></item><item><title>JSON-RPC 的適用條件：最小夠用的訊息層</title><link>https://tarrragon.github.io/blog/backend/11-api-design/styles/rpc-revival/rpc-revival-jsonrpc-conditions/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/backend/11-api-design/styles/rpc-revival/rpc-revival-jsonrpc-conditions/</guid><description>&lt;p>JSON-RPC 落在一組很具體的條件上：本地 process 之間、雙向、低頻、需要 notification（不等回應的單向通知）語意、且生態工具要求零 codegen、可自省。這組條件湊齊時、JSON-RPC 的「最小夠用訊息層」剛好夠、而重型 RPC 的能力反而變成負擔。以下界定這組條件、並用兩份現代協議的採用當實證。&lt;/p>
&lt;h2 id="條件組合什麼時候最小訊息層剛好夠">條件組合：什麼時候最小訊息層剛好夠&lt;/h2>
&lt;p>JSON-RPC 只規定訊息的形狀：一個 request 帶 method、params、id、一個對應的 response、以及沒有 id 的 notification。它不管傳輸（誰負責搬 bytes 由外層決定）、不強制 schema、不要 codegen。這個「少」在對外 web API 是缺點 —— 缺工具生態、缺標準化的錯誤與分頁 —— 但在下面這組條件裡剛好是優點：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>本地 process 間&lt;/strong>：傳輸是 stdio 或本機 socket、不需要 HTTP/2 的 multiplexing 與流量控制。&lt;/li>
&lt;li>&lt;strong>雙向且需要 notification&lt;/strong>：server 要能主動推事件給 client（編輯器的診斷、agent 的進度）、JSON-RPC 的 notification 原生支援這個語意。&lt;/li>
&lt;li>&lt;strong>零 codegen、可自省&lt;/strong>：工具（編輯器外掛、agent runtime）要能不經 build pipeline 就發一個請求、JSON 純文字可讀、不必先產 client stub。&lt;/li>
&lt;/ul>
&lt;p>這組條件下、gRPC 的 HTTP/2 加 codegen 成本全是負資產（此為選型判讀、見下方對照）—— 你付了重量、換不到對應的價值。&lt;/p>
&lt;p>JSON-RPC 不限於本地 —— Ethereum 節點的 JSON-RPC API 就是網路遠端、走 HTTP、也不低頻。本篇 scope 到「本地雙向低頻」、是因為那是它明顯勝過 gRPC 的區間、不是 JSON-RPC 的全部適用面。&lt;/p>
&lt;h2 id="實證lsp-與-mcp-都在-json-rpc-上加約束">實證：LSP 與 MCP 都在 JSON-RPC 上加約束&lt;/h2>
&lt;p>兩份現代協議在這組條件下選了 JSON-RPC、而且都是「在它上面加約束」而非發明新協議 —— 這個做法本身是選型訊號。LSP（編輯器與 language server 的協議）明文用 JSON-RPC 描述 requests、responses、notifications、固定 &lt;code>jsonrpc: &amp;quot;2.0&amp;quot;&lt;/code>、外層自訂 Content-Length header 當傳輸框（見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/rpc-jsonrpc-lsp-mcp-revival/" data-link-title="11.C34 JSON-RPC 重生：LSP 與 MCP 都選它當訊息層" data-link-desc="死在 web API、活在編輯器與 agent 協議：最小夠用訊息層的選型條件組合">11.C34&lt;/a>）。MCP（agent 與工具的協議、2025-06-18 spec）規定所有訊息 MUST follow JSON-RPC 2.0、並在其上收緊約束（request ID 不可為 null、同 session 不可重用）、傳輸支援 stdio 與 HTTP。&lt;/p>
&lt;p>這裡有一個引用邊界要標明：兩份 spec 都只陳述「採用 JSON-RPC」這個事實、沒有寫「為什麼選它」的理由段。上一節那組條件是本模組從採用事實反推的判讀、不是 spec 的原話。能直接學的做法是：選一個最小夠用的訊息層、然後在它上面加你自己場景需要的約束（ID 語意、session 規則）、而不是為每個新協議重造訊息結構。&lt;/p>
&lt;h2 id="對照與-grpctrpc-的分工">對照：與 gRPC、tRPC 的分工&lt;/h2>
&lt;p>JSON-RPC 跟 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/grpc/grpc-internal-rpc-selection/" data-link-title="gRPC 內部 RPC 的選型位置：框架層集中的組織前提" data-link-desc="gRPC 值得選的判準是要不要一個框架層集中點、不是序列化效能；用規模兩端判讀何時集中價值蓋過 debug 代價">gRPC&lt;/a> 服務的是不同 deployment shape：gRPC 落在跨服務、高吞吐、要框架層集中的位置；JSON-RPC 落在本地、雙向、低頻的位置。兩者不是競爭、是消費者形狀這條軸的不同列 —— 判準見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/api-style-selection/" data-link-title="11.2 風格選型總覽" data-link-desc="REST 式 HTTP&amp;#43;JSON、GraphQL、gRPC、tRPC、JSON-RPC、event 之間選哪個 — 用消費者形狀、演進成本、操作可及性三軸判讀">11.2&lt;/a>。&lt;/p>
&lt;p>還有一個當代共性值得點出：MCP 的 schema 以 TypeScript 為 source of truth、跟 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/rpc-revival/rpc-revival-trpc-type-sharing/" data-link-title="tRPC 型別共享：型別即契約的前提與代價" data-link-desc="tRPC 靠 TypeScript 型別推導同步契約、零 codegen；適用同倉 TS-only、公開第三方 API 不適用">tRPC&lt;/a> 用型別系統當契約、都指向「用 TypeScript 型別當契約源頭」的模式。這是觀察到的趨勢共性、不是說兩者可互換 —— MCP 仍是跨語言協議、tRPC 綁單語言。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>高吞吐跨服務的對照位置：&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/grpc/grpc-internal-rpc-selection/" data-link-title="gRPC 內部 RPC 的選型位置：框架層集中的組織前提" data-link-desc="gRPC 值得選的判準是要不要一個框架層集中點、不是序列化效能；用規模兩端判讀何時集中價值蓋過 debug 代價">gRPC 內部 RPC 的選型位置&lt;/a>&lt;/li>
&lt;li>型別當契約的同倉路線：&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/rpc-revival/rpc-revival-trpc-type-sharing/" data-link-title="tRPC 型別共享：型別即契約的前提與代價" data-link-desc="tRPC 靠 TypeScript 型別推導同步契約、零 codegen；適用同倉 TS-only、公開第三方 API 不適用">tRPC 型別共享&lt;/a>&lt;/li>
&lt;li>三軸選型判準：&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/api-style-selection/" data-link-title="11.2 風格選型總覽" data-link-desc="REST 式 HTTP&amp;#43;JSON、GraphQL、gRPC、tRPC、JSON-RPC、event 之間選哪個 — 用消費者形狀、演進成本、操作可及性三軸判讀">11.2 風格選型總覽&lt;/a>&lt;/li>
&lt;li>案例原文：&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/" data-link-title="模組十一案例庫：API 設計與對外契約" data-link-desc="API 風格流派、版本與相容、介面語意、規範治理的已驗證公開案例集；含反例與覆蓋缺口標明">模組十一案例庫&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>JSON-RPC 落在一組很具體的條件上：本地 process 之間、雙向、低頻、需要 notification（不等回應的單向通知）語意、且生態工具要求零 codegen、可自省。這組條件湊齊時、JSON-RPC 的「最小夠用訊息層」剛好夠、而重型 RPC 的能力反而變成負擔。以下界定這組條件、並用兩份現代協議的採用當實證。</p>
<h2 id="條件組合什麼時候最小訊息層剛好夠">條件組合：什麼時候最小訊息層剛好夠</h2>
<p>JSON-RPC 只規定訊息的形狀：一個 request 帶 method、params、id、一個對應的 response、以及沒有 id 的 notification。它不管傳輸（誰負責搬 bytes 由外層決定）、不強制 schema、不要 codegen。這個「少」在對外 web API 是缺點 —— 缺工具生態、缺標準化的錯誤與分頁 —— 但在下面這組條件裡剛好是優點：</p>
<ul>
<li><strong>本地 process 間</strong>：傳輸是 stdio 或本機 socket、不需要 HTTP/2 的 multiplexing 與流量控制。</li>
<li><strong>雙向且需要 notification</strong>：server 要能主動推事件給 client（編輯器的診斷、agent 的進度）、JSON-RPC 的 notification 原生支援這個語意。</li>
<li><strong>零 codegen、可自省</strong>：工具（編輯器外掛、agent runtime）要能不經 build pipeline 就發一個請求、JSON 純文字可讀、不必先產 client stub。</li>
</ul>
<p>這組條件下、gRPC 的 HTTP/2 加 codegen 成本全是負資產（此為選型判讀、見下方對照）—— 你付了重量、換不到對應的價值。</p>
<p>JSON-RPC 不限於本地 —— Ethereum 節點的 JSON-RPC API 就是網路遠端、走 HTTP、也不低頻。本篇 scope 到「本地雙向低頻」、是因為那是它明顯勝過 gRPC 的區間、不是 JSON-RPC 的全部適用面。</p>
<h2 id="實證lsp-與-mcp-都在-json-rpc-上加約束">實證：LSP 與 MCP 都在 JSON-RPC 上加約束</h2>
<p>兩份現代協議在這組條件下選了 JSON-RPC、而且都是「在它上面加約束」而非發明新協議 —— 這個做法本身是選型訊號。LSP（編輯器與 language server 的協議）明文用 JSON-RPC 描述 requests、responses、notifications、固定 <code>jsonrpc: &quot;2.0&quot;</code>、外層自訂 Content-Length header 當傳輸框（見 <a href="/blog/backend/11-api-design/cases/rpc-jsonrpc-lsp-mcp-revival/" data-link-title="11.C34 JSON-RPC 重生：LSP 與 MCP 都選它當訊息層" data-link-desc="死在 web API、活在編輯器與 agent 協議：最小夠用訊息層的選型條件組合">11.C34</a>）。MCP（agent 與工具的協議、2025-06-18 spec）規定所有訊息 MUST follow JSON-RPC 2.0、並在其上收緊約束（request ID 不可為 null、同 session 不可重用）、傳輸支援 stdio 與 HTTP。</p>
<p>這裡有一個引用邊界要標明：兩份 spec 都只陳述「採用 JSON-RPC」這個事實、沒有寫「為什麼選它」的理由段。上一節那組條件是本模組從採用事實反推的判讀、不是 spec 的原話。能直接學的做法是：選一個最小夠用的訊息層、然後在它上面加你自己場景需要的約束（ID 語意、session 規則）、而不是為每個新協議重造訊息結構。</p>
<h2 id="對照與-grpctrpc-的分工">對照：與 gRPC、tRPC 的分工</h2>
<p>JSON-RPC 跟 <a href="/blog/backend/11-api-design/styles/grpc/grpc-internal-rpc-selection/" data-link-title="gRPC 內部 RPC 的選型位置：框架層集中的組織前提" data-link-desc="gRPC 值得選的判準是要不要一個框架層集中點、不是序列化效能；用規模兩端判讀何時集中價值蓋過 debug 代價">gRPC</a> 服務的是不同 deployment shape：gRPC 落在跨服務、高吞吐、要框架層集中的位置；JSON-RPC 落在本地、雙向、低頻的位置。兩者不是競爭、是消費者形狀這條軸的不同列 —— 判準見 <a href="/blog/backend/11-api-design/api-style-selection/" data-link-title="11.2 風格選型總覽" data-link-desc="REST 式 HTTP&#43;JSON、GraphQL、gRPC、tRPC、JSON-RPC、event 之間選哪個 — 用消費者形狀、演進成本、操作可及性三軸判讀">11.2</a>。</p>
<p>還有一個當代共性值得點出：MCP 的 schema 以 TypeScript 為 source of truth、跟 <a href="/blog/backend/11-api-design/styles/rpc-revival/rpc-revival-trpc-type-sharing/" data-link-title="tRPC 型別共享：型別即契約的前提與代價" data-link-desc="tRPC 靠 TypeScript 型別推導同步契約、零 codegen；適用同倉 TS-only、公開第三方 API 不適用">tRPC</a> 用型別系統當契約、都指向「用 TypeScript 型別當契約源頭」的模式。這是觀察到的趨勢共性、不是說兩者可互換 —— MCP 仍是跨語言協議、tRPC 綁單語言。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>高吞吐跨服務的對照位置：<a href="/blog/backend/11-api-design/styles/grpc/grpc-internal-rpc-selection/" data-link-title="gRPC 內部 RPC 的選型位置：框架層集中的組織前提" data-link-desc="gRPC 值得選的判準是要不要一個框架層集中點、不是序列化效能；用規模兩端判讀何時集中價值蓋過 debug 代價">gRPC 內部 RPC 的選型位置</a></li>
<li>型別當契約的同倉路線：<a href="/blog/backend/11-api-design/styles/rpc-revival/rpc-revival-trpc-type-sharing/" data-link-title="tRPC 型別共享：型別即契約的前提與代價" data-link-desc="tRPC 靠 TypeScript 型別推導同步契約、零 codegen；適用同倉 TS-only、公開第三方 API 不適用">tRPC 型別共享</a></li>
<li>三軸選型判準：<a href="/blog/backend/11-api-design/api-style-selection/" data-link-title="11.2 風格選型總覽" data-link-desc="REST 式 HTTP&#43;JSON、GraphQL、gRPC、tRPC、JSON-RPC、event 之間選哪個 — 用消費者形狀、演進成本、操作可及性三軸判讀">11.2 風格選型總覽</a></li>
<li>案例原文：<a href="/blog/backend/11-api-design/cases/" data-link-title="模組十一案例庫：API 設計與對外契約" data-link-desc="API 風格流派、版本與相容、介面語意、規範治理的已驗證公開案例集；含反例與覆蓋缺口標明">模組十一案例庫</a></li>
</ul>
]]></content:encoded></item></channel></rss>