Add binary blob resource pattern to patterns guide
Document how to serve binary content (e.g., video) via MCP resources as base64-encoded blobs. Includes server-side registration using the `blob` field and client-side fetching with conversion to data URIs. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
246e1ab138
commit
13615613d4
2 changed files with 105 additions and 2 deletions
|
|
@ -249,6 +249,53 @@ try {
|
|||
}
|
||||
```
|
||||
|
||||
## Serving binary blobs via resources
|
||||
|
||||
Binary content (e.g., video) can be served via MCP resources as base64-encoded blobs. The server returns the data in the `blob` field of the resource content, and the App fetches it via `resources/read` for use in the browser.
|
||||
|
||||
**Server-side**: Register a resource that returns binary data in the `blob` field:
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts source="./patterns.tsx#binaryBlobResourceServer"
|
||||
server.registerResource(
|
||||
"Video",
|
||||
new ResourceTemplate("video://{id}", { list: undefined }),
|
||||
{
|
||||
description: "Video data served as base64 blob",
|
||||
mimeType: "video/mp4",
|
||||
},
|
||||
async (uri, { id }): Promise<ReadResourceResult> => {
|
||||
// Fetch or load your binary data
|
||||
const idString = Array.isArray(id) ? id[0] : id;
|
||||
const buffer = await getVideoData(idString);
|
||||
const blob = Buffer.from(buffer).toString("base64");
|
||||
|
||||
return { contents: [{ uri: uri.href, mimeType: "video/mp4", blob }] };
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
**Client-side**: Fetch the resource and convert the base64 blob to a data URI:
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts source="./patterns.tsx#binaryBlobResourceClient"
|
||||
const result = await app.request(
|
||||
{ method: "resources/read", params: { uri: `video://${videoId}` } },
|
||||
ReadResourceResultSchema,
|
||||
);
|
||||
|
||||
const content = result.contents[0];
|
||||
if (!content || !("blob" in content)) {
|
||||
throw new Error("Resource did not contain blob data");
|
||||
}
|
||||
|
||||
const videoEl = document.querySelector("video")!;
|
||||
videoEl.src = `data:${content.mimeType!};base64,${content.blob}`;
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> For a full example that implements this pattern, see: [`examples/video-resource-server/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/video-resource-server).
|
||||
|
||||
## Adapting to host context (theme, styling, fonts, and safe areas)
|
||||
|
||||
The host provides context about its environment via {@link types!McpUiHostContext `McpUiHostContext`}. Use this to adapt your app's appearance and layout:
|
||||
|
|
|
|||
|
|
@ -14,12 +14,19 @@ import {
|
|||
applyHostStyleVariables,
|
||||
} from "../src/styles.js";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
||||
import type {
|
||||
CallToolResult,
|
||||
ReadResourceResult,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import { ReadResourceResultSchema } from "@modelcontextprotocol/sdk/types.js";
|
||||
import type { McpUiHostContext } from "../src/types.js";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useApp } from "../src/react/index.js";
|
||||
import { registerAppTool } from "../src/server/index.js";
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import {
|
||||
McpServer,
|
||||
ResourceTemplate,
|
||||
} from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
|
|
@ -213,6 +220,53 @@ function chunkedDataClient(app: App, resourceId: string) {
|
|||
//#endregion chunkedDataClient
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Serving binary blobs via resources (server-side)
|
||||
*/
|
||||
function binaryBlobResourceServer(
|
||||
server: McpServer,
|
||||
getVideoData: (id: string) => Promise<ArrayBuffer>,
|
||||
) {
|
||||
//#region binaryBlobResourceServer
|
||||
server.registerResource(
|
||||
"Video",
|
||||
new ResourceTemplate("video://{id}", { list: undefined }),
|
||||
{
|
||||
description: "Video data served as base64 blob",
|
||||
mimeType: "video/mp4",
|
||||
},
|
||||
async (uri, { id }): Promise<ReadResourceResult> => {
|
||||
// Fetch or load your binary data
|
||||
const idString = Array.isArray(id) ? id[0] : id;
|
||||
const buffer = await getVideoData(idString);
|
||||
const blob = Buffer.from(buffer).toString("base64");
|
||||
|
||||
return { contents: [{ uri: uri.href, mimeType: "video/mp4", blob }] };
|
||||
},
|
||||
);
|
||||
//#endregion binaryBlobResourceServer
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Serving binary blobs via resources (client-side)
|
||||
*/
|
||||
async function binaryBlobResourceClient(app: App, videoId: string) {
|
||||
//#region binaryBlobResourceClient
|
||||
const result = await app.request(
|
||||
{ method: "resources/read", params: { uri: `video://${videoId}` } },
|
||||
ReadResourceResultSchema,
|
||||
);
|
||||
|
||||
const content = result.contents[0];
|
||||
if (!content || !("blob" in content)) {
|
||||
throw new Error("Resource did not contain blob data");
|
||||
}
|
||||
|
||||
const videoEl = document.querySelector("video")!;
|
||||
videoEl.src = `data:${content.mimeType!};base64,${content.blob}`;
|
||||
//#endregion binaryBlobResourceClient
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Adapting to host context (theme, CSS variables, fonts, safe areas)
|
||||
*/
|
||||
|
|
@ -406,6 +460,8 @@ void pollingVanillaJs;
|
|||
void pollingReact;
|
||||
void chunkedDataServer;
|
||||
void chunkedDataClient;
|
||||
void binaryBlobResourceServer;
|
||||
void binaryBlobResourceClient;
|
||||
void hostContextVanillaJs;
|
||||
void hostContextReact;
|
||||
void persistViewStateServer;
|
||||
|
|
|
|||
Loading…
Reference in a new issue