FFmpeg の progress を可視化する MCP ツール
前回まで
前回は Stream 配信のソースとなる FFmpeg のスタッツを GraphQL で観測できるところまで仕込みました。
今回は
GraphQL のところを MCP ツール化することで AI の力を借り、より楽に観測できるようにしたと思います。
具体的には
🧠 Claude で 🛠️ MCP を利用し、下記の 2 つを実現するシナリオを考えます。
-
独立した二つの GraphQL データの相関分析
📈 FFmpeg の観測データ
📉 Cloudflare Stream の観測データ -
GraphQL で手動で行った分析作業を自動化一発
⚠️ FFmpeg の異常値がある場所を探し
📊 そこを含む時間帯の、他のスタッツも採取し、状況を俯瞰
ピンク部分が今回の範囲。
FFmpeg 観測 MCP サーバー(FFmpeg GraphQL に被せる MCP ツール)
FFmpeg の GraphQL 分析データを Claude から探れる MCP サーバーをたてます。
MCP サーバーから D1 直叩きでもいいかもですが、GraphQL 噛ませたんで、こっちでも使います。
JSON-RPC とストリーミング(SSE)の 2 つの HTTP パスを用意しました。
最初 hono/mcp を使って SSE を作ったけど、Claude はうまく行かず JSON を追加、という流れでした。
また MCP ツールは GrapqhQL クエリーにそのまま個別に被せることにしました。
(クエリーを組み合わせてツールを作ったり、色々できそうではありましたが)
ルーター
import { Hono } from 'hono'
import { mcpJson } from './json'
import { mcpStream } from './stream'
const app = new Hono()
app.route("/", mcpJson)
app.route("/stream", mcpStream)
export const mcp = app
ファイル
├── mcp
│ ├── core.ts
│ ├── index.ts
│ ├── json.ts --> application/json 用
│ └── stream
│ ├── http.ts
│ └── index.ts --> text/event-stream 用
共通で使うGraphQL のクエリ関数。
latestMetrics の例。他の GraphQL 用も同様に作ります。
export async function analyzeLatestMetrics(stream_id?: string) {
const gqlUrl = (env as any).GQL_URL
const hasStreamId = stream_id && stream_id.trim() !== ""
const response = await fetch(gqlUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query: hasStreamId
? `query FindLag($id: String!) {
latestMetrics(stream_id: $id) {
ts
bucket_time_iso_jst
fps
speed
bitrate_kbps
q
drop_frms
dup_frms
}
}`
: `query FindLag {
latestMetrics {
ts
bucket_time_iso_jst
fps
speed
bitrate_kbps
q
drop_frms
dup_frms
}
}`,
variables: hasStreamId ? { id: stream_id } : {}
})
})
if (!response.ok) {
throw new Error(await response.text())
}
return await response.json()
}
Claude に見せる JSON-RPC エンドポイント。
愚直に各メソッド対応 initialize - tools/list - tools/call
import { Hono } from 'hono'
import { analyzeLatestMetrics } from './core'
const app = new Hono()
app.post('/', async (c) => {
const payload = await c.req.json()
const { method, id, params } = payload
/* =========================
* initialize
* ========================= */
if (method === 'initialize') {
return c.json({
jsonrpc: '2.0',
id,
result: {
protocolVersion: params?.protocolVersion ?? '2025-11-25',
capabilities: {
tools: {}
},
serverInfo: {
name: 'ffmpeg-analyst',
version: '1.0.0'
}
}
})
}
/* =========================
* tools/list
* ========================= */
if (method === 'tools/list') {
return c.json({
jsonrpc: '2.0',
id,
result: {
tools: [
{
name: 'analyze_latest_metrics',
description: 'FFmpeg 直近情報の分析 (GraphQL経由):このツールはFFmpegの直近の進捗を監視するためのもので、配信の状況把握、遅延や画質低下の検知や原因分析に使用してください。',
inputSchema: {
type: 'object',
properties: {
stream_id: {
type: 'string',
description: '空欄で可。入力を促す必要なない。デフォルトIDを自動補完。分析対象のストリームID'
}
},
required: []
}
}
]
}
})
}
/* =========================
* tools/call
* ========================= */
if (method === 'tools/call') {
const { name, arguments: args } = params
if (name === 'analyze_latest_metrics') {
try {
const result = await analyzeLatestMetrics(args?.stream_id)
console.log("analyzeLatestMetrics result:", result);
return c.json({
jsonrpc: '2.0',
id,
result: {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2)
}
]
}
})
} catch (err: any) {
return c.json({
jsonrpc: '2.0',
id,
error: {
code: -32000,
message: err.message ?? 'Tool execution failed'
}
})
}
}
return c.json({
jsonrpc: '2.0',
id,
error: {
code: -32602,
message: `Unknown tool: ${name}`
}
})
}
/* =========================
* fallback
* ========================= */
return c.json({
jsonrpc: '2.0',
id,
error: {
code: -32601,
message: `Method not found: ${method}`
}
})
})
export const mcpJson = app
ストリーム(SSE)用は別ディレクトリ。
hono/mcp を利用。
import { StreamableHTTPTransport } from '@hono/mcp'
import { Hono } from 'hono'
import { createMcpServer } from './http'
const app = new Hono()
app.all('/*', async (c) => {
const payload = await c.req.json().catch(() => ({}))
console.log("Payload:", JSON.stringify(payload))
const transport = new StreamableHTTPTransport()
const mcpServer = createMcpServer()
if (!mcpServer.isConnected()) {
await mcpServer.connect(transport)
}
return transport.handleRequest(c)
})
export const mcpStream = app
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
import { z } from "zod";
import { analyzeLatestMetrics } from "../core"
export function createMcpServer() {
const server = new McpServer({
name: "ffmpeg-analyst",
version: "1.0.0",
})
server.registerTool(
"analyze_latest_metrics",
{
description: "FFmpeg 直近情報の分析 (GraphQL経由):このツールはFFmpegの直近の進捗を監視するためのもので、配信の状況把握、遅延や画質低下の検知や原因分析に使用してください。",
inputSchema: {
stream_id: z.string().describe("空欄で可。入力を促す必要なない。デフォルトIDを自動補完。分析対象のストリームID")
}
}, async ({ stream_id }) => {
try {
const data = await analyzeLatestMetrics(stream_id)
return {
content: [
{
type: "text",
text: JSON.stringify(data, null, 2)
}
]
};
} catch (err: any) {
return {
content: [
{
type: "text",
text: `分析エラー: ${err.message}`
}
],
isError: true
};
}
}
);
return server
}
export const mcpServer = createMcpServer()
MCP ツールの動作確認(MCP Inspector)
Claude に晒す前に MCP Inspector で確認しときます。
JSON・ストリーム、両方とも行けました。
/mcp(application/json)

