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サンドボックス。抽象的なマーケティング表現ではなく、あなたのサーバーを動かしている実コードに直接対応しています。

JSON-RPC 2.0Streamable HTTP + 旧SSEOAuth 2.0 (RFC 8414 / 9728)スコープ付きAPIキーinitialize疎通検証nerdctl / containerdFirecracker マイクロVMScale-to-zero

全体アーキテクチャ

処理を担うのは3つのコンポーネントです。Builder(Rust、ベアメタル)はリポジトリをクローンし、ランタイムを検出し、Dockerfileを生成し、nerdctlでOCIイメージをビルドし、Firecrackerマイクロ VM上で起動します。(ビルド時に注入される)stdioアダプタは stdin/stdout のMCPサーバーをラップし、代わりにStreamable HTTPを話します。Proxy(Rust、ベアメタル)はすべてのサーバーの前段に立ち、認証・スコープ・キャッシュ・セッションアフィニティ・クォータ・MCP固有のレスポンス変換を担います。4つ目の任意コンポーネントである Runner は、AIが書いたコードを厳重に隔離したDenoサンドボックスで実行します。

   MCP client  (Claude Desktop / Cursor / your agent)
        │   Streamable HTTP · legacy SSE   (JSON-RPC 2.0)
        ▼
 ┌──────────────────────────────────────────────┐
 │  PROXY  (Rust · bare-metal)                   │
 │  API key / OAuth · per-request scope check    │
 │  caller-scoped cache · session affinity ·     │
 │  rate limit + monthly quota · tool transforms │
 └───────────────────────┬──────────────────────┘
        │   POST {endpoint}/mcp   (pinned to one Firecracker VM)
        ▼
 ┌──────────────────────────────────────────────┐
 │  Firecracker micro-VM  (per MCP server)       │
 │  ┌────────────────────────────────────────┐   │
 │  │ stdio-adapter.cjs  (injected at build)  │   │
 │  │ stdin/stdout  <->  Streamable HTTP      │   │
 │  └──────────────────┬─────────────────────┘   │
 │         │  JSON-RPC over stdio                 │
 │         ▼                                      │
 │  ┌──────────────────────────────────────────┐  │
 │  │  YOUR MCP SERVER                          │  │
 │  │  node · python · go · rust · docker       │  │
 │  └──────────────────────────────────────────┘  │
 └──────────────────────────────────────────────┘

 BUILDER (Rust · bare-metal)
   GitHub repo ─▶ detect runtime ─▶ Dockerfile ─▶ nerdctl build ─▶ Firecracker VM ─▶ verify initialize

 RUNNER  (Deno)  run_code ─▶ sandboxed Deno subprocess ─▶ tools.* ─▶ proxy (scope re-checked per call)

proxyはコンテナ名から決定論的に導出されたFirecracker VMのポートへルーティングするため、再起動をまたいでもセッションアフィニティが安定します。stdioアダプタが任意の言語のMCPサーバーをHTTPの世界へ橋渡しします。

Builder:GitHubリポジトリから稼働中のMCPへ

builderはベアメタル上で動作するRust製サービスです。特定コミットのリポジトリをクローンし、ビルド・起動方法を判定し、Dockerfileを生成し、nerdctl(containerd)でOCIイメージをビルドし、Firecrackerマイクロ VMとして起動し、最後にMCPサーバーが本当に応答することを検証します。

  1. 1

    ソース取得

    GitHub Appのtarball(または公開git)でクローン。root_directoryはパストラバーサル対策付きで解決し、モノレポ(npm/yarn/pnpmワークスペース)を自動検出して、リポジトリ直下に巻き上げられた依存も正しく解決できるようにします。

  2. 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 は常に優先されます。package-lock.jsonのトランジティブな engines.node がより新しいメジャーバージョンを要求する場合、Dockerfile内のNode.jsバージョンも自動的に引き上げられます。

  3. 3

    Dockerfile生成

    ランタイム別のDockerfileを生成します(あるいは持ち込みのDockerfileを検証・ラップ)。stdioサーバーの場合、stdioアダプタ(stdio-adapter.cjs)をコピーしてコンテナのエントリポイントに設定し、あなたのサーバーを子プロセスとして起動させます。

  4. 4

    シークレット注入

    環境変数はDBから復号し、ビルド引数やargvには一切渡さず、VMの起動時に環境変数として注入します。既知のトークン形状を伏せ字にするredaction処理がビルドログをクライアントへストリームする前に走ります。

  5. 5

    ビルドと起動

    ベアメタル上で nerdctl build(containerd)によりOCIイメージをビルドし、任意でプライベートレジストリへpushし、Firecrackerマイクロ VMとして起動します。ホストポートはコンテナ名からFNV-1aハッシュで決定論的に導出され、ポート割り当てサービスなしで再起動をまたいでも安定したルーティングを実現します。

  6. 6

    本物のinitializeで検証

    VMが起動しただけでは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
