👏

MCPサーバーにREST APIを併設した設計判断

に公開

MCPサーバーにREST APIを併設した設計判断

はじめに

SaaS「Suidasu」をひとりで開発しています。画像・PDFからAIでデータを抽出するサービスで、MCPサーバーを提供しています。

MCPサーバーを実装する中で、バイナリファイルのアップロードというJSON-RPCの根本的な制約にぶつかりました。本記事では、その課題に対して**「MCPサーバーにREST APIを併設する」**という設計判断に至った経緯と、アーキテクチャのポイントをまとめます。


なぜ併設が必要だったか

MCPはJSON-RPCベースのプロトコルです。すべてのデータはJSONに載せて送受信します。

Suidasuのコア機能は「画像をアップロードして、AIがデータを抽出する」こと。しかしJSON-RPCでは multipart/form-data によるバイナリ送信ができません。Base64エンコードすれば送れますが、サイズが約1.33倍に膨張し、MCP SDKのデフォルト上限(4MB)にすぐ引っかかります。

最新スマホの写真は3〜5MB。Base64にすると4〜7MB。普通の写真が送れない。

この問題はMCPの仕様レベルで未解決です(#155#527)。将来的にHTTP RESTトランスポート(SEP-1597)などの提案はありますが、実装はまだ先の話です。

「今日サービスを届ける」ために、MCPと並行してREST APIを提供する判断をしました。


設計方針 — 内部APIを共有する

MCPとREST APIで同じビジネスロジックを二重実装するのは悪手です。バグの温床になるし、プラン制限や課金の整合性も崩れます。

そこで採用したのが、内部APIゲートウェイを挟む構成です。

ポイント

  • REST APImultipart/form-data でバイナリを直接受け取る
  • MCPimage_url(HTTPS URL)を受け取り、サーバー側でダウンロード
  • どちらも最終的には同じ内部APIエンドポイントにリクエストを投げる
  • ビジネスロジック(AI抽出、プラン制限チェック、課金記録)は一箇所に集約

3つのサービス、1つのデータベース

Suidasuは3つの独立したサービスとして動いています。

サービス ポート 役割
webapp 8080 Web管理画面(HTMX)
api(REST) 8082 モバイルアプリ・外部API
mcp 8081 AIエージェント連携

3つのサービスは同じPostgreSQLデータベースを共有し、サービス間通信は内部API経由で行います。

webapp と mcp は、OCR処理が必要なとき REST API の内部エンドポイントを呼び出します。OCR処理(AI API呼び出し、画像保存、課金記録)をREST APIサービスに集約することで、ロジックの重複を防いでいます。


MCPとREST APIの実装差分

同じ「画像からデータ抽出」を実現するのに、入り口の部分だけが異なります。

REST API — 直接アップロード

POST /v1/api/extract
Content-Type: multipart/form-data
Authorization: Bearer sk_live_xxx

file: (バイナリデータ)
project_id: "uuid"
extraction_mode: "ocr"

クライアントがファイルを直接アップロードします。HTTPの標準的なファイルアップロードなので、cURL、Postman、任意のHTTPクライアントから送信可能です。サイズ制限はサーバー側で自由に設定できます。

MCP — URL参照方式

{
  "method": "tools/call",
  "params": {
    "name": "extract_data",
    "arguments": {
      "image_url": "https://example.com/photo.jpg",
      "project_id": "uuid",
      "extraction_mode": "ocr"
    }
  }
}

クライアント(AIエージェント)は画像のURLを指定します。MCPサーバーがそのURLから画像をダウンロードし、内部APIに転送します。

Base64でのインライン送信は当初サポートしていましたが、サイズ制約が実用的でないため廃止しました。

内部API — 共通のゴール地点

どちらのルートも、最終的に同じ内部エンドポイントに到達します。

POST /v1/internal/upload
X-Internal-Secret: (共有シークレット)
Content-Type: multipart/form-data

user_id: "uuid"
project_id: "uuid"
file: (バイナリデータ)
upload_source: "api" or "mcp" or "web"

upload_source フィールドでリクエストの出所を追跡しています。これにより「MCPからの利用が何件、REST APIからが何件」という分析が可能です。


認証の統一

MCPもREST APIも、同じAPIキー形式で認証しています。

  • フォーマット: sk_live_ + 64文字の16進数(32バイトのランダム値)
  • 保存: bcryptハッシュ(平文は保存しない)
  • 認証: Authorization: Bearer sk_live_xxx ヘッダー

ユーザーはWeb管理画面でAPIキーを1つ発行すれば、MCPにもREST APIにも同じキーでアクセスできます。「MCPキー」「REST APIキー」と分ける必要はありません。

プランによるアクセス制御は認証ミドルウェアで行っています。

プラン MCP REST API
Trial / Lite / Standard x x
Pro o o

OpenAPI仕様書とSwagger UI

REST APIを提供するなら、ドキュメントは必須です。

SuidasuではOpenAPI 3.1.0仕様書をGoバイナリに埋め込み、以下のエンドポイントで提供しています。

エンドポイント 内容
GET /v1/openapi.json OpenAPI仕様書(JSON)
GET /v1/docs Swagger UI

Swagger UIがあれば、開発者はブラウザ上でAPIを試せます。APIキーを入力してそのまま実行できるので、導入のハードルが大きく下がります。

MCPは tools/list メソッドでツール一覧を返す仕組みがプロトコルに組み込まれていますが、REST APIにはそれに相当するものがありません。OpenAPI仕様書がその役割を果たします。


MCPとREST APIの使い分け

両方を提供した結果、それぞれの得意領域が見えてきました。

特性 MCP REST API
テキストベースの操作(CRUD、検索) 最適 可能
バイナリファイルのアップロード 制約あり(URL参照のみ) 最適(multipart)
AIエージェントからの利用 ネイティブサポート 可能だがツール定義が必要
既存システムとの統合 JSON-RPCクライアントが必要 cURL一発
ドキュメント tools/list で自動提供 OpenAPI仕様書を別途用意

MCPはAIエージェントとの対話に特化した素晴らしいプロトコルですが、すべてのユースケースをカバーするものではありません。REST APIを併設することで、MCPの弱点を補完できます。


得られた効果

開発面

  • ビジネスロジックの一元管理。バグ修正が一箇所で済む
  • 新しいツール/エンドポイントの追加が容易。入り口(MCPハンドラー / RESTハンドラー)を書くだけで、内部処理は共通
  • upload_source によるチャネル別の利用分析

ユーザー面

  • AIエージェント(Claude、ChatGPTなど)からはMCPで利用
  • 自社システムとの統合にはREST APIで利用
  • 同じAPIキーでどちらも使えるシンプルさ

ビジネス面

  • MCPのバイナリ制約で「使えない」と判断されるリスクを回避
  • REST APIがあることで、MCP非対応の環境からもサービスにアクセス可能
  • Swagger UIによるセルフサービス型のAPI導入体験

まとめ

MCPサーバーを開発するとき、REST APIの併設を最初から計画に入れることをおすすめします

特にファイルアップロードが必要なサービスでは、MCPのJSON-RPC制約が大きなボトルネックになります。REST APIを併設し、内部APIで処理を共有する設計にすれば、両方のメリットを活かせます。

設計のポイントをまとめると:

  1. 内部APIゲートウェイで処理を共有する — ビジネスロジックの二重実装を避ける
  2. MCPはURL参照、REST APIはmultipartアップロード — それぞれの得意な方式で受け取る
  3. 認証は統一する — ユーザーに複数のキーを管理させない
  4. OpenAPI仕様書を提供する — REST APIのセルフサービス型ドキュメント
  5. upload_sourceで出所を追跡する — チャネル別の利用分析に活用

MCPのエコシステムは急速に進化しています。将来的にバイナリアップロートのネイティブサポートが実現すれば、REST APIの役割は変わるかもしれません。でも、今日ユーザーに価値を届けるためには、現実的な設計判断が必要です。

この記事が、同じ課題に直面している開発者の参考になれば幸いです。

Discussion