「922個のnpm MCPサーバーを調べて学んだこと」

Dev.to / 2026/5/19

💬 オピニオンDeveloper Stack & InfrastructureSignals & Early TrendsModels & Research

要点

  • この調査では、npmで公開されている922個のMCPサーバーに対してJSON-RPCのイントロスペクション(initialize + tools/list)を実行し、そのうち359件のみがツール一覧を正常に返し、563件が失敗しました。
  • 最も多い失敗パターン(261件)は、initialize中の120秒タイムアウトで、サーバーの起動時に外部のアップストリームAPIへ接続(いわゆる“電話をかける”処理)しようとすることが原因だとされています。
  • レポートは、MCPサーバーのメンテナーに対して、外部/アップストリームへの接続は最初のツール呼び出しまで遅延させ、初期化とディスカバリーを確実に完了できるようにすることを提案しています。
  • initializeの後にtools/listを使うことで、READMEのスクレイピングやLLMによる推測に頼らずに、ツールのスキーマを正確に取得できます。
  • 実行者はnpxでstdio経由にサーバーを起動し、並列8本・サーバーごと120秒のタイムアウトでJSONLの結果(状態、ツール数、可能な場合はツールのスキーマ)を収集し、失敗は15のカテゴリに分類したと述べています。

TL;DR: 私たちは 922 個の npm 公開 MCP サーバに対して npx -y <package> を実行し、JSON-RPC の initializetools/list 呼び出しを送って、それらが何をしたかを記録しました。359 件は応答しました。563 件は 15 通りの異なる失敗で、MCP 自体というより npm のパッケージング事情がよく分かる結果でした。

261 サーバを壊した stderr のシグネチャ

[stderr] connecting to upstream...
[stderr] (no further output)
[timeout after 120s]

これは現時点の npm MCP エコシステムで最も多い単一の失敗パターンです。プロセスは起動し、パッケージが読み込まれ、コンストラクタが実行されます。そして、プロトコルに応答する前にサーバが上流(upstream)の API に電話しようとします。イントロスペクション用の実行器は 120 秒待って諦めます。261 サーバ、公開された全体の 28% は、自身のスタートアップを越えることができませんでした。

あなたが MCP サーバをメンテしていて、initialize 中に外部へ接続するなら、このカテゴリに入ります。修正は、最初のツール呼び出しが来るまで上流接続を遅延させることです。

実際に私たちが実行したこと

プロトコルには、発見(ディスカバリ)メソッドがあります: tools/listinitialize の後に送ると、サーバは公開しているすべてのツールの完全な JSON スキーマを返します。README のスクレイピングは不要、LLM の解釈も不要、推測も不要です。これは、MCP クライアントが接続したときに行うのとまったく同じことです。

実行器はパッケージごとに次を行います:

1. spawn:  npx -y <package>(stdio 経由)
2. write:  {"jsonrpc":"2.0","id":1,"method":"initialize",
            "params":{"protocolVersion":"2024-11-05",
                      "capabilities":{},
                      "clientInfo":{"name":"introspector","version":"1.0"}}}
3. read:   initialize の応答
4. write:  {"jsonrpc":"2.0","method":"notifications/initialized"}
5. write:  {"jsonrpc":"2.0","id":2,"method":"tools/list"}
6. read:   ツール一覧
7. kill:   stdin を閉じる、SIGTERM

並列度は 8、サーバごとのタイムアウトは 120 秒で、922 パッケージの総ウォールタイムは約 25 分でした。出力はサーバごとに JSONL 1 行で、ステータス、ツール数、そして(利用可能な場合は)すべてのツールの完全な入力スキーマを含みます。

15 個の失敗バケット

クリーンなツール配列を返さなかったすべてのサーバは、終了コード、stderr、そして(もしあれば)JSON-RPC エラーを調べることで分類しました。内訳:

Status Count Meaning
ok 359 クリーンな tools/list の応答
init_timeout 261 起動したが initialize に応答しなかった
npm_install_generic 172 npx -y 自体が失敗
needs_cli_args 54 使用法エラーで終了
needs_env_var 42 汎用の環境変数が不足
broken_install 11 不正なパッケージ、main または bin が不適切
error 8 非ゼロ終了で分類不能なクラッシュ
needs_setup_step 3 最初に CLI のセットアップウィザードの実行が必要
needs_slack_token 2 SLACK_BOT_TOKEN がないと拒否
needs_azure_creds 2 Azure 認証がないと拒否
tools_list_timeout 2 initialize には応答したが tools/list で停止(ハング)
needs_google_creds 2 Google 認証がないと拒否
needs_stripe_key 1 STRIPE_API_KEY がないと拒否
needs_config_file 1 設定ファイルのパスがないと拒否
needs_external_runtime 1 インストールされていないバイナリにシェルアウト
needs_openai_key 1 OPENAI_API_KEY がないと拒否

