7月27日、Teng Liが「My eval said a perfect MCP server was broken. It was the eval that was lying. · Teng Li」と題した記事を公開した。この記事では、MCPサーバーの評価(eval)が「完璧なサーバーを壊れている」と誤判定した原因を追跡し、わずか$0.60の検証コストで評価設計の根本的な欠陥を突き止めた過程が詳しく紹介されている。
evalがサーバーを「38%」と判定した。間違っていたのはevalの方だった
Teng Liは、MCPサーバーの品質を測定するツールmcpgradeにLLMベースのevalモードを追加した。MCP(Model Context Protocol)とは、LLMがツールを呼び出す際に使う標準プロトコルのことだ。
最初の実運用でこんな数字が出た:
| サーバー | 静的スコア | ツール選択精度 | 引数 | 拒否 |
|---|---|---|---|---|
| context7(2ツール) | 100 | 38% | 100% | 100% |
| server-memory(9ツール) | 81 | 93% | 100% | 100% |
| server-slack(8ツール) | 97 | 54% | 100% | 100% |
静的スコアが満点のcontext7が、ツール選択を62%の確率で「失敗」している。8タスク中5回、モデルが「間違った」ツールを選んだという結果だ。この数字をそのまま公開していたら、システマティックに不公平な評価を世に出すことになっていた。
問題の核心:「パイプラインツール」を1ステップ問題として採点していた
全ての「ミス」は一つの原因に行き着いた。
Slackのpost_messageはthread_ts(スレッドのタイムスタンプ)を引数として必要とする。この値はget_channel_historyを先に呼び出さないと取得できない。同様にcontext7のget-library-docsは、resolve-library-idが返すライブラリIDを先に取得しなければ使えない。これらは「パイプラインツール」——前の呼び出しの出力を受け取ることが前提のツールだ。
evalのタスク生成器はその事情を知らなかった。「障害について話したスレッドに返信せよ」というタスクをスレッドのタイムスタンプなしで生成し、モデルが「まずget_channel_historyで履歴を取ってこよう」と判断すると、それを「不正解」と採点していた。
モデルは混乱していなかった。モデルは正しかった。 server-memoryが93%を出していたのも後から見れば当然で、あのサーバーのツールは単独で完結するから影響を受けなかった。
修正は合成プロンプトへの制約を一行加えるだけだった:「すべての必須パラメーターに具体的な値を埋め込むこと」。たとえば「#incidentsチャンネルのスレッド1721581200.123456に返信せよ」のように。
結果、Round 2でcontext7は38%→**100%、Slackは54%→100%**。
修正後のevalは「差をつけられるか」——firecrawlで検証
全員が100%を取るならそれはただの飾りだ。Round 3では、過去の36サーバースキャンで最も静的スコアが低かったfirecrawl(26ツール)を対象にした。
結果は2つの点で明確だった。
1. 選択ミスが静的解析の指摘箇所と一致した。 選択精度は84%で、残り16%のミスはランダムではなかった。extract↔scrape、agent_status↔check_crawl_statusなど、静的ルール(N002、C001)がすでに「紛らわしい」と指摘していたペアと完全に一致した。つまり静的lintはLLM evalと同じ失敗箇所を予測できる——無料で、10秒で。
2. 拒否(refusal)が崩壊した。 小規模で整理されたカタログでは100%だった「対象外タスクの正しい拒否」が、firecrawlの26ツールでは50%に落ちた。半分のケースで、モデルは「もっともらしい」ツールを見つけて呼び出してしまった。大きく曖昧なカタログは誤選択を引き起こすだけでなく、「何もしないのが正解」な場面で行動させる——本番環境では最も怖い失敗パターンだ。
全検証コストは$0.60
3ラウンド、4サーバーの検証全体にかかったコストは約**$0.60**。1サーバーあたり$0.04〜$0.20の範囲に収まる。PRごとに走らせても誤差の範囲だ。
Teng Liはこう述べている:ベンチマーク設計者の多くはリーダーボードに直行するが、「自分の指標が嘘をついているか確かめる」キャリブレーションのステップこそが、測定値とランダムな数字の生成器を分けるものだ、と。
読者の指摘でさらに2つの欠陥が発覚
記事公開後、読者のMads Hansenが元記事へのコメントで2点の問題を指摘した。なお、以下の指摘はtengli.devの記事に対するコメントとして寄せられたものであり、GitHubのissueとは別に起票されている。
1つ目は分類の粒度。現在の「拒否」カテゴリは「確認を求める」「明示的に断る」「もっともらしい誤ったツールを自信満々で呼び出す」を同一視している。本番でのコストは全く異なるため、4バケット+重み付けが必要だという指摘だ。
2つ目はより根本的で、合成タスクがそのカタログ自体の語彙で書かれてしまう問題だ。タスク生成器はカタログを読んでタスクを生成するため、説明が雑なカタログほど自分に有利なタスクが生成される。修正策は、ツール名を一切見ないステップでパラフレーズしてから、テスト分割を凍結するという手法だ。
両方ともGitHubのissueに登録済みだ。
詳細はMy eval said a perfect MCP server was broken. It was the eval that was lying. · Teng Liを参照していただきたい。