9月18日、PostmanのAnthony Viardが「SDK vs CLI vs MCP, a dev story」と題した記事を公開した。AIエージェントからGitHub APIを呼び出す際にSDK・CLI・MCPサーバーのどれを選ぶべきかを、トークンコストと認証モデルの観点から実測・比較した知見をまとめたものだ。
問いの立て方が間違っていた
Anthony Viardは社内向けのリリース準備チェッカーを構築している。毎朝「このリポジトリは安全にリリースできるか?」という問いに答えるツールで、実体は4つのGitHub APIコール(未解決PRの確認、最新コミットのCIステータス、ドラフトリリースの有無など)と短いサマリー生成だ。
このツールには3種類の呼び出し元がある。cronジョブ、手動実行、そしてClaude Codeのインタラクティブセッション。この3者が「それぞれ異なるものを必要としている」という事実に気づくまでに、Viardは一度SDKを選び、MCPに乗り換え、また考え直すという回り道をした。
「どのインターフェースがベストか」という問いをやめた。「誰が呼び出すか」という問いに替えたとき、答えが出た。
3つの選択肢の実態
SDK(Octokit)
型付きクライアントライブラリ。Viardが最初に選んだ手段で、現在もcronジョブが使っている。
import { Octokit } from "@octokit/rest";
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
const { data: pulls } = await octokit.rest.pulls.list({
owner: "postman-devrel",
repo: "release-tools",
state: "open",
per_page: 100,
});
型の恩恵は本物で、GitHubが昨春レスポンスのフィールド名を変更した際、TypeScriptがビルド時に検出した。深夜の障害ではなくCIで気づけた。
弱点は即興対応。エージェントがSDKを使うには、対象の呼び出しコードがあらかじめ書かれていなければならない。「先週木曜のリリースがなぜ止まったか調べて」という曖昧な指示には対応できない。エージェントがその場で新しいTypeScriptを書いて実行することはできるが、1回しか使わない問いのために毎回コンパイルするのは非効率だ。Viardはこれを2回試みて諦めた。
CLI(ghコマンド)
gh pr list \
--repo postman-devrel/release-tools \
--state open \
--json number,title,updatedAt,isDraft \
--jq '[.[] | select(.isDraft == false)] | length'
Claude Codeはすでにghに習熟していた。誰も教えていないのに、フラグが不明なときはgh pr list --helpを実行して自分で読む。ターミナルはエージェントがすでに持っているインターフェースなので、アダプターも設定も不要だ。
コスト面でも優秀で、エージェントが実行するまでコンテキストウィンドウを一切消費しない。--jqフィルターでPRオブジェクト6件の代わりに整数1つだけ返すといった制御も効く。
弱点は出力がテキストであること(パースが必要)、エラーが型例外でなく終了コードとstderrで返ること、そして**ghは常に1つのアイデンティティとして認証する**点だ。個人のマシンなら問題ないが、12人で共有するツールには向かない。
MCPサーバー
Model Context Protocol(MCP)は、モデルがツールを発見・呼び出すための標準仕様だ。設定は2行で済む:
{
"mcpServers": {
"github": {
"type": "http",
"url": "https://api.githubcopilot.com/mcp/"
}
}
}
即興的な探索には3つの中で最も優れた体験だった。構造化された引数を渡して構造化された結果が返る。シェルアウトも不要、パースも不要。
しかしトークンコストが問題になる。あるブログの計測によると、GitHub MCPサーバーのスキーマ・引数型・ペイロードのサイズは約42,000トークンに達する。3,000以上のサーバーを対象にした調査では中央値は約1,900トークンだが、実際によく使われる大規模サーバーを3つ接続すると、会話開始前に200,000トークンウィンドウの10%超を消費する。
対策はツールセットの絞り込みだ:
"args": [
"run", "-i", "--rm",
"-e", "GITHUB_PERSONAL_ACCESS_TOKEN",
"ghcr.io/github/github-mcp-server",
"--toolsets", "pull_requests,repos",
"--read-only"
]
--read-onlyフラグはすべての書き込みツールをスキップする。読み取りしかしないツールにとってはコスト削減と安全性向上が同時に得られる。
MCPをViardが手放さなかった最大の理由は認証モデルだ。プロトコルがユーザーごとの認証を持ち、サーバーは誰が呼び出しているかをログに残せる。これはシェルコマンドでは再現できない。
結論:3つは競合ではなく、同じAPIの3つの射影
Viardが行き着いた判断基準を表にまとめている:
| 観点 | SDK | CLI | MCPサーバー |
|---|---|---|---|
| 呼び出し元 | アプリケーションコード | オペレーターまたはシェル上のエージェント | クライアント経由のエージェント |
| ビルド時に確定 | Yes | No | No |
| アイドル時のコンテキスト消費 | なし | なし | ツール定義を常時ロード |
| レスポンス形式 | 型付きオブジェクト | フィルター可能なテキスト | 構造化結果 |
| 認証 | プロセス環境変数 | ローカルマシン認証 | ユーザーごと+監査ログ |
| 最適用途 | 出荷済みインテグレーション | 無人エージェント作業 | インタラクティブ・マルチユーザー |
Viardの最終的な答えは「3つ全部」だ。cronジョブはSDK、Claude Codeのインタラクティブセッションはgh、複数リポジトリをまたぐ探索的作業にはMCPサーバー。
3つを手で書かずに生成する
3つの表面を別々に手動メンテナンスするのは現実的でない。Viardは一度、内部APIのCLIラッパーを書きかけたが、それをSDKと、さらにAPIと永遠に同期し続けることを考えてエディタを閉じた。
解決策は1つのOpenAPI仕様から全部を生成することだ:
openapi.yml
|
|-- fern generate ........ SDKs(9言語)+ CLIをnpmに公開
|
+-- Postmanにインポート ... MCPジェネレーター ..... MCPサーバー
- Fern: OpenAPIファイルから9言語の型安全SDKと、npm公開CLIを生成する。
generators.ymlで同一グループに設定すれば、fern generate一発で両方が更新され、ズレが起きない。 - PostmanのMCPジェネレーター: 公開したいリクエストを選ぶと、各エンドポイントをツールに変換してMCPサーバーを出力する。Claude Code・Cursor・VS Code Copilotに対応。Postman API Networkに公開済みのAPIを対象としている(※内部向けAPIへの適用要件については元記事を直接参照されたい)。
openapi.ymlをバージョン管理に入れ、変更のたびにCIでfern generateを走らせ、同じトリガーでPostmanに再インポートする。仕様を1箇所だけ管理すれば3つの表面が自動で更新される。
実践的なハマりどころ
- モデルの性能低下と思ったらMCPサーバーのせいだった: Viardはある午後、Claude Codeの精度が落ちたと感じてプロンプトを書き直し続けた。実際にはその週に追加した2つのMCPサーバーがリポジトリのファイルツリーを保持していたコンテキストを圧迫していただけだった。プロンプトを疑う前にサーバーのトークンコストを確認すること。
- CLIは
--jsonフラグがあるかどうかで別物になる:gh pr listは人間向けテーブルを返す。gh pr list --jsonはデータを返す。エージェントに使わせるなら構造化出力フラグを--helpに明記すること(エージェントはそこを読む)。 --read-onlyは最も安価な安全策: 読み取りだけのツールなら迷わず有効化する。ツール定義のサイズも減る。
詳細はSDK vs CLI vs MCP, a dev storyを参照していただきたい。