この最後のステップがMCP固有の安全網です。エントリパスを誤ると、あなたのサーバーが死んでいてもアダプタのHTTPだけは立ち上がってしまいます。NodeFlareはinitializeを叩くので、「デプロイ済み」=「実際にMCPが応答している」を意味します。

何が嬉しいか

リポジトリをpushするだけで動くMCPエンドポイントが手に入ります ― Dockerfileもポート配線もstdioの糊付けも書く必要がありません。Node.jsバージョンも依存関係に合わせて自動更新されます。しかもデプロイは本物の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がそれを専用のFirecracker VM上で動くマルチクライアントのクラウドエンドポイントに変えます。

Proxy:認証・スコープ・MCPを理解したルーティング

ホスティング済みサーバーへの全リクエストはベアメタル上で動くRust製proxyを通ります。リクエストのサブドメインから対象サーバーを解決し、呼び出し元を認証し、MCPメソッド単位で認可を強制し、その上で上流のFirecracker VMへ転送します ― 戻り道でMCPを理解した変換を適用します。

MCPプロトコルを理解しているため、素のリバースプロキシにはできないことができます:個々の tools/call をツール名で認可し、tools/list を呼び出し元ごとにフィルタし、MCPセッションをVMに固定し、リポジトリが有効なMCPサーバーかを自動検出します。

APIキーとOAuth

Bearerトークンは mcp_* のAPIキー(ハッシュ化・Redis 60秒TTLキャッシュ・総当たりロックアウト付き)か、DBで検証するOAuthアクセストークンのいずれかです。proxyはOAuthのディスカバリメタデータ(RFC 8414 / 9728)も配信するので、標準準拠のMCPクライアントは認証を自動ディスカバリできます。

リクエスト単位のスコープ強制

転送前に、呼び出し元のスコープを正確なMCPメソッドと対象(例:tools:call:get_weather)に照合します。拒否時はJSON-RPCエラーを返し、あなたのサーバーには到達しません。

セッションアフィニティ

initializeが返した Mcp-Session-Id を特定のFirecracker VMポートにRedis(TTL 2時間)で束ねます。後続リクエストは同じVMへ固定され、古い束ねは自動で検出・修復されます。

呼び出し元単位のキャッシュとコアレッシング

読み取り専用のlist系メソッド(tools/list・resources/list・prompts/list)はキャッシュ(60秒TTL)・重複排除します。キャッシュキーには常に呼び出し元の識別子を含めるため、ある資格情報のフィルタ済みリストが別の資格情報に渡ることはありません。

レート制限と月次クォータ

IPアドレス単位の分間レート制限とワークスペース単位の月次クォータをRedisのアトミックLuaスクリプトで強制します。並行リクエスト下でもオーバーシュートしないよう、クォータは転送前にインクリメントします。

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サービス(proxyとは別サービス)へ送り、リクエストごとに新しい、厳重に隔離されたDenoサブプロセスで実行します。サブプロセスはファイルシステムアクセスなし・環境変数なし・プロセス生成なし ― ネットワークはproxyのツールコールバックエンドポイントへの外向き通信1つだけが許可されます。

サブプロセス内では 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. 1

    短命トークンを発行

    proxyは使い捨てのUUIDトークンを発行し、server_id・target_url・呼び出し元スコープを(実行タイムアウト + 10秒)のTTLでRedisに保存し、JavaScriptをランナーサービスへ送ります。

  2. 2

    隔離環境で実行

    新しいDenoサブプロセスが --allow-net をコールバックエンドポイントホストのみに制限して起動します。ファイルシステムなし・環境変数なし・サブプロセス生成なし。実時間タイムアウトを超えるとSIGKILLされます。

  3. 3

    ツール呼び出しごとにスコープ再チェック

    コールバックエンドポイントはトークンを引き、このサーバーに束ねられていることを確認し、スコープ(例:tools:call:search_videos)を再検証してから本物の tools/call を上流へ転送します。サンドボックス側のラッパーは決してセキュリティ境界ではなく、proxyがそれを担います。

  4. 4

    結果を返す

    ツールエラーはモデルが同一実行内でcatch・リトライできるようラップされ、最終の戻り値はシリアライズされて run_code の結果として返ります。

