Demonstrates serving binary content (video) via MCP resources using the
base64 blob pattern:
1. Server fetches video from CDN
2. Returns as base64 blob via `videos://{id}` resource template
3. Widget fetches resource and plays in `<video>` element
Includes multiple video sizes for testing (1MB to 150MB).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
124 lines
3.6 KiB
TypeScript
124 lines
3.6 KiB
TypeScript
/**
|
|
* Video Resource Player
|
|
*
|
|
* Demonstrates fetching binary content (video) via MCP resources.
|
|
* The video is served as a base64 blob and converted to a data URI for playback.
|
|
*/
|
|
import { App } from "@modelcontextprotocol/ext-apps";
|
|
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
import { ReadResourceResultSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
import "./global.css";
|
|
import "./mcp-app.css";
|
|
|
|
const log = {
|
|
info: console.log.bind(console, "[VIDEO]"),
|
|
error: console.error.bind(console, "[VIDEO]"),
|
|
};
|
|
|
|
// Get element references
|
|
const loadingEl = document.getElementById("loading")!;
|
|
const loadingTextEl = document.getElementById("loading-text")!;
|
|
const errorEl = document.getElementById("error")!;
|
|
const errorMessageEl = document.getElementById("error-message")!;
|
|
const playerEl = document.getElementById("player")!;
|
|
const videoEl = document.getElementById("video") as HTMLVideoElement;
|
|
const videoInfoEl = document.getElementById("video-info")!;
|
|
|
|
// Parse tool result to get video URI
|
|
function parseToolResult(
|
|
result: CallToolResult,
|
|
): { videoUri: string; description: string } | null {
|
|
const text = result.content
|
|
?.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
.map((c) => c.text)
|
|
.join("");
|
|
|
|
if (!text) return null;
|
|
|
|
try {
|
|
return JSON.parse(text) as { videoUri: string; description: string };
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Show states
|
|
function showLoading(text: string) {
|
|
loadingTextEl.textContent = text;
|
|
loadingEl.style.display = "flex";
|
|
errorEl.style.display = "none";
|
|
playerEl.style.display = "none";
|
|
}
|
|
|
|
function showError(message: string) {
|
|
errorMessageEl.textContent = message;
|
|
loadingEl.style.display = "none";
|
|
errorEl.style.display = "block";
|
|
playerEl.style.display = "none";
|
|
}
|
|
|
|
function showPlayer(dataUri: string, info: string) {
|
|
videoEl.src = dataUri;
|
|
videoInfoEl.textContent = info;
|
|
loadingEl.style.display = "none";
|
|
errorEl.style.display = "none";
|
|
playerEl.style.display = "block";
|
|
}
|
|
|
|
// Create app instance
|
|
const app = new App({ name: "Video Resource Player", version: "1.0.0" });
|
|
|
|
// Handle tool result - this is called when the tool execution completes
|
|
app.ontoolresult = async (result) => {
|
|
log.info("Received tool result:", result);
|
|
|
|
const parsed = parseToolResult(result);
|
|
if (!parsed) {
|
|
showError("Invalid tool result - could not parse video URI");
|
|
return;
|
|
}
|
|
|
|
const { videoUri, description } = parsed;
|
|
log.info("Video URI:", videoUri, "Description:", description);
|
|
|
|
showLoading("Fetching video from MCP resource...");
|
|
|
|
try {
|
|
log.info("Requesting resource:", videoUri);
|
|
|
|
const resourceResult = await app.request(
|
|
{ method: "resources/read", params: { uri: videoUri } },
|
|
ReadResourceResultSchema,
|
|
);
|
|
|
|
log.info(
|
|
"Resource received, blob size:",
|
|
resourceResult.contents[0]?.blob?.length,
|
|
);
|
|
|
|
const content = resourceResult.contents[0];
|
|
if (!content?.blob) {
|
|
throw new Error("Resource response did not contain blob data");
|
|
}
|
|
|
|
showLoading("Converting to data URI...");
|
|
|
|
const mimeType = content.mimeType || "video/mp4";
|
|
const dataUri = `data:${mimeType};base64,${content.blob}`;
|
|
|
|
log.info("Data URI created, length:", dataUri.length);
|
|
|
|
showPlayer(dataUri, `Loaded via MCP resource (${description})`);
|
|
} catch (err) {
|
|
log.error("Error fetching resource:", err);
|
|
showError(err instanceof Error ? err.message : String(err));
|
|
}
|
|
};
|
|
|
|
app.onerror = (err) => {
|
|
log.error("App error:", err);
|
|
showError(err instanceof Error ? err.message : String(err));
|
|
};
|
|
|
|
// Connect to host
|
|
app.connect();
|