資格情報(クレデンシャル)ウォールのバケット(すべての needs_*)は合計 109 サーバで、公開セットのほぼ 12% です。README を解析してエージェントのツール一覧を埋めている場合、これらのサーバはインデックス内で 0 ツールサーバとして登録されます。それらは 0 ツールサーバではありません。5 ツール、12 ツール、40 ツールのサーバで、あなたが渡していないキーを待っているだけです。

Windows 固有の落とし穴

実行器は Windows 上で動いています。問題になることが 2 つあります。

npx のスポーン。 Windows 上の npxnpx.cmd に解決されるため、バッチスクリプトになります。シェルなしでの Node の child_process.spawn.cmd を呼び出しません。その結果、where npx が PATH 上にあることを示していても、フラットな ENOENT が発生します。修正は次の通り:

const child = spawn("cmd", ["/c", "npx", "-y", pkg], {
  stdio: ["pipe", "pipe", "pipe"],
});

この同じパターンは、Windows のすべての claude_desktop_config.json に現れます。自作の MCP クライアントを書くなら、これも必要です。

UTF-8 の stdio。 Windows のデフォルトのコードページは UTF-8 ではありません。MCP サーバが、非 ASCII 文字(ドイツ語のウムラウト、em ダッシュ、カールした引用符など)を含むツール説明を書き込み、それを UTF-8 を強制せずに stdio から読み取ると、tools/list の途中で JSON のパースエラーになります。UTF-8 を強制してください:

proc = subprocess.Popen(
    cmd,
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    encoding="utf-8",
    errors="replace",
)

今回の実行で、3つのサーバーがASCII以外の文字を含むツール説明を持っていました。encodingフラグがなければ、3つとも error として誤って数えられていたはずです。

サーバー作者に対してこれが伝えていること

データから見えてきたいくつかのパターンを、サーバーを配布する誰にでも向けてまとめます:

  1. initialize の間に上流へ接続しないでください。 サーバーが起動時にAPIへ連絡する場合、イントロスペクションに失敗し、さらに、資格情報を最初に消費することなくツールを列挙しようとするクライアントも失敗します。最初のツール呼び出し時に遅延接続してください。

  2. ツール一覧を表示するためにCLI引数を要求しないでください。 54のサーバーが、何を公開しているのかをあなたに伝える前に、使用法エラーで終了します。設定パスが必要なら、環境変数から取得するか、それなしで tools/list を受け付けてください。

  3. READMEで環境変数を文書化してください。 分類器は、stderrに明確な「missing X」という行を書いたサーバーに対して、資格情報の名前を正常に特定できました。そうでないものは、汎用の needs_env_var バケットに入ってしまいました。このバケットはあなたのバグ報告キューです。

  4. 実際の bin エントリを同梱してください。 broken_install の11のサーバーでは、存在しないファイルを指している package.json がありました。あるいは、require時にクラッシュする main フィールドがありました。いずれも資格情報は必要ありませんでした。必要だったのは、公開時(publish時)のスモークテストです。

完全なデータセット

9,922個のツールスキーマと、922件のサーバー状態(status)行は、CC-BY-4.0のもとで HuggingFace に automatelab/mcp-servers-tool-catalog としてあります。パイプラインのソースは AutomateLab-tech/mcp-tool-catalog で、GitHub Actions を通じて毎月1日に再実行されます。
製品ページ: https://automatelab.tech/products/datasets/mcp-tool-catalog/

from datasets import load_dataset

servers = load_dataset(
    "automatelab/mcp-servers-tool-catalog", "servers", split="train"
)

# 到達できないものだけを、失敗モードごとにまとめる
from collections import Counter
print(Counter(r["status"] for r in servers if r["status"] != "ok"))

失敗バケットに入っていて、本来そこに入るべきではない場合は、パイプラインのリポジトリでPRを開いてください。次回の月次実行でそれが拾われます。