AIエージェントでMCPサーバーを開発した際のトラブルと解決策


Created by @_shimizu

2025年11月

自己紹介

自画像
  • 氏名:清水正行
  • 所在:群馬県高崎市在住
  • 仕事:データ可視化と地理情報システムのエンジニア
     📊 日経ビジュアルデータ
  • 趣味:資産運用
  • 最近一番注目していたイベント:NVIDIAの決算!

開発したMCPサーバー

🗺️ OSM-GeoJSON-MCP-Server

  • OpenStreetMapデータをGeoJSON形式で取得
  • Overpass APIを活用した効率的なデータ取得

📍 GitHub Repository

🎨 ModelsLab Text2Image MCP Server

  • ModelsLab APIを使用したText2imageを提供
  • AIがMCPを通じて画像を生成

🎨 GitHub Repository

OpenStreetMap & Overpass API

🗺️ OpenStreetMap(OSM)とは

  • 世界規模の協働地図プロジェクト - みんなで作るWikipediaの地図版
  • オープンデータ - 商用利用も可能な自由なライセンス
  • 詳細な地理情報 - 建物、道路、施設、自然地形まで網羅
  • リアルタイム更新 - 世界中の貢献者により常に最新情報に更新

🔍 Overpass API

  • OSMデータ専用クエリAPI - 効率的な地理データ検索
  • 無料で利用可能 - API制限はあるが基本無料
  • 独自のクエリ言語 - 複雑な条件でのデータ抽出が可能
     だが学習コストが高い

🗺️ OpenStreetMap 📖 Overpass API Wiki

AIエージェント(Claude Code)で開発

🤖 開発背景

  • Claude Codeを使ってOpenStreetMap MCP サーバーを開発
  • 8種類のツールで地理データ取得(建物、道路、施設など)
  • Overpass APIからOSMデータをGeoJSON形式で提供
  • AIエージェント主導で段階的に機能を実装

しかし、AIエージェントが作った当初の仕様に重大な問題が...

AIエージェントが作った当初の仕様

❌ 当初の仕様: APIレスポンスを直接LLMに返却


// AIエージェントが実装した当初のコード
export async function getBuildings(client, args) {
  const osmData = await client.query(query);
  const geojson = osmToGeoJSON(osmData);
  
  return {
    content: [{
      type: 'text',
      text: JSON.stringify(geojson, null, 2)  // 🚨 巨大データを直接返す
    }]
  };
}
                    

AIエージェントの想定: 「MCPツールはAPIデータをそのまま返すもの」
大容量データの場合のトークン消費を考慮していなかった

大容量APIを扱う際の重要な考慮事項

⚠️ 大容量データAPIの特徴

  • 地理データAPI: OpenStreetMap、Google Maps等
  • 画像・動画API: メディア配信サービス
  • 大規模データセットAPI: 統計データ、ログデータ等
  • 検索結果API: 大量の結果を返すAPI

💡 事前に確認すべきポイント

  • レスポンスサイズの想定
  • 巨大データが返却される可能性
  • トークン換算での計算
  • ページネーション機能
  • ファイルダウンロード機能
  • ストリーミング対応

 Toolの仕様を変更

✅ 新しい仕様: ファイル保存 + メタデータ返却


export async function getBuildings(client, args) {
  const { output_path } = args;
  
  if (output_path) {
    // 🎯 ファイル保存手法: 大容量データをファイルに保存
    const result = await executeGeoJSONQuery(client, query, output_path);
    
    return {
      content: [{
        type: 'text',
        text: JSON.stringify({
          status: 'success',
          message: '建物データをファイルに保存しました',
          file: output_path,              // ✅ ファイルパスのみ
          size: result.size,              // ✅ メタデータ
          feature_count: result.feature_count  // ✅ サマリー情報
        }, null, 2)
      }]
    };
  }
  // 小さなデータは従来通り直接返却
}
                    

💡 APIリクエスト時の処理

大容量データ → ファイル保存
LLMには → メタデータのみ返却

ファイル保存前の変換処理

🔄 OSMデータ → GeoJSONファイル変換フロー


export async function executeGeoJSONQuery(overpassClient, query, outputPath) {
  // 1. OSMデータを一時ファイルにダウンロード
  const tempPath = outputPath + '.tmp.osm.json';
  await downloadToFile(server.url, fullQuery, tempPath);
  
  // 2. OSMデータを読み込み
  const osmData = JSON.parse(await fs.readFile(tempPath));
  
  // 3. GeoJSONに変換
  const geojson = osmtogeojson(osmData);
  
  // 4. 最終ファイルに保存
  await fs.writeFile(outputPath, JSON.stringify(geojson, null, 2));
  
  // 5. 一時ファイル削除
  await fs.unlink(tempPath);
  
  return {
    size: `${(Buffer.byteLength(JSON.stringify(geojson))/1024/1024).toFixed(2)} MB`,
    feature_count: geojson.features.length
  };
}
                    

💡 なぜ2段階処理?

  • OSMのデータ形式 → 標準GeoJSON形式
  • 地理情報システムで利用可能なフォーマットに

ファイル保存処理

utils/file-downloader.js


export async function downloadToFile(url, query, outputPath) {
  return new Promise((resolve, reject) => {
    const req = https.request(options, (res) => {
      const writeStream = createWriteStream(outputPath);
      let downloadedBytes = 0;

      res.on('data', (chunk) => {
        downloadedBytes += chunk.length;
        // 大容量ファイルの進行状況表示
        if (downloadedBytes % (1024 * 1024) === 0) {
          console.error(`Downloaded: ${(downloadedBytes/1024/1024).toFixed(1)} MB`);
        }
      });

      // 🔥 ストリーミング書き込みでメモリ効率化
      res.pipe(writeStream);
      
      writeStream.on('finish', () => {
        resolve({ 
          size: `${(downloadedBytes/1024/1024).toFixed(2)} MB`,
          bytes: downloadedBytes 
        });
      });
    });
    
    req.write(query);
    req.end();
  });
}
                    

🎯 ファイル保存手法の利点

  • メモリ効率: ストリーミング処理
  • 高速: 直接ディスク書き込み

最終的なMCPの仕様

🎯ツールとユーティリティ

📍 ツール

  • test_connection(接続テスト)
  • get_api_stats(API情報取得)
  • get_buildings(建物)
  • get_roads(道路)
  • get_amenities(施設)
  • get_waterways(水域)
  • get_green_spaces(緑地)
  • get_railways(鉄道)

🛠️ ユーティリティ

  • file-downloader(ダウンロード)
  • converter(変換)
  • overpass(API管理)
  • cache(キャシング)
  • logger(ログ保存)
  • validator(入力検証)

MCPサーバー開発で学んだ教訓

🤖 AIエージェントの盲点

AIエージェントはコードを書くことはできるが、大容量データのトークン影響は人間が指摘する必要があった

💡 最重要な学び

データを返すAPIを扱う場合は、最初からファイル保存手法を検討する
後から修正するよりも、設計段階で組み込む方が効率的

結論

🤖 AIエージェント開発者への重要メッセージ

MCPサーバーは比較的シンプルな仕組みなのでAIエージェントがサクッとつくってくれるけど、AIは大容量データのトークンへの影響は考慮してくれないので、気づかないと不効率な実装になっていることがあるので気をつけましょう。

てか、従量課金だったら終わってた。

おしまい