ガードレール:設定可能な実時間タイムアウト(既定600秒)と、実行あたりの最大ツール呼び出し数(既定50)。各Denoサブプロセスは128MBのV8ヒープで上限されます。ランナーが未設定ならコードモードは穏当に縮退し、run_code は「利用不可」を報告します。

何が嬉しいか

モデルは多段のツール処理を N 回ではなく1往復で行えます ― フィルタ・結合・ループがサンドボックス内で完結し、トークンもレイテンシも削れます。しかもセキュリティの妥協ではありません:各ツール呼び出しは、モデルが直接呼んだ場合と全く同じようにサーバー側でスコープ検証されます。

MCPの使われ方に合わせたホスティング

MCPサーバーのトラフィックは独特です ― 長い間アイドルで、エージェントに使われると急にバーストします。NodeFlareは専用ベアメタルサーバー上で動作し、MCPサーバーごとにFirecrackerマイクロ VMを使います。この組み合わせがScale-to-zeroを高速かつ安価にし、ノイジーネイバーの問題もありません。

Scale-to-zero

FirecrackerマイクロVMはアイドル時に停止し、次のリクエストで自動再起動します。proxyはRedisにlast_request時刻を記録してアイドルを追跡します(Redis負荷を避けるためスロットル書き込み)。使われていないVMは0課金で、OCIイメージはコールドブートに備えて待機します。

パススルー認証

auth_enabled = false にすると、NodeFlareは自身の認証層をスキップし、呼び出し元の資格情報をそのままサーバーへ転送します。すでに自前でOAuthを行うMCP(Notion・GitHub等)では、二重にラップせずネイティブの認証をそのまま使えます。

適正サイズのVM

各サーバーはVMメモリを固定の階梯(256MB〜2GB、プランで上限)から選び、デプロイリージョンを指定でき、公開一覧に載せずプライベートに保てます。ブラウザを使う重いサーバー(Playwright、Puppeteerなど)は2GB、小さなツールサーバーは256MBのまま。

上流OAuthパススルー

サーバーは外部OAuthプロバイダ(Google、GitHubなどカスタムも可)からのユーザー許可トークンを、MCPリクエストを受ける前に要求できます。NodeFlareがOAuthの手続きを代行し、プロバイダトークンをサーバーの環境に注入します ― あなたのサーバーコードは認証に触れません。

何が嬉しいか

MCPが実際に持つ形 ― 24時間アイドルのVMではなくバースト ― に対して支払えます。自前認証を持つサーバーもNodeFlareの認証と衝突せず収まります。ホスティングが「1つの型」に押し込めるのではなく、サーバーに合わせて適応します。

セキュリティモデル

認可はproxyとコールバックエンドポイントが強制します ― クライアントでも、サンドボックスのラッパーでも、あなたのサーバーでもありません。

  • スコープはリクエストごとに正確なMCPメソッドと対象に照合され、拒否時はサーバーに到達する前にJSON-RPCエラーを返します。
  • キャッシュとリクエストコアレッシングのキーは呼び出し元の識別子を含むため、スコープやサブスクリプションでフィルタされた結果が資格情報をまたいで漏れることはありません。
  • コード実行トークンは用途が単一で、1サーバーに束ねられ、短命(TTL=実行タイムアウト + 10秒)で、ツール呼び出しのたびにスコープを再チェックされます。
  • コードサンドボックスのサブプロセスはファイルシステムなし・環境変数なし・行き先1つのネットワーク許可リストのみ。保持するのは実行毎トークンだけで、あなたの資格情報は持ちません。
  • シークレットは保存時に暗号化し、VMの起動時に環境変数として注入されます。ビルド引数やイメージレイヤーには一切書き込まれず、ビルドログからは既知のトークン形状を伏せ字にします。
  • デプロイは本物のMCP initializeが往復して初めて「成功」です。クラッシュループするサーバーは、静かにHTTP 500を返す代わりにデプロイを失敗させます。
  • APIキーのルックアップはRedisキャッシュ(60秒TTL)付きで、総当たりロックアウトを実施します。間違ったキーが5回連続で使われると、15分間そのキーをロックします。

内部構造について質問がある、あるいはここに載っていない情報が必要ですか?

お問い合わせ →