---
title: "MCP か CLI かは問いの立て方が違います。状態がどこにあるかを問いましょう"
description: "Tobi Lütke は、MCP か CLI かの議論は間違ったレイヤーで行われていると言います。ステップ間で状態が保持されれば、どちらも機能するからです。同感です。同期の場合、その状態は REPL のヒープではなく連携そのもののレコードです。だからこそ Tajo の MCP ツールはセッションに何も保持しません。"
url: https://tajo.io/jp/blog/mcp-or-cli-is-the-wrong-question/
markdown: https://tajo.io/jp/blog/mcp-or-cli-is-the-wrong-question.md
lang: ja-JP
author: "Set Noa"
author_url: https://tajo.io/jp/set-noa/
published: 2026-11-04
updated: 2026-11-05
alternates: {en-US: /blog/mcp-or-cli-is-the-wrong-question.md, bg-BG: /bg/blog/mcp-or-cli-is-the-wrong-question.md, cs-CZ: /cz/blog/mcp-or-cli-is-the-wrong-question.md, de-DE: /de/blog/mcp-or-cli-is-the-wrong-question.md, da-DK: /dk/blog/mcp-or-cli-is-the-wrong-question.md, es-ES: /es/blog/mcp-or-cli-is-the-wrong-question.md, fr-FR: /fr/blog/mcp-or-cli-is-the-wrong-question.md, el-GR: /gr/blog/mcp-or-cli-is-the-wrong-question.md, hi-IN: /hi/blog/mcp-or-cli-is-the-wrong-question.md, id-ID: /in/blog/mcp-or-cli-is-the-wrong-question.md, hr-HR: /hr/blog/mcp-or-cli-is-the-wrong-question.md, hu-HU: /hu/blog/mcp-or-cli-is-the-wrong-question.md, it-IT: /it/blog/mcp-or-cli-is-the-wrong-question.md, ko-KR: /kr/blog/mcp-or-cli-is-the-wrong-question.md, ar-SA: /ar/blog/mcp-or-cli-is-the-wrong-question.md, nl-NL: /nl/blog/mcp-or-cli-is-the-wrong-question.md, no-NO: /no/blog/mcp-or-cli-is-the-wrong-question.md, pl-PL: /pl/blog/mcp-or-cli-is-the-wrong-question.md, pt-BR: /pt/blog/mcp-or-cli-is-the-wrong-question.md, ro-RO: /ro/blog/mcp-or-cli-is-the-wrong-question.md, ru-RU: /ru/blog/mcp-or-cli-is-the-wrong-question.md, sv-SE: /se/blog/mcp-or-cli-is-the-wrong-question.md, zh-CN: /zh-cn/blog/mcp-or-cli-is-the-wrong-question.md, sl-SI: /sl/blog/mcp-or-cli-is-the-wrong-question.md, sr-Latn-RS: /sr/blog/mcp-or-cli-is-the-wrong-question.md, th-TH: /th/blog/mcp-or-cli-is-the-wrong-question.md, tr-TR: /tr/blog/mcp-or-cli-is-the-wrong-question.md, ur-PK: /ur/blog/mcp-or-cli-is-the-wrong-question.md, vi-VN: /vi/blog/mcp-or-cli-is-the-wrong-question.md, sk-SK: /sk/blog/mcp-or-cli-is-the-wrong-question.md}
---