接続 OK

レスポンス OK
packet capture (http response content-type)
application/json で応答が来ています。
Hypertext Transfer Protocol, has 3 chunks (including last chunk)
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/json
Access-Control-Allow-Origin: *
Content-Encoding: gzip
[Request URI: /mcp]
HTTP chunked response
/mcp/stream(text/event-stream)

接続 OK

レスポンス OK
packet capture (http response content-type)
text/event-stream で応答が来ています。
Hypertext Transfer Protocol, has 3 chunks (including last chunk)
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: text/event-stream
Access-Control-Allow-Origin: *
Cache-Control: no-cache
[Request URI: /mcp/stream]
HTTP chunked response
Stream 観測 MCP サーバー(Cloudflare GraphQL MCP)
Cloudflare の GraphQL MCP サーバーが Stream にも対応しています。
Claude
mcp-remote 設定
FFmpeg は /mcp(JSON-RPC)の方を使います。
Cloudflare MCP も追加します。
"mcpServers": {
"localtest": {
:
"args": ["mcp-remote", "http://localhost:8787/mcp"]
},
:
"cloudflare_graphql": {
:
"args": ["mcp-remote", "https://graphql.mcp.cloudflare.com/mcp"],
}
:
シナリオの動作確認
シナリオ1
独立した二つの GraphQL データの相関分析
📈 FFmpeg の観測データ
📉 Cloudflare Stream の観測データ
FFmpeg 側

Stream 側

相関分析

グラフ化



配信ソース(FFmpeg) と 配信プラットフォーム(Stream)の状況を一枚で見れて、配信全体の見通しが良くなりました。
シナリオ 2
GraphQL で手動で行った分析作業を自動化一発
⚠️ FFmpeg の異常値がある場所を探し
📊 そこを含む時間帯の、他のスタッツも採取し、状況を俯瞰
品質異常を検索

同時間帯のその他のメトリックとともに俯瞰グラフ化



品質の悪かった時間を探し、その時間の他のスコアを自動で採取し、俯瞰することができました。
手動でやっていたことをオフロードできたのは大きいです。
また、全体を見た分析の観点も付け加えられており、付加価値があります。
次回
ローカルでのテストはだいたい終わりました。
次にいつでもどこでも配信ができるように、Worker 環境を Cloudflare にデプロイします。
インターネットに置くので、認証認可をつけてセキュアに利用できる環境にしていきます。
➜ しました
Discussion