NodeFlareの仕組み
builder、stdio→HTTPアダプタ、proxy、サンドボックス化されたコードランナー ― NodeFlareを「汎用コンテナホスト」ではなく「MCPネイティブなプラットフォーム」たらしめている内部構造を解説します。
汎用コンテナではなく、MCPのために作られている
NodeFlareは Model Context Protocol(MCP)サーバーに特化したホスティングプラットフォームです。リポジトリのビルド方法、公開の仕方、リクエストのルーティング ― すべての層がMCPのセマンティクス(initialize / tools/list / tools/call / resources/* / prompts/* といったJSON-RPCメソッド、MCPセッション、ツール単位・リソース単位の認可)を理解しています。
汎用PaaSが渡してくれるのは「コンテナとポート」だけです。NodeFlareは、stdin/stdoutしか話せないMCPサーバーであっても、認証・スコープ制御・トークン最適化が効いた Streamable HTTP エンドポイントに変換し、あらゆるMCPクライアントがURLひとつで接続できるようにします。
このページは実際の内部構造をそのまま記述します:Rust製のbuilder、注入されるNode製のstdioアダプタ、Rust製のproxy、そしてAIが書いたコードを実行するDenoサンドボックス。抽象的なマーケティング表現ではなく、あなたのサーバーを動かしている実コードに直接対応しています。
全体アーキテクチャ
処理を担うのは3つのコンポーネントです。Builder はGitHubリポジトリを稼働中のFly.ioマシンに変えます。(ビルド時に注入される)stdioアダプタは stdin/stdout のMCPサーバーをラップし、代わりにHTTPを話します。Proxy はすべてのサーバーの前段に立ち、認証・スコープ・キャッシュ・セッションアフィニティ・クォータ・MCP固有のレスポンス変換を担います。4つ目の任意コンポーネントである Runner は、AIが書いたコードを厳重に隔離したサンドボックスで実行します。
MCP client (Claude Desktop / Cursor / your agent)
│ Streamable HTTP · legacy SSE (JSON-RPC 2.0)
▼
┌──────────────────────────────────────────────┐
│ PROXY (Rust) │
│ API key / OAuth · per-request scope check │
│ caller-scoped cache · session affinity · │
│ rate limit + monthly quota · tool transforms │
└───────────────────────┬──────────────────────┘
│ POST {endpoint}/mcp (pinned to one Fly machine)
▼
┌──────────────────────────────────────────────┐
│ stdio-adapter.cjs (Node, injected at build) │
│ stdin/stdout <-> Streamable HTTP │
└───────────────────────┬──────────────────────┘
│ JSON-RPC over stdio
▼
┌──────────────────────────────────────────────┐
│ YOUR MCP SERVER │
│ node · python · go · rust · docker │
└──────────────────────────────────────────────┘
BUILDER (Rust) GitHub repo -> detect -> Dockerfile -> Fly image -> deploy -> verify initialize
RUNNER (Deno) run_code -> Firecracker sandbox -> tools.* -> proxy (scope re-checked per call)proxyは常に endpoint_url に対してHTTPを話すだけなので、完全に言語非依存です。任意の言語のMCPサーバーをそのHTTPの世界へ橋渡しするのが stdioアダプタの役割です。
Builder:GitHubリポジトリから稼働中のMCPへ
builderはRust製のサービスで、特定コミットのリポジトリをクローンし、ビルド・起動方法を判定し、Dockerfileを生成し、Fly.io上でリモートビルドし、マシンをデプロイし、最後にMCPサーバーが本当に応答することを検証します。
- 1
ソース取得
GitHub Appのtarball(または公開git)でクローン。root_directoryはパストラバーサル対策付きで解決し、モノレポ(npm/yarn/pnpmワークスペース)を検出して、リポジトリ直下に巻き上げられた依存も解決できるようにします。
- 2
ランタイムとコマンドの判定
manifest(package.json、pyproject.toml / requirements.txt / uv.lock、go.mod、Cargo.toml)から node/python/go/rust/docker を推定します。パッケージマネージャ(npm/pnpm)や起動・ビルドコマンドも自動導出します(Nodeは scripts.start → main → bin、Pythonは [project.scripts] や server.py / main.py など)。明示した entry_command / build_command は常に優先されます。
- 3
Dockerfile生成
ランタイム別のDockerfileを生成します(あるいは持ち込みのDockerfileを検証・ラップ)。stdioサーバーの場合、stdioアダプタをコピーしてコンテナのエントリポイントにします。
- 4
シークレット注入
環境変数はDBから復号し、argvではなくstdin経由でFlyに渡します。ビルドログには出さず、既知のトークン形状を伏せ字にするredaction処理も走ります。
- 5
ビルドとデプロイ
イメージをリモートビルドし(flyctl deploy --remote-only)、指定リージョンで、解決済みのメモリと内部ポートを持つマシンを起動します。
- 6
本物のinitializeで検証
マシンが起動しただけではMCPが動いている証明になりません。builderは本物のMCP initializeをPOSTします(最大8回)。アダプタの背後で子プロセスがクラッシュループしていればデプロイは失敗扱いにし、偽の成功ではなく実際のサーバーエラーを提示します。
# What the Builder emits for a stdio MCP server (any language):
CMD ["node", "stdio-adapter.cjs", "npm", "start"] # Node
CMD ["node", "stdio-adapter.cjs", "python", "server.py"] # Python
CMD ["node", "stdio-adapter.cjs", "./server"] # Go / Rust binary
CMD ["node", "stdio-adapter.cjs", "npx", "-y", "pkg"] # npx package何が嬉しいか
リポジトリをpushするだけで動くMCPエンドポイントが手に入ります ― Dockerfileもポート配線もstdioの糊付けも書く必要がありません。しかもデプロイは本物のinitializeが往復して初めて成功になるので、エントリコマンドの誤りは最初のクライアントの失敗リクエストからではなく、デプロイ時点で分かります。
stdio → Streamable HTTP アダプタ
多くのMCPサーバーは、Claude Desktopがnpxで起動するように、ローカルでstdin/stdout越しに動く前提で書かれています。まさにそれがホスティングを難しくしています。NodeFlareの答えは、ビルド時にイメージへ注入され、コンテナのエントリポイントになる小さなNode製アダプタです。
アダプタはあなたのMCPサーバーを子プロセスとして起動し、そのstdioのJSON-RPCを Streamable HTTP(および古いクライアント向けに旧2024-11-05のHTTP+SSE)へ橋渡しします。完全に言語非依存で、子プロセスがNodeでもPythonでもGoでもRustでも関係なく、stdin/stdout越しのJSON-RPCだけを扱います。
// Each client request gets a private JSON-RPC id, so concurrent
// callers never collide on the one shared child process.
const internalId = `nf-${n++}`;
child.stdin.write(JSON.stringify({ ...msg, id: internalId }) + "\n");
// On reply, the caller's original id is restored before responding.
// Health: /health returns 503 once the restart budget is spent,
// so Fly marks the machine unhealthy instead of silently 500-ing.並行安全なid再マッピング
受け取ったリクエストは共有の子プロセスに渡す前に専用のJSON-RPC idへ書き換え、返す際に呼び出し元の元idへ戻します。多数のクライアントが1つのプロセスを衝突なく共有できます。
ウォームな自動initialize
起動時に子プロセスをinitializeして結果をキャッシュします。デプロイの健全性を検知でき、クライアントが「already initialized」エラーに当たりません。
予算付きの再起動+バックオフ
クラッシュは指数バックオフ(1秒 → 30秒)で予算の範囲内までリトライします。予算を使い切ると /health が503を返し、クラッシュループを隠さずプラットフォームに失敗を表面化させます。
セッションとパスの配線
セッションidはinitialize時に発行します。MCPパス(既定 /mcp)・ホスト・ポートはビルド設定から配線され、proxyが到達できるようにします。
何が嬉しいか
Claude Desktop向けに書かれた ― どんな言語の ― MCPサーバーも、コードを1行も変えずに共有可能なHTTPS URLになります。あなたは普通のstdioサーバーを書き続けるだけで、NodeFlareがそれをマルチクライアントのクラウドエンドポイントに変えます。
Proxy:認証・スコープ・MCPを理解したルーティング
ホスティング済みサーバーへの全リクエストはRust製proxyを通ります。リクエストのサブドメインから対象サーバーを解決し、呼び出し元を認証し、MCPメソッド単位で認可を強制し、その上で上流エンドポイントへ転送します ― 戻り道でMCPを理解した変換を適用します。
MCPプロトコルを理解しているため、素のリバースプロキシにはできないことができます:個々の tools/call をツール名で認可し、tools/list を呼び出し元ごとにフィルタし、MCPセッションを生成元のマシンに固定します。
APIキーとOAuth
Bearerトークンは mcp_* のAPIキー(ハッシュ化・Redisキャッシュ・総当たりロックアウト付き)か、DBで検証するOAuthアクセストークンのいずれかです。proxyはOAuthのディスカバリメタデータ(RFC 8414 / 9728)も配信します。
リクエスト単位のスコープ強制
転送前に、呼び出し元のスコープを正確なMCPメソッドと対象(例:tools:call:get_weather)に照合します。拒否時はJSON-RPCエラーを返し、あなたのサーバーには到達しません。
Flyマシンへのセッションアフィニティ
initializeがマシンを選び、返ってきた Mcp-Session-Id をRedisでそのマシンに束ねます。後続リクエストは同じマシンへ固定され、古い束ねは検出して修復します。
呼び出し元単位のキャッシュとコアレッシング
読み取り専用のlist系メソッドはキャッシュ・重複排除しますが、キャッシュキーに呼び出し元の識別子を含めます。ある資格情報のフィルタ済みリストが別の資格情報に渡ることはありません。
レート制限と月次クォータ
分単位の制限とワークスペース単位の月次クォータをRedisのアトミックスクリプトで強制します。クォータは転送前にインクリメントし、超過を防ぎます。
MCPの3プリミティブすべて
同じ認証・スコープ・キャッシュ・フィルタが tools・resources・prompts に一様に適用されます。resources/read や prompts/get も tools/call と同じようにスコープ管理され、素通しにはなりません。
スコープ文法
* full access
tools:list list tools
tools:call call any tool
tools:call:get_weather call only the get_weather tool
resources:read:<uri> read one specific resource
prompts:get:<name> get one specific prompt何が嬉しいか
認証・ツール単位の認可・MCPセッションの正しさが、何もせず手に入ります ― どのホスティング済みMCPにも必要なのに、ほとんどのサーバーが自前実装していないものです。スコープ付きのキーを渡せば、呼び出し元は許可していないツール・リソース・プロンプトに物理的に到達できません。
MCPトークン最適化機能
大きなツールカタログはコンテキストを浪費します。60個のツールを公開するサーバーは、モデルが何かする前に tools/list だけで数千トークンを使いかねません。NodeFlareはMCPの表面をトークン節約の形へ作り替える、サーバー単位の4つのスイッチを備えています ― すべてproxy側で透過的に適用され、あなたのサーバーのコードには手を加えません。
tool_list_filter_by_scope呼べるツールだけ見せる
tools/list を、呼び出し元の資格情報が実際に呼び出せるツールだけにフィルタします。リストは狭まりますが、スキーマは同一です。
tool_schema_slim肥大した説明を刈り込む
500文字を超えるツール説明を(UTF-8安全に)切り詰めます。名前と入力スキーマはそのままです。
tool_search_modeカタログを検索に置き換える
tools/list を2つのメタツール ― search_tools(query) と call_tool(name, args) ― に畳み込みます。モデルはカタログを検索(字句一致、埋め込み設定時は意味検索)して必要に応じてツールを呼ぶため、カタログの大きさによらず初期トークンコストがほぼ一定になります。
tool_code_modeモデルにコードを書かせる
run_code と search_tools を公開します。何度もツールを往復する代わりに、モデルは tools.* を呼ぶJavaScriptを書き、サンドボックスで実行します(下記参照)。多数の呼び出しをまたいだフィルタ・結合に最適です。
何が嬉しいか
同じMCPサーバーが、コードを変えずに1ターンあたり大幅に少ないトークンで済みます。大きなカタログでも、検索・コードモードなら初期のツールリストがツール追加のたびに膨らまず、ほぼ一定に保たれる ― コンテキストは安く、モデルは集中できます。
コードモードとサンドボックスRunner
tool_code_mode が有効なとき、モデルは run_code にJavaScriptのブロックを渡して呼び出せます。NodeFlareはそれを専用の Deno-on-Firecracker サンドボックス(proxyとは別サービス)で実行します。ネットワークは許可リスト制で、行き先はただ1つ ― proxyのツールコールバックエンドポイントだけです。
サンドボックス内では tools.NAME(args) が非同期関数として注入されます。各呼び出しは実行毎のBearerトークンを携えてproxyへPOSTで戻ります。ランナーがあなたの資格情報や上流URLを見ることはありません。
// tool_code_mode: the model writes JavaScript instead of many
// round-trips. tools.* is injected; every call is scope-checked
// server-side and counts against a per-run tool-call budget.
const top = await tools.searchVideos({ query: "rust async", maxResults: 5 });
const details = await Promise.all(
top.map(v => tools.getVideoDetails({ videoIds: [v.id] }))
);
return details.filter(d => d.likeToViewRatio > 0.04);- 1
短命トークンを発行
proxyは使い捨てのUUIDトークンを発行し、server_id・target_url・scopes を実行タイムアウト + 10秒のTTLでRedisに保存し、コードをランナーへ送ります。
- 2
隔離環境で実行
新しいDenoサブプロセスが、ファイルシステムなし・環境変数なし・サブプロセスなし ― コールバックエンドポイントへの外向き通信のみ ― でコードを実行します。実時間タイムアウトでSIGKILLされます。
- 3
ツール呼び出しごとにスコープ再チェック
コールバックエンドポイントはトークンを引き、このサーバーに束ねられていることを確認し、スコープ(例:tools:call:X)を再検証してから本物の tools/call を上流へ転送します。サンドボックス側のラッパーは決してセキュリティ境界ではありません。
- 4
結果を返す
ツールエラーはモデルがcatch・リトライできるようラップされ、最終値はシリアライズされて run_code の結果として返ります。
何が嬉しいか
モデルは多段のツール処理を N 回ではなく1往復で行えます ― フィルタ・結合・ループがサンドボックス内で完結し、トークンもレイテンシも削れます。しかもセキュリティの妥協ではありません:各ツール呼び出しは、モデルが直接呼んだ場合と全く同じようにサーバー側でスコープ検証されます。
MCPの使われ方に合わせたホスティング
MCPサーバーのトラフィックは独特です ― 長い間アイドルで、エージェントに使われると急にバーストします。NodeFlareのランタイムとアクセスモデルは、常駐Webサービスではなく、その形に合わせて調整されています。
Scale-to-zero
アイドル時にマシンを停止し(min_machines_running = 0)、次のリクエストで自動起動します。いま誰も使っていないMCPサーバーは0課金で、必要時に起き上がります。
パススルー認証
auth_enabled = false にすると、NodeFlareは自身の認証層をスキップし、呼び出し元の資格情報をそのままサーバーへ転送します。すでに自前でOAuthを行うMCP(Notion・GitHub等)では、二重にラップせずネイティブの認証をそのまま使えます。
適正サイズ&プライベート
各サーバーはマシンメモリを固定の階梯(256MB〜2GB、プランで上限)から選び、デプロイリージョンを指定でき、公開一覧に載せずプライベートに保てます。ブラウザを使う重いサーバーは2GB、小さなツールサーバーは256MBのまま。
何が嬉しいか
MCPが実際に持つ形 ― 24時間アイドルのマシンではなくバースト ― に対して支払えます。自前認証を持つサーバーもNodeFlareの認証と衝突せず収まります。ホスティングが「1つの型」に押し込めるのではなく、サーバーに合わせて適応します。
セキュリティモデル
認可はproxyとコールバックエンドポイントが強制します ― クライアントでも、サンドボックスのラッパーでも、あなたのサーバーでもありません。
- ✓スコープはリクエストごとに正確なMCPメソッドと対象に照合され、拒否時はサーバーに到達する前にJSON-RPCエラーを返します。
- ✓キャッシュとリクエストコアレッシングのキーは呼び出し元の識別子を含むため、スコープやサブスクリプションでフィルタされた結果が資格情報をまたいで漏れることはありません。
- ✓コード実行トークンは用途が単一で、1サーバーに束ねられ、短命で、ツール呼び出しのたびにスコープを再チェックされます。
- ✓コードサンドボックスはファイルシステムなし・環境変数なし・行き先1つのネットワーク許可リストのみ。保持するのは実行毎トークンだけで、あなたの資格情報は持ちません。
- ✓シークレットは保存時に暗号化し、builderへはstdinで注入し、ビルドログからは伏せ字にします。
- ✓デプロイは本物のMCP initializeが往復して初めて「成功」です。クラッシュループするサーバーは、静かに500を返す代わりにデプロイを失敗させます。
内部構造について質問がある、あるいはここに載っていない情報が必要ですか?
お問い合わせ →