[AI エージェント](https://tajo.io/jp/blog/?category=ai-agents)

# MCP か CLI かは問いの立て方が違います。状態がどこにあるかを問いましょう

Tobi Lütke は、MCP か CLI かの議論は間違ったレイヤーで行われていると言います。ステップ間で状態が保持されれば、どちらも機能するからです。同感です。同期の場合、その状態は REPL のヒープではなく連携そのもののレコードです。だからこそ Tajo の MCP ツールはセッションに何も保持しません。

MCP CLI 比較

AI エージェントは MCP と CLI のどちらを使うべきですか？

MCP か CLI かはレイヤーが違うという Lütke の指摘は正しいです。ステップ間で状態が保持されればどちらも機能し、bash が勝つのはファイルシステムがその状態だからにすぎません。ただし同期の場合、状態は REPL のヒープではありません。連携そのもののレコード、つまりカーソル、保留中のプロポーザル、リース、プランです。だからこそ Tajo の MCP ツールはセッションに何も保持せず、どのクライアントのどのエージェントもレコードから続きを実行できます。Tajo は現在、顧客のコードを実行しておらず、REPL もありません。このことは以下で率直に説明します。

[詳しく見る](https://tajo.io/jp/blog/mcp-or-cli-is-the-wrong-question/#%E5%BD%BC%E3%81%8C%E8%A8%80%E3%81%86%E3%83%AC%E3%82%A4%E3%83%A4%E3%83%BC)

2026年9月21日、UTC 0時33分に、Tobi Lütke は次のように投稿しました。「LLM にとっての MCP か CLI かは、間違った議論です。人々は間違ったレイヤーで議論しています」。その後[10月23日](https://x.com/tobi/status/2113596638899974567)、彼は新しい [dquickts](https://github.com/stencil-hq/dquickts) というリポジトリを紹介しました。これは QuickJS のフォークで、`for` ループの途中でプログラムを一時停止し、ヒープ全体をバイト列に書き出し、別のプロセスで再開できます。「コードモードの環境として素晴らしい」と彼は書きました。

レイヤーについて、彼は正しいです。まず彼自身の言葉で彼に同意し、そのうえで、同期に使えることを言えるところまで主張を絞り込みたいと思います。

## 彼が言うレイヤー

彼が述べたとおりの議論を引用します。曲げるつもりの議論を引用するなら、これが唯一公正なやり方です。

> どちらも、状態を保持できる REPL のような環境を通して実行される限り、驚くほどうまく機能する。CLI の面白いところは、CLI ツールがふつう BASH を通して使われ、その BASH がたまたま永続状態（ファイルシステム）を持つ REPL であることだ。だから CLI は MCP よりずっとうまく機能する。だが、これは MCP の本質的な性質とはまったく関係がない。より良いハーネスが必要だというだけのことだ。

これは正しく、多くの人が説明のないまま気づいていたことを説明しています。CLI を与えられたエージェントは、ファイルを書き、3ステップ後にそれを読み戻し、grep し、パイプでつなぎます。素の MCP サーバーを与えられたエージェントは、1回の呼び出しにつき1つの結果を受け取り、それ以外のすべてをコンテキストウィンドウで持ち運ぶしかありません。CLI はプロトコルで勝ったのではありません。bash に物を置く場所が付いていたから勝ったのです。

彼の賭けはそこから導かれます。誰かが、組み込み可能な SQLite 型のエンジンを作り、bash、TypeScript、ツール呼び出しを1つのプランに変え、実行前に検査し、永続実行のために設計する、というものです。dquickts はそこへ向かう一歩です。[その README](https://github.com/stencil-hq/dquickts) は、「関数と変数が再起動後も残る」REPL について説明しています。永続的なホスト呼び出しについても説明しています。「スクリプトが、数時間後に応答が届くホスト関数を呼び出す。プロセスはスクリプトを保存して終了する」。

ここまでは完全に同意します。ここから絞り込みます。

## どの状態を、どの仕事のために

「状態を保持する」という言葉には問いが隠れています。誰の状態を、どのような形で保持するのか、という問いです。コーディングエージェントにとって、状態はリポジトリとシェルであり、再起動後も残る REPL は良い答えです。2つの SaaS システム間の同期を構築するエージェントにとって、ステップ間で必要な状態は仕事そのものによってすでに定義されています。それを運ぶのは4つのレコードで、どれもスクリプトの変数ではありません。

**カーソル。** 同期はソースをウィンドウ単位で読み取り、前回のウィンドウがどこで終わったかを知っている必要があります。Tajo の同期エンジンでは、これは同期とスコープごとに保存される[カーソル](https://tajo.io/jp/resources/help/troubleshooting/recover-a-stuck-or-failed-sync/#cursor)で、失敗したレコードが0件のバッチの後に書き込まれ、次の実行はそこから再開します。スケジュール実行は何よりも先に開始点を保存するため、初回の実行が失敗しても、次の実行は同じ地点から読み取ります。その背景にある原則、つまりテーブルではなく差分を送るという考え方は、[リバース ETL の解説](https://tajo.io/jp/blog/what-is-reverse-etl/)にあります。カーソルの話はそちらに任せます。

**保留中のプロポーザル。** Tajo のコネクタ MCP ツールでは、書き込みは2回の呼び出しです。`brevo_propose` は入力を宣言済みのアクションに照らして検証し、プロポーザルを記録します。何も送信されません。結果にはプロポーザル ID が含まれ、人が判断する必要がある場合は、それを承認するページも含まれます。`brevo_apply` は、その接続の承認済みの書き込みを順番に実行し、まだ人を待っている最初の書き込みで停止します。その間にあるレコードが状態です。レコードには入力とそのダイジェストが保持されるため、承認された書き込みを判断から apply までの間に編集することはできません。プロバイダーの応答値は保持しません。追記専用の承認レコードが個人データの保管場所になってはならないからです。

**リース。** 同期の1回の実行は、その全期間にわたって1つの[排他的なリース](https://tajo.io/jp/resources/help/troubleshooting/recover-a-stuck-or-failed-sync/#lease)を保持します。リースには所有者トークン、デフォルトで15分の時間制限、そして更新があります。古い所有者が新しいリースを解放することはできません。リースストアに到達できない場合、実行は推測せずに開始を拒否します。どのワーカーが実行を保持しているかは、どのプロセスのメモリでもなく、リースの中にある事実です。

**プラン。** `syncs.dryRun` はカーソルから次のウィンドウを読み取り、レコードごとに挿入、更新、削除、変更なしのどれかを判断し、ちょうど1行を書き込みます。それがプランで、1時間保持されます。カーソルは動かさず、どちらのシステムにも何も書き込みません。`syncs.apply` はそのプランを1回だけ書き込みます。カーソルが動いていた場合や、その間に許容される割合を超えるレコードが変わっていた場合は拒否します。繰り返し実行すると、再び書き込む代わりに最初の結果を返します。

これがステップ間の状態です。ID を持つ4つのレコードで、閲覧を許可されたどのエージェントでも読み取れます。

## MCP 側がセッションに何も保持しない理由

状態がレコードの中にあれば、セッションが保持すべきものは何も残りません。そのため Tajo の MCP エンドポイントはセッションを保持しません。リクエストごとに新しいサーバーと、セッション ID を持たないトランスポートが用意されます。読み取りは1回の呼び出しにつき1リクエストで、1つの応答がターンを埋め尽くさないよう結果には上限があります。書き込みは propose の後に apply で、持ち運ぶのはプロポーザルの ID です。

プロトコルも同じ方向に動きました。[MCP 仕様の2026-07-28改訂](https://modelcontextprotocol.io/specification/2026-07-28/changelog)は、Streamable HTTP トランスポートからプロトコルレベルのセッションと `Mcp-Session-Id` ヘッダーを削除しました。継続性が必要なサーバーは、いまでは自分でハンドルを発行し、それを通常の引数として受け取ります。プロポーザル ID とはまさにそれです。[MCP サーバーとは](https://tajo.io/jp/blog/what-is-an-mcp-server/)では、この改訂と、ツール呼び出しが永続的な同期とどう違うかを扱っています。この記事は状態がどこに置かれるかだけを扱います。

連携の仕事にとって重要な帰結はこうです。Claude Code のエージェントが書き込みを提案し、そのセッションが終了したとします。1時間後に人がそれを承認します。別のクライアントのエージェント、あるいは翌日の同じエージェントが ID を指定して apply を呼び出すと、書き込みは1回だけ反映されます。クラッシュしたエージェントが失うのはコンテキストウィンドウだけで、それ以外は何も失いません。何も一時停止されていないので、何も再開する必要がありませんでした。

## 私たちにないもの、そしてその代償

正当な反論は REPL 側から来て、次のようなものです。「たしかにレコードは残ります。でも、呼び出しのたびに前の呼び出しがすでに持っていたものを読み直します。スクリプトならコンタクトを1回取得し、結合し、絞り込み、1つの数字を返すでしょう。あなたたちのエージェントは5回呼び出し、そのたびに代償を払います」。この反論は仕組みについては正しく、私たちはその代償を測定していません。状態を持ち運ぶ場合と持ち運ばない場合でタスクごとの呼び出し回数を数える評価は、まだ作られていません。それができるまで、呼び出しモードのほうがトークンを多く消費するという主張には反論できないままです。

さらに3つのことを率直に述べておく必要があります。この記事を読み終えた読者が、それらがあると思い込まないようにするためです。

- **現在、顧客が書いたコードは実行されていません。** コネクタの読み取りは1回の呼び出しにつき1リクエストで、その経路にサンドボックスはありません。私たちのスタックにはコードランナーが存在しますが、それは社内ツールです。本番環境では2つの別々のフラグが設定されない限り無効で、管理者にしか応答しません。呼び出しごとに新しいコンテキスト、読み取りのみを許可するバインディング、そして同期コードに対する2秒の予算が与えられます。そのヘッダー自体が、これは正しさの境界でありセキュリティの境界ではない、と大文字で明記しています。
- **REPL はありません。** エージェントが1回の呼び出しで計算したものは、ID を持つレコードとして書き込まれない限り、次の呼び出しでは使えません。
- **永続的なエージェントセッションはありません。** クライアントは会話を保持します。Tajo はプロポーザル、プラン、カーソル、リースを保持します。3つ目のものはありません。

## 永続エンジンは何のためにあるのか

これはどれも dquickts に反対する主張ではありません。README にはスナップショットの用途が挙げられており、その一覧は良いものです。アイドル状態のスクリプトを休止させてメモリを解放する。長い計算を、空いているワーカーで分割して実行する。セットアップを繰り返す代わりに、すべてのインスタンスをスナップショットからウォームスタートする。後で応答が届くホスト呼び出しでスクリプトを待機させる。コーディングエージェントのワークスペースや、再起動後も残るべきノートブックには、これが適切なツールです。

それでも同期はこれに依存すべきではありません。理由は3つあり、README 自体がそれを教えてくれます。1つ目。「セッションファイルは、それを書き込んだ qjs ビルドにしか読み込めない」。あるエンジンのバージョンが書き込んだヒープは、アップグレード後には死んだファイルです。カーソルは文字列です。2つ目。「スナップショットはバイトコードと同じく信頼された入力である」、そして「悪意のあるものは検出されない」。保存されたヒープは守らなければならないものであって、レビューできるものではありません。プロポーザルは、人が承認する前に読む行です。3つ目。スナップショットは、顧客のレコードも含めて、スクリプトが読み取ったものをすべて保持します。上で述べたレコードは逆の考え方で作られています。プランは値ではなくダイジェストを保存し、プロポーザルのレコードはプロバイダーの応答を決して保存しません。

私たちが従うルールの1つは、決定的であることは巧妙であることに勝る、というものです。ループの途中から再開するヒープは巧妙です。カーソル、プロポーザル、リース、プランは地味で、検査でき、どのエンジンでも、どのクライアントでも、どのクラッシュの後でも同じです。

Lütke は正しい問いを立てました。状態はどこにあるのか。同期にとって、その答えがプロセスの中になることは決してありませんでした。答えはいつもレコードの中にあるはずでした。書き込みを承認する人がそれを読めなければならない場所が、そこだからです。

## よくある質問

**AI エージェントには MCP と CLI のどちらが適していますか？**

それ自体ではどちらでもありません。2026年9月21日の Tobi Lütke の指摘は正しく、ステップ間で状態を保持する仕組みを通して呼び出しが実行されれば、どちらも機能します。現在 CLI のほうが使いやすく感じられるのは、bash がファイルシステムに状態を保持するからです。違いはプロトコルではなく、ハーネスにあります。

**エージェントは同期のステップ間でどのような状態を必要としますか？**

連携そのもののレコードです。前回の実行がどこで止まったかを示すカーソル、人の承認を待つプロポーザル、どのワーカーが実行を保持しているかを示すリース、そして apply が一致しなければならないドライランのプランです。どれもスクリプトの変数ではありません。ID を持つ行です。

**なぜ Tajo の MCP サーバーはセッションに何も保持しないのですか？**

リクエストごとに新しいサーバーと、セッション ID を持たないトランスポートが用意されます。次の呼び出しに必要なものはすべてレコードです。プロポーザル ID、プラン ID、カーソルです。そのため、別のクライアントにいる2つ目のエージェントが1つ目のエージェントが止まったところから続きを実行でき、クラッシュしたエージェントが失うのは自分のコンテキストウィンドウだけです。

**Tajo はエージェントが書いたコードを実行しますか？**

顧客向けには実行しません。コネクタツールでの読み取りは1回の呼び出しにつき1リクエストで、サンドボックスはありません。存在するコードランナーは2つのフラグの背後にある社内ツールで、管理者だけが利用でき、読み取り専用のバインディングを持ち、呼び出しごとに新しいコンテキストを使います。このランナーは自らをセキュリティ境界ではないと明記しています。REPL も永続セッションもありません。

**dquickts のような永続的な JavaScript エンジンは何に向いていますか？**

README がその用途を挙げています。アイドル状態のスクリプトを休止させる、長い計算を複数のワーカーに分割する、スナップショットからウォームスタートする、数時間後に応答が届くホスト呼び出しでスクリプトを待機させる、といったものです。どれも実際の用途です。それでも同期はこれに依存すべきではありません。スナップショットは書き込んだエンジンのビルドにしか読み込めず、レビュー担当者には中身が見えず、スクリプトが読み取った顧客データをそのまま保持するからです。

## 関連記事

### [答えは正しい。しかし経路が間違っている。](https://tajo.io/jp/blog/the-answer-is-right-the-path-is-wrong/)

2026年10月8日 · 5 分読む

3 セクション

### [MCP のツール呼び出しごとに何をログに残すべきか](https://tajo.io/jp/blog/what-to-log-for-every-mcp-tool-call/)

2026年11月6日 · 10 分読む

6 セクション

### [MCP サーバーとは？ 2026年の MCP と MCP UI の仕組み](https://tajo.io/jp/blog/what-is-an-mcp-server/)

2026年10月3日 · 18 分読む

ステップ1 → ステップ6
