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 API は
multipart/form-dataでバイナリを直接受け取る -
MCP は
image_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で処理を共有する設計にすれば、両方のメリットを活かせます。
設計のポイントをまとめると:
- 内部APIゲートウェイで処理を共有する — ビジネスロジックの二重実装を避ける
- MCPはURL参照、REST APIはmultipartアップロード — それぞれの得意な方式で受け取る
- 認証は統一する — ユーザーに複数のキーを管理させない
- OpenAPI仕様書を提供する — REST APIのセルフサービス型ドキュメント
- upload_sourceで出所を追跡する — チャネル別の利用分析に活用
MCPのエコシステムは急速に進化しています。将来的にバイナリアップロートのネイティブサポートが実現すれば、REST APIの役割は変わるかもしれません。でも、今日ユーザーに価値を届けるためには、現実的な設計判断が必要です。
この記事が、同じ課題に直面している開発者の参考になれば幸いです。
Discussion