
AIエージェントにCMSのメディアを運用させる ― 画像のバイト列はLLMに通さない
平原 典彦 — Tesseraを開発しているエンジニア。ヘッドレスCMSとAI連携に関心があります。
AIエージェント(Claude CodeやCursor)にブログ記事を書かせるところまでは、もう普通にできるようになりました。ではその記事に載せる画像――アイキャッチや説明図――は、誰がアップロードするのか。ここもエージェントに任せたくなりますが、意外な落とし穴があります。
結論から言うと、Tesseraはメディアの投入経路を2つ用意し、**「大きな画像のバイト列は、LLMのコンテキストに通さない」**という一線で使い分けています。この記事は、AIエージェントにCMSのメディアを運用させるときの、その設計の話です。
まず素直な道:MCPでBase64アップロード
TesseraのMCPサーバーには upload_asset というツールがあります。ファイル名・MIMEタイプ・Base64エンコードした中身を渡すと、アセットが作られる。エージェントから見れば、ツールを1回呼ぶだけ。いちばん手軽な経路です。
小さい画像や、テキスト/CSVのような軽いファイルなら、これで十分です。実際 upload_asset は image/*・PDF・mp4/webm・text/plain・text/csv を受け付けます。
問題は、サイズが大きくなったときです。
Base64は「LLMのコンテキストを通る」
upload_asset はBase64文字列を引数に取ります。つまり画像のバイト列が、エージェント(LLM)のコンテキストを一度通過するということです。
ここが厄介です。1MBの画像はBase64にすると約1.3MBの文字列になります。この文字列がツール呼び出しの引数としてLLMのコンテキストに載る。すると――
- トークンを大量に消費する(画像1枚で数十万トークン規模になり得る)
- コンテキストウィンドウを圧迫し、進行中の他の作業を押し出す
- そもそも大きすぎてツール呼び出しが失敗する
アイキャッチ用のPNGは、平気で数百KB〜数MBになります。つまり「エージェントにアイキャッチを上げさせる」という、いちばんやりたい用途で、Base64経路はいちばん詰まりやすい。Tesseraの upload_asset が約5MBを上限にしているのも、この経路の性質を踏まえた歯止めです。
だからもう1本、HTTPの投入口を用意した
そこでTesseraは、MCPとは別にHTTPのメディアAPIを持たせました。
POST https://tesseracms.com/api/assets
これは multipart/form-data でファイルをストリームとしてそのまま送るエンドポイントです。ポイントは、バイト列がLLMのコンテキストを一切通らないこと。エージェントは、手元のファイルを curl で直接投げればいい。
curl -X POST https://tesseracms.com/api/assets \
-H "Authorization: Bearer $TESSERA_API_KEY" \
-F "file=@cover.png" \
-F "visibility=public"
返ってくるのは、作られたアセットの情報です。
{
"asset": {
"id": "109bdc45-…",
"fileName": "cover.png",
"mimeType": "image/png",
"sizeBytes": 1015772,
"visibility": "public",
"url": "https://media.tesseracms.com/<workspaceId>/<assetId>.png",
"createdAt": "…"
}
}
- 認証はAPIキー(
read_writeスコープ)。MCPと同じキーで、HTTPからも書き込める - 上限は20MB(Base64経路の約5MBより大きい)
visibilityで公開/非公開を選べる。公開なら永続の直リンク、非公開なら短命の署名付きURLが返る
エージェントは、この url(や id)を受け取って、記事のMarkdownに画像として差し込めます。バイト列は curl のプロセスからTesseraへ直接流れ、LLMは「このファイルを送れ」という指示と、返ってきたURLしか見ていない。 これが狙いです。
(種明かしをすると、この記事のアイキャッチも、同じ /api/assets 経由で入れています。ドッグフーディングです。)
「投入」だけでなく「発見」もエージェントに
メディア運用は、上げて終わりではありません。「さっき上げた画像のURLは?」「前に使ったあのロゴ、もう一度貼りたい」――**発見(discovery)**が要ります。
ここはMCPの list_assets が担います。ワークスペースのアセットを新しい順に一覧し、ファイル名で部分一致の絞り込みができ、各アセットの配信URL(公開なら公開URL、非公開なら署名付きURL)を返します。
つまりエージェントの中では、こういう運用が回ります。
- 画像を用意して
curlで/api/assetsへ投入(コンテキストを通さない) - 記事本文に貼る画像URLは
list_assetsで引いて差し込む - 使い回したい既存アセットも
list_assetsで名前から探す
投入はコンテキストを迂回するHTTP、発見はMCP。役割で経路を分けています。
使い分けの整理
- 小さい画像・テキスト/CSVを手軽に → MCP
upload_asset(Base64)。ツール1回で済むが、中身がLLMのコンテキストを通る・約5MB上限 - アイキャッチ等の大きい画像・動画 → HTTP
POST /api/assets(multipart・curl)。バイト列がLLMのコンテキストを通らない・20MB・公開/非公開を選べる - アップ済みアセットを探す / URLを得る → MCP
list_assets。新しい順・ファイル名で絞り込み・配信URLを返す
設計の考え方:何をLLMに通すか
AIにCMSを運用させるとき、地味に効いてくるのが「何をLLMのコンテキストに通すか」の線引きです。
スキーマやエントリのテキストは、LLMを通す価値があります。エージェントが構造を理解して提案・生成するための材料だからです。一方で、画像のバイト列をLLMに通す必要はありません。エージェントが判断に使うのは「どのファイルを・どこに・公開/非公開で置くか」であって、ピクセルの中身そのものではない。
だからTesseraは、判断に関わる軽いもの・発見はMCP(upload_asset の軽量ケース・list_assets)で、重いバイト列はコンテキストを迂回するHTTP導線(/api/assets)に逃がしました。「AIがCMSを運用する」を現実的に回すには、この“通す/通さない”の設計がいる、というのが今回の話です。
まとめ
- AIエージェントに画像を上げさせるとき、Base64経路(MCP
upload_asset)はバイト列がLLMのコンテキストを通る。大きな画像ではトークン浪費・コンテキスト圧迫・失敗につながる(約5MB上限) - そこでHTTPのメディアAPI
POST /api/assets(multipart・curl・APIキーread_write)を用意。バイト列はLLM文脈を通らず、20MB・公開/非公開に対応。返るurlをそのまま本文に差せる - 発見はMCP
list_assets:新しい順・ファイル名絞り込み・配信URLを返す。投入と発見で経路を分ける - 本質は「何をLLMに通すか」の線引き。テキスト(構造)は通し、画像のバイト列は迂回させる
エージェントにブログを書かせるなら、画像もエージェントに運用させたい。そのときに詰まらないための、地味だけれど効く導線の話でした。