Claude Codeの「npm run dev問題」を解決するMCPサーバーを作った話
開発のきっかけ
Claude Codeを使い始めてしばらく経った頃、毎回同じ問題に直面していました。「npm run devを実行してください」とお願いすると、コマンドは実行されるものの、開発サーバーが起動した瞬間にClaude Codeのターミナルが止まってしまうのです。
最初は仕方がないと思っていました。別のターミナルを開いて手動で開発サーバーを起動し、Claude Codeには他の作業をしてもらう。でも、これって明らかに非効率ですよね。Claude Codeに全部任せられたら、どれだけスムーズになるか。
さらに厄介だったのが、デバッグ時の情報収集でした。「ブラウザのコンソールにエラーが出ています」と報告しても、Claude Codeからはブラウザの中を見ることができません。開発者ツールのスクリーンショットを撮って共有するか、エラーメッセージを手動でコピペするか。これも明らかに非効率です。
そんなもどかしさが積み重なって、ついに重い腰を上げてMCP(Model Context Protocol)サーバーを自作することにしました。サーバー管理だけでなく、ブラウザの内部状況まで含めて、Claude Codeが開発環境全体を把握できるようにしたかったのです。
問題の本質を理解する
なぜnpm run devがClaude Codeをブロックしてしまうのか。答えは単純でした。開発サーバーは基本的にフォアグラウンドプロセスとして動作し、ターミナルの制御を占有し続けるからです。
そして、なぜブラウザの情報がClaude Codeに届かないのか。これも単純な理由でした。ブラウザとサーバーサイドの開発環境は、完全に分離されているからです。ブラウザのコンソールに出力されるエラーやログは、開発サーバーからは見えません。
普通の開発フローでは、これらは大きな問題になりません。開発者は別のターミナルタブを開いて作業を続けたり、開発者ツールを見ながらデバッグしたりします。しかし、Claude Codeのような自動化ツールにとって、これらは致命的な制約でした。
私が欲しかったのは:
- 開発サーバーをバックグラウンドで起動し、すぐに制御を返してくれる仕組み
- サーバーの状態を監視したり、ログを確認したり、必要に応じて再起動できる機能
- ブラウザのコンソールログやネットワークエラーを、サーバーサイドから確認できる仕組み
つまり、開発環境全体の「可視性」を向上させることでした。
設計の悩みと決断
最初に考えたのは、シンプルなラッパースクリプトでした。nohup npm run dev &のような感じで。でも、これだと管理が大変です。プロセスが生きているかわからないし、ログも見づらい。
ブラウザログの収集については、さらに悩みました。ブラウザ拡張機能にするか、Webアプリ側にライブラリを組み込むか、それとも別のアプローチか。最終的にChrome拡張機能を選択したのは、既存のWebアプリケーションに一切の変更を加えずに済むからでした。
次に思いついたのがdockerを使う方法。でも、これは大げさすぎる。開発環境の構成を変えずに、既存のワークフローに自然に組み込めるものが欲しかったのです。
最終的にMCPサーバーという形に落ち着いたのは、Claude Codeとの親和性を考えてのことでした。MCPなら、Claude自体がネイティブにサポートしているプロトコルです。新しいツールをClaude Codeに教えるのも簡単ですし、将来的な拡張性も期待できます。
実装での試行錯誤
プロセス管理の罠
最初のバージョンでは、単純にspawnでプロセスを起動していました。でも、これだけでは不十分でした。子プロセスが正常に起動したかの判定が難しい。特に、起動直後に失敗するケースをどう検出するか。
// 最初の実装(問題あり)
const childProcess = spawn(cmd, cmdArgs, options);
// すぐに成功として返してしまう
return { success: true };
解決策として、起動チェック機能を実装しました。プロセスを起動後、2秒間待機して、その間にプロセスが異常終了しないかを監視します。さらに、500ミリ秒後に生存確認も行います。
const startupPromise = new Promise<boolean>((resolve) => {
const timeout = setTimeout(() => resolve(true), 2000);childProcess.once('exit', () => {
clearTimeout(timeout);
resolve(false);
});
setTimeout(() => {
if (!childProcess.killed && childProcess.exitCode === null) {
clearTimeout(timeout);
resolve(true);
}
}, 500);
});
ブラウザログ収集の技術的挑戦
ブラウザのコンソールログを取得するのは、想像以上に複雑でした。Chrome拡張機能のManifest V3では、セキュリティ制約が厳しく、単純にconsole.logを傍受するだけでは不十分です。
最終的に、3層構造のアーキテクチャを採用しました:
- Injected Script: ページのコンテキストで動作し、console.*メソッドをオーバーライド
- Content Script: Injected Scriptからのメッセージを中継
- Background Service Worker: MCPサーバーへのHTTP通信を担当
// Injected Scriptでのconsole.logの傍受
Object.keys(originalConsole).forEach(method => {
console[method] = function(...args) {
// 元のメソッドを呼び出し
originalConsole[method].apply(console, args);
// ログ情報をContent Scriptに送信
sendLog(method, args);
};
});
さらに、HTTP通信エラーの検出も重要でした。fetchとXMLHttpRequestの両方をオーバーライドして、ネットワークエラーを自動的にキャッチします:
// fetchのエラー検出
const originalFetch = window.fetch;
window.fetch = function(...args) {
const request = originalFetch.apply(this, args);
return request.then(response => {
if (!response.ok) {
const url = args[0];
const method = args[1]?.method || 'GET';
sendLog('error', [`${method} ${url} ${response.status} (${response.statusText})`]);
}
return response;
}).catch(error => {
// ネットワークエラーをログに記録
const url = args[0];
const method = args[1]?.method || 'GET';
sendLog('error', [`Network Error: ${method} ${url}`, error.message]);
throw error;
});
};
ログ管理の工夫
開発サーバーのログをどう管理するかも悩みどころでした。すべてのログを保存していると、メモリを圧迫してしまいます。かといって、ログがないとデバッグが困難です。
最終的に、循環バッファ方式を採用しました。最新の100行だけを保持し、古いログは自動的に削除されます。また、長すぎるログ行は途中で切り捨てて、メモリ使用量を制御しています。
const timestamp = new Date().toISOString();
const truncatedLog = log.length > this.maxLogLength
? log.substring(0, this.maxLogLength) + '...[truncated]'
: log;
server.logs.push(`[${timestamp}] ${truncatedLog}`);
if (server.logs.length > this.maxLogLines) {
server.logs = server.logs.slice(-this.maxLogLines);
}
}
ブラウザログについても同様の仕組みを実装しましたが、さらにポート別の分類機能を追加しました。localhost:3000とlocalhost:8080で動作する複数のサービスのログを、別々に管理できるようにしたのです。
既存サーバーの発見機能
開発中に気づいたのが、「MCPサーバー経由で起動していないサーバーは見えない」という問題でした。既にnpm run devで起動済みのサーバーがあっても、MCPシステムからは認識されません。
この問題を解決するため、discover_running_servers機能を実装しました。lsofコマンドを使って、実際に動作しているサーバーを検出し、MCPの管理下に取り込む仕組みです:
// ポートスキャンによるサーバー発見
const { stdout } = await execAsync(`lsof -i :${port}`);
const lines = stdout.trim().split('\n');
if (lines.length > 1) {
const firstLine = lines[1];
const parts = firstLine.trim().split(/\s+/);
const pid = parseInt(parts[1]);
// プロセス情報を取得してサーバーリストに追加
const { stdout: commandOutput } = await execAsync(`ps -p ${pid} -o command=`);
const command = commandOutput.trim();
return { port, pid, command, protocol: 'tcp' };
}
クリーンアップの重要性
開発中に何度もプロセスが残ってしまう問題に遭遇しました。MCPサーバー自体が異常終了した際に、起動した開発サーバーがゾンビプロセスとして残ってしまうのです。
この問題を解決するため、プロセス終了時のクリーンアップ処理を丁寧に実装しました。
constructor() {
process.on('exit', () => this.cleanup());
process.on('SIGINT', () => this.cleanup());
process.on('SIGTERM', () => this.cleanup());
}
private cleanup(): void {
for (const [name, server] of this.servers) {
if (server.status === 'running' || server.status === 'starting') {
console.error(`Cleaning up server "${name}"`);
server.process.kill('SIGTERM');
}
}
}
実際の使用感と効果
完成したdevtools-mcpを使い始めて、開発体験が劇的に変わりました。Claude Codeに「開発サーバーを起動して、APIエンドポイントをテストしてください」と頼むと、本当にそれをやってくれるようになったのです。
特に印象的だったのは、ブラウザで発生したエラーを、Claude Codeが自動的に認識してくれることでした。JavaScriptエラーやHTTPエラーが発生すると、Claude
Codeが「ブラウザでエラーが発生しています」と教えてくれるだけでなく、具体的なエラー内容とその解決策まで提案してくれるようになったのです。
複数のマイクロサービスを扱う際の効果も顕著でした。フロントエンド、バックエンド、そしてドキュメントサーバーを順次起動して、相互接続のテストまで一気通貫でClaude Codeに任せられるようになりました。
サーバーの状態監視機能も予想以上に便利でした。「なんかサーバーが重い」と感じた時、Claude Codeに状況確認を依頼すると、CPUやメモリの使用状況まで含めて詳細な報告をしてくれます。
実際の動作を見てみる
開発サーバーの起動と監視
Claude Codeに「Next.jsアプリの開発サーバーを起動してください」と依頼すると、以下のような流れで処理されます。
Claude Code: start_dev_serverツールを呼び出し中...
引数: {
"command": "npm run dev",
"name": "nextjs-app",
"port": 3000,
"cwd": "/Users/dev/my-project"
}
結果:
Development server "nextjs-app" started successfully in background.
Command: npm run dev
Port: 3000
PID: 12345
Working Directory: /Users/dev/my-project
Server is running in the background. Use check_dev_server to monitor status.
この時点で、Claude Codeのターミナルは完全に自由になります。開発サーバーはバックグラウンドで動作し続けているのに、Claude Codeは次のタスクに移れるのです。
ブラウザログの統合監視
Chrome拡張機能をインストールしていると、ブラウザでの操作が自動的にMCPサーバーに送信されます。Claude Codeに「最近のブラウザログを確認してください」と依頼すると:
{
"totalLogs": 157,
"portStats": {
"3000": 98,
"8080": 59
},
"recentErrors": [
{
"timestamp": "2024-06-10T15:23:45.123Z",
"level": "error",
"message": "POST http://localhost:3000/api/users 500 (Internal Server Error)",
"url": "http://localhost:3000/dashboard",
"port": "3000"
},
{
"timestamp": "2024-06-10T15:24:12.456Z",
"level": "error",
"message": "Uncaught TypeError: Cannot read properties of undefined (reading 'name')",
"url": "http://localhost:3000/profile",
"port": "3000"
}
]
}
サーバーログとブラウザログが統合されることで、フルスタックでの問題解決が可能になりました。
既存サーバーの発見と取り込み
開発中に、別のターミナルで既に起動しているサーバーを発見したい場合:
Claude Code: discover_running_serversツールを呼び出し中...
引数: {
"portRange": "3000-8000",
"importFound": true
}
結果:
{
"discovered": 3,
"imported": 3,
"servers": [
{
"port": 3000,
"pid": 5433,
"command": "node server.js",
"protocol": "tcp"
},
{
"port": 3001,
"pid": 58559,
"command": "next-server (v15.3.3)",
"protocol": "tcp"
},
{
"port": 8080,
"pid": 6789,
"command": "webpack-dev-server",
"protocol": "tcp"
}
]
}
発見されたサーバーは自動的にMCPの管理下に取り込まれ、以後list_running_serversで一元管理できるようになります。
統合デバッグ体験
開発中に遭遇した印象的なエピソードがあります。静的ファイルの読み込みエラーでブラウザが真っ白になってしまった時のことです。
従来なら、「あれ?なんで表示されないんだろう」と原因を調べて、ターミナルでサーバーを手動再起動していたでしょう。でも、devtools-mcpがあると、Claude Codeが自動的に問題を解決してくれます:
⏺ ブラウザでエラーが発生していることを検出しました。詳細を確認します。
⏺ devtools:get_browser_logs (MCP)(filter: {"level": ["error"], "limit": 10})
⎿ 最新のエラーログ:
- GET http://localhost:3000/_next/static/css/app.css 404 (Not Found)
- GET http://localhost:3000/favicon.ico 404 (Not Found)
⏺ 静的ファイルが見つからないエラーが発生しています。開発サーバーのログも確認してみましょう。
⏺ devtools:get_server_logs (MCP)(name: "nextjs-app", lines: 10)
⎿ サーバーログから、ビルドプロセスでエラーが発生していることがわかりました。
⏺ 開発サーバーを再起動してみましょう。
⏺ devtools:restart_dev_server (MCP)(name: "nextjs-app")
⎿ Development server "nextjs-app" started successfully in background.
Server is running in the background.
⏺ 開発サーバーを再起動しました。ブラウザでページをリロード(⌘+R)してみてください。
今後の展望
現在のバージョンは基本的な機能に絞っていますが、いくつかの機能拡張を検討しています。
ブラウザログ収集については、さらなる高度化を予定しています。現在はコンソールログとHTTPエラーに限定していますが、パフォーマンス指標やユーザーインタラクション、リソース読み込み時間なども収集対象に加えたいと考えています。
サーバー管理機能では、環境変数の動的設定、複数プロジェクトの並行管理、Docker統合、そしてCI/CDパイプラインとの連携などを検討しています。特に、開発チーム全体での利用を考えると、設定の共有機能なども必要になってくるでしょう。
また、パフォーマンス監視機能の強化も重要です。現在は基本的なプロセス情報しか取得していませんが、より詳細なメトリクスを収集して、パフォーマンスボトルネックの特定に役立てたいと考えています。
おわりに
devtools-mcpの開発を通じて、「小さな不便」を解決することの価値を改めて実感しました。npm run devのブロッキング問題や、ブラウザとサーバーの情報分断は、一見些細な問題に見えるかもしれません。でも、これらを解決することで、AI支援開発の可能性が大幅に広がったのです。
何より、Claude Codeが真の意味で「開発パートナー」として機能するようになったことが、最大の収穫でした。単なるコード生成ツールではなく、開発環境全体を理解し、サポートしてくれる存在として。フロントエンドからバックエンド、ブラウザからサーバーまで、開発フロー全体を見渡し
て適切な判断を下してくれるパートナーとして。
これからも、開発者とAIの協働をより円滑にするツールを作り続けていきたいと思います。小さな改善の積み重ねが、いずれ大きな変化をもたらすはずです。開発環境の「可視性」を向上させることで、AI支援開発の新しい可能性を切り開いていければと思います。